From 379c480e1d2b0ea7596143050b2e13f7b0d16463 Mon Sep 17 00:00:00 2001 From: Dave Airlie Date: Thu, 27 Sep 2018 15:16:41 +1000 Subject: [PATCH 001/766] include in a few places that use it --- tensorflow/core/lib/strings/str_util.cc | 1 + tensorflow/core/platform/posix/net.cc | 1 + tensorflow/core/util/command_line_flags.cc | 1 + 3 files changed, 3 insertions(+) diff --git a/tensorflow/core/lib/strings/str_util.cc b/tensorflow/core/lib/strings/str_util.cc index 3aba5ec80e..5c784c1801 100644 --- a/tensorflow/core/lib/strings/str_util.cc +++ b/tensorflow/core/lib/strings/str_util.cc @@ -16,6 +16,7 @@ limitations under the License. #include "tensorflow/core/lib/strings/str_util.h" #include +#include #include #include #include "tensorflow/core/lib/strings/numbers.h" diff --git a/tensorflow/core/platform/posix/net.cc b/tensorflow/core/platform/posix/net.cc index 414ee6c50c..c873f8fd46 100644 --- a/tensorflow/core/platform/posix/net.cc +++ b/tensorflow/core/platform/posix/net.cc @@ -17,6 +17,7 @@ limitations under the License. #include #include +#include #include #include diff --git a/tensorflow/core/util/command_line_flags.cc b/tensorflow/core/util/command_line_flags.cc index 55f1e30880..58ea046e8d 100644 --- a/tensorflow/core/util/command_line_flags.cc +++ b/tensorflow/core/util/command_line_flags.cc @@ -14,6 +14,7 @@ limitations under the License. ==============================================================================*/ #include +#include #include #include "tensorflow/core/lib/core/stringpiece.h" -- GitLab From 3491251120952da685f9f2ccdde9bcf927310d30 Mon Sep 17 00:00:00 2001 From: Mateusz Chudyk Date: Mon, 5 Nov 2018 21:08:24 +0100 Subject: [PATCH 002/766] Correct error messages in graph transforms. --- tensorflow/tools/graph_transforms/README.md | 2 +- tensorflow/tools/graph_transforms/remove_attribute.cc | 2 +- tensorflow/tools/graph_transforms/rename_attribute.cc | 4 ++-- tensorflow/tools/graph_transforms/rename_op.cc | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tensorflow/tools/graph_transforms/README.md b/tensorflow/tools/graph_transforms/README.md index 9f6f553ba1..d24b671774 100644 --- a/tensorflow/tools/graph_transforms/README.md +++ b/tensorflow/tools/graph_transforms/README.md @@ -805,7 +805,7 @@ Status RenameOp(const GraphDef& input_graph_def, !context.params.count("new_op_name") || (context.params.at("new_op_name").size() != 1)) { return errors::InvalidArgument( - "remove_nodes expects exactly one 'old_op_name' and 'new_op_name' " + "rename_op expects exactly one 'old_op_name' and 'new_op_name' " "argument, e.g. rename_op(old_op_name=Mul, new_op_name=Multiply)"); } diff --git a/tensorflow/tools/graph_transforms/remove_attribute.cc b/tensorflow/tools/graph_transforms/remove_attribute.cc index b1a04c0f28..70a68a261f 100644 --- a/tensorflow/tools/graph_transforms/remove_attribute.cc +++ b/tensorflow/tools/graph_transforms/remove_attribute.cc @@ -42,7 +42,7 @@ Status RemoveAttribute(const GraphDef& input_graph_def, if (context.params.count("op_name")) { if (context.params.at("op_name").size() != 1) { return errors::InvalidArgument( - "remove_nodes expects a single op_name argument, but found ", + "remove_attribute expects a single op_name argument, but found ", context.params.at("op_name").size()); } op_name = context.params.at("op_name")[0]; diff --git a/tensorflow/tools/graph_transforms/rename_attribute.cc b/tensorflow/tools/graph_transforms/rename_attribute.cc index bd066aab5b..fbc466c59c 100644 --- a/tensorflow/tools/graph_transforms/rename_attribute.cc +++ b/tensorflow/tools/graph_transforms/rename_attribute.cc @@ -35,9 +35,9 @@ Status RenameAttribute(const GraphDef& input_graph_def, !context.params.count("new_attribute_name") || (context.params.at("new_attribute_name").size() != 1)) { return errors::InvalidArgument( - "remove_nodes expects exactly one 'old_attribute_name' and one " + "rename_attribute expects exactly one 'old_attribute_name' and one " "'new_attribute_name' argument, e.g. " - "remove_attribute(old_attribute_name=foo, new_attribute_name=bar)"); + "rename_attribute(old_attribute_name=foo, new_attribute_name=bar)"); } string op_name; diff --git a/tensorflow/tools/graph_transforms/rename_op.cc b/tensorflow/tools/graph_transforms/rename_op.cc index e1e13c1be4..7f51c29104 100644 --- a/tensorflow/tools/graph_transforms/rename_op.cc +++ b/tensorflow/tools/graph_transforms/rename_op.cc @@ -36,7 +36,7 @@ Status RenameOp(const GraphDef& input_graph_def, !context.params.count("new_op_name") || (context.params.at("new_op_name").size() != 1)) { return errors::InvalidArgument( - "remove_nodes expects exactly one 'old_op_name' and 'new_op_name' " + "rename_op expects exactly one 'old_op_name' and 'new_op_name' " "argument, e.g. rename_op(old_op_name=Mul, new_op_name=Multiply)"); } -- GitLab From 7da99ff50e705751aac286c80abc2ad93cbcf350 Mon Sep 17 00:00:00 2001 From: Spencer Schaber Date: Sat, 1 Dec 2018 08:15:04 -0600 Subject: [PATCH 003/766] Update docs for tf.contrib.layers.optimize_loss Easier to read and corrects name of example callable --- tensorflow/contrib/layers/python/layers/optimizers.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tensorflow/contrib/layers/python/layers/optimizers.py b/tensorflow/contrib/layers/python/layers/optimizers.py index 2fdcd849b0..97e65b2e5d 100644 --- a/tensorflow/contrib/layers/python/layers/optimizers.py +++ b/tensorflow/contrib/layers/python/layers/optimizers.py @@ -109,11 +109,12 @@ def optimize_loss(loss, gradient_multipliers: dict of variables or variable names to floats. If present, gradients for specified variables will be multiplied by given constant. - clip_gradients: float, callable or `None`. If float, is provided, a global - clipping is applied to prevent the norm of the gradient to exceed this - value. Alternatively, a callable can be provided e.g.: adaptive_clipping. - This callable takes a `list` of `(gradients, variables)` `tuple`s and - returns the same thing with the gradients modified. + clip_gradients: float, callable or `None`. If a float is provided, a global + clipping is applied to prevent the norm of the gradient from exceeding + this value. Alternatively, a callable can be provided e.g., + adaptive_clipping_fn(). This callable takes a `list` of + `(gradients, variables)` `tuple`s and returns the same thing with the + gradients modified. learning_rate_decay_fn: function, takes `learning_rate` and `global_step` `Tensor`s, returns `Tensor`. Can be used to implement any learning rate decay -- GitLab From 4b4a085091ef54c1b34900ab7d887ec0a896ba0a Mon Sep 17 00:00:00 2001 From: Spencer Schaber Date: Sat, 1 Dec 2018 08:43:15 -0600 Subject: [PATCH 004/766] Update optimizers.py --- tensorflow/contrib/layers/python/layers/optimizers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/contrib/layers/python/layers/optimizers.py b/tensorflow/contrib/layers/python/layers/optimizers.py index 97e65b2e5d..839b2c66e3 100644 --- a/tensorflow/contrib/layers/python/layers/optimizers.py +++ b/tensorflow/contrib/layers/python/layers/optimizers.py @@ -111,7 +111,7 @@ def optimize_loss(loss, variables will be multiplied by given constant. clip_gradients: float, callable or `None`. If a float is provided, a global clipping is applied to prevent the norm of the gradient from exceeding - this value. Alternatively, a callable can be provided e.g., + this value. Alternatively, a callable can be provided, e.g., adaptive_clipping_fn(). This callable takes a `list` of `(gradients, variables)` `tuple`s and returns the same thing with the gradients modified. -- GitLab From cecdc921b27be9e46a832daa6e52b0e1adfadbfa Mon Sep 17 00:00:00 2001 From: Niels Ole Salscheider Date: Wed, 5 Dec 2018 16:00:27 +0100 Subject: [PATCH 005/766] Add gradient for tf.extract_volume_patches This implementation is based on tf.extract_image_patches. --- tensorflow/python/kernel_tests/BUILD | 14 +++ .../extract_volume_patches_grad_test.py | 109 ++++++++++++++++++ tensorflow/python/ops/array_grad.py | 67 +++++++++++ 3 files changed, 190 insertions(+) create mode 100644 tensorflow/python/kernel_tests/extract_volume_patches_grad_test.py diff --git a/tensorflow/python/kernel_tests/BUILD b/tensorflow/python/kernel_tests/BUILD index f990b0ecc4..1697892170 100644 --- a/tensorflow/python/kernel_tests/BUILD +++ b/tensorflow/python/kernel_tests/BUILD @@ -2839,6 +2839,20 @@ cuda_py_test( tags = ["notap"], # http://b/31080670 ) +cuda_py_test( + name = "extract_volume_patches_grad_test", + size = "medium", + srcs = ["extract_volume_patches_grad_test.py"], + additional_deps = [ + "//third_party/py/numpy", + "//tensorflow/python:array_ops", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework", + "//tensorflow/python:framework_for_generated_wrappers", + ], + tags = ["notap"], # http://b/31080670 +) + cuda_py_test( name = "stage_op_test", size = "medium", diff --git a/tensorflow/python/kernel_tests/extract_volume_patches_grad_test.py b/tensorflow/python/kernel_tests/extract_volume_patches_grad_test.py new file mode 100644 index 0000000000..63b225c4a6 --- /dev/null +++ b/tensorflow/python/kernel_tests/extract_volume_patches_grad_test.py @@ -0,0 +1,109 @@ +# 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 ExtractVolumePatches gradient.""" + +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 dtypes +from tensorflow.python.framework import random_seed as random_seed_lib +from tensorflow.python.framework import test_util +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import gradient_checker +from tensorflow.python.ops import gradients_impl +from tensorflow.python.ops import variable_scope +from tensorflow.python.platform import test + + +class ExtractVolumePatchesGradTest(test.TestCase): + """Gradient-checking for ExtractVolumePatches op.""" + + _TEST_CASES = [ + { + 'in_shape': [2, 5, 5, 5, 3], + 'ksizes': [1, 1, 1, 1, 1], + 'strides': [1, 2, 3, 4, 1], + }, + { + 'in_shape': [2, 7, 7, 7, 3], + 'ksizes': [1, 3, 3, 3, 1], + 'strides': [1, 1, 1, 1, 1], + }, + { + 'in_shape': [2, 5, 7, 6, 3], + 'ksizes': [1, 3, 2, 2, 1], + 'strides': [1, 1, 1, 1, 1], + }, + { + 'in_shape': [2, 7, 8, 6, 3], + 'ksizes': [1, 2, 3, 2, 1], + 'strides': [1, 2, 4, 3, 1], + }, + ] + + @test_util.run_deprecated_v1 + def testGradient(self): + # Set graph seed for determinism. + random_seed = 42 + random_seed_lib.set_random_seed(random_seed) + + with self.cached_session(): + for test_case in self._TEST_CASES: + np.random.seed(random_seed) + in_shape = test_case['in_shape'] + in_val = constant_op.constant( + np.random.random(in_shape), dtype=dtypes.float32) + + for padding in ['VALID', 'SAME']: + out_val = array_ops.extract_volume_patches(in_val, + test_case['ksizes'], + test_case['strides'], + padding) + out_shape = out_val.get_shape().as_list() + + err = gradient_checker.compute_gradient_error(in_val, in_shape, + out_val, out_shape) + + print('extract_volume_patches gradient err: %.4e' % err) + self.assertLess(err, 1e-4) + + @test_util.run_deprecated_v1 + def testConstructGradientWithLargeVolumess(self): + batch_size = 4 + planes = 8 + height = 32 + width = 32 + ksize = 5 + volumes = variable_scope.get_variable('inputs', + (batch_size, planes, + height, width, 1)) + patches = array_ops.extract_volume_patches(volumes, + ksizes=[1, ksize, + ksize, ksize, 1], + strides=[1, 1, 1, 1, 1], + padding='SAME') + # Github issue: #20146 + # tf.extract_volume_patches() gradient very slow at graph construction time + gradients = gradients_impl.gradients(patches, volumes) + # Won't time out. + self.assertIsNotNone(gradients) + + +if __name__ == '__main__': + test.main() diff --git a/tensorflow/python/ops/array_grad.py b/tensorflow/python/ops/array_grad.py index 45e741ef22..818d0cc429 100644 --- a/tensorflow/python/ops/array_grad.py +++ b/tensorflow/python/ops/array_grad.py @@ -795,6 +795,73 @@ def _ExtractImagePatchesGrad(op, grad): return [grad_out] +@ops.RegisterGradient("ExtractVolumePatches") +def _ExtractVolumePatchesGrad(op, grad): + batch_size, planes_in, rows_in, cols_in, channels = [ + dim.value for dim in op.inputs[0].shape.dims + ] + input_bphwc = array_ops.shape(op.inputs[0]) + batch_size = input_bphwc[0] + channels = input_bphwc[4] + + # Create indices matrix for input tensor. + # Note that 0 is preserved for padding location, + # so indices for input start from 1 to 1 + rows_in * cols_in. + input_indices_num = 1 + planes_in * rows_in * cols_in + input_idx = array_ops.reshape(math_ops.range(1, input_indices_num, + dtype=ops.dtypes.int64), + (1, planes_in, rows_in, cols_in, 1)) + input_idx_patched = gen_array_ops.extract_volume_patches( + input_idx, + op.get_attr("ksizes"), + op.get_attr("strides"), + op.get_attr("padding")) + + # Create indices matrix for output tensor. + _, planes_out, rows_out, cols_out, _ = [dim.value for dim in + op.outputs[0].shape.dims] + _, ksize_p, ksize_r, ksize_c, _ = op.get_attr("ksizes") + # Indices for output start from 0. + prc_indices_num = planes_out * rows_out * cols_out + output_indices_num = prc_indices_num * ksize_p * ksize_r * ksize_c + output_idx = array_ops.reshape(math_ops.range(output_indices_num, + dtype=ops.dtypes.int64), + (1, planes_out, rows_out, cols_out, + ksize_p * ksize_r * ksize_c)) + + # Construct mapping table for indices: (input -> output). + idx_matrix = array_ops.concat( + [array_ops.expand_dims(input_idx_patched, axis=-1), + array_ops.expand_dims(output_idx, axis=-1)], + axis=-1) + idx_map = array_ops.reshape(idx_matrix, (-1, 2)) + + sp_shape = (input_indices_num, output_indices_num) + sp_mat_full = sparse_tensor.SparseTensor( + idx_map, + array_ops.ones([output_indices_num], dtype=grad.dtype), + sp_shape) + # Remove all padding locations [0, :]. + sp_mat = sparse_ops.sparse_slice(sp_mat_full, + (1, 0), + (input_indices_num - 1, output_indices_num)) + + grad_expanded = array_ops.transpose( + array_ops.reshape( + grad, (batch_size, planes_out, rows_out, cols_out, ksize_p, ksize_r, + ksize_c, channels)), + (1, 2, 3, 4, 5, 6, 0, 7)) + grad_flat = array_ops.reshape(grad_expanded, (-1, batch_size * channels)) + + jac = sparse_ops.sparse_tensor_dense_matmul(sp_mat, grad_flat) + + grad_out = array_ops.reshape(jac, (planes_in, rows_in, cols_in, batch_size, + channels)) + grad_out = array_ops.transpose(grad_out, (3, 0, 1, 2, 4)) + + return [grad_out] + + @ops.RegisterGradient("ScatterNd") def _ScatterNdGrad(op, grad): indices = op.inputs[0] -- GitLab From 5ac8933c9728b6bbd27e3e153b92848c097e8c30 Mon Sep 17 00:00:00 2001 From: neargye Date: Sun, 9 Dec 2018 22:50:00 +0500 Subject: [PATCH 006/766] update android tflite example --- tensorflow/lite/examples/android/app/build.gradle | 6 +++--- tensorflow/lite/examples/android/app/download-models.gradle | 3 --- .../lite/examples/android/app/src/main/AndroidManifest.xml | 5 ----- tensorflow/lite/examples/android/build.gradle | 6 ++---- 4 files changed, 5 insertions(+), 15 deletions(-) diff --git a/tensorflow/lite/examples/android/app/build.gradle b/tensorflow/lite/examples/android/app/build.gradle index 35e7887852..c7e620e212 100644 --- a/tensorflow/lite/examples/android/app/build.gradle +++ b/tensorflow/lite/examples/android/app/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.application' android { compileSdkVersion 26 - buildToolsVersion '26.0.2' + buildToolsVersion '28.0.3' defaultConfig { applicationId "org.tensorflow.lite.demo" minSdkVersion 15 @@ -49,6 +49,6 @@ project.ext.TMP_DIR = project.buildDir.toString() + '/downloads' apply from: "download-models.gradle" dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - compile 'org.tensorflow:tensorflow-lite:0.0.0-nightly' + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'org.tensorflow:tensorflow-lite:0.0.0-nightly' } diff --git a/tensorflow/lite/examples/android/app/download-models.gradle b/tensorflow/lite/examples/android/app/download-models.gradle index 36bd177a1f..514eeb0135 100644 --- a/tensorflow/lite/examples/android/app/download-models.gradle +++ b/tensorflow/lite/examples/android/app/download-models.gradle @@ -67,9 +67,6 @@ task extractModels(type: Copy) { } } - - - tasks.whenTaskAdded { task -> if (task.name == 'assembleDebug') { task.dependsOn 'extractModels' diff --git a/tensorflow/lite/examples/android/app/src/main/AndroidManifest.xml b/tensorflow/lite/examples/android/app/src/main/AndroidManifest.xml index bc9574d646..d4c98c61cc 100644 --- a/tensorflow/lite/examples/android/app/src/main/AndroidManifest.xml +++ b/tensorflow/lite/examples/android/app/src/main/AndroidManifest.xml @@ -24,12 +24,7 @@ - - diff --git a/tensorflow/lite/examples/android/build.gradle b/tensorflow/lite/examples/android/build.gradle index 74dacbcddb..90b8f0f3ba 100644 --- a/tensorflow/lite/examples/android/build.gradle +++ b/tensorflow/lite/examples/android/build.gradle @@ -2,13 +2,11 @@ buildscript { repositories { + google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.1' - - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files + classpath 'com.android.tools.build:gradle:3.2.1' } } -- GitLab From a8ab29c29282cdc3799fbc7eeb18554fcf164dc4 Mon Sep 17 00:00:00 2001 From: Viacheslav Kovalevskyi Date: Sun, 9 Dec 2018 18:32:26 -0800 Subject: [PATCH 007/766] Update of the README --- tensorflow/lite/experimental/micro/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 673daed74c..99cb01acd4 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -2,6 +2,8 @@ This an experimental port of TensorFlow Lite aimed at micro controllers and other devices with only kilobytes of memory. It doesn't require any operating system support, any standard C or C++ libraries, or dynamic memory allocation, so it's designed to be portable even to 'bare metal' systems. The core runtime fits in 16KB on a Cortex M3, and with enough operators to run a speech keyword detection model, takes up a total of 22KB. +*Note: not all kernelas are ported yet.* + The design goals are for the framework to be: - **Readable**: We want embedded software engineers to be able to understand what's required to run ML inference without having to study research papers. We've tried to keep the code base small, modular, and have reference implementations of all operations to help with this. -- GitLab From b95c838aec2a9e7921cf75db3ef12b70c7731730 Mon Sep 17 00:00:00 2001 From: Viacheslav Kovalevskyi Date: Sun, 9 Dec 2018 18:37:33 -0800 Subject: [PATCH 008/766] intel_neon now downloaded with all other dependancies. --- .../lite/experimental/micro/tools/make/download_dependencies.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tensorflow/lite/experimental/micro/tools/make/download_dependencies.sh b/tensorflow/lite/experimental/micro/tools/make/download_dependencies.sh index 6749858bdb..4ca611a33e 100755 --- a/tensorflow/lite/experimental/micro/tools/make/download_dependencies.sh +++ b/tensorflow/lite/experimental/micro/tools/make/download_dependencies.sh @@ -33,6 +33,7 @@ GEMMLOWP_URL="https://github.com/google/gemmlowp/archive/719139ce755a0f31cbf1c37 FLATBUFFERS_URL="https://github.com/google/flatbuffers/archive/1f5eae5d6a135ff6811724f6c57f911d1f46bb15.tar.gz" CMSIS_URL="https://github.com/ARM-software/CMSIS_5/archive/5.4.0.zip" STM32_BARE_LIB_URL="https://github.com/google/stm32_bare_lib/archive/c07d611fb0af58450c5a3e0ab4d52b47f99bc82d.zip" +INTEL_NEON="https://github.com/intel/ARM_NEON_2_x86_SSE/archive/97a126f08ce318023be604d03f88bf0820a9464a.zip" download_and_extract() { local usage="Usage: download_and_extract URL DIR" @@ -69,5 +70,6 @@ download_and_extract "${GEMMLOWP_URL}" "${DOWNLOADS_DIR}/gemmlowp" download_and_extract "${FLATBUFFERS_URL}" "${DOWNLOADS_DIR}/flatbuffers" download_and_extract "${CMSIS_URL}" "${DOWNLOADS_DIR}/cmsis" download_and_extract "${STM32_BARE_LIB_URL}" "${DOWNLOADS_DIR}/stm32_bare_lib" +download_and_extract "${INTEL_NEON}" "${DOWNLOADS_DIR}/intel_neon" echo "download_dependencies.sh completed successfully." >&2 -- GitLab From acefd6b67ae0f8db4a8f4812508051f526ad1cb3 Mon Sep 17 00:00:00 2001 From: Viacheslav Kovalevskyi Date: Sun, 9 Dec 2018 20:16:02 -0800 Subject: [PATCH 009/766] instructions how to install brew install caskroom/cask/gcc-arm-embedded on Mac OS added. --- .../lite/experimental/micro/examples/micro_speech/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/README.md b/tensorflow/lite/experimental/micro/examples/micro_speech/README.md index 500eed33ba..df2be53f5f 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/README.md +++ b/tensorflow/lite/experimental/micro/examples/micro_speech/README.md @@ -31,6 +31,8 @@ To understand how TensorFlow Lite does this, you can look at the `TestInvoke()` Once you have downloaded the dependencies and got the x86/Linux build working, you can try building a version for the STM32F103 'bluepill' device. The following command will build the test and then run it on an emulator, assuming you have Docker installed: +*On Mac OS you need ot have ARM compiler installed, one way of doing so is with brew: brew install caskroom/cask/gcc-arm-embedded* + ``` make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=bluepill test_micro_speech ``` -- GitLab From 261d9679648a148686df8ec04f413d5f6e703c21 Mon Sep 17 00:00:00 2001 From: Viacheslav Kovalevskyi Date: Sun, 9 Dec 2018 21:00:10 -0800 Subject: [PATCH 010/766] Instructions how to train the model in GCP --- .../micro/examples/micro_speech/README.md | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/README.md b/tensorflow/lite/experimental/micro/examples/micro_speech/README.md index df2be53f5f..e586f34ffe 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/README.md +++ b/tensorflow/lite/experimental/micro/examples/micro_speech/README.md @@ -103,3 +103,54 @@ Finally, convert the file into a C source file that can be compiled into an embe ``` xxd -i /tmp/tiny_conv.tflite > /tmp/tiny_conv_model_data.cc ``` + +### Creating Your Own Model With Google Cloud + +If want to train your model in Google Cloud you can do so by using pre-configured Deep Learning images. + +First create the VM: + +``` +export IMAGE_FAMILY="tf-latest-cpu" +export ZONE="us-west1-b" # Or any other required region +export INSTANCE_NAME="model-trainer" +export INSTANCE_TYPE="n1-standard-8" # or any other instance type +gcloud compute instances create $INSTANCE_NAME \ + --zone=$ZONE \ + --image-family=$IMAGE_FAMILY \ + --image-project=deeplearning-platform-release \ + --machine-type=$INSTANCE_TYPE \ + --boot-disk-size=120GB \ + --min-cpu-platform=Intel\ Skylake +``` + +As soon as instance has been created you can SSH to it(as a jupyter user!): + +``` +gcloud compute ssh "jupyter@${INSTANCE_NAME}" +``` + +now install Bazel: + +``` +wget https://github.com/bazelbuild/bazel/releases/download/0.15.0/bazel-0.15.0-installer-linux-x86_64.sh +sudo bash ./bazel-0.15.0-installer-linux-x86_64.sh +source /usr/local/lib/bazel/bin/bazel-complete.bash +sudo ln /usr/local/bin/bazel /usr/bin/bazel +``` +and finally run the build: + +``` +# TensorFlow already pre-baked on the image +cd src/tensorflow +bazel run -c opt --copt=-mavx2 --copt=-mfma \ +tensorflow/examples/speech_commands:train -- \ +--model_architecture=tiny_conv --window_stride=20 --preprocess=average \ +--wanted_words="yes,no" --silence_percentage=25 --unknown_percentage=25 --quantize=1 +``` + +After build is over follow the rest of the instrucitons from this tutorial. And finally do not forget to remove the instance when training is done: + +``` +gcloud compute instances delete "${INSTANCE_NAME}" --zone="${ZONE}" +``` -- GitLab From 355cfa78c909aeda1409a06336bbebc0879b79a0 Mon Sep 17 00:00:00 2001 From: Pete Warden Date: Mon, 5 Nov 2018 11:09:46 -0800 Subject: [PATCH 011/766] Fix for the Mac OS build --- .../micro/tools/make/targets/osx_x86_64_makefile.inc | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 tensorflow/lite/experimental/micro/tools/make/targets/osx_x86_64_makefile.inc diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/osx_x86_64_makefile.inc b/tensorflow/lite/experimental/micro/tools/make/targets/osx_x86_64_makefile.inc new file mode 100644 index 0000000000..78febaf5dd --- /dev/null +++ b/tensorflow/lite/experimental/micro/tools/make/targets/osx_x86_64_makefile.inc @@ -0,0 +1,10 @@ +# Settings for x86 on Mac +ifeq ($(TARGET), osx) + ifeq ($(TARGET_ARCH), x86_64) + PLATFORM_FLAGS = \ + -DTF_LITE_DISABLE_X86_NEON + CXXFLAGS += $(PLATFORM_FLAGS) + CCFLAGS += $(PLATFORM_FLAGS) + endif +endif + -- GitLab From e3c6555dc58c0d8049eed6b78b3e9e10281118a0 Mon Sep 17 00:00:00 2001 From: Viacheslav Kovalevskyi Date: Tue, 11 Dec 2018 09:51:49 -0800 Subject: [PATCH 012/766] Typo fixed. --- .../lite/experimental/micro/examples/micro_speech/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/README.md b/tensorflow/lite/experimental/micro/examples/micro_speech/README.md index e586f34ffe..dda98d7000 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/README.md +++ b/tensorflow/lite/experimental/micro/examples/micro_speech/README.md @@ -31,7 +31,7 @@ To understand how TensorFlow Lite does this, you can look at the `TestInvoke()` Once you have downloaded the dependencies and got the x86/Linux build working, you can try building a version for the STM32F103 'bluepill' device. The following command will build the test and then run it on an emulator, assuming you have Docker installed: -*On Mac OS you need ot have ARM compiler installed, one way of doing so is with brew: brew install caskroom/cask/gcc-arm-embedded* +*On Mac OS you need to have ARM compiler installed, one way of doing so is with brew: brew install caskroom/cask/gcc-arm-embedded* ``` make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=bluepill test_micro_speech -- GitLab From 8b7209e1bc850a3cd98af190f60860f7fd755340 Mon Sep 17 00:00:00 2001 From: Filip Matzner Date: Fri, 2 Nov 2018 21:13:15 +0100 Subject: [PATCH 013/766] [TFLite] Enable experimental API --- tensorflow/lite/tools/make/Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tensorflow/lite/tools/make/Makefile b/tensorflow/lite/tools/make/Makefile index 994f660dba..0bb55c9a38 100644 --- a/tensorflow/lite/tools/make/Makefile +++ b/tensorflow/lite/tools/make/Makefile @@ -86,6 +86,8 @@ CORE_CC_ALL_SRCS := \ $(wildcard tensorflow/lite/*.cc) \ $(wildcard tensorflow/lite/*.c) \ $(wildcard tensorflow/lite/c/*.c) \ +$(wildcard tensorflow/lite/experimental/c/*.c) \ +$(wildcard tensorflow/lite/experimental/c/*.cc) \ $(wildcard tensorflow/lite/core/*.cc) \ $(wildcard tensorflow/lite/core/api/*.cc) ifneq ($(BUILD_TYPE),micro) -- GitLab From 724bb9a4c35f5e25b439c7ab418e64fb5a937055 Mon Sep 17 00:00:00 2001 From: Filip Matzner Date: Mon, 5 Nov 2018 13:28:36 +0100 Subject: [PATCH 014/766] [TFLite] Build position independent code --- tensorflow/lite/tools/make/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/tools/make/Makefile b/tensorflow/lite/tools/make/Makefile index 0bb55c9a38..0fa2b31e51 100644 --- a/tensorflow/lite/tools/make/Makefile +++ b/tensorflow/lite/tools/make/Makefile @@ -51,7 +51,7 @@ LIBS := \ # There are no rules for compiling objects for the host system (since we don't # generate things like the protobuf compiler that require that), so all of # these settings are for the target compiler. -CXXFLAGS := -O3 -DNDEBUG +CXXFLAGS := -O3 -DNDEBUG -fPIC CXXFLAGS += $(EXTRA_CXXFLAGS) CCFLAGS := ${CXXFLAGS} CXXFLAGS += --std=c++11 -- GitLab From 9a7a6acfb2646c7170b59648f0e6757d5a719fd2 Mon Sep 17 00:00:00 2001 From: WeberXie Date: Thu, 20 Dec 2018 11:14:37 +0800 Subject: [PATCH 015/766] fix hadoop_file_system's bug when not provide namenode info --- tensorflow/core/platform/hadoop/hadoop_file_system.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/core/platform/hadoop/hadoop_file_system.cc b/tensorflow/core/platform/hadoop/hadoop_file_system.cc index eb35531e9f..68db153baa 100644 --- a/tensorflow/core/platform/hadoop/hadoop_file_system.cc +++ b/tensorflow/core/platform/hadoop/hadoop_file_system.cc @@ -155,7 +155,7 @@ Status HadoopFileSystem::Connect(StringPiece fname, hdfsFS* fs) { StringPiece defaultScheme, defaultCluster, defaultPath; io::ParseURI(defaultFS, &defaultScheme, &defaultCluster, &defaultPath); - if (scheme != defaultScheme || namenode != defaultCluster) { + if (scheme != defaultScheme || (namenode != "" && namenode != defaultCluster)) { return errors::Unimplemented( "viewfs is only supported as a fs.defaultFS."); } @@ -164,7 +164,7 @@ Status HadoopFileSystem::Connect(StringPiece fname, hdfsFS* fs) { // https://github.com/tensorflow/tensorflow/blob/v1.0.0/third_party/hadoop/hdfs.h#L259 hdfs_->hdfsBuilderSetNameNode(builder, "default"); } else { - hdfs_->hdfsBuilderSetNameNode(builder, nn.c_str()); + hdfs_->hdfsBuilderSetNameNode(builder, nn == "" ? "default" : nn.c_str()); } // KERB_TICKET_CACHE_PATH will be deleted in the future, Because KRB5CCNAME is // the build in environment variable of Kerberos, so KERB_TICKET_CACHE_PATH -- GitLab From a1e5738523dad2a0b8942bb97639f038d8961cdc Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Fri, 28 Dec 2018 14:09:30 +0000 Subject: [PATCH 016/766] Fix bug in test_streaming_accuracy.cc This fix fixes the issue raised in 24601 where clip_duration_ms should be clip_duration_samples to calculate audio_data_end in test_streaming_accuracy.cc. (Thanks Alireza89). This fix 24601. Signed-off-by: Yong Tang --- tensorflow/examples/speech_commands/test_streaming_accuracy.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/examples/speech_commands/test_streaming_accuracy.cc b/tensorflow/examples/speech_commands/test_streaming_accuracy.cc index 2972ab778b..6a32744276 100644 --- a/tensorflow/examples/speech_commands/test_streaming_accuracy.cc +++ b/tensorflow/examples/speech_commands/test_streaming_accuracy.cc @@ -244,7 +244,7 @@ int main(int argc, char* argv[]) { std::vector> all_found_words; tensorflow::StreamingAccuracyStats previous_stats; - const int64 audio_data_end = (sample_count - clip_duration_ms); + const int64 audio_data_end = (sample_count - clip_duration_samples); for (int64 audio_data_offset = 0; audio_data_offset < audio_data_end; audio_data_offset += clip_stride_samples) { const float* input_start = &(audio_data[audio_data_offset]); -- GitLab From b27f484594e64c4278d4b55a125c6a3e81723a99 Mon Sep 17 00:00:00 2001 From: awesomealex1 Date: Sat, 5 Jan 2019 19:01:12 +0000 Subject: [PATCH 017/766] Update README.md Removed typos and increased readability. --- tensorflow/contrib/slim/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tensorflow/contrib/slim/README.md b/tensorflow/contrib/slim/README.md index f2bb458848..7cb2e9e482 100644 --- a/tensorflow/contrib/slim/README.md +++ b/tensorflow/contrib/slim/README.md @@ -11,7 +11,7 @@ import tensorflow.contrib.slim as slim ## Why TF-Slim? -TF-Slim is a library that makes building, training and evaluation neural +TF-Slim is a library that makes defining, training and evaluating neural networks simple: * Allows the user to define models much more compactly by eliminating @@ -78,7 +78,7 @@ provides convenience wrappers for variable creation and manipulation. ## Defining Models Models can be succinctly defined using TF-Slim by combining its variables, -layers and scopes. Each of these elements are defined below. +layers and scopes. Each of these elements is defined below. ### Variables @@ -164,7 +164,7 @@ While the set of TensorFlow operations is quite extensive, developers of neural networks typically think of models in terms of higher level concepts like "layers", "losses", "metrics", and "networks". A layer, such as a Convolutional Layer, a Fully Connected Layer or a BatchNorm Layer -are more abstract than a single TensorFlow operation and typically involve +is more abstract than a single TensorFlow operation and typically involve several operations. Furthermore, a layer usually (but not always) has variables (tunable parameters) associated with it, unlike more primitive operations. For example, a Convolutional Layer in a neural network @@ -455,7 +455,7 @@ loss = slim.losses.softmax_cross_entropy(predictions, labels) ``` In this example, we start by creating the model (using TF-Slim's VGG -implementation), and add the standard classification loss. Now, lets turn +implementation), and add the standard classification loss. Now, let's turn to the case where we have a multi-task model that produces multiple outputs: @@ -555,7 +555,7 @@ that we'll save a model checkpoint every 10 minutes. ### Working Example: Training the VGG16 Model -To illustrate this, lets +To illustrate this, let's examine the following sample of training the VGG network: ```python @@ -738,7 +738,7 @@ slim.learning.train(train_op, log_dir, init_fn=init_fn) Once we've trained a model (or even while the model is busy training) we'd like to see how well the model performs in practice. This is accomplished by picking -a set of evaluation metrics, which will grade the models performance, and the +a set of evaluation metrics, which will grade the model's performance, and the evaluation code which actually loads the data, performs inference, compares the results to the ground truth and records the evaluation scores. This step may be performed once or repeated periodically. -- GitLab From 643488bac46bb70db5f98819d596036db7cac309 Mon Sep 17 00:00:00 2001 From: 1e100 <38598618+1e100@users.noreply.github.com> Date: Sat, 12 Jan 2019 22:20:49 -0800 Subject: [PATCH 018/766] Add aarch64 build script for TFLite (#2) * Add build and cross-build instructions and scripts for arbitrary aarch64 dev boards * Update devguide with link to aarch64 build instructions --- tensorflow/contrib/lite/g3doc/devguide.md | 235 ++++++++++++++++++ .../lite/tools/make/build_aarch64_lib.sh | 22 ++ .../tools/make/targets/aarch64_makefile.inc | 33 +++ tensorflow/lite/g3doc/devguide.md | 14 +- tensorflow/lite/g3doc/linux_aarch64.md | 58 +++++ .../lite/g3doc/performance/linux_aarch64.md | 58 +++++ tensorflow/lite/tools/make/Makefile | 4 + 7 files changed, 419 insertions(+), 5 deletions(-) create mode 100644 tensorflow/contrib/lite/g3doc/devguide.md create mode 100755 tensorflow/contrib/lite/tools/make/build_aarch64_lib.sh create mode 100644 tensorflow/contrib/lite/tools/make/targets/aarch64_makefile.inc create mode 100644 tensorflow/lite/g3doc/linux_aarch64.md create mode 100644 tensorflow/lite/g3doc/performance/linux_aarch64.md diff --git a/tensorflow/contrib/lite/g3doc/devguide.md b/tensorflow/contrib/lite/g3doc/devguide.md new file mode 100644 index 0000000000..0a5cfc6177 --- /dev/null +++ b/tensorflow/contrib/lite/g3doc/devguide.md @@ -0,0 +1,235 @@ + +# 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 +[TensorFlow tutorials](../../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, similar 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 +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 Android demo guide for details. + +The Android mobile 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 iOS demo 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). + +### ARM32 and ARM64 Linux + +Compile Tensorflow Lite for a Raspberry Pi by following the +[RPi build instructions](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/lite/g3doc/rpi.md) +Compile Tensorflow Lite for a generic aarch64 board such as Odroid C2, Pine64, NanoPi, and others by following the +[ARM64 Linux build instructions](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/lite/g3doc/linux_aarch64.md) +This compiles a static library file (`.a`) used to build your app. There are +plans for Python bindings and a demo app. diff --git a/tensorflow/contrib/lite/tools/make/build_aarch64_lib.sh b/tensorflow/contrib/lite/tools/make/build_aarch64_lib.sh new file mode 100755 index 0000000000..5c1cf12ecb --- /dev/null +++ b/tensorflow/contrib/lite/tools/make/build_aarch64_lib.sh @@ -0,0 +1,22 @@ +#!/bin/bash -x +# 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. +# ============================================================================== + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR/../../../../.." + +CC_PREFIX=aarch64-linux-gnu- make -j 3 -f tensorflow/contrib/lite/tools/make/Makefile TARGET=aarch64 TARGET_ARCH=armv8-a diff --git a/tensorflow/contrib/lite/tools/make/targets/aarch64_makefile.inc b/tensorflow/contrib/lite/tools/make/targets/aarch64_makefile.inc new file mode 100644 index 0000000000..5127266047 --- /dev/null +++ b/tensorflow/contrib/lite/tools/make/targets/aarch64_makefile.inc @@ -0,0 +1,33 @@ +# Settings for Raspberry Pi. +ifeq ($(TARGET),aarch64) + # The aarch64 architecture covers all 64-bit ARM chips. This arch mandates + # NEON, so FPU flags are not needed below. + TARGET_ARCH := armv8-a + TARGET_TOOLCHAIN_PREFIX := aarch64-linux-gnu- + + CXXFLAGS += \ + -march=armv8-a \ + -funsafe-math-optimizations \ + -ftree-vectorize \ + -fPIC + + CCFLAGS += \ + -march=armv8-a \ + -funsafe-math-optimizations \ + -ftree-vectorize \ + -fPIC + + LDFLAGS := \ + -Wl,--no-export-dynamic \ + -Wl,--exclude-libs,ALL \ + -Wl,--gc-sections \ + -Wl,--as-needed + + + LIBS := \ + -lstdc++ \ + -lpthread \ + -lm \ + -ldl + +endif diff --git a/tensorflow/lite/g3doc/devguide.md b/tensorflow/lite/g3doc/devguide.md index fdd02638f9..2bbc89042e 100644 --- a/tensorflow/lite/g3doc/devguide.md +++ b/tensorflow/lite/g3doc/devguide.md @@ -216,8 +216,12 @@ trained Tensorflow models to the devices. To use the converter, refer to the [Tensorflow-CoreML converter documentation](https://github.com/tf-coreml/tf-coreml). -### Raspberry Pi - -Compile Tensorflow Lite for a Raspberry Pi by following the -[RPi build instructions](rpi.md) This compiles a static library file (`.a`) used -to build your app. There are plans for Python bindings and a demo app. +### ARM32 and ARM64 Linux + +Compile Tensorflow Lite for a Raspberry Pi by following the [RPi build +instructions](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/g3doc/rpi.md) +Compile Tensorflow Lite for a generic aarch64 board such as Odroid C2, Pine64, +NanoPi, and others by following the [ARM64 Linux build +instructions](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/g3doc/linux_aarch64.md) +This compiles a static library file (`.a`) used to build your app. There are +plans for Python bindings and a demo app. diff --git a/tensorflow/lite/g3doc/linux_aarch64.md b/tensorflow/lite/g3doc/linux_aarch64.md new file mode 100644 index 0000000000..9fd65d6219 --- /dev/null +++ b/tensorflow/lite/g3doc/linux_aarch64.md @@ -0,0 +1,58 @@ +# TensorFlow Lite for generic ARM64 boards + +## Cross compiling + +### Installing the toolchain + +```bash +sudo apt-get update +sudo apt-get install crossbuild-essential-arm64 +``` + +### Building + +Clone this Tensorflow repository. +Run this script at the root of the repository to download all the dependencies: + +> The Tensorflow repository is in `/tensorflow` if you are using `tensorflow/tensorflow:nightly-devel` docker image, just try it. + +```bash +./tensorflow/contrib/lite/tools/make/download_dependencies.sh +``` + +Note that you only need to do this once. + +Compile: + +```bash +./tensorflow/contrib/lite/tools/make/build_aarch64_lib.sh +``` + +This should compile a static library in: +`tensorflow/contrib/lite/gen/gen/aarch64_armv8-a/lib/libtensorflow-lite.a`. + +## Native compiling + +These steps were tested on HardKernel Odroid C2, gcc version 5.4.0. + +Log in to your board, install the toolchain. + +```bash +sudo apt-get install build-essential +``` + +First, clone the TensorFlow repository. Run this at the root of the repository: + +```bash +./tensorflow/contrib/lite/tools/make/download_dependencies.sh +``` +Note that you only need to do this once. + +Compile: + +```bash +./tensorflow/contrib/lite/tools/make/build_aarch64_lib.sh +``` + +This should compile a static library in: +`tensorflow/contrib/lite/gen/gen/aarch64_armv8-a/lib/libtensorflow-lite.a`. diff --git a/tensorflow/lite/g3doc/performance/linux_aarch64.md b/tensorflow/lite/g3doc/performance/linux_aarch64.md new file mode 100644 index 0000000000..9fd65d6219 --- /dev/null +++ b/tensorflow/lite/g3doc/performance/linux_aarch64.md @@ -0,0 +1,58 @@ +# TensorFlow Lite for generic ARM64 boards + +## Cross compiling + +### Installing the toolchain + +```bash +sudo apt-get update +sudo apt-get install crossbuild-essential-arm64 +``` + +### Building + +Clone this Tensorflow repository. +Run this script at the root of the repository to download all the dependencies: + +> The Tensorflow repository is in `/tensorflow` if you are using `tensorflow/tensorflow:nightly-devel` docker image, just try it. + +```bash +./tensorflow/contrib/lite/tools/make/download_dependencies.sh +``` + +Note that you only need to do this once. + +Compile: + +```bash +./tensorflow/contrib/lite/tools/make/build_aarch64_lib.sh +``` + +This should compile a static library in: +`tensorflow/contrib/lite/gen/gen/aarch64_armv8-a/lib/libtensorflow-lite.a`. + +## Native compiling + +These steps were tested on HardKernel Odroid C2, gcc version 5.4.0. + +Log in to your board, install the toolchain. + +```bash +sudo apt-get install build-essential +``` + +First, clone the TensorFlow repository. Run this at the root of the repository: + +```bash +./tensorflow/contrib/lite/tools/make/download_dependencies.sh +``` +Note that you only need to do this once. + +Compile: + +```bash +./tensorflow/contrib/lite/tools/make/build_aarch64_lib.sh +``` + +This should compile a static library in: +`tensorflow/contrib/lite/gen/gen/aarch64_armv8-a/lib/libtensorflow-lite.a`. diff --git a/tensorflow/lite/tools/make/Makefile b/tensorflow/lite/tools/make/Makefile index 994f660dba..d0ee99f8ce 100644 --- a/tensorflow/lite/tools/make/Makefile +++ b/tensorflow/lite/tools/make/Makefile @@ -1,3 +1,7 @@ +# Make uses /bin/sh by default, which is incompatible with the bashisms seen +# below. +SHELL := /bin/bash + # Find where we're running from, so we can store generated files here. ifeq ($(origin MAKEFILE_DIR), undefined) MAKEFILE_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) -- GitLab From b222429f66446ea1515be748ce61194eb2c01840 Mon Sep 17 00:00:00 2001 From: 1e100 <38598618+1e100@users.noreply.github.com> Date: Sat, 12 Jan 2019 22:24:38 -0800 Subject: [PATCH 019/766] restore devguide --- tensorflow/lite/g3doc/devguide.md | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/tensorflow/lite/g3doc/devguide.md b/tensorflow/lite/g3doc/devguide.md index 2bbc89042e..fdd02638f9 100644 --- a/tensorflow/lite/g3doc/devguide.md +++ b/tensorflow/lite/g3doc/devguide.md @@ -216,12 +216,8 @@ trained Tensorflow models to the devices. To use the converter, refer to the [Tensorflow-CoreML converter documentation](https://github.com/tf-coreml/tf-coreml). -### ARM32 and ARM64 Linux - -Compile Tensorflow Lite for a Raspberry Pi by following the [RPi build -instructions](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/g3doc/rpi.md) -Compile Tensorflow Lite for a generic aarch64 board such as Odroid C2, Pine64, -NanoPi, and others by following the [ARM64 Linux build -instructions](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/g3doc/linux_aarch64.md) -This compiles a static library file (`.a`) used to build your app. There are -plans for Python bindings and a demo app. +### Raspberry Pi + +Compile Tensorflow Lite for a Raspberry Pi by following the +[RPi build instructions](rpi.md) This compiles a static library file (`.a`) used +to build your app. There are plans for Python bindings and a demo app. -- GitLab From 6dec657b7c49e04fc27b190307d2c2eea3bf538b Mon Sep 17 00:00:00 2001 From: 1e100 <38598618+1e100@users.noreply.github.com> Date: Sat, 12 Jan 2019 22:25:18 -0800 Subject: [PATCH 020/766] Re-add the aarch64 changes to devguide --- tensorflow/lite/g3doc/devguide.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tensorflow/lite/g3doc/devguide.md b/tensorflow/lite/g3doc/devguide.md index fdd02638f9..2bbc89042e 100644 --- a/tensorflow/lite/g3doc/devguide.md +++ b/tensorflow/lite/g3doc/devguide.md @@ -216,8 +216,12 @@ trained Tensorflow models to the devices. To use the converter, refer to the [Tensorflow-CoreML converter documentation](https://github.com/tf-coreml/tf-coreml). -### Raspberry Pi - -Compile Tensorflow Lite for a Raspberry Pi by following the -[RPi build instructions](rpi.md) This compiles a static library file (`.a`) used -to build your app. There are plans for Python bindings and a demo app. +### ARM32 and ARM64 Linux + +Compile Tensorflow Lite for a Raspberry Pi by following the [RPi build +instructions](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/g3doc/rpi.md) +Compile Tensorflow Lite for a generic aarch64 board such as Odroid C2, Pine64, +NanoPi, and others by following the [ARM64 Linux build +instructions](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/g3doc/linux_aarch64.md) +This compiles a static library file (`.a`) used to build your app. There are +plans for Python bindings and a demo app. -- GitLab From 6ee08e5ff786e1c09e85910f69d9552e23ab0f93 Mon Sep 17 00:00:00 2001 From: 1e100 <38598618+1e100@users.noreply.github.com> Date: Sat, 12 Jan 2019 22:26:12 -0800 Subject: [PATCH 021/766] Remove stale devguide --- tensorflow/contrib/lite/g3doc/devguide.md | 235 ---------------------- 1 file changed, 235 deletions(-) delete mode 100644 tensorflow/contrib/lite/g3doc/devguide.md diff --git a/tensorflow/contrib/lite/g3doc/devguide.md b/tensorflow/contrib/lite/g3doc/devguide.md deleted file mode 100644 index 0a5cfc6177..0000000000 --- a/tensorflow/contrib/lite/g3doc/devguide.md +++ /dev/null @@ -1,235 +0,0 @@ - -# 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 -[TensorFlow tutorials](../../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, similar 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 -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 Android demo guide for details. - -The Android mobile 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 iOS demo 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). - -### ARM32 and ARM64 Linux - -Compile Tensorflow Lite for a Raspberry Pi by following the -[RPi build instructions](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/lite/g3doc/rpi.md) -Compile Tensorflow Lite for a generic aarch64 board such as Odroid C2, Pine64, NanoPi, and others by following the -[ARM64 Linux build instructions](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/lite/g3doc/linux_aarch64.md) -This compiles a static library file (`.a`) used to build your app. There are -plans for Python bindings and a demo app. -- GitLab From c94bcd5460b4de1dc5fea08903b9830c6c0e3a56 Mon Sep 17 00:00:00 2001 From: 1e100 <38598618+1e100@users.noreply.github.com> Date: Sat, 12 Jan 2019 22:28:04 -0800 Subject: [PATCH 022/766] Delete spurious file --- .../lite/g3doc/performance/linux_aarch64.md | 58 ------------------- 1 file changed, 58 deletions(-) delete mode 100644 tensorflow/lite/g3doc/performance/linux_aarch64.md diff --git a/tensorflow/lite/g3doc/performance/linux_aarch64.md b/tensorflow/lite/g3doc/performance/linux_aarch64.md deleted file mode 100644 index 9fd65d6219..0000000000 --- a/tensorflow/lite/g3doc/performance/linux_aarch64.md +++ /dev/null @@ -1,58 +0,0 @@ -# TensorFlow Lite for generic ARM64 boards - -## Cross compiling - -### Installing the toolchain - -```bash -sudo apt-get update -sudo apt-get install crossbuild-essential-arm64 -``` - -### Building - -Clone this Tensorflow repository. -Run this script at the root of the repository to download all the dependencies: - -> The Tensorflow repository is in `/tensorflow` if you are using `tensorflow/tensorflow:nightly-devel` docker image, just try it. - -```bash -./tensorflow/contrib/lite/tools/make/download_dependencies.sh -``` - -Note that you only need to do this once. - -Compile: - -```bash -./tensorflow/contrib/lite/tools/make/build_aarch64_lib.sh -``` - -This should compile a static library in: -`tensorflow/contrib/lite/gen/gen/aarch64_armv8-a/lib/libtensorflow-lite.a`. - -## Native compiling - -These steps were tested on HardKernel Odroid C2, gcc version 5.4.0. - -Log in to your board, install the toolchain. - -```bash -sudo apt-get install build-essential -``` - -First, clone the TensorFlow repository. Run this at the root of the repository: - -```bash -./tensorflow/contrib/lite/tools/make/download_dependencies.sh -``` -Note that you only need to do this once. - -Compile: - -```bash -./tensorflow/contrib/lite/tools/make/build_aarch64_lib.sh -``` - -This should compile a static library in: -`tensorflow/contrib/lite/gen/gen/aarch64_armv8-a/lib/libtensorflow-lite.a`. -- GitLab From ad398f462f1cc91d2259ff0d772da2dffd86d983 Mon Sep 17 00:00:00 2001 From: 1e100 <38598618+1e100@users.noreply.github.com> Date: Sat, 12 Jan 2019 22:30:46 -0800 Subject: [PATCH 023/766] Move file from contrib to new location --- .../{contrib => }/lite/tools/make/targets/aarch64_makefile.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename tensorflow/{contrib => }/lite/tools/make/targets/aarch64_makefile.inc (90%) diff --git a/tensorflow/contrib/lite/tools/make/targets/aarch64_makefile.inc b/tensorflow/lite/tools/make/targets/aarch64_makefile.inc similarity index 90% rename from tensorflow/contrib/lite/tools/make/targets/aarch64_makefile.inc rename to tensorflow/lite/tools/make/targets/aarch64_makefile.inc index 5127266047..d89440651e 100644 --- a/tensorflow/contrib/lite/tools/make/targets/aarch64_makefile.inc +++ b/tensorflow/lite/tools/make/targets/aarch64_makefile.inc @@ -1,4 +1,4 @@ -# Settings for Raspberry Pi. +# Settings for generic aarch64 boards such as Odroid C2 or Pine64. ifeq ($(TARGET),aarch64) # The aarch64 architecture covers all 64-bit ARM chips. This arch mandates # NEON, so FPU flags are not needed below. -- GitLab From ccc62a0b7cbf1eeb312d9d7ce2d887cae048d1a9 Mon Sep 17 00:00:00 2001 From: 1e100 <38598618+1e100@users.noreply.github.com> Date: Sat, 12 Jan 2019 22:35:38 -0800 Subject: [PATCH 024/766] Remove last traces of contrib --- tensorflow/lite/g3doc/devguide.md | 10 ++++------ tensorflow/lite/g3doc/linux_aarch64.md | 14 ++++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tensorflow/lite/g3doc/devguide.md b/tensorflow/lite/g3doc/devguide.md index 2bbc89042e..7143bc169f 100644 --- a/tensorflow/lite/g3doc/devguide.md +++ b/tensorflow/lite/g3doc/devguide.md @@ -219,9 +219,7 @@ devices. To use the converter, refer to the ### ARM32 and ARM64 Linux Compile Tensorflow Lite for a Raspberry Pi by following the [RPi build -instructions](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/g3doc/rpi.md) -Compile Tensorflow Lite for a generic aarch64 board such as Odroid C2, Pine64, -NanoPi, and others by following the [ARM64 Linux build -instructions](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/g3doc/linux_aarch64.md) -This compiles a static library file (`.a`) used to build your app. There are -plans for Python bindings and a demo app. +instructions](rpi.md) Compile Tensorflow Lite for a generic aarch64 board such +as Odroid C2, Pine64, NanoPi, and others by following the [ARM64 Linux build +instructions](linux_aarch64.md) This compiles a static library file (`.a`) used +to build your app. There are plans for Python bindings and a demo app. diff --git a/tensorflow/lite/g3doc/linux_aarch64.md b/tensorflow/lite/g3doc/linux_aarch64.md index 9fd65d6219..fda7a69961 100644 --- a/tensorflow/lite/g3doc/linux_aarch64.md +++ b/tensorflow/lite/g3doc/linux_aarch64.md @@ -9,6 +9,8 @@ sudo apt-get update sudo apt-get install crossbuild-essential-arm64 ``` +> If you are using Docker, you may not use `sudo`. + ### Building Clone this Tensorflow repository. @@ -17,7 +19,7 @@ Run this script at the root of the repository to download all the dependencies: > The Tensorflow repository is in `/tensorflow` if you are using `tensorflow/tensorflow:nightly-devel` docker image, just try it. ```bash -./tensorflow/contrib/lite/tools/make/download_dependencies.sh +./tensorflow/lite/tools/make/download_dependencies.sh ``` Note that you only need to do this once. @@ -25,11 +27,11 @@ Note that you only need to do this once. Compile: ```bash -./tensorflow/contrib/lite/tools/make/build_aarch64_lib.sh +./tensorflow/lite/tools/make/build_aarch64_lib.sh ``` This should compile a static library in: -`tensorflow/contrib/lite/gen/gen/aarch64_armv8-a/lib/libtensorflow-lite.a`. +`tensorflow/lite/gen/gen/aarch64_armv8-a/lib/libtensorflow-lite.a`. ## Native compiling @@ -44,15 +46,15 @@ sudo apt-get install build-essential First, clone the TensorFlow repository. Run this at the root of the repository: ```bash -./tensorflow/contrib/lite/tools/make/download_dependencies.sh +./tensorflow/lite/tools/make/download_dependencies.sh ``` Note that you only need to do this once. Compile: ```bash -./tensorflow/contrib/lite/tools/make/build_aarch64_lib.sh +./tensorflow/lite/tools/make/build_aarch64_lib.sh ``` This should compile a static library in: -`tensorflow/contrib/lite/gen/gen/aarch64_armv8-a/lib/libtensorflow-lite.a`. +`tensorflow/lite/gen/gen/aarch64_armv8-a/lib/libtensorflow-lite.a`. -- GitLab From e071e5080f9408e817488cd3693c84b6e9688ca2 Mon Sep 17 00:00:00 2001 From: Anshuman Tripathy Date: Mon, 14 Jan 2019 15:45:36 +0530 Subject: [PATCH 025/766] Lite: Gather operator test case improvement --- tensorflow/lite/kernels/gather_test.cc | 43 ++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tensorflow/lite/kernels/gather_test.cc b/tensorflow/lite/kernels/gather_test.cc index 7b5f843489..bba424fa16 100644 --- a/tensorflow/lite/kernels/gather_test.cc +++ b/tensorflow/lite/kernels/gather_test.cc @@ -96,6 +96,15 @@ TEST(GatherOpTest, Test0DIndexWith0DResult) { EXPECT_TRUE(m.GetOutputShape().empty()); } +TEST(GatherOpTest, Test1DInput1DIndex) { + GatherOpModel m({TensorType_FLOAT32, {3}}, {TensorType_INT32, {1}}); + m.SetInput({1.0, 3.0, 5.0}); + m.SetPositions({1}); + m.Invoke(); + EXPECT_THAT(m.GetOutput(), ElementsAreArray(ArrayFloatNear({3.0}))); + EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1})); +} + TEST(GatherOpTest, Test2DIndexWith2DResult) { GatherOpModel m({TensorType_FLOAT32, {3}}, {TensorType_INT32, {1, 2}}); m.SetInput({1.0, 2.0, 3.0}); @@ -137,6 +146,29 @@ TEST(FloatGatherOpTest, Axis1) { EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 2, 3})); } +TEST(FloatGatherOpTest, Axis10DIndex) { + const int axis = 1; + GatherOpModel m({TensorType_FLOAT32, {1, 3, 2}}, {TensorType_INT32, {}}, + axis); + m.SetInput({1, 2, 3, 4, 5, 6}); + m.SetPositions({1}); + m.Invoke(); + EXPECT_THAT(m.GetOutput(), ElementsAreArray(ArrayFloatNear({3, 4}))); + EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 2})); +} + +TEST(FloatGatherOpTest, Axis1Slice) { + const int axis = 1; + GatherOpModel m({TensorType_FLOAT32, {1, 4, 2}}, {TensorType_INT32, {2}}, + axis); + m.SetInput({1, 2, 3, 4, 5, 6, 7, 8}); + m.SetPositions({3, 1}); + m.Invoke(); + EXPECT_THAT(m.GetOutput(), + ElementsAreArray(ArrayFloatNear({7, 8, 3, 4}))); + EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 2, 2})); +} + TEST(FloatGatherOpTest, LastAxis) { const int axis = -1; GatherOpModel m({TensorType_FLOAT32, {1, 2, 3}}, {TensorType_INT32, {2}}, @@ -149,6 +181,17 @@ TEST(FloatGatherOpTest, LastAxis) { EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 2, 2})); } +TEST(FloatGatherOpTest, LastAxis0DIndex) { + const int axis = -1; + GatherOpModel m({TensorType_FLOAT32, {1, 2, 3}}, {TensorType_INT32, {}}, + axis); + m.SetInput({1, 2, 3, 4, 5, 6}); + m.SetPositions({2}); + m.Invoke(); + EXPECT_THAT(m.GetOutput(), ElementsAreArray(ArrayFloatNear({3, 6}))); + EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 2})); +} + TEST(TypesGatherOpTest, Float32Int32) { GatherOpModel m({TensorType_FLOAT32, {2, 2}}, {TensorType_INT32, {2}}); m.SetInput({13.3, -13.4, -1.4, 1.5}); -- GitLab From 05f97f5e75de372c368c21ed5d72efc70fe3416a Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Mon, 7 Jan 2019 21:22:47 +0000 Subject: [PATCH 026/766] Fix warning in keras base_layer_utils.py While debugging 24741 I noticed the following warning: ``` WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/tensorflow/python/keras/engine/base_layer_utils.py:125: calling Zeros.__init__ (from tensorflow.python.ops.init_ops) with dtype is deprecated and will be removed in a future version. Instructions for updating: Call initializer instance with the dtype argument instead of passing it to the constructor ``` This fix update the base_layer_utils.py to remove the warning. Note dtype is passed in the subsequent calls already so there is no need to call with dtype in __init__ in this case. Signed-off-by: Yong Tang --- tensorflow/python/keras/engine/base_layer_utils.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tensorflow/python/keras/engine/base_layer_utils.py b/tensorflow/python/keras/engine/base_layer_utils.py index 0d0257a1dd..fea5886dea 100644 --- a/tensorflow/python/keras/engine/base_layer_utils.py +++ b/tensorflow/python/keras/engine/base_layer_utils.py @@ -119,9 +119,7 @@ def make_variable(name, variable_dtype = None else: # Instantiate initializer if provided initializer is a type object. - if isinstance(initializer, type(init_ops.Initializer)): - initializer = initializer(dtype=dtype) - elif isinstance(initializer, type(init_ops_v2.Initializer)): + if isinstance(initializer, (type(init_ops.Initializer), type(init_ops_v2.Initializer))): initializer = initializer() init_val = lambda: initializer(shape, dtype=dtype) variable_dtype = dtype.base_dtype -- GitLab From 8d367542f19a1e3a24d52d7584f5eb2516544dc7 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Tue, 15 Jan 2019 00:36:13 +0000 Subject: [PATCH 027/766] Fix Line too long issues Signed-off-by: Yong Tang --- tensorflow/python/keras/engine/base_layer_utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tensorflow/python/keras/engine/base_layer_utils.py b/tensorflow/python/keras/engine/base_layer_utils.py index fea5886dea..c9fac8e343 100644 --- a/tensorflow/python/keras/engine/base_layer_utils.py +++ b/tensorflow/python/keras/engine/base_layer_utils.py @@ -119,7 +119,8 @@ def make_variable(name, variable_dtype = None else: # Instantiate initializer if provided initializer is a type object. - if isinstance(initializer, (type(init_ops.Initializer), type(init_ops_v2.Initializer))): + if isinstance(initializer, (type(init_ops.Initializer), + type(init_ops_v2.Initializer))): initializer = initializer() init_val = lambda: initializer(shape, dtype=dtype) variable_dtype = dtype.base_dtype -- GitLab From 3e03941886ffa33ad6b1139d1105c0d2a5e09b74 Mon Sep 17 00:00:00 2001 From: Spencer Schaber Date: Mon, 14 Jan 2019 19:24:34 -0600 Subject: [PATCH 028/766] Updated optimizers.py according to PR feedback --- tensorflow/contrib/layers/python/layers/optimizers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/contrib/layers/python/layers/optimizers.py b/tensorflow/contrib/layers/python/layers/optimizers.py index 839b2c66e3..3b07587503 100644 --- a/tensorflow/contrib/layers/python/layers/optimizers.py +++ b/tensorflow/contrib/layers/python/layers/optimizers.py @@ -112,8 +112,8 @@ def optimize_loss(loss, clip_gradients: float, callable or `None`. If a float is provided, a global clipping is applied to prevent the norm of the gradient from exceeding this value. Alternatively, a callable can be provided, e.g., - adaptive_clipping_fn(). This callable takes a `list` of - `(gradients, variables)` `tuple`s and returns the same thing with the + `adaptive_clipping_fn()`. This callable takes a list of + `(gradients, variables)` tuples and returns the same thing with the gradients modified. learning_rate_decay_fn: function, takes `learning_rate` and `global_step` `Tensor`s, returns `Tensor`. -- GitLab From b7db6c6545a95877d95901b7e360628c8a963c2b Mon Sep 17 00:00:00 2001 From: Matt Conley Date: Tue, 15 Jan 2019 16:39:43 -0800 Subject: [PATCH 029/766] Fix to restore desired blocks-per-core behavior to XLA. --- .../xla/service/gpu/partition_assignment.cc | 9 +++++-- .../stream_executor/cuda/cuda_gpu_executor.cc | 24 +++++++++++++++++++ .../stream_executor/device_description.cc | 1 + .../stream_executor/device_description.h | 10 ++++++++ 4 files changed, 42 insertions(+), 2 deletions(-) diff --git a/tensorflow/compiler/xla/service/gpu/partition_assignment.cc b/tensorflow/compiler/xla/service/gpu/partition_assignment.cc index bfed4f5230..b40c33371f 100644 --- a/tensorflow/compiler/xla/service/gpu/partition_assignment.cc +++ b/tensorflow/compiler/xla/service/gpu/partition_assignment.cc @@ -40,8 +40,13 @@ std::ostream& operator<<(std::ostream& out, } int64 ThreadsPerBlockLimit(const se::DeviceDescription& device_desc) { - int64 threads_per_block = device_desc.threads_per_block_limit(); - if (threads_per_block == 0) { + auto threads_per_core = device_desc.threads_per_core_limit(); + auto blocks_per_core = device_desc.blocks_per_core_limit(); + int64 threads_per_block; + if (threads_per_core != 0 && blocks_per_core != 0) { + threads_per_block = device_desc.threads_per_core_limit() / + device_desc.blocks_per_core_limit(); + } else { static std::atomic log_count{0}; if (log_count.fetch_add(1) < 8) { LOG(WARNING) << "Attempting to calculate launch dimensions for GPU " diff --git a/tensorflow/stream_executor/cuda/cuda_gpu_executor.cc b/tensorflow/stream_executor/cuda/cuda_gpu_executor.cc index 70004495cd..8a4c9cc9a6 100644 --- a/tensorflow/stream_executor/cuda/cuda_gpu_executor.cc +++ b/tensorflow/stream_executor/cuda/cuda_gpu_executor.cc @@ -1129,6 +1129,30 @@ DeviceDescription *CUDAExecutor::PopulateDeviceDescription() const { CU_DEVICE_ATTRIBUTE_MAX_REGISTERS_PER_MULTIPROCESSOR, device_) .ValueOrDie()); + const char* blank_ptx = + ".version 6.0\n" + ".target sm_30\n" + ".address_size 64\n" + "\n" + " // .globl _Z6ValAddPf\n" + ".visible .entry _Z6ValAddPf(\n" + ")\n" + "{\n" + " ret;\n" + "}\n"; + const char* kernel_name = "_Z6ValAddPf"; + + CUmodule blank_module; + CUfunction blank_function; + CUDADriver::LoadPtx(context_, blank_ptx, &blank_module); + CUDADriver::GetModuleFunction(context_, blank_module, kernel_name, + &blank_function); + + int bpc; + CUresult result = cuOccupancyMaxActiveBlocksPerMultiprocessor( + &bpc, blank_function, 1, (size_t)1); + builder.set_blocks_per_core_limit(bpc); + auto built = builder.Build(); return built.release(); } diff --git a/tensorflow/stream_executor/device_description.cc b/tensorflow/stream_executor/device_description.cc index 0b991b7ba8..532cc19067 100644 --- a/tensorflow/stream_executor/device_description.cc +++ b/tensorflow/stream_executor/device_description.cc @@ -37,6 +37,7 @@ DeviceDescription::DeviceDescription() kUninitializedUint64), block_dim_limit_(kUninitializedUint64, kUninitializedUint64, kUninitializedUint64), + blocks_per_core_limit_(kUninitializedUint64), threads_per_core_limit_(kUninitializedUint64), threads_per_block_limit_(kUninitializedUint64), threads_per_warp_(kUninitializedUint64), diff --git a/tensorflow/stream_executor/device_description.h b/tensorflow/stream_executor/device_description.h index 8ddf18629d..b71959d8c8 100644 --- a/tensorflow/stream_executor/device_description.h +++ b/tensorflow/stream_executor/device_description.h @@ -78,6 +78,10 @@ class DeviceDescription { // legitimate kernel launch request. const BlockDim &block_dim_limit() const { return block_dim_limit_; } + // Returns the limit on the number of simultaneously resident blocks + // on a multiprocessor. + uint64 blocks_per_core_limit() const { return blocks_per_core_limit_; } + // Returns the limit on the total number of threads that can be launched in a // single block; i.e. the limit on x * y * z dimensions of a ThreadDim. // This limit affects what constitutes a legitimate kernel launch request. @@ -174,6 +178,8 @@ class DeviceDescription { ThreadDim thread_dim_limit_; BlockDim block_dim_limit_; + uint64 blocks_per_core_limit_; + uint64 threads_per_core_limit_; uint64 threads_per_block_limit_; uint64 threads_per_warp_; @@ -237,6 +243,10 @@ class DeviceDescriptionBuilder { device_description_->block_dim_limit_ = value; } + void set_blocks_per_core_limit(uint64 value) { + device_description_->blocks_per_core_limit_ = value; + } + void set_threads_per_core_limit(uint64 value) { device_description_->threads_per_core_limit_ = value; } -- GitLab From eff095289b4947d6cd113620a158b20f782e2f9a Mon Sep 17 00:00:00 2001 From: MattConley Date: Tue, 15 Jan 2019 18:05:04 -0800 Subject: [PATCH 030/766] Remove unnecessary cast --- tensorflow/stream_executor/cuda/cuda_gpu_executor.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/stream_executor/cuda/cuda_gpu_executor.cc b/tensorflow/stream_executor/cuda/cuda_gpu_executor.cc index 8a4c9cc9a6..496b039325 100644 --- a/tensorflow/stream_executor/cuda/cuda_gpu_executor.cc +++ b/tensorflow/stream_executor/cuda/cuda_gpu_executor.cc @@ -1150,7 +1150,7 @@ DeviceDescription *CUDAExecutor::PopulateDeviceDescription() const { int bpc; CUresult result = cuOccupancyMaxActiveBlocksPerMultiprocessor( - &bpc, blank_function, 1, (size_t)1); + &bpc, blank_function, 1, 1); builder.set_blocks_per_core_limit(bpc); auto built = builder.Build(); -- GitLab From a363b67e4ee5a669d1e0127f99565499b1b1d4ff Mon Sep 17 00:00:00 2001 From: MattConley Date: Wed, 16 Jan 2019 11:20:23 -0800 Subject: [PATCH 031/766] Update partition_assignment error handling Now handles -1 value for an unset parameter --- tensorflow/compiler/xla/service/gpu/partition_assignment.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/compiler/xla/service/gpu/partition_assignment.cc b/tensorflow/compiler/xla/service/gpu/partition_assignment.cc index b40c33371f..94cc008d65 100644 --- a/tensorflow/compiler/xla/service/gpu/partition_assignment.cc +++ b/tensorflow/compiler/xla/service/gpu/partition_assignment.cc @@ -43,7 +43,7 @@ int64 ThreadsPerBlockLimit(const se::DeviceDescription& device_desc) { auto threads_per_core = device_desc.threads_per_core_limit(); auto blocks_per_core = device_desc.blocks_per_core_limit(); int64 threads_per_block; - if (threads_per_core != 0 && blocks_per_core != 0) { + if (threads_per_core > 0 && blocks_per_core > 0) { threads_per_block = device_desc.threads_per_core_limit() / device_desc.blocks_per_core_limit(); } else { -- GitLab From 36a065b4ada1ad5226077ecc57fd1253d6ff8ed9 Mon Sep 17 00:00:00 2001 From: Ben Barsdell Date: Wed, 16 Jan 2019 10:00:19 -0800 Subject: [PATCH 032/766] Use safe variance epsilon for float16 layer_norm - The original epsilon value (1e-12) is too small for float16 and can cause NaNs in the output when the variance is small. - This commit also adds float16 and float32 cases to LayerNormTest and improves the numerical robustness of the test logic. --- .../contrib/layers/python/layers/layers.py | 4 ++- .../layers/python/layers/layers_test.py | 26 ++++++++++++++++--- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/tensorflow/contrib/layers/python/layers/layers.py b/tensorflow/contrib/layers/python/layers/layers.py index 403b522ce4..eeefd76110 100644 --- a/tensorflow/contrib/layers/python/layers/layers.py +++ b/tensorflow/contrib/layers/python/layers/layers.py @@ -2312,7 +2312,9 @@ def layer_norm(inputs, norm_axes = list(range(begin_norm_axis, inputs_rank)) mean, variance = nn.moments(inputs, norm_axes, keep_dims=True) # Compute layer normalization using the batch_normalization function. - variance_epsilon = 1e-12 + # Note that epsilon must be increased for float16 due to the limited + # representable range. + variance_epsilon = 1e-12 if dtype != dtypes.float16 else 1e-3 outputs = nn.batch_normalization( inputs, mean, diff --git a/tensorflow/contrib/layers/python/layers/layers_test.py b/tensorflow/contrib/layers/python/layers/layers_test.py index 1c0088186c..2cd72410d3 100644 --- a/tensorflow/contrib/layers/python/layers/layers_test.py +++ b/tensorflow/contrib/layers/python/layers/layers_test.py @@ -2869,10 +2869,19 @@ class LayerNormTest(test.TestCase): tol=1e-5, begin_norm_axis=1, dtype=dtypes.float64): + eps = 1e-12 if dtype != dtypes.float16 else 1e-3 expected_mean = np.zeros(input_shape[:begin_norm_axis]) - expected_var = np.ones(input_shape[:begin_norm_axis]) - for mu in [0.0, 1e2]: - for sigma in [1.0, 0.1]: + expected_var_uncorrected = np.ones(input_shape[:begin_norm_axis]) + sigma_list = [1.0, 0.1] + if dtype == dtypes.float16: + # This causes the variance to underflow in float16, and requires that + # variance_epsilon be set appropriately to avoid NaNs in the output. + sigma_list.append(1e-4) + # Note that the mean:variance ratio must be limited to the representable + # range for float16. + for mu in [0.0, 1e2 if dtype != dtypes.float16 else 1e1]: + for sigma in sigma_list: + expected_var = expected_var_uncorrected / (1.0 + eps / sigma**2) input_values = np.random.randn(*input_shape) * sigma + mu with ops.Graph().as_default() as g: with self.session(graph=g) as sess: @@ -2893,10 +2902,13 @@ class LayerNormTest(test.TestCase): outputs, beta, gamma = sess.run((output_t, beta_var, gamma_var)) # Make sure that there are no NaNs self.assertFalse(np.isnan(outputs).any()) + if outputs.dtype != np.float64: + # Cast to float64 before computing mean/variance to avoid + # overflow and precision issues. + outputs = outputs.astype(np.float64) mean = np.mean(outputs, axis=moments_axis) var = np.var(outputs, axis=moments_axis) # Layer-norm implemented in numpy - eps = 1e-12 expected_out = ( (gamma * (input_values - np.mean( input_values, axis=moments_axis, keepdims=True)) / @@ -2933,6 +2945,12 @@ class LayerNormTest(test.TestCase): def testOutputBigInput(self): self.doOutputTest((1, 100, 100, 1)) + def testOutputBigInputFloat32(self): + self.doOutputTest((1, 100, 1000, 1), tol=1e-4, dtype=dtypes.float32) + + def testOutputBigInputFloat16(self): + self.doOutputTest((1, 100, 1000, 1), tol=5e-2, dtype=dtypes.float16) + class GDNTest(test.TestCase): -- GitLab From e31b0d045a72553fe4eab73b6935c88c71ace40b Mon Sep 17 00:00:00 2001 From: Matt Conley Date: Wed, 16 Jan 2019 16:05:47 -0800 Subject: [PATCH 033/766] Addressing comments to improve code. --- .../stream_executor/cuda/cuda_gpu_executor.cc | 31 ++++++++++++------- .../stream_executor/device_description.h | 2 +- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/tensorflow/stream_executor/cuda/cuda_gpu_executor.cc b/tensorflow/stream_executor/cuda/cuda_gpu_executor.cc index 496b039325..ef32107247 100644 --- a/tensorflow/stream_executor/cuda/cuda_gpu_executor.cc +++ b/tensorflow/stream_executor/cuda/cuda_gpu_executor.cc @@ -1129,18 +1129,20 @@ DeviceDescription *CUDAExecutor::PopulateDeviceDescription() const { CU_DEVICE_ATTRIBUTE_MAX_REGISTERS_PER_MULTIPROCESSOR, device_) .ValueOrDie()); - const char* blank_ptx = - ".version 6.0\n" - ".target sm_30\n" - ".address_size 64\n" - "\n" - " // .globl _Z6ValAddPf\n" - ".visible .entry _Z6ValAddPf(\n" - ")\n" - "{\n" - " ret;\n" - "}\n"; - const char* kernel_name = "_Z6ValAddPf"; + // We are loading a dummy ptx kernel to set the device description's + // blocks_per_core_limit by calling the CUDA occupancy calculator. This + // value is currently required XLA GPU's CalculateLaunchDimensions() + const char* blank_ptx = R"( +.version 6.0 +.target sm_30 +.address_size 64 + + // .globl testkernel +.visible .entry testkernel() +{ + ret; +})"; + const char* kernel_name = "testkernel"; CUmodule blank_module; CUfunction blank_function; @@ -1151,7 +1153,12 @@ DeviceDescription *CUDAExecutor::PopulateDeviceDescription() const { int bpc; CUresult result = cuOccupancyMaxActiveBlocksPerMultiprocessor( &bpc, blank_function, 1, 1); + if (result != CUDA_SUCCESS) { + VLOG(1) << "Failed to calculate max blocks per SM using dummy kernel."; + bpc = -1; + } builder.set_blocks_per_core_limit(bpc); + CUDADriver::UnloadModule(context_, blank_module); auto built = builder.Build(); return built.release(); diff --git a/tensorflow/stream_executor/device_description.h b/tensorflow/stream_executor/device_description.h index b71959d8c8..525669bcdc 100644 --- a/tensorflow/stream_executor/device_description.h +++ b/tensorflow/stream_executor/device_description.h @@ -78,7 +78,7 @@ class DeviceDescription { // legitimate kernel launch request. const BlockDim &block_dim_limit() const { return block_dim_limit_; } - // Returns the limit on the number of simultaneously resident blocks + // Returns the maximum number of simultaneously resident blocks // on a multiprocessor. uint64 blocks_per_core_limit() const { return blocks_per_core_limit_; } -- GitLab From eee44d9232cf2e60c5b27ffabd5d267dbefecc9d Mon Sep 17 00:00:00 2001 From: MattConley Date: Thu, 17 Jan 2019 14:49:30 -0800 Subject: [PATCH 034/766] Correctly log error --- tensorflow/stream_executor/cuda/cuda_gpu_executor.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/stream_executor/cuda/cuda_gpu_executor.cc b/tensorflow/stream_executor/cuda/cuda_gpu_executor.cc index ef32107247..89a786b980 100644 --- a/tensorflow/stream_executor/cuda/cuda_gpu_executor.cc +++ b/tensorflow/stream_executor/cuda/cuda_gpu_executor.cc @@ -1154,7 +1154,7 @@ DeviceDescription *CUDAExecutor::PopulateDeviceDescription() const { CUresult result = cuOccupancyMaxActiveBlocksPerMultiprocessor( &bpc, blank_function, 1, 1); if (result != CUDA_SUCCESS) { - VLOG(1) << "Failed to calculate max blocks per SM using dummy kernel."; + LOG(ERROR) << "Failed to calculate max blocks per SM using dummy kernel."; bpc = -1; } builder.set_blocks_per_core_limit(bpc); -- GitLab From 7428e058eb3d69d801bc3ed84822ae33b4c37fab Mon Sep 17 00:00:00 2001 From: Matt Conley Date: Thu, 17 Jan 2019 15:21:26 -0800 Subject: [PATCH 035/766] Update error handling for other CUDADriver calls. --- .../stream_executor/cuda/cuda_gpu_executor.cc | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/tensorflow/stream_executor/cuda/cuda_gpu_executor.cc b/tensorflow/stream_executor/cuda/cuda_gpu_executor.cc index 89a786b980..1b5ed79f12 100644 --- a/tensorflow/stream_executor/cuda/cuda_gpu_executor.cc +++ b/tensorflow/stream_executor/cuda/cuda_gpu_executor.cc @@ -1146,19 +1146,25 @@ DeviceDescription *CUDAExecutor::PopulateDeviceDescription() const { CUmodule blank_module; CUfunction blank_function; - CUDADriver::LoadPtx(context_, blank_ptx, &blank_module); - CUDADriver::GetModuleFunction(context_, blank_module, kernel_name, - &blank_function); - - int bpc; - CUresult result = cuOccupancyMaxActiveBlocksPerMultiprocessor( - &bpc, blank_function, 1, 1); - if (result != CUDA_SUCCESS) { + int bpc = -1; + bool ptx_success = CUDADriver::LoadPtx(context_, blank_ptx, &blank_module); + if (ptx_success) { + ptx_success = CUDADriver::GetModuleFunction(context_, blank_module, + kernel_name, &blank_function); + if (ptx_success) { + CUresult result = cuOccupancyMaxActiveBlocksPerMultiprocessor( + &bpc, blank_function, 1, 1); + if (result != CUDA_SUCCESS) { + bpc = -1; + ptx_success = false; + } + } + CUDADriver::UnloadModule(context_, blank_module); + } + if (!ptx_success) { LOG(ERROR) << "Failed to calculate max blocks per SM using dummy kernel."; - bpc = -1; } builder.set_blocks_per_core_limit(bpc); - CUDADriver::UnloadModule(context_, blank_module); auto built = builder.Build(); return built.release(); -- GitLab From 538c2ac339b4cc49bdfc796493068bb80a5f76b8 Mon Sep 17 00:00:00 2001 From: Lucas Hendren Date: Mon, 21 Jan 2019 15:09:16 -0500 Subject: [PATCH 036/766] Fixed Trace for tf.einsum in python in base case --- tensorflow/python/ops/special_math_ops.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/tensorflow/python/ops/special_math_ops.py b/tensorflow/python/ops/special_math_ops.py index 21f4996798..d102098b5c 100644 --- a/tensorflow/python/ops/special_math_ops.py +++ b/tensorflow/python/ops/special_math_ops.py @@ -217,12 +217,13 @@ def einsum(equation, *inputs, **kwargs): inputs = list(inputs) input_axis_labels = match.group(1).split(',') - + print('inputs: ',inputs,input_axis_labels,match) if len(inputs) != len(input_axis_labels): raise ValueError('Got %d arguments for equation "%s", expecting %d' % (len(inputs), equation, len(input_axis_labels))) axis_labels = set(''.join(input_axis_labels)) + print('axis_labels: ',axis_labels) if match.group(2): output_axis_labels = match.group(2)[2:] else: @@ -235,14 +236,16 @@ def einsum(equation, *inputs, **kwargs): output_axis_labels = ''.join( sorted(ax for ax in indices if counts[ax] == 1)) - + print('output: ',output_axis_labels) for a in axis_labels: for input_labels in input_axis_labels: + print('input lables loop: ',input_labels, input_axis_labels,input_labels.count(a),len(input_axis_labels)) + if len(input_axis_labels) == 1 and input_labels.count(a) == 2: + return math_ops.trace(inputs[0]) if input_labels.count(a) > 1: raise ValueError( 'Subscript not supported: an axis appears more than once: %s' % input_labels) - for a in axis_labels: input_count = sum(1 for s in input_axis_labels if a in s) if input_count > 2 and a not in output_axis_labels: @@ -253,28 +256,37 @@ def einsum(equation, *inputs, **kwargs): temp = inputs[0] temp_axis_labels = input_axis_labels[0] + print('temp: ',temp,temp_axis_labels) for i in xrange(len(inputs) - 1): axes_to_sum = ( set(temp_axis_labels) & set(input_axis_labels[i + 1]) - set(output_axis_labels)) + print('axes to sum',axes_to_sum) temp, temp_axis_labels = _einsum_reduction( temp, temp_axis_labels, inputs[i + 1], input_axis_labels[i + 1], axes_to_sum) + print('temp and temp_axis_labels to sum',temp,temp_axis_labels) + missing_indices = set(temp_axis_labels) - set(output_axis_labels) + print('missing_indices',missing_indices) if missing_indices: axis = [ i for i, a in enumerate(temp_axis_labels) if a not in output_axis_labels ] + print('axis',axis) temp = math_ops.reduce_sum(temp, axis=axis) + print('math_ops.reduce_sum:',axis,':',temp) temp_axis_labels = ''.join( a for a in temp_axis_labels if a in output_axis_labels) + print('math_ops.reduce_sum temp_axis_labels',temp_axis_labels) if sorted(temp_axis_labels) != sorted(output_axis_labels): raise ValueError('Invalid equation: %s' % equation) perm = [temp_axis_labels.index(a) for a in output_axis_labels] + print('perm: ',perm,temp) return _transpose_if_necessary(temp, perm) -- GitLab From 0f660699dac6753c1546416d645a880f0f8a6633 Mon Sep 17 00:00:00 2001 From: Lucas Hendren Date: Mon, 21 Jan 2019 16:35:55 -0500 Subject: [PATCH 037/766] Added in Unit tests for Trace in einsum --- tensorflow/python/ops/special_math_ops.py | 14 +------------- tensorflow/python/ops/special_math_ops_test.py | 4 +++- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/tensorflow/python/ops/special_math_ops.py b/tensorflow/python/ops/special_math_ops.py index d102098b5c..a8f93d5f2d 100644 --- a/tensorflow/python/ops/special_math_ops.py +++ b/tensorflow/python/ops/special_math_ops.py @@ -217,13 +217,11 @@ def einsum(equation, *inputs, **kwargs): inputs = list(inputs) input_axis_labels = match.group(1).split(',') - print('inputs: ',inputs,input_axis_labels,match) if len(inputs) != len(input_axis_labels): raise ValueError('Got %d arguments for equation "%s", expecting %d' % (len(inputs), equation, len(input_axis_labels))) axis_labels = set(''.join(input_axis_labels)) - print('axis_labels: ',axis_labels) if match.group(2): output_axis_labels = match.group(2)[2:] else: @@ -236,11 +234,10 @@ def einsum(equation, *inputs, **kwargs): output_axis_labels = ''.join( sorted(ax for ax in indices if counts[ax] == 1)) - print('output: ',output_axis_labels) for a in axis_labels: for input_labels in input_axis_labels: print('input lables loop: ',input_labels, input_axis_labels,input_labels.count(a),len(input_axis_labels)) - if len(input_axis_labels) == 1 and input_labels.count(a) == 2: + if len(input_axis_labels) == 1 and input_labels.count(a) == 2 and input_labels==input_labels[::-1]: return math_ops.trace(inputs[0]) if input_labels.count(a) > 1: raise ValueError( @@ -256,37 +253,28 @@ def einsum(equation, *inputs, **kwargs): temp = inputs[0] temp_axis_labels = input_axis_labels[0] - print('temp: ',temp,temp_axis_labels) for i in xrange(len(inputs) - 1): axes_to_sum = ( set(temp_axis_labels) & set(input_axis_labels[i + 1]) - set(output_axis_labels)) - print('axes to sum',axes_to_sum) temp, temp_axis_labels = _einsum_reduction( temp, temp_axis_labels, inputs[i + 1], input_axis_labels[i + 1], axes_to_sum) - print('temp and temp_axis_labels to sum',temp,temp_axis_labels) missing_indices = set(temp_axis_labels) - set(output_axis_labels) - print('missing_indices',missing_indices) if missing_indices: axis = [ i for i, a in enumerate(temp_axis_labels) if a not in output_axis_labels ] - print('axis',axis) temp = math_ops.reduce_sum(temp, axis=axis) - print('math_ops.reduce_sum:',axis,':',temp) temp_axis_labels = ''.join( a for a in temp_axis_labels if a in output_axis_labels) - print('math_ops.reduce_sum temp_axis_labels',temp_axis_labels) - if sorted(temp_axis_labels) != sorted(output_axis_labels): raise ValueError('Invalid equation: %s' % equation) perm = [temp_axis_labels.index(a) for a in output_axis_labels] - print('perm: ',perm,temp) return _transpose_if_necessary(temp, perm) diff --git a/tensorflow/python/ops/special_math_ops_test.py b/tensorflow/python/ops/special_math_ops_test.py index 94aaebed95..34f891edea 100644 --- a/tensorflow/python/ops/special_math_ops_test.py +++ b/tensorflow/python/ops/special_math_ops_test.py @@ -249,7 +249,9 @@ class EinsumTest(test.TestCase): 'ab, ab, cd, cd, ef, ef -> ', 'abc, bac', 'iJ, Ki -> JK', - 'iJk, Jklm -> Jk' + 'iJk, Jklm -> Jk', + 'ii', + 'ijji' ] long_cases = [ -- GitLab From 6a414d08341626d9dd3bd3b4a8889e8c9b6621d3 Mon Sep 17 00:00:00 2001 From: Lucas Hendren Date: Mon, 21 Jan 2019 16:43:46 -0500 Subject: [PATCH 038/766] Removed logging from einsum --- tensorflow/python/ops/special_math_ops.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tensorflow/python/ops/special_math_ops.py b/tensorflow/python/ops/special_math_ops.py index a8f93d5f2d..eb5880b611 100644 --- a/tensorflow/python/ops/special_math_ops.py +++ b/tensorflow/python/ops/special_math_ops.py @@ -236,7 +236,6 @@ def einsum(equation, *inputs, **kwargs): sorted(ax for ax in indices if counts[ax] == 1)) for a in axis_labels: for input_labels in input_axis_labels: - print('input lables loop: ',input_labels, input_axis_labels,input_labels.count(a),len(input_axis_labels)) if len(input_axis_labels) == 1 and input_labels.count(a) == 2 and input_labels==input_labels[::-1]: return math_ops.trace(inputs[0]) if input_labels.count(a) > 1: -- GitLab From b20d3db459ed50f74a1ad53de9d371a3d10df1af Mon Sep 17 00:00:00 2001 From: I-Hong Jhuo Date: Wed, 23 Jan 2019 14:42:06 -0800 Subject: [PATCH 039/766] replace initialize_all_variables --- tensorflow/tools/compatibility/testdata/test_file_v0_11.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/tools/compatibility/testdata/test_file_v0_11.py b/tensorflow/tools/compatibility/testdata/test_file_v0_11.py index 68ba7a2630..a73ad302d9 100644 --- a/tensorflow/tools/compatibility/testdata/test_file_v0_11.py +++ b/tensorflow/tools/compatibility/testdata/test_file_v0_11.py @@ -196,14 +196,14 @@ class TestUpgrade(test_util.TensorFlowTestCase): # make some variables _ = [tf.Variable([1, 2, 3], dtype=tf.float32), tf.Variable([1, 2, 3], dtype=tf.int32)] - s.run(tf.initialize_all_variables()) + s.run(tf.global_variables_initializer()) _ = [v.name for v in tf.all_variables()] _ = [v.name for v in tf.local_variables()] def testSummaries(self): with self.cached_session() as s: var = tf.Variable([1, 2, 3], dtype=tf.float32) - s.run(tf.initialize_all_variables()) + s.run(tf.global_variables_initializer()) x, y = np.meshgrid(np.linspace(-10, 10, 256), np.linspace(-10, 10, 256)) image = np.sin(x**2 + y**2) / np.sqrt(x**2 + y**2) * .5 + .5 image = image[None, :, :, None] -- GitLab From ba08b73076b853c028500969f4eb733f190e9cac Mon Sep 17 00:00:00 2001 From: Thomas Deegan Date: Wed, 23 Jan 2019 16:48:59 -0800 Subject: [PATCH 040/766] Update profiler ui documentation. --- tensorflow/core/profiler/README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tensorflow/core/profiler/README.md b/tensorflow/core/profiler/README.md index 57d76eb4cb..17c0268cec 100644 --- a/tensorflow/core/profiler/README.md +++ b/tensorflow/core/profiler/README.md @@ -35,9 +35,8 @@ bazel-bin/tensorflow/core/profiler/profiler \ --profile_path=/tmp/train_dir/profile_xx tfprof> op -select micros,bytes,occurrence -order_by micros -# To be open sourced... -bazel-bin/tensorflow/python/profiler/profiler_ui \ - --profile_path=/tmp/profiles/profile_1 +# Profiler ui available at: https://github.com/tensorflow/profiler-ui +python ui.py --profile_context_path=/tmp/train_dir/profile_xx ``` ![ProfilerUI](g3doc/profiler_ui.jpg) -- GitLab From 00cadb21201683faa2948906a724f61904c50a3f Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Fri, 25 Jan 2019 05:52:55 +0000 Subject: [PATCH 041/766] Fix incorrect link in the docs This fix fixes incorrect link in the doc where there is a duplicate: `http://https://` -> `https://` Signed-off-by: Yong Tang --- tensorflow/contrib/gan/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/contrib/gan/README.md b/tensorflow/contrib/gan/README.md index 4eac4e80cd..3605fd425c 100644 --- a/tensorflow/contrib/gan/README.md +++ b/tensorflow/contrib/gan/README.md @@ -9,7 +9,7 @@ explicitly model the distribution and without writing an explicit loss. For example, the generator could learn to draw samples from the distribution of natural images. For more details on this technique, see ['Generative Adversarial Networks'](https://arxiv.org/abs/1406.2661) by -Goodfellow et al. See [tensorflow/models](https://github.com/tensorflow/models/tree/master/research/gan/) for examples, and [this tutorial](http://https://github.com/tensorflow/models/tree/master/research/gan/tutorial.ipynb) for an +Goodfellow et al. See [tensorflow/models](https://github.com/tensorflow/models/tree/master/research/gan/) for examples, and [this tutorial](https://github.com/tensorflow/models/tree/master/research/gan/tutorial.ipynb) for an introduction. #### Usage -- GitLab From 2b47090304d9eec526b7de8fa3e6ebc6e4d7cdcb Mon Sep 17 00:00:00 2001 From: Pariksheet Pinjari Date: Fri, 25 Jan 2019 15:23:51 +0530 Subject: [PATCH 042/766] Keras added missing test case in image_test Missing test cases added --- tensorflow/python/keras/preprocessing/image_test.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tensorflow/python/keras/preprocessing/image_test.py b/tensorflow/python/keras/preprocessing/image_test.py index 4abaadfcd3..f7cbb589dc 100644 --- a/tensorflow/python/keras/preprocessing/image_test.py +++ b/tensorflow/python/keras/preprocessing/image_test.py @@ -386,6 +386,8 @@ class TestImage(test.TestCase): _ = keras.preprocessing.image.random_shift(x, 0.2, 0.2) _ = keras.preprocessing.image.random_shear(x, 2.) _ = keras.preprocessing.image.random_zoom(x, (0.5, 0.5)) + _ = keras.preprocessing.image.apply_channel_shift(x, 2, 2) + _ = keras.preprocessing.image.apply_affine_transform(x, 2) with self.assertRaises(ValueError): keras.preprocessing.image.random_zoom(x, (0, 0, 0)) _ = keras.preprocessing.image.random_channel_shift(x, 2.) -- GitLab From 7024ea175a0db2d5e1ecce7853cda42b27f01081 Mon Sep 17 00:00:00 2001 From: 1e100 <38598618+1e100@users.noreply.github.com> Date: Mon, 28 Jan 2019 01:33:14 -0800 Subject: [PATCH 043/766] Forgot to add build script --- .../lite/tools/make/build_aarch64_lib.sh | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 tensorflow/lite/tools/make/build_aarch64_lib.sh diff --git a/tensorflow/lite/tools/make/build_aarch64_lib.sh b/tensorflow/lite/tools/make/build_aarch64_lib.sh new file mode 100644 index 0000000000..054b3daedf --- /dev/null +++ b/tensorflow/lite/tools/make/build_aarch64_lib.sh @@ -0,0 +1,22 @@ +#!/bin/bash -x +# 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. +# ============================================================================== + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR/../../../.." + +CC_PREFIX=aarch64-linux-gnu- make -j 3 -f tensorflow/lite/tools/make/Makefile TARGET=aarch64 TARGET_ARCH=armv8-a -- GitLab From 5cd15413e094e2446bb910a47417b54d85278b12 Mon Sep 17 00:00:00 2001 From: 1e100 <38598618+1e100@users.noreply.github.com> Date: Mon, 28 Jan 2019 01:34:54 -0800 Subject: [PATCH 044/766] Moved script from contrib to where Lite is now --- .../lite/tools/make/build_aarch64_lib.sh | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100755 tensorflow/contrib/lite/tools/make/build_aarch64_lib.sh diff --git a/tensorflow/contrib/lite/tools/make/build_aarch64_lib.sh b/tensorflow/contrib/lite/tools/make/build_aarch64_lib.sh deleted file mode 100755 index 5c1cf12ecb..0000000000 --- a/tensorflow/contrib/lite/tools/make/build_aarch64_lib.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash -x -# 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. -# ============================================================================== - -set -e - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$SCRIPT_DIR/../../../../.." - -CC_PREFIX=aarch64-linux-gnu- make -j 3 -f tensorflow/contrib/lite/tools/make/Makefile TARGET=aarch64 TARGET_ARCH=armv8-a -- GitLab From 27594c96cdfba2261400ef040a962f5d8e8d5ea4 Mon Sep 17 00:00:00 2001 From: Joseph Friedman <4613488+DecentGradient@users.noreply.github.com> Date: Mon, 28 Jan 2019 15:48:04 -0500 Subject: [PATCH 045/766] Broken Link fix broken link to post training quantization doc --- tensorflow/lite/g3doc/devguide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/g3doc/devguide.md b/tensorflow/lite/g3doc/devguide.md index cbad036407..6395c92da1 100644 --- a/tensorflow/lite/g3doc/devguide.md +++ b/tensorflow/lite/g3doc/devguide.md @@ -250,7 +250,7 @@ tflite_quantized_model=converter.convert() open(“quantized_model.tflite”, “wb”).write(tflite_quantized_model) ``` -Read the full documentation [here](performance/post_training_quantization) and see a tutorial [here](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/tutorials/post_training_quant.ipynb). +Read the full documentation [here](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/g3doc/performance/post_training_quantization.md) and see a tutorial [here](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/tutorials/post_training_quant.ipynb). ### GPU Run on GPU GPUs are designed to have high throughput for massively -- GitLab From 332eab8b81c289446155862df40ac075108917fa Mon Sep 17 00:00:00 2001 From: musikisomorphie Date: Thu, 17 Jan 2019 20:17:42 +0100 Subject: [PATCH 046/766] achieve tensor[... > a] syntax remove some comments remove some comments remove shape check for x[x>a] syntax remove shape check for x[x>a] syntax in array_ops_test.py --- .../python/kernel_tests/array_ops_test.py | 47 +++++++++++++++---- tensorflow/python/ops/array_ops.py | 12 +++-- 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/tensorflow/python/kernel_tests/array_ops_test.py b/tensorflow/python/kernel_tests/array_ops_test.py index f4c442b7b1..b017e7feec 100644 --- a/tensorflow/python/kernel_tests/array_ops_test.py +++ b/tensorflow/python/kernel_tests/array_ops_test.py @@ -507,6 +507,19 @@ class StridedSliceChecker(object): def __getitem__(self, spec): op = self.x.__getitem__(spec) + + def eval_if_tensor(x): + try: + return x.eval() + except AttributeError: + return x + + if isinstance(spec, (np.ndarray, ops.Tensor)): + tensor = op.eval() + np_specs = eval_if_tensor(spec) + self.test.assertAllEqual(self.x_np[np_specs], tensor) + return tensor + if not isinstance(spec, (list, tuple)): spec = [spec] @@ -515,12 +528,6 @@ class StridedSliceChecker(object): # Make a numpy spec that pre-evals the tensors np_specs = [] - def eval_if_tensor(x): - try: - return x.eval() - except AttributeError: - return x - for s in spec: if isinstance(s, slice): start = eval_if_tensor(s.start) @@ -722,6 +729,21 @@ class StridedSliceTest(test_util.TensorFlowTestCase): # First axis slice _ = checker[np.newaxis, 1:] + @test_util.run_deprecated_v1 + def testMasks(self): + with self.session(use_gpu=True): + # Test tensor type mask + checker = StridedSliceChecker(self, StridedSliceChecker.REF_TENSOR) + _ = checker[checker.x > 2] + _ = checker[checker.x <= 5] + + # Test numpy array type mask + raw = np.array([[[[[1, 2, 4, 5], [5, 6, 7, 8], [9, 10, 11, 12]]], + [[[13, 14, 15, 16], [17, 18, 19, 20], [21, 22, 23, 24]]]]]) + checker1 = StridedSliceChecker(self, raw) + _ = checker1[raw >= 4] + _ = checker1[raw < 19] + class StridedSliceShapeChecker(object): @@ -807,7 +829,7 @@ class GradSliceChecker(object): analytic_grad2 = 2 * slice_val dy = variables.Variable( - array_ops.ones(shape=slice_var.get_shape(), dtype=dtypes.float32)) + array_ops.ones_like(slice_var, dtype=dtypes.float32)) assign = dy.assign(slice_var) slice_val_grad, = gradients_impl.gradients(slice_val, self.var, grad_ys=dy) slice_val_grad2, = gradients_impl.gradients( @@ -821,6 +843,8 @@ class GradSliceChecker(object): # compute analytic gradient for slice np_val_grad = (2 * self.varnp * self.varnp) np_sliceval_grad = np.zeros(self.var.get_shape()) + if isinstance(spec, ops.Tensor): + spec = self.sess.run([spec]) np_sliceval_grad[spec] = np_val_grad[spec] # verify gradient self.test.assertAllEqual(slice_val_grad_evaled, np_sliceval_grad) @@ -838,8 +862,8 @@ class StridedSliceGradTest(test_util.TensorFlowTestCase): init = variables.global_variables_initializer() sess.run(init) - grad = GradSliceChecker(self, sess, var, - np.array(range(1, 97, 1)).reshape((6, 4, 4))) + raw = np.array(range(1, 97, 1)).reshape((6, 4, 4)) + grad = GradSliceChecker(self, sess, var, raw) _ = grad[2:6:2, 1:3, 1:3] _ = grad[3:0:-2, 1:3, 1:3] _ = grad[3:0:-2, array_ops.newaxis, 1:3, 2, array_ops.newaxis] @@ -851,6 +875,11 @@ class StridedSliceGradTest(test_util.TensorFlowTestCase): with self.assertRaisesRegexp(ValueError, "out of bounds"): _ = grad[:, 200, :] + # Test numpy array type mask + _ = grad[raw > 51] + # Test tensor type mask + _ = grad[ops.convert_to_tensor(raw) <= 76] + def testGradientZero(self): with self.session(use_gpu=True) as sess: var = variables.Variable(8.) diff --git a/tensorflow/python/ops/array_ops.py b/tensorflow/python/ops/array_ops.py index 014fdd25cc..b167ff9647 100644 --- a/tensorflow/python/ops/array_ops.py +++ b/tensorflow/python/ops/array_ops.py @@ -553,6 +553,10 @@ def _slice_helper(tensor, slice_spec, var=None): print(foo[tf.newaxis, :, :].eval()) # => [[[1,2,3], [4,5,6], [7,8,9]]] print(foo[tf.newaxis, ...].eval()) # => [[[1,2,3], [4,5,6], [7,8,9]]] print(foo[tf.newaxis].eval()) # => [[[1,2,3], [4,5,6], [7,8,9]]] + + # masks + foo = tf.constant([[1,2,3], [4,5,6], [7,8,9]]) + print(foo[foo > 2].eval()) # => [3, 4, 5, 6, 7, 8, 9] ``` Notes: @@ -575,6 +579,8 @@ def _slice_helper(tensor, slice_spec, var=None): TypeError: If the slice indices aren't int, slice, ellipsis, tf.newaxis or scalar int32/int64 tensors. """ + if isinstance(slice_spec, (np.ndarray, ops.Tensor)): + return boolean_mask(tensor=tensor, mask=slice_spec) if not isinstance(slice_spec, (list, tuple)): slice_spec = [slice_spec] @@ -1324,11 +1330,11 @@ def boolean_mask(tensor, mask, name="boolean_mask", axis=None): axis = 0 if axis is None else axis shape_tensor[axis:axis + ndims_mask].assert_is_compatible_with(shape_mask) - leading_size = gen_math_ops.prod(shape(tensor)[axis:axis + ndims_mask], [0]) + leading_size = gen_math_ops.prod(shape_tensor[axis:axis + ndims_mask], [0]) tensor = reshape(tensor, concat([ - shape(tensor)[:axis], [leading_size], - shape(tensor)[axis + ndims_mask:] + shape_tensor[:axis], [leading_size], + shape_tensor[axis + ndims_mask:] ], 0)) first_dim = shape_tensor[axis:axis + ndims_mask].num_elements() tensor.set_shape( -- GitLab From f5c6287407116fbeecb7926af4801177edc3afa3 Mon Sep 17 00:00:00 2001 From: Lucas Hendren Date: Tue, 29 Jan 2019 02:58:42 -0500 Subject: [PATCH 047/766] Update doc for einsum to show trace option --- tensorflow/python/ops/special_math_ops.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tensorflow/python/ops/special_math_ops.py b/tensorflow/python/ops/special_math_ops.py index eb5880b611..3ba29cb6a0 100644 --- a/tensorflow/python/ops/special_math_ops.py +++ b/tensorflow/python/ops/special_math_ops.py @@ -172,6 +172,9 @@ def einsum(equation, *inputs, **kwargs): # Transpose >>> einsum('ij->ji', m) # output[j,i] = m[i,j] + # Trace + >>> einsum('ii', m) # output[j,i] = trace_i + # Batch matrix multiplication >>> einsum('aij,ajk->aik', s, t) # out[a,i,k] = sum_j s[a,i,j] * t[a, j, k] ``` @@ -180,7 +183,7 @@ def einsum(equation, *inputs, **kwargs): * Ellipses (subscripts like `ij...,jk...->ik...`) * Subscripts where an axis appears more than once for a single input - (e.g. `ijj,k->ik`). + (e.g. `ijj,k->ik`) unless it is a trace (e.g. `ijji`). Args: equation: a `str` describing the contraction, in the same format as -- GitLab From 986a2231b7ce55468098bdcb1194d24917fd3fd7 Mon Sep 17 00:00:00 2001 From: drpngx Date: Tue, 29 Jan 2019 10:17:45 -0800 Subject: [PATCH 048/766] Update doc to be slightly more specific. --- tensorflow/python/ops/special_math_ops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/ops/special_math_ops.py b/tensorflow/python/ops/special_math_ops.py index 3ba29cb6a0..e3a79e3f72 100644 --- a/tensorflow/python/ops/special_math_ops.py +++ b/tensorflow/python/ops/special_math_ops.py @@ -173,7 +173,7 @@ def einsum(equation, *inputs, **kwargs): >>> einsum('ij->ji', m) # output[j,i] = m[i,j] # Trace - >>> einsum('ii', m) # output[j,i] = trace_i + >>> einsum('ii', m) # output[j,i] = trace(m) = sum_i m[i, i] # Batch matrix multiplication >>> einsum('aij,ajk->aik', s, t) # out[a,i,k] = sum_j s[a,i,j] * t[a, j, k] -- GitLab From 8d205ceaad662170a4d233c450f67c29c3cba554 Mon Sep 17 00:00:00 2001 From: jwu Date: Tue, 29 Jan 2019 20:59:20 +0100 Subject: [PATCH 049/766] remove @test_util.run_deprecated_v1 for testMasks function --- tensorflow/python/kernel_tests/array_ops_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tensorflow/python/kernel_tests/array_ops_test.py b/tensorflow/python/kernel_tests/array_ops_test.py index b017e7feec..a9cd010880 100644 --- a/tensorflow/python/kernel_tests/array_ops_test.py +++ b/tensorflow/python/kernel_tests/array_ops_test.py @@ -729,7 +729,6 @@ class StridedSliceTest(test_util.TensorFlowTestCase): # First axis slice _ = checker[np.newaxis, 1:] - @test_util.run_deprecated_v1 def testMasks(self): with self.session(use_gpu=True): # Test tensor type mask -- GitLab From 1e407d6096328079054d879c26cf25563fbc220c Mon Sep 17 00:00:00 2001 From: Lucas Hendren Date: Tue, 29 Jan 2019 19:35:42 -0500 Subject: [PATCH 050/766] Fixed linting issues --- tensorflow/python/ops/special_math_ops.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/ops/special_math_ops.py b/tensorflow/python/ops/special_math_ops.py index 3ba29cb6a0..e301c65101 100644 --- a/tensorflow/python/ops/special_math_ops.py +++ b/tensorflow/python/ops/special_math_ops.py @@ -239,8 +239,9 @@ def einsum(equation, *inputs, **kwargs): sorted(ax for ax in indices if counts[ax] == 1)) for a in axis_labels: for input_labels in input_axis_labels: - if len(input_axis_labels) == 1 and input_labels.count(a) == 2 and input_labels==input_labels[::-1]: - return math_ops.trace(inputs[0]) + if (len(input_axis_labels) == 1 and input_labels.count(a) == 2 + and input_labels == input_labels[::-1]): + return math_ops.trace(inputs[0]) if input_labels.count(a) > 1: raise ValueError( 'Subscript not supported: an axis appears more than once: %s' % -- GitLab From fa8b0f37f39449fea13259b6e954753c5b613128 Mon Sep 17 00:00:00 2001 From: Lucas Hendren Date: Wed, 30 Jan 2019 01:50:00 -0500 Subject: [PATCH 051/766] Fixed formatting error for einsum --- tensorflow/python/ops/special_math_ops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/ops/special_math_ops.py b/tensorflow/python/ops/special_math_ops.py index e90876bb77..5a0f0782dd 100644 --- a/tensorflow/python/ops/special_math_ops.py +++ b/tensorflow/python/ops/special_math_ops.py @@ -240,7 +240,7 @@ def einsum(equation, *inputs, **kwargs): for a in axis_labels: for input_labels in input_axis_labels: if (len(input_axis_labels) == 1 and input_labels.count(a) == 2 - and input_labels == input_labels[::-1]): + and input_labels == input_labels[::-1] and '->' not in equation): return math_ops.trace(inputs[0]) if input_labels.count(a) > 1: raise ValueError( -- GitLab From a2652de97e2f38dde953c406b63d10e6f5047769 Mon Sep 17 00:00:00 2001 From: jwu Date: Wed, 30 Jan 2019 20:43:01 +0100 Subject: [PATCH 052/766] resolve tensor[tf.constant(0)] error, add corresponding test in array_ops_test.py --- tensorflow/python/kernel_tests/array_ops_test.py | 9 +++++++-- tensorflow/python/ops/array_ops.py | 11 ++++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/tensorflow/python/kernel_tests/array_ops_test.py b/tensorflow/python/kernel_tests/array_ops_test.py index a9cd010880..6ba175704c 100644 --- a/tensorflow/python/kernel_tests/array_ops_test.py +++ b/tensorflow/python/kernel_tests/array_ops_test.py @@ -517,8 +517,10 @@ class StridedSliceChecker(object): if isinstance(spec, (np.ndarray, ops.Tensor)): tensor = op.eval() np_specs = eval_if_tensor(spec) - self.test.assertAllEqual(self.x_np[np_specs], tensor) - return tensor + ndims_np_specs = np_specs.ndim + if ndims_np_specs != 0: + self.test.assertAllEqual(self.x_np[np_specs], tensor) + return tensor if not isinstance(spec, (list, tuple)): spec = [spec] @@ -731,10 +733,12 @@ class StridedSliceTest(test_util.TensorFlowTestCase): def testMasks(self): with self.session(use_gpu=True): + scalar = np.array(0) # Test tensor type mask checker = StridedSliceChecker(self, StridedSliceChecker.REF_TENSOR) _ = checker[checker.x > 2] _ = checker[checker.x <= 5] + _ = checker[ops.convert_to_tensor(scalar)] # Test numpy array type mask raw = np.array([[[[[1, 2, 4, 5], [5, 6, 7, 8], [9, 10, 11, 12]]], @@ -742,6 +746,7 @@ class StridedSliceTest(test_util.TensorFlowTestCase): checker1 = StridedSliceChecker(self, raw) _ = checker1[raw >= 4] _ = checker1[raw < 19] + _ = checker1[scalar] class StridedSliceShapeChecker(object): diff --git a/tensorflow/python/ops/array_ops.py b/tensorflow/python/ops/array_ops.py index b167ff9647..5349810176 100644 --- a/tensorflow/python/ops/array_ops.py +++ b/tensorflow/python/ops/array_ops.py @@ -579,11 +579,16 @@ def _slice_helper(tensor, slice_spec, var=None): TypeError: If the slice indices aren't int, slice, ellipsis, tf.newaxis or scalar int32/int64 tensors. """ - if isinstance(slice_spec, (np.ndarray, ops.Tensor)): + if isinstance(slice_spec, np.ndarray): + ndims_slice_spec = slice_spec.ndim + if ndims_slice_spec != 0: return boolean_mask(tensor=tensor, mask=slice_spec) - if not isinstance(slice_spec, (list, tuple)): - slice_spec = [slice_spec] + if isinstance(slice_spec, ops.Tensor): + shape_slice_spec = slice_spec.get_shape() + ndims_slice_spec = shape_slice_spec.ndims + if ndims_slice_spec != 0: + return boolean_mask(tensor=tensor, mask=slice_spec) begin, end, strides = [], [], [] index = 0 -- GitLab From fd3c04d654fabfae76ec6bf4d9648343cc0cf8fc Mon Sep 17 00:00:00 2001 From: Shahzad Lone Date: Wed, 30 Jan 2019 21:01:04 -0500 Subject: [PATCH 053/766] Fix variables that seem to not be modified to consts. --- tensorflow/stream_executor/cuda/cuda_dnn.cc | 42 ++++++++++----------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/tensorflow/stream_executor/cuda/cuda_dnn.cc b/tensorflow/stream_executor/cuda/cuda_dnn.cc index 283c907ab0..3d233250aa 100644 --- a/tensorflow/stream_executor/cuda/cuda_dnn.cc +++ b/tensorflow/stream_executor/cuda/cuda_dnn.cc @@ -338,7 +338,7 @@ class CudnnAccess { mutex_lock lock(mutex_); gpu::ScopedActivateExecutorContext context(executor); CUstream cu_stream = stream ? AsGpuStreamValue(stream) : cudaStreamLegacy; - auto status = cudnnSetStream(handle_, cu_stream); + const auto status = cudnnSetStream(handle_, cu_stream); CHECK_EQ(status, CUDNN_STATUS_SUCCESS) << "Failed to set cuDNN stream."; return CudnnHandle(std::move(context), std::move(lock), handle_); } @@ -453,7 +453,7 @@ CudnnSupport::CudnnSupport(GpuExecutor* parent) : parent_(parent) {} port::Status CudnnSupport::Init() { ScopedActivateExecutorContext context(parent_); cudnnHandle_t cudnn_handle = nullptr; - auto status = cudnnCreate(&cudnn_handle); + const auto status = cudnnCreate(&cudnn_handle); if (status == CUDNN_STATUS_SUCCESS) { CudnnVersion source_version(CUDNN_MAJOR, CUDNN_MINOR, CUDNN_PATCHLEVEL); @@ -886,7 +886,7 @@ class CudnnPoolingDescriptor { std::transform(shape64.cbegin(), shape64.cend(), shape.begin(), &CheckedNarrowing); bool propagate_nans = pooling_descriptor.propagate_nans(); - auto cudnn_max_pooling_mode = RequireDeterminism() + const auto cudnn_max_pooling_mode = RequireDeterminism() ? CUDNN_POOLING_MAX_DETERMINISTIC : CUDNN_POOLING_MAX; CHECK_CUDNN_OK(cudnnSetPoolingNdDescriptor( @@ -2506,7 +2506,7 @@ port::StatusOr GetCudnnConvolutionForwardAlgorithm( algo_desc = dnn::AlgorithmDesc(algo, /*use_tensor_ops=*/true); } - auto scratch_or = AllocateCudnnConvolutionForwardWorkspace( + const auto scratch_or = AllocateCudnnConvolutionForwardWorkspace( stream, cudnn, input_nd, filter, conv, output_nd, *algo_desc, scratch_allocator); @@ -2555,7 +2555,7 @@ port::StatusOr GetCudnnConvolutionBackwardDataAlgorithm( algo_desc = dnn::AlgorithmDesc(algo, /*use_tensor_ops=*/true); } - auto scratch_or = AllocateCudnnConvolutionBackwardDataWorkspace( + const auto scratch_or = AllocateCudnnConvolutionBackwardDataWorkspace( stream, cudnn, input_nd, filter, conv, output_nd, *algo_desc, scratch_allocator); @@ -2924,7 +2924,7 @@ port::Status CudnnSupport::DoConvolve( } } - auto get_fwd_bugs = [&]() -> port::Status { + const auto get_fwd_bugs = [&]() -> port::Status { // Report an error if we might be hitting a cuDNN bug that accesses illegal // memory. See nvbugs/2138754, b/80018418. if (CUDNN_VERSION < 7300) { @@ -2935,7 +2935,7 @@ port::Status CudnnSupport::DoConvolve( return port::Status::OK(); } // Checks that a*b is within the valid range (as provided by NVIDIA). - auto check_sizes = [](size_t a, size_t b) { + const auto check_sizes = [](size_t a, size_t b) { if ((a * b * 4608 - 1) >> 31 == 0) { return port::Status::OK(); } @@ -2989,7 +2989,7 @@ port::Status CudnnSupport::DoConvolve( return port::Status::OK(); }; - auto get_bwd_filter_bugs = [&]() -> port::Status { + const auto get_bwd_filter_bugs = [&]() -> port::Status { // Report an error if we might be hitting a cuDNN bug that produces // incorrect results. See nvbugs/2072856 if (CUDNN_VERSION < 7300) { @@ -3675,7 +3675,7 @@ bool CudnnSupport::DoTransformTensor(Stream* stream, CudnnTensorDescriptor output_tensor_desc( output_desc, ToCudnnDataType(output_type, output_desc.layout())); auto cudnn = cudnn_->GetHandle(parent_, stream); - auto status = [&] { + auto const status = [&] { RETURN_IF_CUDNN_ERROR(cudnnTransformTensor( cudnn.handle(), &scale, input_tensor_desc.handle(), input_data.opaque(), &beta, output_tensor_desc.handle(), output_data->opaque())); @@ -3908,7 +3908,7 @@ bool CudnnSupport::DoBiasAdd(Stream* stream, auto cudnn = cudnn_->GetHandle(parent_, stream); - auto status = [&] { + const auto status = [&] { RETURN_IF_CUDNN_ERROR(cudnnAddTensor( cudnn.handle(), &alpha, bias_descriptor.handle(), biases.opaque(), &beta, input_descriptor.handle(), output_data->opaque())); @@ -3933,7 +3933,7 @@ bool CudnnSupport::DoActivate(Stream* stream, float beta = 0.0; auto cudnn = cudnn_->GetHandle(parent_, stream); - auto status = [&] { + const auto status = [&] { RETURN_IF_CUDNN_ERROR(cudnnActivationForward( cudnn.handle(), activation_desc.handle(), &alpha, input_nd.handle(), input_data.opaque(), &beta, input_nd.handle(), output_data->opaque())); @@ -3958,7 +3958,7 @@ bool CudnnSupport::DoPoolForward( CudnnPoolingDescriptor pooling_desc(pooling_dimensions); auto cudnn = cudnn_->GetHandle(parent_, stream); - auto status = [&] { + const auto status = [&] { RETURN_IF_CUDNN_ERROR(cudnnPoolingForward( cudnn.handle(), pooling_desc.handle(), &alpha, src_desc.handle(), input_data.opaque(), &beta, dest_desc.handle(), output_data->opaque())); @@ -3983,7 +3983,7 @@ bool CudnnSupport::DoPoolForward( CudnnPoolingDescriptor pooling_desc(pooling_dimensions); auto cudnn = cudnn_->GetHandle(parent_, stream); - auto status = [&] { + const auto status = [&] { RETURN_IF_CUDNN_ERROR(cudnnPoolingForward( cudnn.handle(), pooling_desc.handle(), &alpha, src_desc.handle(), input_data.opaque(), &beta, dest_desc.handle(), output_data->opaque())); @@ -4008,7 +4008,7 @@ bool CudnnSupport::DoPoolForward( CudnnTensorDescriptor dest_desc(output_dimensions, CUDNN_DATA_HALF); CudnnPoolingDescriptor pooling_desc(pooling_dimensions); auto cudnn = cudnn_->GetHandle(parent_, stream); - auto status = [&] { + const auto status = [&] { RETURN_IF_CUDNN_ERROR(cudnnPoolingForward( cudnn.handle(), pooling_desc.handle(), &alpha, src_desc.handle(), input_data.opaque(), &beta, dest_desc.handle(), output_data->opaque())); @@ -4033,7 +4033,7 @@ bool CudnnSupport::DoPoolForward( CudnnPoolingDescriptor pooling_desc(pooling_dimensions); auto cudnn = cudnn_->GetHandle(parent_, stream); - auto status = [&] { + const auto status = [&] { RETURN_IF_CUDNN_ERROR(cudnnPoolingForward( cudnn.handle(), pooling_desc.handle(), &alpha, src_desc.handle(), input_data.opaque(), &beta, dest_desc.handle(), output_data->opaque())); @@ -4061,7 +4061,7 @@ bool CudnnSupport::DoPoolBackward( CudnnPoolingDescriptor pooling_desc(pooling_dimensions); auto cudnn = cudnn_->GetHandle(parent_, stream); - auto status = [&] { + const auto status = [&] { RETURN_IF_CUDNN_ERROR(cudnnPoolingBackward( cudnn.handle(), pooling_desc.handle(), &alpha, dest_desc.handle(), output_data.opaque(), dest_desc.handle(), input_diff_data.opaque(), @@ -4091,7 +4091,7 @@ bool CudnnSupport::DoPoolBackward( CudnnPoolingDescriptor pooling_desc(pooling_dimensions); auto cudnn = cudnn_->GetHandle(parent_, stream); - auto status = [&] { + const auto status = [&] { RETURN_IF_CUDNN_ERROR(cudnnPoolingBackward( cudnn.handle(), pooling_desc.handle(), &alpha, dest_desc.handle(), output_data.opaque(), dest_desc.handle(), input_diff_data.opaque(), @@ -4121,7 +4121,7 @@ bool CudnnSupport::DoPoolBackward( CudnnPoolingDescriptor pooling_desc(pooling_dimensions); auto cudnn = cudnn_->GetHandle(parent_, stream); - auto status = [&] { + const auto status = [&] { RETURN_IF_CUDNN_ERROR(cudnnPoolingBackward( cudnn.handle(), pooling_desc.handle(), &alpha, dest_desc.handle(), output_data.opaque(), dest_desc.handle(), input_diff_data.opaque(), @@ -4164,7 +4164,7 @@ bool CudnnSupport::DoNormalizeWithDimensions( auto cudnn = cudnn_->GetHandle(parent_, stream); // Launch the normalization. - auto status = [&] { + const auto status = [&] { RETURN_IF_CUDNN_ERROR(cudnnLRNCrossChannelForward( cudnn.handle(), normalize.handle(), CUDNN_LRN_CROSS_CHANNEL_DIM1, &alpha, dims.handle(), input_data.opaque(), &beta, dims.handle(), @@ -4198,7 +4198,7 @@ bool CudnnSupport::DoNormalizeBackwardWithDimensions( float beta = 0.0f; auto cudnn = cudnn_->GetHandle(parent_, stream); - auto status = [&] { + const auto status = [&] { RETURN_IF_CUDNN_ERROR(cudnnLRNCrossChannelBackward( cudnn.handle(), normalize.handle(), CUDNN_LRN_CROSS_CHANNEL_DIM1, &alpha, dims.handle(), normalized_data.opaque(), dims.handle(), @@ -4321,7 +4321,7 @@ bool CudnnSupport::DeriveOutputBatchDescriptor( int dn = batch_descriptor.ndims() + 2; std::vector dims(dn); // in BDYX - auto status = [&] { + const auto status = [&] { RETURN_IF_CUDNN_ERROR(cudnnGetConvolutionNdForwardOutputDim( conv.handle(), input_nd.handle(), filter.handle(), dn, dims.data())); output_batch_descriptor->set_count(dims[0]) -- GitLab From 3a8557aa74e63af0f377bfd956d4b02e598b919c Mon Sep 17 00:00:00 2001 From: Shahzad Lone Date: Wed, 30 Jan 2019 21:30:46 -0500 Subject: [PATCH 054/766] Fix according to code review. Referance: [https://github.com/tensorflow/tensorflow/pull/25347#discussion_r]. --- tensorflow/stream_executor/cuda/cuda_dnn.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/stream_executor/cuda/cuda_dnn.cc b/tensorflow/stream_executor/cuda/cuda_dnn.cc index 3d233250aa..60e5cdb34f 100644 --- a/tensorflow/stream_executor/cuda/cuda_dnn.cc +++ b/tensorflow/stream_executor/cuda/cuda_dnn.cc @@ -3675,7 +3675,7 @@ bool CudnnSupport::DoTransformTensor(Stream* stream, CudnnTensorDescriptor output_tensor_desc( output_desc, ToCudnnDataType(output_type, output_desc.layout())); auto cudnn = cudnn_->GetHandle(parent_, stream); - auto const status = [&] { + const auto status = [&] { RETURN_IF_CUDNN_ERROR(cudnnTransformTensor( cudnn.handle(), &scale, input_tensor_desc.handle(), input_data.opaque(), &beta, output_tensor_desc.handle(), output_data->opaque())); -- GitLab From c10aed994b967f3331c2b879f33f7a1d3cecbb51 Mon Sep 17 00:00:00 2001 From: jwu Date: Thu, 31 Jan 2019 21:17:14 +0100 Subject: [PATCH 055/766] dispatch bool type to boolean_mask , add corresponding test in array_ops_test.py --- .../python/kernel_tests/array_ops_test.py | 29 +++++++++++++++---- tensorflow/python/ops/array_ops.py | 20 ++++++------- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/tensorflow/python/kernel_tests/array_ops_test.py b/tensorflow/python/kernel_tests/array_ops_test.py index 6ba175704c..e260cf4f61 100644 --- a/tensorflow/python/kernel_tests/array_ops_test.py +++ b/tensorflow/python/kernel_tests/array_ops_test.py @@ -514,13 +514,19 @@ class StridedSliceChecker(object): except AttributeError: return x - if isinstance(spec, (np.ndarray, ops.Tensor)): + def convert_if_possible(spec): + try: + return ops.convert_to_tensor(spec) + except: + pass + return None + + bool_spec = convert_if_possible(spec) + if bool_spec is not None and bool_spec.dtype == dtypes.bool: tensor = op.eval() - np_specs = eval_if_tensor(spec) - ndims_np_specs = np_specs.ndim - if ndims_np_specs != 0: - self.test.assertAllEqual(self.x_np[np_specs], tensor) - return tensor + np_spec = eval_if_tensor(bool_spec) + self.test.assertAllEqual(self.x_np[np_spec], tensor) + return tensor if not isinstance(spec, (list, tuple)): spec = [spec] @@ -687,6 +693,10 @@ class StridedSliceTest(test_util.TensorFlowTestCase): _ = checker[0.0] with self.assertRaisesRegexp(TypeError, expected): _ = checker[constant_op.constant(0.0)] + with self.assertRaisesRegexp(TypeError, expected): + _ = checker[constant_op.constant([1, 2, 3])] + with self.assertRaisesRegexp(TypeError, expected): + _ = checker[[2.1, -0.7, 1.5]] @test_util.run_deprecated_v1 def testExpand(self): @@ -748,6 +758,13 @@ class StridedSliceTest(test_util.TensorFlowTestCase): _ = checker1[raw < 19] _ = checker1[scalar] + # Test boolean and non boolean cases + mask = np.array([True, False, True]) + raw1 = np.array([[1, 2, 4, 5], [5, 6, 7, 8], [9, 10, 11, 12]]) + checker2 = StridedSliceChecker(self, raw1) + _ = checker2[mask] + _ = checker2[ops.convert_to_tensor(mask)] + class StridedSliceShapeChecker(object): diff --git a/tensorflow/python/ops/array_ops.py b/tensorflow/python/ops/array_ops.py index 5349810176..ab87dbd193 100644 --- a/tensorflow/python/ops/array_ops.py +++ b/tensorflow/python/ops/array_ops.py @@ -579,16 +579,16 @@ def _slice_helper(tensor, slice_spec, var=None): TypeError: If the slice indices aren't int, slice, ellipsis, tf.newaxis or scalar int32/int64 tensors. """ - if isinstance(slice_spec, np.ndarray): - ndims_slice_spec = slice_spec.ndim - if ndims_slice_spec != 0: - return boolean_mask(tensor=tensor, mask=slice_spec) - - if isinstance(slice_spec, ops.Tensor): - shape_slice_spec = slice_spec.get_shape() - ndims_slice_spec = shape_slice_spec.ndims - if ndims_slice_spec != 0: - return boolean_mask(tensor=tensor, mask=slice_spec) + def convert_if_possible(slice_spec): + try: + return ops.convert_to_tensor(slice_spec) + except: + pass + return None + + bool_spec = convert_if_possible(slice_spec) + if bool_spec is not None and bool_spec.dtype == dtypes.bool: + return boolean_mask(tensor=tensor, mask=bool_spec) begin, end, strides = [], [], [] index = 0 -- GitLab From 31f10d26c6f66eb709568cb91d7bcf3f31f49c41 Mon Sep 17 00:00:00 2001 From: Loo Rong Jie Date: Sat, 2 Feb 2019 10:03:20 +0800 Subject: [PATCH 056/766] Fix Windows build due to winsock2.h conflicts --- configure.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/configure.py b/configure.py index 8dcd318220..d6c2597cc3 100644 --- a/configure.py +++ b/configure.py @@ -1508,6 +1508,8 @@ def set_windows_build_flags(environ_cp): write_to_bazelrc('build --config monolithic') # Suppress warning messages write_to_bazelrc('build --copt=-w --host_copt=-w') + # Fix winsock2.h conflicts + write_to_bazelrc('build --copt=-DWIN32_LEAN_AND_MEAN --host_copt=-DWIN32_LEAN_AND_MEAN') # Output more verbose information when something goes wrong write_to_bazelrc('build --verbose_failures') # The host and target platforms are the same in Windows build. So we don't -- GitLab From cbee85962c75a08f496473e224bde322c26208ba Mon Sep 17 00:00:00 2001 From: jwu Date: Mon, 4 Feb 2019 12:39:05 +0100 Subject: [PATCH 057/766] run boolean check for boolean_mask() run boolean check for boolean_mask() change indent --- .../python/kernel_tests/array_ops_test.py | 17 ++++++----------- tensorflow/python/ops/array_ops.py | 18 ++++++++---------- 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/tensorflow/python/kernel_tests/array_ops_test.py b/tensorflow/python/kernel_tests/array_ops_test.py index e260cf4f61..f369e30ab3 100644 --- a/tensorflow/python/kernel_tests/array_ops_test.py +++ b/tensorflow/python/kernel_tests/array_ops_test.py @@ -514,17 +514,12 @@ class StridedSliceChecker(object): except AttributeError: return x - def convert_if_possible(spec): - try: - return ops.convert_to_tensor(spec) - except: - pass - return None - - bool_spec = convert_if_possible(spec) - if bool_spec is not None and bool_spec.dtype == dtypes.bool: + if isinstance(spec, bool) or \ + (isinstance(spec, ops.Tensor) and spec.dtype == dtypes.bool) or \ + (isinstance(spec, np.ndarray) and spec.dtype == bool) or \ + (isinstance(spec, (list, tuple)) and np.asarray(spec).dtype == bool): tensor = op.eval() - np_spec = eval_if_tensor(bool_spec) + np_spec = eval_if_tensor(spec) self.test.assertAllEqual(self.x_np[np_spec], tensor) return tensor @@ -752,7 +747,7 @@ class StridedSliceTest(test_util.TensorFlowTestCase): # Test numpy array type mask raw = np.array([[[[[1, 2, 4, 5], [5, 6, 7, 8], [9, 10, 11, 12]]], - [[[13, 14, 15, 16], [17, 18, 19, 20], [21, 22, 23, 24]]]]]) + [[[13, 14, 15, 16], [17, 18, 19, 20], [21, 22, 23, 24]]]]]) checker1 = StridedSliceChecker(self, raw) _ = checker1[raw >= 4] _ = checker1[raw < 19] diff --git a/tensorflow/python/ops/array_ops.py b/tensorflow/python/ops/array_ops.py index ab87dbd193..563c994aeb 100644 --- a/tensorflow/python/ops/array_ops.py +++ b/tensorflow/python/ops/array_ops.py @@ -579,16 +579,14 @@ def _slice_helper(tensor, slice_spec, var=None): TypeError: If the slice indices aren't int, slice, ellipsis, tf.newaxis or scalar int32/int64 tensors. """ - def convert_if_possible(slice_spec): - try: - return ops.convert_to_tensor(slice_spec) - except: - pass - return None - - bool_spec = convert_if_possible(slice_spec) - if bool_spec is not None and bool_spec.dtype == dtypes.bool: - return boolean_mask(tensor=tensor, mask=bool_spec) + if isinstance(slice_spec, bool) or \ + (isinstance(slice_spec, ops.Tensor) and slice_spec.dtype == dtypes.bool) or \ + (isinstance(slice_spec, np.ndarray) and slice_spec.dtype == bool) or \ + (isinstance(slice_spec, (list, tuple)) and np.asarray(slice_spec).dtype == bool): + return boolean_mask(tensor=tensor, mask=slice_spec) + + if not isinstance(slice_spec, (list, tuple)): + slice_spec = [slice_spec] begin, end, strides = [], [], [] index = 0 -- GitLab From 19b1639d9007aa4a852041d22150f9cffb7af3ca Mon Sep 17 00:00:00 2001 From: wangsiyu Date: Sun, 9 Dec 2018 12:42:04 +0800 Subject: [PATCH 058/766] [Bug Fix]Fix bug of passing different assignment_map object created in each replica thread in init_from_checkpoint --- tensorflow/contrib/distribute/python/BUILD | 1 + .../python/checkpoint_utils_test.py | 51 ++++++++++++++++--- .../python/training/checkpoint_utils.py | 11 ++++ 3 files changed, 57 insertions(+), 6 deletions(-) diff --git a/tensorflow/contrib/distribute/python/BUILD b/tensorflow/contrib/distribute/python/BUILD index 509eb78128..562965a410 100644 --- a/tensorflow/contrib/distribute/python/BUILD +++ b/tensorflow/contrib/distribute/python/BUILD @@ -761,6 +761,7 @@ cuda_py_test( "//tensorflow/python:training", "//tensorflow/python:variable_scope", "//tensorflow/python:variables", + "//tensorflow/python/distribute:values", ], tags = [ "multi_and_single_gpu", diff --git a/tensorflow/contrib/distribute/python/checkpoint_utils_test.py b/tensorflow/contrib/distribute/python/checkpoint_utils_test.py index 7ee50f0315..7e821b821b 100644 --- a/tensorflow/contrib/distribute/python/checkpoint_utils_test.py +++ b/tensorflow/contrib/distribute/python/checkpoint_utils_test.py @@ -29,6 +29,7 @@ import os from absl.testing import parameterized from tensorflow.contrib.distribute.python import combinations +from tensorflow.python.distribute import values from tensorflow.python.framework import ops from tensorflow.python.ops import variable_scope from tensorflow.python.ops import variables @@ -56,6 +57,13 @@ def _create_checkpoints(sess, checkpoint_dir): class CheckpointUtilsWithDistributionStrategyTest( test.TestCase, parameterized.TestCase): + def _get_test_object(self): + checkpoint_dir = self.get_temp_dir() + with self.cached_session() as session: + v1, v2, v3, v4 = checkpoint_utils_test._create_checkpoints( + session, checkpoint_dir) + return checkpoint_dir, v1, v2, v3, v4 + @combinations.generate(combinations.combine( distribution=[combinations.default_strategy, combinations.one_device_strategy, @@ -66,11 +74,9 @@ class CheckpointUtilsWithDistributionStrategyTest( in_replica_mode=[True, False], mode=["graph"])) def testInitFromCheckpoint(self, distribution, in_replica_mode): - checkpoint_dir = self.get_temp_dir() - with self.cached_session() as session: - v1_value, v2_value = _create_checkpoints(session, checkpoint_dir) + checkpoint_dir, v1_value, v2_value, _, _ = self._get_test_object() - def init_and_verify(g): + def init_from_constant_name_and_verify(g): v1 = variable_scope.get_variable("new_var1", [1, 10]) v2 = variable_scope.get_variable( "new_var2", [10, 10], @@ -85,11 +91,44 @@ class CheckpointUtilsWithDistributionStrategyTest( self.assertAllEqual(v1_value, self.evaluate(v1)) self.assertAllEqual(v2_value, self.evaluate(v2)) + def init_from_new_name_and_verify(g): + v1 = variable_scope.get_variable("new_var1", [1, 10]) + # Use string add to create new object in each replica + prefix = "new_" + suffix = "var1" + new_var1 = prefix + suffix + checkpoint_utils.init_from_checkpoint(checkpoint_dir, { + "var1": new_var1, + }) + with self.test_session(graph=g) as session: + session.run(variables.global_variables_initializer()) + self.assertAllEqual(v1_value, self.evaluate(v1)) + with ops.Graph().as_default() as g, distribution.scope(): if in_replica_mode: - distribution.extended.call_for_each_replica(init_and_verify, args=[g]) + distribution.call_for_each_replica( + init_from_constant_name_and_verify, args=[g]) else: - init_and_verify(g) + init_from_constant_name_and_verify(g) + + with ops.Graph().as_default() as g, distribution.scope(): + if in_replica_mode: + distribution.call_for_each_replica( + init_from_new_name_and_verify, g) + + def testAssignmentMapHasDifferentValues(self): + checkpoint_dir, v1_value, _, _, _ = self._get_test_object() + + def init_from_assignment_map_and_raise_error(g): + v1 = variable_scope.get_variable("new_var1", [1, 10]) + assignment_map = values.regroup( + {"/gpu:0": {"var1": "new_var1"}, + "/gpu:1": {"var1": "new_var2"}}) + checkpoint_utils.init_from_checkpoint( + checkpoint_dir, assignment_map) + + with ops.Graph().as_default() as g, self.assertRaises(ValueError): + init_from_assignment_map_and_raise_error(g) if __name__ == "__main__": diff --git a/tensorflow/python/training/checkpoint_utils.py b/tensorflow/python/training/checkpoint_utils.py index 5e18f4b722..0616fdb9c4 100644 --- a/tensorflow/python/training/checkpoint_utils.py +++ b/tensorflow/python/training/checkpoint_utils.py @@ -206,6 +206,8 @@ def _init_from_checkpoint(_, ckpt_dir_or_file, assignment_map): and all(_is_variable(v) for v in current_var_or_name)): var = current_var_or_name else: + if hasattr(current_var_or_name, "_index"): + current_var_or_name = _validate_and_get_var_or_name(current_var_or_name) store_vars = vs._get_default_variable_store()._vars # pylint:disable=protected-access # Check if this variable is in var_store. var = store_vars.get(current_var_or_name, None) @@ -362,6 +364,15 @@ def _is_variable(x): return (isinstance(x, variables.Variable) or resource_variable_ops.is_resource_variable(x)) +def _validate_and_get_var_or_name(distributed_value): + assert distribution_strategy_context.get_cross_replica_context() + assert hasattr(distributed_value, "_index") + v0 = distributed_value._index.items()[0][1] # pylint:disable=protected-access + for (d, v) in six.iteritems(distributed_value._index): # pylint:disable=protected-access + if v != v0: + raise ValueError("Values on different device should be the same: " + " %s != %s" % (v, v0)) + return v0 def _collect_partitioned_variable(name, all_vars): """Returns list of `tf.Variable` that comprise the partitioned variable.""" -- GitLab From 127a25ff64fbe68382a24c28b7e8b8d17a654d46 Mon Sep 17 00:00:00 2001 From: wangsiyu Date: Sun, 20 Jan 2019 22:33:06 +0800 Subject: [PATCH 059/766] remove unused lines and refine coding styles --- .../python/checkpoint_utils_test.py | 54 +++++++++---------- .../python/training/checkpoint_utils.py | 22 ++------ 2 files changed, 30 insertions(+), 46 deletions(-) diff --git a/tensorflow/contrib/distribute/python/checkpoint_utils_test.py b/tensorflow/contrib/distribute/python/checkpoint_utils_test.py index 7e821b821b..0f730e5069 100644 --- a/tensorflow/contrib/distribute/python/checkpoint_utils_test.py +++ b/tensorflow/contrib/distribute/python/checkpoint_utils_test.py @@ -60,9 +60,8 @@ class CheckpointUtilsWithDistributionStrategyTest( def _get_test_object(self): checkpoint_dir = self.get_temp_dir() with self.cached_session() as session: - v1, v2, v3, v4 = checkpoint_utils_test._create_checkpoints( - session, checkpoint_dir) - return checkpoint_dir, v1, v2, v3, v4 + v1, v2 = _create_checkpoints(session, checkpoint_dir) + return checkpoint_dir, v1, v2 @combinations.generate(combinations.combine( distribution=[combinations.default_strategy, @@ -74,9 +73,9 @@ class CheckpointUtilsWithDistributionStrategyTest( in_replica_mode=[True, False], mode=["graph"])) def testInitFromCheckpoint(self, distribution, in_replica_mode): - checkpoint_dir, v1_value, v2_value, _, _ = self._get_test_object() + checkpoint_dir, v1_value, v2_value = self._get_test_object() - def init_from_constant_name_and_verify(g): + def init_and_verify(g): v1 = variable_scope.get_variable("new_var1", [1, 10]) v2 = variable_scope.get_variable( "new_var2", [10, 10], @@ -91,7 +90,25 @@ class CheckpointUtilsWithDistributionStrategyTest( self.assertAllEqual(v1_value, self.evaluate(v1)) self.assertAllEqual(v2_value, self.evaluate(v2)) - def init_from_new_name_and_verify(g): + with ops.Graph().as_default() as g, distribution.scope(): + if in_replica_mode: + distribution.extended.call_for_each_replica(init_and_verify, args=[g]) + else: + init_and_verify(g) + + @combinations.generate(combinations.combine( + distribution=[combinations.default_strategy, + combinations.one_device_strategy, + combinations.mirrored_strategy_with_gpu_and_cpu, + combinations.mirrored_strategy_with_two_gpus, + combinations.core_mirrored_strategy_with_gpu_and_cpu, + combinations.core_mirrored_strategy_with_two_gpus], + in_replica_mode=[True, False], + mode=["graph"])) + def testInitFromDifferentNameObject(self, distribution, in_replica_mode): + checkpoint_dir, v1_value, _ = self._get_test_object() + + def init_and_verify(g): v1 = variable_scope.get_variable("new_var1", [1, 10]) # Use string add to create new object in each replica prefix = "new_" @@ -106,29 +123,8 @@ class CheckpointUtilsWithDistributionStrategyTest( with ops.Graph().as_default() as g, distribution.scope(): if in_replica_mode: - distribution.call_for_each_replica( - init_from_constant_name_and_verify, args=[g]) - else: - init_from_constant_name_and_verify(g) - - with ops.Graph().as_default() as g, distribution.scope(): - if in_replica_mode: - distribution.call_for_each_replica( - init_from_new_name_and_verify, g) - - def testAssignmentMapHasDifferentValues(self): - checkpoint_dir, v1_value, _, _, _ = self._get_test_object() - - def init_from_assignment_map_and_raise_error(g): - v1 = variable_scope.get_variable("new_var1", [1, 10]) - assignment_map = values.regroup( - {"/gpu:0": {"var1": "new_var1"}, - "/gpu:1": {"var1": "new_var2"}}) - checkpoint_utils.init_from_checkpoint( - checkpoint_dir, assignment_map) - - with ops.Graph().as_default() as g, self.assertRaises(ValueError): - init_from_assignment_map_and_raise_error(g) + distribution.extended.call_for_each_replica( + init_and_verify, [g]) if __name__ == "__main__": diff --git a/tensorflow/python/training/checkpoint_utils.py b/tensorflow/python/training/checkpoint_utils.py index 0616fdb9c4..d8b45efbd5 100644 --- a/tensorflow/python/training/checkpoint_utils.py +++ b/tensorflow/python/training/checkpoint_utils.py @@ -183,16 +183,16 @@ def init_from_checkpoint(ckpt_dir_or_file, assignment_map): ValueError: If missing variables in current graph, or if missing checkpoints or tensors in checkpoints. """ + init_from_checkpoint_fn = lambda _ : _init_from_checkpoint( + ckpt_dir_or_file, assignment_map) if distribution_strategy_context.get_cross_replica_context(): - _init_from_checkpoint(None, ckpt_dir_or_file, assignment_map) + init_from_checkpoint_fn(None) else: distribution_strategy_context.get_replica_context().merge_call( - _init_from_checkpoint, args=(ckpt_dir_or_file, assignment_map)) + init_from_checkpoint_fn) - -def _init_from_checkpoint(_, ckpt_dir_or_file, assignment_map): +def _init_from_checkpoint(ckpt_dir_or_file, assignment_map): """See `init_from_checkpoint` for documentation.""" - ckpt_file = _get_checkpoint_filename(ckpt_dir_or_file) reader = load_checkpoint(ckpt_dir_or_file) variable_map = reader.get_variable_to_shape_map() @@ -206,8 +206,6 @@ def _init_from_checkpoint(_, ckpt_dir_or_file, assignment_map): and all(_is_variable(v) for v in current_var_or_name)): var = current_var_or_name else: - if hasattr(current_var_or_name, "_index"): - current_var_or_name = _validate_and_get_var_or_name(current_var_or_name) store_vars = vs._get_default_variable_store()._vars # pylint:disable=protected-access # Check if this variable is in var_store. var = store_vars.get(current_var_or_name, None) @@ -364,16 +362,6 @@ def _is_variable(x): return (isinstance(x, variables.Variable) or resource_variable_ops.is_resource_variable(x)) -def _validate_and_get_var_or_name(distributed_value): - assert distribution_strategy_context.get_cross_replica_context() - assert hasattr(distributed_value, "_index") - v0 = distributed_value._index.items()[0][1] # pylint:disable=protected-access - for (d, v) in six.iteritems(distributed_value._index): # pylint:disable=protected-access - if v != v0: - raise ValueError("Values on different device should be the same: " - " %s != %s" % (v, v0)) - return v0 - def _collect_partitioned_variable(name, all_vars): """Returns list of `tf.Variable` that comprise the partitioned variable.""" if name + "/part_0" in all_vars: -- GitLab From 288bb2633081a8e12a23465190ebb937de250ad5 Mon Sep 17 00:00:00 2001 From: wangsiyu Date: Tue, 5 Feb 2019 13:51:58 +0800 Subject: [PATCH 060/766] remove unused --- tensorflow/contrib/distribute/python/BUILD | 1 - tensorflow/contrib/distribute/python/checkpoint_utils_test.py | 1 - 2 files changed, 2 deletions(-) diff --git a/tensorflow/contrib/distribute/python/BUILD b/tensorflow/contrib/distribute/python/BUILD index 562965a410..509eb78128 100644 --- a/tensorflow/contrib/distribute/python/BUILD +++ b/tensorflow/contrib/distribute/python/BUILD @@ -761,7 +761,6 @@ cuda_py_test( "//tensorflow/python:training", "//tensorflow/python:variable_scope", "//tensorflow/python:variables", - "//tensorflow/python/distribute:values", ], tags = [ "multi_and_single_gpu", diff --git a/tensorflow/contrib/distribute/python/checkpoint_utils_test.py b/tensorflow/contrib/distribute/python/checkpoint_utils_test.py index 0f730e5069..66fc6c9c07 100644 --- a/tensorflow/contrib/distribute/python/checkpoint_utils_test.py +++ b/tensorflow/contrib/distribute/python/checkpoint_utils_test.py @@ -29,7 +29,6 @@ import os from absl.testing import parameterized from tensorflow.contrib.distribute.python import combinations -from tensorflow.python.distribute import values from tensorflow.python.framework import ops from tensorflow.python.ops import variable_scope from tensorflow.python.ops import variables -- GitLab From 008ef579142c96fb2868fed80bfedb725096df0c Mon Sep 17 00:00:00 2001 From: jwu Date: Tue, 5 Feb 2019 06:38:46 +0100 Subject: [PATCH 061/766] change shape_tensor back to shape(tensor) fix pylint non-whitelisted errors --- tensorflow/python/kernel_tests/array_ops_test.py | 5 +++-- tensorflow/python/ops/array_ops.py | 13 +++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/tensorflow/python/kernel_tests/array_ops_test.py b/tensorflow/python/kernel_tests/array_ops_test.py index f369e30ab3..58d8ae4679 100644 --- a/tensorflow/python/kernel_tests/array_ops_test.py +++ b/tensorflow/python/kernel_tests/array_ops_test.py @@ -747,7 +747,8 @@ class StridedSliceTest(test_util.TensorFlowTestCase): # Test numpy array type mask raw = np.array([[[[[1, 2, 4, 5], [5, 6, 7, 8], [9, 10, 11, 12]]], - [[[13, 14, 15, 16], [17, 18, 19, 20], [21, 22, 23, 24]]]]]) + [[[13, 14, 15, 16], [17, 18, 19, 20], + [21, 22, 23, 24]]]]]) checker1 = StridedSliceChecker(self, raw) _ = checker1[raw >= 4] _ = checker1[raw < 19] @@ -860,7 +861,7 @@ class GradSliceChecker(object): np_val_grad = (2 * self.varnp * self.varnp) np_sliceval_grad = np.zeros(self.var.get_shape()) if isinstance(spec, ops.Tensor): - spec = self.sess.run([spec]) + spec = self.sess.run([spec]) np_sliceval_grad[spec] = np_val_grad[spec] # verify gradient self.test.assertAllEqual(slice_val_grad_evaled, np_sliceval_grad) diff --git a/tensorflow/python/ops/array_ops.py b/tensorflow/python/ops/array_ops.py index 563c994aeb..131f356361 100644 --- a/tensorflow/python/ops/array_ops.py +++ b/tensorflow/python/ops/array_ops.py @@ -580,9 +580,10 @@ def _slice_helper(tensor, slice_spec, var=None): tf.newaxis or scalar int32/int64 tensors. """ if isinstance(slice_spec, bool) or \ - (isinstance(slice_spec, ops.Tensor) and slice_spec.dtype == dtypes.bool) or \ - (isinstance(slice_spec, np.ndarray) and slice_spec.dtype == bool) or \ - (isinstance(slice_spec, (list, tuple)) and np.asarray(slice_spec).dtype == bool): + (isinstance(slice_spec, ops.Tensor) and slice_spec.dtype == dtypes.bool) or \ + (isinstance(slice_spec, np.ndarray) and slice_spec.dtype == bool) or \ + (isinstance(slice_spec, (list, tuple)) and + np.asarray(slice_spec).dtype == bool): return boolean_mask(tensor=tensor, mask=slice_spec) if not isinstance(slice_spec, (list, tuple)): @@ -1333,11 +1334,11 @@ def boolean_mask(tensor, mask, name="boolean_mask", axis=None): axis = 0 if axis is None else axis shape_tensor[axis:axis + ndims_mask].assert_is_compatible_with(shape_mask) - leading_size = gen_math_ops.prod(shape_tensor[axis:axis + ndims_mask], [0]) + leading_size = gen_math_ops.prod(shape(tensor)[axis:axis + ndims_mask], [0]) tensor = reshape(tensor, concat([ - shape_tensor[:axis], [leading_size], - shape_tensor[axis + ndims_mask:] + shape(tensor)[:axis], [leading_size], + shape(tensor)[axis + ndims_mask:] ], 0)) first_dim = shape_tensor[axis:axis + ndims_mask].num_elements() tensor.set_shape( -- GitLab From 33fac2b5d12e18dbe76eaaaab102b9af36eab034 Mon Sep 17 00:00:00 2001 From: wangsiyu Date: Tue, 5 Feb 2019 21:30:47 +0800 Subject: [PATCH 062/766] fix pylint --- tensorflow/contrib/distribute/python/checkpoint_utils_test.py | 2 ++ tensorflow/python/training/checkpoint_utils.py | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/tensorflow/contrib/distribute/python/checkpoint_utils_test.py b/tensorflow/contrib/distribute/python/checkpoint_utils_test.py index 66fc6c9c07..286255afd6 100644 --- a/tensorflow/contrib/distribute/python/checkpoint_utils_test.py +++ b/tensorflow/contrib/distribute/python/checkpoint_utils_test.py @@ -124,6 +124,8 @@ class CheckpointUtilsWithDistributionStrategyTest( if in_replica_mode: distribution.extended.call_for_each_replica( init_and_verify, [g]) + else: + init_and_verify(g) if __name__ == "__main__": diff --git a/tensorflow/python/training/checkpoint_utils.py b/tensorflow/python/training/checkpoint_utils.py index d8b45efbd5..b6c5d304ed 100644 --- a/tensorflow/python/training/checkpoint_utils.py +++ b/tensorflow/python/training/checkpoint_utils.py @@ -183,7 +183,7 @@ def init_from_checkpoint(ckpt_dir_or_file, assignment_map): ValueError: If missing variables in current graph, or if missing checkpoints or tensors in checkpoints. """ - init_from_checkpoint_fn = lambda _ : _init_from_checkpoint( + init_from_checkpoint_fn = lambda _: _init_from_checkpoint( ckpt_dir_or_file, assignment_map) if distribution_strategy_context.get_cross_replica_context(): init_from_checkpoint_fn(None) @@ -191,6 +191,7 @@ def init_from_checkpoint(ckpt_dir_or_file, assignment_map): distribution_strategy_context.get_replica_context().merge_call( init_from_checkpoint_fn) + def _init_from_checkpoint(ckpt_dir_or_file, assignment_map): """See `init_from_checkpoint` for documentation.""" ckpt_file = _get_checkpoint_filename(ckpt_dir_or_file) -- GitLab From 3a7cb1190e4143f394a12111f8cbddd420d7c6c1 Mon Sep 17 00:00:00 2001 From: Siju Date: Wed, 6 Feb 2019 16:58:45 +0530 Subject: [PATCH 063/766] Update depthwise_conv.h --- .../kernels/internal/reference/integer_ops/depthwise_conv.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/kernels/internal/reference/integer_ops/depthwise_conv.h b/tensorflow/lite/kernels/internal/reference/integer_ops/depthwise_conv.h index 90a7d613a9..b6195c0da9 100644 --- a/tensorflow/lite/kernels/internal/reference/integer_ops/depthwise_conv.h +++ b/tensorflow/lite/kernels/internal/reference/integer_ops/depthwise_conv.h @@ -92,7 +92,7 @@ inline void DepthwiseConvPerChannel( // [-128, 127]), which is [-32512, 32512]. log2(32512) // = 14.98, which means we can accumulate at least 2^16 // multiplications without overflow. The accumulator is - // applied to a filter so the accumation logic will hold as + // applied to a filter so the accumulation logic will hold as // long as the filter size (filter_y * filter_x * in_channel) // does not exceed 2^16, which is the case in all the models // we have seen so far. -- GitLab From c0aeb3c6bb0f667b0c7017222d17f04e84281e36 Mon Sep 17 00:00:00 2001 From: Siju Date: Wed, 6 Feb 2019 17:00:45 +0530 Subject: [PATCH 064/766] Update conv.h --- tensorflow/lite/kernels/internal/reference/integer_ops/conv.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/kernels/internal/reference/integer_ops/conv.h b/tensorflow/lite/kernels/internal/reference/integer_ops/conv.h index 14e449e2bb..4ad2a70b31 100644 --- a/tensorflow/lite/kernels/internal/reference/integer_ops/conv.h +++ b/tensorflow/lite/kernels/internal/reference/integer_ops/conv.h @@ -94,7 +94,7 @@ inline void ConvPerChannel( // [-128, 127]), which is [-32512, 32512]. log2(32512) // = 14.98, which means we can accumulate at least 2^16 // multiplications without overflow. The accumulator is - // applied to a filter so the accumation logic will hold as + // applied to a filter so the accumulation logic will hold as // long as the filter size (filter_y * filter_x * in_channel) // does not exceed 2^16, which is the case in all the models // we have seen so far. -- GitLab From 28d6c5d1c4577a66db7858ab3008d48273159a42 Mon Sep 17 00:00:00 2001 From: Dayananda-V Date: Wed, 6 Feb 2019 18:23:57 +0530 Subject: [PATCH 065/766] TF Keras merge_test missing test cases add 1- Subtract api test case 2- compute_mask api test case 3- Add, Subtract, Concatenate invalif test case --- tensorflow/python/keras/layers/merge_test.py | 69 +++++++++++++++++++- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/keras/layers/merge_test.py b/tensorflow/python/keras/layers/merge_test.py index f962a75b32..3335d0d9f0 100644 --- a/tensorflow/python/keras/layers/merge_test.py +++ b/tensorflow/python/keras/layers/merge_test.py @@ -21,6 +21,7 @@ from __future__ import print_function import numpy as np from tensorflow.python import keras +from tensorflow.python.keras import backend as K from tensorflow.python.framework import test_util as tf_test_util from tensorflow.python.keras import keras_parameterized from tensorflow.python.keras import testing_utils @@ -35,7 +36,8 @@ class MergeLayersTest(keras_parameterized.TestCase): i2 = keras.layers.Input(shape=(4, 5)) i3 = keras.layers.Input(shape=(4, 5)) - o = keras.layers.add([i1, i2, i3]) + add_layer = keras.layers.Add() + o = add_layer([i1, i2, i3]) self.assertListEqual(o.shape.as_list(), [None, 4, 5]) model = keras.models.Model([i1, i2, i3], o) model.run_eagerly = testing_utils.should_run_eagerly() @@ -47,6 +49,52 @@ class MergeLayersTest(keras_parameterized.TestCase): self.assertEqual(out.shape, (2, 4, 5)) self.assertAllClose(out, x1 + x2 + x3, atol=1e-4) + self.assertEqual(add_layer.compute_mask([i1, i2, i3], + [None, None, None]), None) + self.assertTrue(np.all(K.eval( + add_layer.compute_mask([i1, i2], + [K.variable(x1), K.variable(x2)])))) + + with self.assertRaisesRegexp(ValueError, "`mask` should be a list."): + add_layer.compute_mask([i1, i2, i3], x1) + with self.assertRaisesRegexp(ValueError, "`inputs` should be a list."): + add_layer.compute_mask(i1, [None, None, None]) + with self.assertRaisesRegexp(ValueError, " should have the same length."): + add_layer.compute_mask([i1, i2, i3], [None, None]) + + def test_merge_subtract(self): + i1 = keras.layers.Input(shape=(4, 5)) + i2 = keras.layers.Input(shape=(4, 5)) + i3 = keras.layers.Input(shape=(4, 5)) + + subtract_layer = keras.layers.Subtract() + o = subtract_layer([i1, i2]) + self.assertListEqual(o.shape.as_list(), [None, 4, 5]) + model = keras.models.Model([i1, i2], o) + model.run_eagerly = testing_utils.should_run_eagerly() + + x1 = np.random.random((2, 4, 5)) + x2 = np.random.random((2, 4, 5)) + out = model.predict([x1, x2]) + self.assertEqual(out.shape, (2, 4, 5)) + self.assertAllClose(out, x1 - x2, atol=1e-4) + + self.assertEqual(subtract_layer.compute_mask([i1, i2], [None, None]), None) + self.assertTrue(np.all(K.eval( + subtract_layer.compute_mask([i1, i2], + [K.variable(x1), K.variable(x2)])))) + + with self.assertRaisesRegexp(ValueError, "`mask` should be a list."): + subtract_layer.compute_mask([i1, i2], x1) + with self.assertRaisesRegexp(ValueError, "`inputs` should be a list."): + subtract_layer.compute_mask(i1, [None, None]) + with self.assertRaisesRegexp(ValueError, + "layer should be called on exactly 2 inputs"): + subtract_layer([i1, i2, i3]) + with self.assertRaisesRegexp(ValueError, + "layer should be called on exactly 2 inputs"): + subtract_layer([i1]) + def test_merge_multiply(self): i1 = keras.layers.Input(shape=(4, 5)) i2 = keras.layers.Input(shape=(4, 5)) @@ -108,7 +156,8 @@ class MergeLayersTest(keras_parameterized.TestCase): def test_merge_concatenate(self): i1 = keras.layers.Input(shape=(4, 5)) i2 = keras.layers.Input(shape=(4, 5)) - o = keras.layers.concatenate([i1, i2], axis=1) + concat_layer = keras.layers.Concatenate(axis=1) + o = concat_layer([i1, i2]) self.assertListEqual(o.shape.as_list(), [None, 8, 5]) model = keras.models.Model([i1, i2], o) model.run_eagerly = testing_utils.should_run_eagerly() @@ -119,6 +168,22 @@ class MergeLayersTest(keras_parameterized.TestCase): self.assertEqual(out.shape, (2, 8, 5)) self.assertAllClose(out, np.concatenate([x1, x2], axis=1), atol=1e-4) + self.assertEqual(concat_layer.compute_mask([i1, i2], [None, None]), None) + self.assertTrue(np.all(K.eval( + concat_layer.compute_mask([i1, i2], + [K.variable(x1), K.variable(x2)])))) + + with self.assertRaisesRegexp(ValueError, "`mask` should be a list."): + concat_layer.compute_mask([i1, i2], x1) + with self.assertRaisesRegexp(ValueError, "`inputs` should be a list."): + concat_layer.compute_mask(i1, [None, None]) + with self.assertRaisesRegexp(ValueError, + "should have the same length"): + concat_layer.compute_mask([i1, i2], [None]) + with self.assertRaisesRegexp(ValueError, + "layer should be called on a list of inputs"): + concat_layer(i1) + def test_merge_dot(self): i1 = keras.layers.Input(shape=(4,)) i2 = keras.layers.Input(shape=(4,)) -- GitLab From 09c4d1a1ac38c4dfe90f8e1d1439557b934364ff Mon Sep 17 00:00:00 2001 From: Lukas Geiger Date: Wed, 30 Jan 2019 17:01:21 +0100 Subject: [PATCH 066/766] Support every MinMax op in monotonic optimizer --- tensorflow/core/grappler/op_types.cc | 10 ++++++++++ tensorflow/core/grappler/op_types.h | 2 ++ .../grappler/optimizers/arithmetic_optimizer.cc | 15 +++++++++++++-- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/tensorflow/core/grappler/op_types.cc b/tensorflow/core/grappler/op_types.cc index f403e16492..7b1aca9f2b 100644 --- a/tensorflow/core/grappler/op_types.cc +++ b/tensorflow/core/grappler/op_types.cc @@ -47,12 +47,22 @@ bool IsAnyDiv(const NodeDef& node) { node.op() == "FloorDiv" || node.op() == "TruncateDiv"; } +bool IsAnyMax(const NodeDef& node) { + const auto& op = node.op(); + return op == "Max" || op == "SegmentMax" || op == "UnsortedSegmentMax"; +} + bool IsAnyMaxPool(const NodeDef& node) { const auto& op = node.op(); return op == "MaxPool" || op == "MaxPoolV2" || op == "MaxPool3D" || op == "MaxPoolWithArgmax" || op == "FractionalMaxPool"; } +bool IsAnyMin(const NodeDef& node) { + const auto& op = node.op(); + return op == "Min" || op == "SegmentMin" || op == "UnsortedSegmentMin"; +} + bool IsApproximateEqual(const NodeDef& node) { return node.op() == "ApproximateEqual"; } diff --git a/tensorflow/core/grappler/op_types.h b/tensorflow/core/grappler/op_types.h index bc1d8c15ac..2d52a81ffb 100644 --- a/tensorflow/core/grappler/op_types.h +++ b/tensorflow/core/grappler/op_types.h @@ -28,7 +28,9 @@ bool IsAll(const NodeDef& node); bool IsAngle(const NodeDef& node); bool IsAny(const NodeDef& node); bool IsAnyDiv(const NodeDef& node); +bool IsAnyMax(const NodeDef& node); bool IsAnyMaxPool(const NodeDef& node); +bool IsAnyMin(const NodeDef& node); bool IsApproximateEqual(const NodeDef& node); bool IsAvgPoolGrad(const NodeDef& node); bool IsAssert(const NodeDef& node); diff --git a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc index 902cb3fdd8..3865d9a325 100644 --- a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc @@ -53,6 +53,7 @@ limitations under the License. #include "tensorflow/core/util/strided_slice_op.h" using tensorflow::strings::StrCat; +using tensorflow::str_util::StringReplace; namespace tensorflow { namespace grappler { @@ -2721,7 +2722,7 @@ class OptimizeMaxOrMinOfMonotonicStage : public ArithmeticOptimizerStage { ~OptimizeMaxOrMinOfMonotonicStage() override = default; bool IsSupported(const NodeDef* node) const override { - return IsMax(*node) || IsMin(*node) || IsAnyMaxPool(*node); + return IsAnyMax(*node) || IsAnyMin(*node) || IsAnyMaxPool(*node); } Status TrySimplify(NodeDef* reduction_node, @@ -2755,7 +2756,7 @@ class OptimizeMaxOrMinOfMonotonicStage : public ArithmeticOptimizerStage { if (!is_non_decreasing) { // Flip Min<->Max if the function is non-increasing, e.g. // Max(Neg(x)) = Neg(Min(x)). - const string opposite = IsMax(*reduction_node) ? "Min" : "Max"; + const string opposite = FlipMinMax(*reduction_node); reduction_node->set_op(opposite); } AddToOptimizationQueue(reduction_node); @@ -2778,6 +2779,16 @@ class OptimizeMaxOrMinOfMonotonicStage : public ArithmeticOptimizerStage { AddToOptimizationQueue(consumer); } } + + private: + string FlipMinMax(const NodeDef& node) { + const string& op = node.op(); + if (IsAnyMax(node)) { + return str_util::StringReplace(op, "Max", "Min", false); + } else { + return str_util::StringReplace(op, "Min", "Max", false); + } + } }; // Replace a chain of type&shape preserving unary ops with a -- GitLab From d38d2e7e3ea99c13c52dd8678c03c8776f7984b0 Mon Sep 17 00:00:00 2001 From: Lukas Geiger Date: Wed, 30 Jan 2019 19:06:30 +0100 Subject: [PATCH 067/766] Support ArgMax in monotonic optimizer --- tensorflow/core/grappler/op_types.cc | 4 ++ tensorflow/core/grappler/op_types.h | 2 + .../optimizers/arithmetic_optimizer.cc | 11 ++++- .../optimizers/arithmetic_optimizer_test.cc | 41 +++++++++++++++++++ 4 files changed, 56 insertions(+), 2 deletions(-) diff --git a/tensorflow/core/grappler/op_types.cc b/tensorflow/core/grappler/op_types.cc index 7b1aca9f2b..39a6bba3fc 100644 --- a/tensorflow/core/grappler/op_types.cc +++ b/tensorflow/core/grappler/op_types.cc @@ -67,6 +67,10 @@ bool IsApproximateEqual(const NodeDef& node) { return node.op() == "ApproximateEqual"; } +bool IsArgMax(const NodeDef& node) { return node.op() == "ArgMax"; } + +bool IsArgMin(const NodeDef& node) { return node.op() == "ArgMin"; } + bool IsAvgPoolGrad(const NodeDef& node) { return node.op() == "AvgPoolGrad"; } bool IsAssign(const NodeDef& node) { diff --git a/tensorflow/core/grappler/op_types.h b/tensorflow/core/grappler/op_types.h index 2d52a81ffb..864371b57a 100644 --- a/tensorflow/core/grappler/op_types.h +++ b/tensorflow/core/grappler/op_types.h @@ -32,6 +32,8 @@ bool IsAnyMax(const NodeDef& node); bool IsAnyMaxPool(const NodeDef& node); bool IsAnyMin(const NodeDef& node); bool IsApproximateEqual(const NodeDef& node); +bool IsArgMax(const NodeDef& node); +bool IsArgMin(const NodeDef& node); bool IsAvgPoolGrad(const NodeDef& node); bool IsAssert(const NodeDef& node); bool IsAssign(const NodeDef& node); diff --git a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc index 3865d9a325..86abf5577a 100644 --- a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc @@ -2722,7 +2722,8 @@ class OptimizeMaxOrMinOfMonotonicStage : public ArithmeticOptimizerStage { ~OptimizeMaxOrMinOfMonotonicStage() override = default; bool IsSupported(const NodeDef* node) const override { - return IsAnyMax(*node) || IsAnyMin(*node) || IsAnyMaxPool(*node); + return IsAnyMax(*node) || IsAnyMin(*node) || IsAnyMaxPool(*node) || + IsArgMax(*node) || IsArgMin(*node); } Status TrySimplify(NodeDef* reduction_node, @@ -2759,6 +2760,12 @@ class OptimizeMaxOrMinOfMonotonicStage : public ArithmeticOptimizerStage { const string opposite = FlipMinMax(*reduction_node); reduction_node->set_op(opposite); } + + if (IsArgMax(*reduction_node) || IsArgMin(*reduction_node)) { + // ArgMax(Sqrt(x)) = ArgMax(x) + inner_function->set_op("Identity"); + } + AddToOptimizationQueue(reduction_node); AddToOptimizationQueue(inner_function); AddToOptimizationQueue(inner_input); @@ -2783,7 +2790,7 @@ class OptimizeMaxOrMinOfMonotonicStage : public ArithmeticOptimizerStage { private: string FlipMinMax(const NodeDef& node) { const string& op = node.op(); - if (IsAnyMax(node)) { + if (IsAnyMax(node) || IsArgMax(node)) { return str_util::StringReplace(op, "Max", "Min", false); } else { return str_util::StringReplace(op, "Min", "Max", false); diff --git a/tensorflow/core/grappler/optimizers/arithmetic_optimizer_test.cc b/tensorflow/core/grappler/optimizers/arithmetic_optimizer_test.cc index 1220aefecf..d7af91d425 100644 --- a/tensorflow/core/grappler/optimizers/arithmetic_optimizer_test.cc +++ b/tensorflow/core/grappler/optimizers/arithmetic_optimizer_test.cc @@ -3466,6 +3466,47 @@ TEST_F(ArithmeticOptimizerTest, OptimizeMaxOrMinOfMonotonicElementWise) { EXPECT_EQ(2, required_node_count); } +TEST_F(ArithmeticOptimizerTest, OptimizeArgMaxOrArgMinOfMonotonicElementWise) { + tensorflow::Scope s = tensorflow::Scope::NewRootScope(); + const auto x = ops::Const(s.WithOpName("x"), {1.0f, 2.0f}, {1, 2}); + Output sqrt = ops::Sqrt(s.WithOpName("sqrt"), x); + Output arg_max = ops::ArgMax(s.WithOpName("arg_max"), sqrt, 1); + Output final_out = ops::Identity(s.WithOpName("final_out"), arg_max); + + GrapplerItem item; + item.fetch = {"final_out"}; + TF_CHECK_OK(s.ToGraphDef(&item.graph)); + const auto tensors_expected = EvaluateNodes(item.graph, item.fetch); + EXPECT_EQ(1, tensors_expected.size()); + + GraphDef output; + ArithmeticOptimizer optimizer; + EnableOnlyOptimizeMaxOrMinOfMonotonic(&optimizer); + OptimizeAndPrune(&optimizer, &item, &output); + const auto tensors = EvaluateNodes(output, item.fetch); + EXPECT_EQ(1, tensors.size()); + + test::ExpectTensorEqual(tensors_expected[0], tensors[0]); + EXPECT_EQ(item.graph.node_size() - 1, output.node_size()); + // Check if the inputs are switched + int required_node_count = 0; + for (int i = 0; i < output.node_size(); ++i) { + const NodeDef& node = output.node(i); + if (node.name() == "final_out") { + EXPECT_EQ("Identity", node.op()); + EXPECT_EQ(1, node.input_size()); + EXPECT_EQ("arg_max", node.input(0)); + ++required_node_count; + } else if (node.name() == "arg_max") { + EXPECT_EQ("ArgMax", node.op()); + EXPECT_EQ(2, node.input_size()); + EXPECT_EQ("x", node.input(0)); + ++required_node_count; + } + } + EXPECT_EQ(2, required_node_count); +} + TEST_F(ArithmeticOptimizerTest, OptimizeMaxOrMinOfMonotonicElementWise_DoNotChangeFetchNode) { tensorflow::Scope s = tensorflow::Scope::NewRootScope(); -- GitLab From 594e4bd9b106d35d272c56f783f817400ed0634f Mon Sep 17 00:00:00 2001 From: Dayananda-V Date: Thu, 7 Feb 2019 10:18:37 +0530 Subject: [PATCH 068/766] TF Ops document updated(unreachable link removed) --- tensorflow/python/ops/ragged/ragged_map_ops.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tensorflow/python/ops/ragged/ragged_map_ops.py b/tensorflow/python/ops/ragged/ragged_map_ops.py index fbe188bd1a..1d342512c0 100644 --- a/tensorflow/python/ops/ragged/ragged_map_ops.py +++ b/tensorflow/python/ops/ragged/ragged_map_ops.py @@ -12,11 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Functional operations. - -See the [Higher Order -Functions](https://tensorflow.org/api_guides/python/functional_ops) guide. -""" +"""Functional operations for RaggedTensors.""" from __future__ import absolute_import from __future__ import division -- GitLab From 708faca72c504a8e790af575799f2c40f134ccde Mon Sep 17 00:00:00 2001 From: Siju Date: Thu, 7 Feb 2019 10:36:07 +0530 Subject: [PATCH 069/766] docstring updated `Raises: ValueError: In case of invalid arguments.` This is already available in line 205 --- tensorflow/python/keras/engine/training_utils.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tensorflow/python/keras/engine/training_utils.py b/tensorflow/python/keras/engine/training_utils.py index 22f976a705..1509abd682 100644 --- a/tensorflow/python/keras/engine/training_utils.py +++ b/tensorflow/python/keras/engine/training_utils.py @@ -213,9 +213,6 @@ def check_num_samples(ins, processed based on the size of the first dimension of the first input numpy array. When steps is not `None` and `batch_size` is `None`, returns `None`. - - Raises: - ValueError: In case of invalid arguments. """ if steps is not None and batch_size is not None: raise ValueError( -- GitLab From 7a5286d9c6192c8715d749e6ad3e7f8dcadfa361 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Thu, 7 Feb 2019 14:05:22 -0800 Subject: [PATCH 070/766] Add CreateTensor() and ExpectEqual() util functions --- .../core/kernels/data/dataset_test_base.cc | 17 +++++++++++++++++ .../core/kernels/data/dataset_test_base.h | 13 +++++++++++++ 2 files changed, 30 insertions(+) diff --git a/tensorflow/core/kernels/data/dataset_test_base.cc b/tensorflow/core/kernels/data/dataset_test_base.cc index 36a862ff00..e8cf529652 100644 --- a/tensorflow/core/kernels/data/dataset_test_base.cc +++ b/tensorflow/core/kernels/data/dataset_test_base.cc @@ -18,6 +18,23 @@ limitations under the License. namespace tensorflow { namespace data { +Status DatasetOpsTestBase::ExpectEqual(const Tensor &a, const Tensor &b) { + EXPECT_EQ(a.dtype(), b.dtype()); + switch (a.dtype()) { +#define CASE(type) \ + case DataTypeToEnum::value: \ + test::ExpectTensorEqual(a, b); \ + break; + TF_CALL_NUMBER_TYPES(CASE); + TF_CALL_string(CASE); + // TODO(feihugis): figure out how to support variant tensors. +#undef CASE + default: + return errors::Internal("Unsupported dtype", a.dtype()); + } + return Status::OK(); +} + Status DatasetOpsTestBase::CreateOpKernel( const NodeDef& node_def, std::unique_ptr* op_kernel) { Status status; diff --git a/tensorflow/core/kernels/data/dataset_test_base.h b/tensorflow/core/kernels/data/dataset_test_base.h index ee5008318c..9c3a62c693 100644 --- a/tensorflow/core/kernels/data/dataset_test_base.h +++ b/tensorflow/core/kernels/data/dataset_test_base.h @@ -47,6 +47,19 @@ class DatasetOpsTestBase : public ::testing::Test { ~DatasetOpsTestBase() {} + // The method validates whether the two tensors have the same shape, dtype, + // and value. + static Status ExpectEqual(const Tensor& a, const Tensor& b); + + // Creates a tensor with the specified dtype, shape, and value. + template + static Tensor CreateTensor(TensorShape input_shape, + const gtl::ArraySlice& input_data) { + Tensor tensor(DataTypeToEnum::value, input_shape); + test::FillValues(&tensor, input_data); + return tensor; + } + // Creates a new op kernel based on the node definition. Status CreateOpKernel(const NodeDef& node_def, std::unique_ptr* op_kernel); -- GitLab From 541b2925b261f973e3d98ca0a0f5b76bef29e592 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Thu, 7 Feb 2019 14:06:20 -0800 Subject: [PATCH 071/766] Add the tests for TensorSliceDataset --- tensorflow/core/kernels/data/BUILD | 39 ++ .../data/tensor_slice_dataset_op_test.cc | 630 ++++++++++++++++++ 2 files changed, 669 insertions(+) create mode 100644 tensorflow/core/kernels/data/tensor_slice_dataset_op_test.cc diff --git a/tensorflow/core/kernels/data/BUILD b/tensorflow/core/kernels/data/BUILD index 18114d6421..0d45850cd6 100644 --- a/tensorflow/core/kernels/data/BUILD +++ b/tensorflow/core/kernels/data/BUILD @@ -443,6 +443,25 @@ tf_kernel_library( ], ) +tf_cc_test( + name = "sparse_tensor_slice_dataset_op_test", + size = "small", + srcs = ["sparse_tensor_slice_dataset_op_test.cc"], + deps = [ + ":dataset_test_base", + ":dataset_utils", + ":iterator_ops", + ":sparse_tensor_slice_dataset_op", + "//tensorflow/core:core_cpu_internal", + "//tensorflow/core:dataset_ops_op_lib", + "//tensorflow/core:framework", + "//tensorflow/core:lib_internal", + "//tensorflow/core:test", + "//tensorflow/core:test_main", + "//tensorflow/core:testlib", + ], +) + tf_kernel_library( name = "tensor_dataset_op", srcs = ["tensor_dataset_op.cc"], @@ -457,12 +476,32 @@ tf_kernel_library( name = "tensor_slice_dataset_op", srcs = ["tensor_slice_dataset_op.cc"], deps = [ + ":dataset_utils", "//tensorflow/core:dataset_ops_op_lib", "//tensorflow/core:framework", "//tensorflow/core:graph", ], ) +tf_cc_test( + name = "tensor_slice_dataset_op_test", + size = "small", + srcs = ["tensor_slice_dataset_op_test.cc"], + deps = [ + ":dataset_test_base", + ":dataset_utils", + ":iterator_ops", + ":tensor_slice_dataset_op", + "//tensorflow/core:core_cpu_internal", + "//tensorflow/core:dataset_ops_op_lib", + "//tensorflow/core:framework", + "//tensorflow/core:lib_internal", + "//tensorflow/core:test", + "//tensorflow/core:test_main", + "//tensorflow/core:testlib", + ], +) + tf_kernel_library( name = "zip_dataset_op", srcs = ["zip_dataset_op.cc"], diff --git a/tensorflow/core/kernels/data/tensor_slice_dataset_op_test.cc b/tensorflow/core/kernels/data/tensor_slice_dataset_op_test.cc new file mode 100644 index 0000000000..09336bc6db --- /dev/null +++ b/tensorflow/core/kernels/data/tensor_slice_dataset_op_test.cc @@ -0,0 +1,630 @@ +/* Copyright 2019 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/dataset.h" +#include "tensorflow/core/framework/fake_input.h" +#include "tensorflow/core/framework/function_testlib.h" +#include "tensorflow/core/framework/node_def_builder.h" +#include "tensorflow/core/framework/partial_tensor_shape.h" +#include "tensorflow/core/framework/variant.h" +#include "tensorflow/core/framework/variant_tensor_data.h" +#include "tensorflow/core/kernels/data/dataset_test_base.h" +#include "tensorflow/core/kernels/data/dataset_utils.h" +#include "tensorflow/core/kernels/data/iterator_ops.h" +#include "tensorflow/core/kernels/ops_testutil.h" +#include "tensorflow/core/platform/test.h" +#include "tensorflow/core/util/ptr_util.h" + +namespace tensorflow { +namespace data { +namespace { + +constexpr char kNodeName[] = "tensor_slice_dataset"; +constexpr char kOpName[] = "TensorSliceDataset"; + +class TensorSliceDatasetOpTest : public DatasetOpsTestBase { + protected: + // Creates a new TensorSliceDataset op kernel. + Status CreateTensorSliceDatasetKernel( + DataTypeVector dtypes, std::vector shapes, + std::unique_ptr *tensor_dataset_kernel) { + std::vector components; + components.reserve(dtypes.size()); + for (int i = 0; i < dtypes.size(); i++) { + components.emplace_back(strings::StrCat("component_", i)); + } + + node_def_ = test::function::NDef( + kNodeName, kOpName, components, + {{"Toutput_types", dtypes}, {"output_shapes", shapes}}); + TF_CHECK_OK(CreateOpKernel(node_def_, tensor_dataset_kernel)); + return Status::OK(); + } + + // Creates a new TensorSliceDataset op kernel context. + Status CreateTensorSliceDatasetContext( + OpKernel *const tensor_dataset_kernel, + gtl::InlinedVector *inputs, + std::unique_ptr *context) { + TF_RETURN_IF_ERROR(CheckOpKernelInput(*tensor_dataset_kernel, *inputs)); + TF_RETURN_IF_ERROR( + CreateOpKernelContext(tensor_dataset_kernel, inputs, context)); + return Status::OK(); + } + + private: + NodeDef node_def_; +}; + +struct TestParam { + std::vector components; + std::vector expected_outputs; + std::vector breakpoints; +} TestCases[] = { + // A single tuple of tensors. + {{{DatasetOpsTestBase::CreateTensor(TensorShape({2}), {1, 2}), + DatasetOpsTestBase::CreateTensor(TensorShape({2, 2}), + {1, 2, 3, 4}), + DatasetOpsTestBase::CreateTensor(TensorShape({2, 1}), + {37.0, 38.0}), + DatasetOpsTestBase::CreateTensor(TensorShape({2, 1}), + {"a", "b"})}}, // components + {{DatasetOpsTestBase::CreateTensor(TensorShape({}), {1}), + DatasetOpsTestBase::CreateTensor(TensorShape({2}), {1, 2}), + DatasetOpsTestBase::CreateTensor(TensorShape({1}), {37.0}), + DatasetOpsTestBase::CreateTensor(TensorShape({1}), {"a"}), + DatasetOpsTestBase::CreateTensor(TensorShape({}), {2}), + DatasetOpsTestBase::CreateTensor(TensorShape({2}), {3, 4}), + DatasetOpsTestBase::CreateTensor(TensorShape({1}), {38.0}), + DatasetOpsTestBase::CreateTensor(TensorShape({1}), + {"b"})}}, // expected_outputs + {{0, 1, 3}}}, // breakpoints + // Nested tensors + {{{DatasetOpsTestBase::CreateTensor( + TensorShape({2, 1}), + {DatasetOpsTestBase::CreateTensor(TensorShape({2, 2}), + {1.0, 2.0, 3.0, 4.0}), + DatasetOpsTestBase::CreateTensor(TensorShape({2, 2}), + {5.0, 6.0, 7.0, 8.0})}), + DatasetOpsTestBase::CreateTensor( + TensorShape({2, 1}), {DatasetOpsTestBase::CreateTensor( + TensorShape({1, 2}), {"a", "b"}), + DatasetOpsTestBase::CreateTensor( + TensorShape({1, 2}), {"c", "d"})}), + DatasetOpsTestBase::CreateTensor( + TensorShape({2, 3}), {1, 2, 3, 4, 5, 6})}}, // components + {{DatasetOpsTestBase::CreateTensor( + TensorShape({1}), {DatasetOpsTestBase::CreateTensor( + TensorShape({2, 2}), {1.0, 2.0, 3.0, 4.0})}), + DatasetOpsTestBase::CreateTensor( + TensorShape({1}), {DatasetOpsTestBase::CreateTensor( + TensorShape({1, 2}), {"a", "b"})}), + DatasetOpsTestBase::CreateTensor(TensorShape({3}), {1, 2, 3}), + DatasetOpsTestBase::CreateTensor( + TensorShape({1}), {DatasetOpsTestBase::CreateTensor( + TensorShape({2, 2}), {5.0, 6.0, 7.0, 8.0})}), + DatasetOpsTestBase::CreateTensor( + TensorShape({1}), {DatasetOpsTestBase::CreateTensor( + TensorShape({1, 2}), {"c", "d"})}), + DatasetOpsTestBase::CreateTensor( + TensorShape({3}), {4, 5, 6})}}, // expected_outputs + {{0, 1, 2}}} // breakpoints +}; + +struct DatasetGetNextTest : TensorSliceDatasetOpTest, + ::testing::WithParamInterface {}; + +TEST_P(DatasetGetNextTest, GetNext) { + int thread_num = 2, cpu_num = 2; + std::vector components = GetParam().components; + std::vector expected_outputs = GetParam().expected_outputs; + size_t num_tensors_per_slice = components.size(); + + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + DataTypeVector dtypes; + std::vector shapes; + gtl::InlinedVector inputs; + for (auto &component : components) { + inputs.push_back(&component); + dtypes.push_back(component.dtype()); + } + for (int i = 0; i < num_tensors_per_slice; ++i) { + shapes.emplace_back(expected_outputs[i].shape()); + } + + std::unique_ptr tensor_slice_dataset_kernel; + TF_ASSERT_OK(CreateTensorSliceDatasetKernel(dtypes, shapes, + &tensor_slice_dataset_kernel)); + std::unique_ptr tensor_slice_dataset_context; + TF_ASSERT_OK( + CreateTensorSliceDatasetContext(tensor_slice_dataset_kernel.get(), + &inputs, &tensor_slice_dataset_context)); + DatasetBase *tensor_slice_dataset; + TF_ASSERT_OK(CreateDataset(tensor_slice_dataset_kernel.get(), + tensor_slice_dataset_context.get(), + &tensor_slice_dataset)); + core::ScopedUnref scored_unref(tensor_slice_dataset); + + std::unique_ptr iterator_context; + TF_ASSERT_OK(CreateIteratorContext(tensor_slice_dataset_context.get(), + &iterator_context)); + std::unique_ptr iterator; + TF_ASSERT_OK(tensor_slice_dataset->MakeIterator(iterator_context.get(), + "Iterator", &iterator)); + bool end_of_sequence = false; + std::vector out_tensors; + int cur_slice = 0; + + while (!end_of_sequence) { + TF_EXPECT_OK(iterator->GetNext(iterator_context.get(), &out_tensors, + &end_of_sequence)); + for (int i = 0; i < out_tensors.size(); ++i) { + EXPECT_LT(i + num_tensors_per_slice * cur_slice, expected_outputs.size()); + if (out_tensors[i].dtype() == DT_VARIANT) { + // Currently `ExpectEqual()` does not support the variant tensor + // yet, so we manually cast the variant to numeric/string tensor. + const Tensor *output = out_tensors[i].scalar()().get(); + const Tensor *expected_output = + expected_outputs[i + num_tensors_per_slice * cur_slice] + .scalar()() + .get(); + TF_EXPECT_OK(ExpectEqual(*output, *expected_output)); + } else { + TF_EXPECT_OK(ExpectEqual( + out_tensors[i], + expected_outputs[i + num_tensors_per_slice * cur_slice])); + } + } + out_tensors.clear(); + cur_slice++; + } +} + +INSTANTIATE_TEST_CASE_P(TensorDatasetSliceOpTest, DatasetGetNextTest, + ::testing::ValuesIn(TestCases)); + +TEST_F(TensorSliceDatasetOpTest, DatasetName) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + Tensor t1 = CreateTensor(TensorShape({2, 2}), {1, 2, 3, 4}); + Tensor t2 = CreateTensor(TensorShape({2, 2}), {5, 6, 7, 8}); + gtl::InlinedVector inputs = {&t1, &t2}; + DataTypeVector dtypes({DT_INT64, DT_INT64}); + std::vector shapes = {PartialTensorShape({2}), + PartialTensorShape({2})}; + std::unique_ptr tensor_slice_dataset_kernel; + TF_ASSERT_OK(CreateTensorSliceDatasetKernel(dtypes, shapes, + &tensor_slice_dataset_kernel)); + std::unique_ptr tensor_slice_dataset_context; + TF_ASSERT_OK( + CreateTensorSliceDatasetContext(tensor_slice_dataset_kernel.get(), + &inputs, &tensor_slice_dataset_context)); + DatasetBase *tensor_slice_dataset; + TF_ASSERT_OK(CreateDataset(tensor_slice_dataset_kernel.get(), + tensor_slice_dataset_context.get(), + &tensor_slice_dataset)); + core::ScopedUnref scored_unref(tensor_slice_dataset); + + EXPECT_EQ(tensor_slice_dataset->name(), kOpName); +} + +struct DatasetOutputDtypesTest : TensorSliceDatasetOpTest, + ::testing::WithParamInterface {}; + +TEST_P(DatasetOutputDtypesTest, DatasetOutputDtypes) { + int thread_num = 2, cpu_num = 2; + std::vector components = GetParam().components; + std::vector expected_outputs = GetParam().expected_outputs; + size_t num_tensors_per_slice = components.size(); + + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + DataTypeVector dtypes; + std::vector shapes; + gtl::InlinedVector inputs; + for (auto &component : components) { + inputs.emplace_back(&component); + dtypes.emplace_back(component.dtype()); + } + for (int i = 0; i < num_tensors_per_slice; ++i) { + shapes.emplace_back(expected_outputs[i].shape()); + } + + std::unique_ptr tensor_slice_dataset_kernel; + TF_ASSERT_OK(CreateTensorSliceDatasetKernel(dtypes, shapes, + &tensor_slice_dataset_kernel)); + std::unique_ptr tensor_slice_dataset_context; + TF_ASSERT_OK( + CreateTensorSliceDatasetContext(tensor_slice_dataset_kernel.get(), + &inputs, &tensor_slice_dataset_context)); + DatasetBase *tensor_slice_dataset; + TF_ASSERT_OK(CreateDataset(tensor_slice_dataset_kernel.get(), + tensor_slice_dataset_context.get(), + &tensor_slice_dataset)); + core::ScopedUnref scored_unref(tensor_slice_dataset); + + const DataTypeVector produced_output_dtypes = + tensor_slice_dataset->output_dtypes(); + EXPECT_EQ(produced_output_dtypes.size(), num_tensors_per_slice); + for (int i = 0; i < num_tensors_per_slice; ++i) { + EXPECT_EQ(produced_output_dtypes[i], expected_outputs[i].dtype()); + } +} + +INSTANTIATE_TEST_CASE_P(TensorDatasetSliceOpTest, DatasetOutputDtypesTest, + ::testing::ValuesIn(TestCases)); + +struct DatasetOutputShapesTest : TensorSliceDatasetOpTest, + ::testing::WithParamInterface {}; + +TEST_P(DatasetOutputShapesTest, DatasetOutputShapes) { + int thread_num = 2, cpu_num = 2; + std::vector components = GetParam().components; + std::vector expected_outputs = GetParam().expected_outputs; + size_t num_tensors_per_slice = components.size(); + + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + DataTypeVector dtypes; + std::vector shapes; + gtl::InlinedVector inputs; + for (auto &component : components) { + inputs.emplace_back(&component); + dtypes.emplace_back(component.dtype()); + } + for (int i = 0; i < num_tensors_per_slice; ++i) { + shapes.emplace_back(expected_outputs[i].shape()); + } + std::unique_ptr tensor_slice_dataset_kernel; + TF_ASSERT_OK(CreateTensorSliceDatasetKernel(dtypes, shapes, + &tensor_slice_dataset_kernel)); + std::unique_ptr tensor_slice_dataset_context; + TF_ASSERT_OK( + CreateTensorSliceDatasetContext(tensor_slice_dataset_kernel.get(), + &inputs, &tensor_slice_dataset_context)); + DatasetBase *tensor_slice_dataset; + TF_ASSERT_OK(CreateDataset(tensor_slice_dataset_kernel.get(), + tensor_slice_dataset_context.get(), + &tensor_slice_dataset)); + core::ScopedUnref scored_unref(tensor_slice_dataset); + + const std::vector produced_output_shapes = + tensor_slice_dataset->output_shapes(); + std::vector expected_output_shapes; + EXPECT_EQ(produced_output_shapes.size(), num_tensors_per_slice); + for (int i = 0; i < num_tensors_per_slice; ++i) { + EXPECT_TRUE( + produced_output_shapes[i].IsIdenticalTo(expected_outputs[i].shape())); + } +} + +INSTANTIATE_TEST_CASE_P(TensorDatasetSliceOpTest, DatasetOutputShapesTest, + ::testing::ValuesIn(TestCases)); + +struct DatasetCardinalityTest : TensorSliceDatasetOpTest, + ::testing::WithParamInterface {}; + +TEST_P(DatasetCardinalityTest, Cardinality) { + int thread_num = 2, cpu_num = 2; + std::vector components = GetParam().components; + std::vector expected_outputs = GetParam().expected_outputs; + size_t num_tensors_per_slice = components.size(); + + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + DataTypeVector dtypes; + std::vector shapes; + gtl::InlinedVector inputs; + for (auto &component : components) { + inputs.emplace_back(&component); + dtypes.emplace_back(component.dtype()); + } + for (int i = 0; i < num_tensors_per_slice; ++i) { + shapes.emplace_back(expected_outputs[i].shape()); + } + std::unique_ptr tensor_slice_dataset_kernel; + TF_ASSERT_OK(CreateTensorSliceDatasetKernel(dtypes, shapes, + &tensor_slice_dataset_kernel)); + std::unique_ptr tensor_slice_dataset_context; + TF_ASSERT_OK( + CreateTensorSliceDatasetContext(tensor_slice_dataset_kernel.get(), + &inputs, &tensor_slice_dataset_context)); + DatasetBase *tensor_slice_dataset; + TF_ASSERT_OK(CreateDataset(tensor_slice_dataset_kernel.get(), + tensor_slice_dataset_context.get(), + &tensor_slice_dataset)); + core::ScopedUnref scored_unref(tensor_slice_dataset); + + EXPECT_EQ(tensor_slice_dataset->Cardinality(), inputs[0].tensor->dim_size(0)); +} + +INSTANTIATE_TEST_CASE_P(TensorDatasetSliceOpTest, DatasetCardinalityTest, + ::testing::ValuesIn(TestCases)); + +TEST_F(TensorSliceDatasetOpTest, DatasetSave) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + Tensor t1 = CreateTensor(TensorShape({2, 2}), {1, 2, 3, 4}); + Tensor t2 = CreateTensor(TensorShape({2, 2}), {5, 6, 7, 8}); + gtl::InlinedVector inputs = {&t1, &t2}; + DataTypeVector dtypes({DT_INT64, DT_INT64}); + std::vector shapes = {PartialTensorShape({2}), + PartialTensorShape({2})}; + std::unique_ptr tensor_slice_dataset_kernel; + TF_ASSERT_OK(CreateTensorSliceDatasetKernel(dtypes, shapes, + &tensor_slice_dataset_kernel)); + std::unique_ptr tensor_slice_dataset_context; + TF_ASSERT_OK( + CreateTensorSliceDatasetContext(tensor_slice_dataset_kernel.get(), + &inputs, &tensor_slice_dataset_context)); + DatasetBase *tensor_slice_dataset; + TF_ASSERT_OK(CreateDataset(tensor_slice_dataset_kernel.get(), + tensor_slice_dataset_context.get(), + &tensor_slice_dataset)); + core::ScopedUnref scored_unref(tensor_slice_dataset); + + std::unique_ptr serialization_context; + TF_ASSERT_OK(CreateSerializationContext(&serialization_context)); + VariantTensorData data; + VariantTensorDataWriter writer(&data); + TF_ASSERT_OK( + tensor_slice_dataset->Save(serialization_context.get(), &writer)); + TF_ASSERT_OK(writer.Flush()); +} + +struct IteratorOutputDtypesTest : TensorSliceDatasetOpTest, + ::testing::WithParamInterface {}; + +TEST_P(IteratorOutputDtypesTest, IteratorOutputDtypes) { + int thread_num = 2, cpu_num = 2; + std::vector components = GetParam().components; + std::vector expected_outputs = GetParam().expected_outputs; + size_t num_tensors_per_slice = components.size(); + + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + DataTypeVector dtypes; + std::vector shapes; + gtl::InlinedVector inputs; + for (auto &component : components) { + inputs.emplace_back(&component); + dtypes.emplace_back(component.dtype()); + } + for (int i = 0; i < num_tensors_per_slice; ++i) { + shapes.emplace_back(expected_outputs[i].shape()); + } + + std::unique_ptr tensor_slice_dataset_kernel; + TF_ASSERT_OK(CreateTensorSliceDatasetKernel(dtypes, shapes, + &tensor_slice_dataset_kernel)); + std::unique_ptr tensor_slice_dataset_context; + TF_ASSERT_OK( + CreateTensorSliceDatasetContext(tensor_slice_dataset_kernel.get(), + &inputs, &tensor_slice_dataset_context)); + DatasetBase *tensor_slice_dataset; + TF_ASSERT_OK(CreateDataset(tensor_slice_dataset_kernel.get(), + tensor_slice_dataset_context.get(), + &tensor_slice_dataset)); + core::ScopedUnref scored_unref(tensor_slice_dataset); + + std::unique_ptr iterator_context; + TF_ASSERT_OK(CreateIteratorContext(tensor_slice_dataset_context.get(), + &iterator_context)); + std::unique_ptr iterator; + TF_ASSERT_OK(tensor_slice_dataset->MakeIterator(iterator_context.get(), + "Iterator", &iterator)); + const DataTypeVector produced_output_dtypes = iterator->output_dtypes(); + + EXPECT_EQ(produced_output_dtypes.size(), num_tensors_per_slice); + for (int i = 0; i < num_tensors_per_slice; ++i) { + EXPECT_EQ(produced_output_dtypes[i], expected_outputs[i].dtype()); + } +} + +INSTANTIATE_TEST_CASE_P(TensorDatasetSliceOpTest, IteratorOutputDtypesTest, + ::testing::ValuesIn(TestCases)); + +struct IteratorOutputShapesTest : TensorSliceDatasetOpTest, + ::testing::WithParamInterface {}; + +TEST_P(IteratorOutputShapesTest, IteratorOutputShapes) { + int thread_num = 2, cpu_num = 2; + std::vector components = GetParam().components; + std::vector expected_outputs = GetParam().expected_outputs; + size_t num_tensors_per_slice = components.size(); + + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + DataTypeVector dtypes; + std::vector shapes; + gtl::InlinedVector inputs; + for (auto &component : components) { + inputs.emplace_back(&component); + dtypes.emplace_back(component.dtype()); + } + for (int i = 0; i < num_tensors_per_slice; ++i) { + shapes.emplace_back(expected_outputs[i].shape()); + } + + std::unique_ptr tensor_slice_dataset_kernel; + TF_ASSERT_OK(CreateTensorSliceDatasetKernel(dtypes, shapes, + &tensor_slice_dataset_kernel)); + std::unique_ptr tensor_slice_dataset_context; + TF_ASSERT_OK( + CreateTensorSliceDatasetContext(tensor_slice_dataset_kernel.get(), + &inputs, &tensor_slice_dataset_context)); + DatasetBase *tensor_slice_dataset; + TF_ASSERT_OK(CreateDataset(tensor_slice_dataset_kernel.get(), + tensor_slice_dataset_context.get(), + &tensor_slice_dataset)); + core::ScopedUnref scored_unref(tensor_slice_dataset); + + std::unique_ptr iterator_context; + TF_ASSERT_OK(CreateIteratorContext(tensor_slice_dataset_context.get(), + &iterator_context)); + std::unique_ptr iterator; + TF_ASSERT_OK(tensor_slice_dataset->MakeIterator(iterator_context.get(), + "Iterator", &iterator)); + const std::vector produced_output_shapes = + iterator->output_shapes(); + EXPECT_EQ(produced_output_shapes.size(), num_tensors_per_slice); + for (int i = 0; i < num_tensors_per_slice; ++i) { + EXPECT_TRUE( + produced_output_shapes[i].IsIdenticalTo(expected_outputs[i].shape())); + } +} + +INSTANTIATE_TEST_CASE_P(TensorDatasetSliceOpTest, IteratorOutputShapesTest, + ::testing::ValuesIn(TestCases)); + +TEST_F(TensorSliceDatasetOpTest, IteratorOutputPrefix) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + Tensor t1 = CreateTensor(TensorShape({2, 2}), {1, 2, 3, 4}); + Tensor t2 = CreateTensor(TensorShape({2, 2}), {5, 6, 7, 8}); + gtl::InlinedVector inputs = {&t1, &t2}; + DataTypeVector dtypes({DT_INT64, DT_INT64}); + std::vector shapes = {PartialTensorShape({2}), + PartialTensorShape({2})}; + std::unique_ptr tensor_slice_dataset_kernel; + TF_ASSERT_OK(CreateTensorSliceDatasetKernel(dtypes, shapes, + &tensor_slice_dataset_kernel)); + std::unique_ptr tensor_slice_dataset_context; + TF_ASSERT_OK( + CreateTensorSliceDatasetContext(tensor_slice_dataset_kernel.get(), + &inputs, &tensor_slice_dataset_context)); + DatasetBase *tensor_slice_dataset; + TF_ASSERT_OK(CreateDataset(tensor_slice_dataset_kernel.get(), + tensor_slice_dataset_context.get(), + &tensor_slice_dataset)); + core::ScopedUnref scored_unref(tensor_slice_dataset); + + std::unique_ptr iterator_context; + TF_ASSERT_OK(CreateIteratorContext(tensor_slice_dataset_context.get(), + &iterator_context)); + std::unique_ptr iterator; + TF_ASSERT_OK(tensor_slice_dataset->MakeIterator(iterator_context.get(), + "Iterator", &iterator)); + EXPECT_EQ(iterator->prefix(), "Iterator::TensorSlice"); +} + +struct IteratorRoundtripTest : TensorSliceDatasetOpTest, + ::testing::WithParamInterface {}; + +TEST_P(IteratorRoundtripTest, Roundtrip) { + int thread_num = 2, cpu_num = 2; + std::vector components = GetParam().components; + std::vector expected_outputs = GetParam().expected_outputs; + std::vector breakpoints = GetParam().breakpoints; + size_t num_tensors_per_slice = components.size(); + + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + DataTypeVector dtypes; + std::vector shapes; + gtl::InlinedVector inputs; + for (auto &component : components) { + inputs.emplace_back(&component); + dtypes.emplace_back(component.dtype()); + } + for (int i = 0; i < num_tensors_per_slice; ++i) { + shapes.emplace_back(expected_outputs[i].shape()); + } + + std::unique_ptr tensor_slice_dataset_kernel; + TF_ASSERT_OK(CreateTensorSliceDatasetKernel(dtypes, shapes, + &tensor_slice_dataset_kernel)); + std::unique_ptr tensor_slice_dataset_context; + TF_ASSERT_OK( + CreateTensorSliceDatasetContext(tensor_slice_dataset_kernel.get(), + &inputs, &tensor_slice_dataset_context)); + DatasetBase *tensor_slice_dataset; + TF_ASSERT_OK(CreateDataset(tensor_slice_dataset_kernel.get(), + tensor_slice_dataset_context.get(), + &tensor_slice_dataset)); + core::ScopedUnref scored_unref(tensor_slice_dataset); + + std::unique_ptr iterator_context; + TF_ASSERT_OK(CreateIteratorContext(tensor_slice_dataset_context.get(), + &iterator_context)); + std::unique_ptr iterator; + TF_ASSERT_OK(tensor_slice_dataset->MakeIterator(iterator_context.get(), + "Iterator", &iterator)); + std::unique_ptr serialization_context; + TF_ASSERT_OK(CreateSerializationContext(&serialization_context)); + + int cur_iteration = 0; + bool end_of_sequence = false; + int64 num_slices = inputs[0].tensor->dim_size(0); + std::vector out_tensors; + + for (int breakpoint : breakpoints) { + while (cur_iteration < breakpoint) { + TF_EXPECT_OK(iterator->GetNext(iterator_context.get(), &out_tensors, + &end_of_sequence)); + cur_iteration++; + } + + if (breakpoint == 0) { + EXPECT_FALSE(end_of_sequence); + } else if (breakpoint <= num_slices) { + for (int i = 0; i < out_tensors.size(); ++i) { + if (out_tensors[i].dtype() == DT_VARIANT) { + const Tensor *output = + out_tensors[i].scalar()().get(); + const Tensor *expected_output = + expected_outputs[i + num_tensors_per_slice * (cur_iteration - 1)] + .scalar()() + .get(); + TF_EXPECT_OK(ExpectEqual(*output, *expected_output)); + } else { + TF_EXPECT_OK(ExpectEqual( + out_tensors[i], expected_outputs[i + num_tensors_per_slice * + (cur_iteration - 1)])); + } + } + } else { + EXPECT_TRUE(end_of_sequence); + } + + VariantTensorData data; + VariantTensorDataWriter writer(&data); + TF_ASSERT_OK(iterator->Save(serialization_context.get(), &writer)); + TF_ASSERT_OK(writer.Flush()); + VariantTensorDataReader reader(&data); + TF_ASSERT_OK(iterator->Restore(iterator_context.get(), &reader)); + } +} + +INSTANTIATE_TEST_CASE_P(TensorDatasetSliceOpTest, IteratorRoundtripTest, + ::testing::ValuesIn(TestCases)); + +} // namespace +} // namespace data +} // namespace tensorflow \ No newline at end of file -- GitLab From 4516ec242fc949f4148239527bb88d4084b8c562 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Thu, 7 Feb 2019 14:08:19 -0800 Subject: [PATCH 072/766] Add the tests for SparseTensorSliceDataset --- .../sparse_tensor_slice_dataset_op_test.cc | 519 ++++++++++++++++++ 1 file changed, 519 insertions(+) create mode 100644 tensorflow/core/kernels/data/sparse_tensor_slice_dataset_op_test.cc diff --git a/tensorflow/core/kernels/data/sparse_tensor_slice_dataset_op_test.cc b/tensorflow/core/kernels/data/sparse_tensor_slice_dataset_op_test.cc new file mode 100644 index 0000000000..d3b5ab66bc --- /dev/null +++ b/tensorflow/core/kernels/data/sparse_tensor_slice_dataset_op_test.cc @@ -0,0 +1,519 @@ +/* Copyright 2019 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/dataset.h" +#include "tensorflow/core/framework/fake_input.h" +#include "tensorflow/core/framework/function_testlib.h" +#include "tensorflow/core/framework/node_def_builder.h" +#include "tensorflow/core/framework/partial_tensor_shape.h" +#include "tensorflow/core/framework/variant.h" +#include "tensorflow/core/framework/variant_tensor_data.h" +#include "tensorflow/core/kernels/data/dataset_test_base.h" +#include "tensorflow/core/kernels/data/dataset_utils.h" +#include "tensorflow/core/kernels/data/iterator_ops.h" +#include "tensorflow/core/kernels/ops_testutil.h" +#include "tensorflow/core/platform/test.h" +#include "tensorflow/core/util/ptr_util.h" + +namespace tensorflow { +namespace data { +namespace { + +constexpr char kNodeName[] = "sparse_tensor_slice_dataset"; +constexpr char kOpName[] = "SparseTensorSliceDataset"; + +class SparseTensorSliceDatasetOpTest : public DatasetOpsTestBase { + protected: + // Creates a new SparseTensorSliceDataset op kernel. + Status CreateSparseTensorSliceDatasetKernel( + DataType tvalues, std::unique_ptr *op_kernel) { + node_def_ = test::function::NDef(kNodeName, kOpName, + {"indices", "values", "dense_shape"}, + {{"Tvalues", tvalues}}); + TF_CHECK_OK(CreateOpKernel(node_def_, op_kernel)); + return Status::OK(); + } + + // Creates a new SparseTensorSliceDataset op kernel context. + Status CreateSparseTensorSliceDatasetContext( + OpKernel *const op_kernel, gtl::InlinedVector *inputs, + std::unique_ptr *context) { + TF_RETURN_IF_ERROR(CheckOpKernelInput(*op_kernel, *inputs)); + TF_RETURN_IF_ERROR(CreateOpKernelContext(op_kernel, inputs, context)); + return Status::OK(); + } + + private: + NodeDef node_def_; +}; + +struct SparseTensorParam { + Tensor indices; + Tensor values; + Tensor dense_shape; +}; + +struct TestParam { + SparseTensorParam input_sparse_tensor; + std::vector expected_outputs; + std::vector breakpoints; +} TestCases[] = { + {{{DatasetOpsTestBase::CreateTensor({2, 2}, {0, 0, 1, 1})}, + {DatasetOpsTestBase::CreateTensor({2}, {888, 999})}, + {DatasetOpsTestBase::CreateTensor({2}, {2, 2})}}, + {{{DatasetOpsTestBase::CreateTensor({1, 1}, {0})}, + {DatasetOpsTestBase::CreateTensor({1}, {888})}, + {DatasetOpsTestBase::CreateTensor({1}, {2})}}, + {{DatasetOpsTestBase::CreateTensor({1, 1}, {1})}, + {DatasetOpsTestBase::CreateTensor({1}, {999})}, + {DatasetOpsTestBase::CreateTensor({1}, {2})}}}, + {0, 1, 2}}, // 2-D sparse tensor + {{{DatasetOpsTestBase::CreateTensor({2, 3}, {0, 0, 0, 1, 1, 1})}, + {DatasetOpsTestBase::CreateTensor({2}, {888.0, 999.0})}, + {DatasetOpsTestBase::CreateTensor({3}, {2, 2, 2})}}, + {{{DatasetOpsTestBase::CreateTensor({1, 2}, {0, 0})}, + {DatasetOpsTestBase::CreateTensor({1}, {888.0})}, + {DatasetOpsTestBase::CreateTensor({2}, {2, 2})}}, + {{DatasetOpsTestBase::CreateTensor({1, 2}, {1, 1})}, + {DatasetOpsTestBase::CreateTensor({1}, {999.0})}, + {DatasetOpsTestBase::CreateTensor({2}, {2, 2})}}}, + {0, 1, 2}}, // 3-D sparse tensor + {{{DatasetOpsTestBase::CreateTensor({2, 4}, + {0, 0, 0, 0, 1, 1, 1, 1})}, + {DatasetOpsTestBase::CreateTensor({2}, {"a", "b"})}, + {DatasetOpsTestBase::CreateTensor({4}, {3, 2, 2, 2})}}, + {{{DatasetOpsTestBase::CreateTensor({1, 3}, {0, 0, 0})}, + {DatasetOpsTestBase::CreateTensor({1}, {"a"})}, + {DatasetOpsTestBase::CreateTensor({3}, {2, 2, 2})}}, + {{DatasetOpsTestBase::CreateTensor({1, 3}, {1, 1, 1})}, + {DatasetOpsTestBase::CreateTensor({1}, {"b"})}, + {DatasetOpsTestBase::CreateTensor({3}, {2, 2, 2})}}, + {{DatasetOpsTestBase::CreateTensor({0, 3}, {})}, + {DatasetOpsTestBase::CreateTensor({0}, {})}, + {DatasetOpsTestBase::CreateTensor({3}, {2, 2, 2})}}}, + {0, 1, 3}}, // 4-D sparse tensor + {{{DatasetOpsTestBase::CreateTensor({2, 5}, + {0, 0, 0, 0, 0, 1, 1, 1, 1, 1})}, + {DatasetOpsTestBase::CreateTensor({2}, {888, 999})}, + {DatasetOpsTestBase::CreateTensor({5}, {3, 2, 2, 2, 2})}}, + {{{DatasetOpsTestBase::CreateTensor({1, 4}, {0, 0, 0, 0})}, + {DatasetOpsTestBase::CreateTensor({1}, {888})}, + {DatasetOpsTestBase::CreateTensor({4}, {2, 2, 2, 2})}}, + {{DatasetOpsTestBase::CreateTensor({1, 4}, {1, 1, 1, 1})}, + {DatasetOpsTestBase::CreateTensor({1}, {999})}, + {DatasetOpsTestBase::CreateTensor({4}, {2, 2, 2, 2})}}, + {{DatasetOpsTestBase::CreateTensor({0, 4}, {})}, + {DatasetOpsTestBase::CreateTensor({0}, {})}, + {DatasetOpsTestBase::CreateTensor({4}, {2, 2, 2, 2})}}}, + {0, 1, 3}} // 5-D sparse tensor + +}; + +struct DatasetGetNextTest : SparseTensorSliceDatasetOpTest, + ::testing::WithParamInterface {}; + +TEST_P(DatasetGetNextTest, GetNext) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + SparseTensorParam input_sparse_tensor = GetParam().input_sparse_tensor; + std::vector expected_outputs = GetParam().expected_outputs; + DataType tvalues = input_sparse_tensor.values.dtype(); + gtl::InlinedVector inputs = { + &input_sparse_tensor.indices, &input_sparse_tensor.values, + &input_sparse_tensor.dense_shape}; + + std::unique_ptr dataset_kernel; + TF_ASSERT_OK(CreateSparseTensorSliceDatasetKernel(tvalues, &dataset_kernel)); + std::unique_ptr dataset_kernel_ctx; + TF_ASSERT_OK(CreateSparseTensorSliceDatasetContext( + dataset_kernel.get(), &inputs, &dataset_kernel_ctx)); + DatasetBase *dataset; + TF_ASSERT_OK( + CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); + core::ScopedUnref scoped_unref(dataset); + + std::unique_ptr iterator_ctx; + TF_ASSERT_OK(CreateIteratorContext(dataset_kernel_ctx.get(), &iterator_ctx)); + std::unique_ptr iterator; + TF_ASSERT_OK( + dataset->MakeIterator(iterator_ctx.get(), "Iterator", &iterator)); + bool end_of_sequence = false; + std::vector out_tensors; + int cur_slice = 0; + while (!end_of_sequence) { + TF_EXPECT_OK( + iterator->GetNext(iterator_ctx.get(), &out_tensors, &end_of_sequence)); + if (!end_of_sequence) { + TF_EXPECT_OK( + ExpectEqual(out_tensors[0], expected_outputs[cur_slice].indices)); + TF_EXPECT_OK( + ExpectEqual(out_tensors[1], expected_outputs[cur_slice].values)); + TF_EXPECT_OK( + ExpectEqual(out_tensors[2], expected_outputs[cur_slice].dense_shape)); + cur_slice++; + } + } +} + +INSTANTIATE_TEST_CASE_P(SparseTensorSliceDatasetOpTest, DatasetGetNextTest, + ::testing::ValuesIn(TestCases)); + +TEST_F(SparseTensorSliceDatasetOpTest, DatasetName) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + int N = 2; + const int NDIM = 2; + Tensor indices = CreateTensor(TensorShape({N, NDIM}), {0, 0, 1, 1}); + Tensor values = CreateTensor(TensorShape({N}), {888, 999}); + Tensor dense_shape = CreateTensor(TensorShape({NDIM}), {5, 5}); + gtl::InlinedVector inputs = {&indices, &values, &dense_shape}; + + std::unique_ptr dataset_kernel; + TF_ASSERT_OK(CreateSparseTensorSliceDatasetKernel(DT_INT32, &dataset_kernel)); + std::unique_ptr dataset_kernel_ctx; + TF_ASSERT_OK(CreateSparseTensorSliceDatasetContext( + dataset_kernel.get(), &inputs, &dataset_kernel_ctx)); + DatasetBase *dataset; + TF_ASSERT_OK( + CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); + core::ScopedUnref scoped_unref(dataset); + + EXPECT_EQ(dataset->name(), kOpName); +} + +struct DatasetOutputDtypesTest : SparseTensorSliceDatasetOpTest, + ::testing::WithParamInterface {}; + +TEST_P(DatasetOutputDtypesTest, DatasetOutputDtypes) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + SparseTensorParam input_sparse_tensor = GetParam().input_sparse_tensor; + std::vector expected_outputs = GetParam().expected_outputs; + DataType tvalues = input_sparse_tensor.values.dtype(); + gtl::InlinedVector inputs = { + &input_sparse_tensor.indices, &input_sparse_tensor.values, + &input_sparse_tensor.dense_shape}; + + std::unique_ptr dataset_kernel; + TF_ASSERT_OK(CreateSparseTensorSliceDatasetKernel(tvalues, &dataset_kernel)); + std::unique_ptr dataset_kernel_ctx; + TF_ASSERT_OK(CreateSparseTensorSliceDatasetContext( + dataset_kernel.get(), &inputs, &dataset_kernel_ctx)); + DatasetBase *dataset; + TF_ASSERT_OK( + CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); + core::ScopedUnref scoped_unref(dataset); + + DataTypeVector expected_output_dtypes = { + expected_outputs[0].indices.dtype(), expected_outputs[0].values.dtype(), + expected_outputs[0].dense_shape.dtype()}; + TF_EXPECT_OK( + VerifyTypesMatch(dataset->output_dtypes(), expected_output_dtypes)); +} + +INSTANTIATE_TEST_CASE_P(SparseTensorDatasetSliceOpTest, DatasetOutputDtypesTest, + ::testing::ValuesIn(TestCases)); + +struct DatasetOutputShapesTest : SparseTensorSliceDatasetOpTest, + ::testing::WithParamInterface {}; + +TEST_P(DatasetOutputShapesTest, DatasetOutputShapes) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + SparseTensorParam input_sparse_tensor = GetParam().input_sparse_tensor; + std::vector expected_outputs = GetParam().expected_outputs; + DataType tvalues = input_sparse_tensor.values.dtype(); + gtl::InlinedVector inputs = { + &input_sparse_tensor.indices, &input_sparse_tensor.values, + &input_sparse_tensor.dense_shape}; + + std::unique_ptr dataset_kernel; + TF_ASSERT_OK(CreateSparseTensorSliceDatasetKernel(tvalues, &dataset_kernel)); + std::unique_ptr dataset_kernel_ctx; + TF_ASSERT_OK(CreateSparseTensorSliceDatasetContext( + dataset_kernel.get(), &inputs, &dataset_kernel_ctx)); + DatasetBase *dataset; + TF_ASSERT_OK( + CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); + core::ScopedUnref scoped_unref(dataset); + + std::vector expected_output_shapes = { + expected_outputs[0].indices.shape(), expected_outputs[0].values.shape(), + expected_outputs[0].dense_shape.shape()}; + TF_EXPECT_OK( + VerifyShapesCompatible(dataset->output_shapes(), expected_output_shapes)); +} + +INSTANTIATE_TEST_CASE_P(SparseTensorDatasetSliceOpTest, DatasetOutputShapesTest, + ::testing::ValuesIn(TestCases)); + +struct DatasetCardinalityTest : SparseTensorSliceDatasetOpTest, + ::testing::WithParamInterface {}; + +TEST_P(DatasetCardinalityTest, Cardinality) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + SparseTensorParam input_sparse_tensor = GetParam().input_sparse_tensor; + std::vector expected_outputs = GetParam().expected_outputs; + DataType tvalues = input_sparse_tensor.values.dtype(); + gtl::InlinedVector inputs = { + &input_sparse_tensor.indices, &input_sparse_tensor.values, + &input_sparse_tensor.dense_shape}; + + std::unique_ptr dataset_kernel; + TF_ASSERT_OK(CreateSparseTensorSliceDatasetKernel(tvalues, &dataset_kernel)); + std::unique_ptr dataset_kernel_ctx; + TF_ASSERT_OK(CreateSparseTensorSliceDatasetContext( + dataset_kernel.get(), &inputs, &dataset_kernel_ctx)); + DatasetBase *dataset; + TF_ASSERT_OK( + CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); + core::ScopedUnref scoped_unref(dataset); + + EXPECT_EQ(dataset->Cardinality(), expected_outputs.size()); +} + +INSTANTIATE_TEST_CASE_P(SparseTensorDatasetSliceOpTest, DatasetCardinalityTest, + ::testing::ValuesIn(TestCases)); + +TEST_F(SparseTensorSliceDatasetOpTest, DatasetSave) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + int N = 2; + const int NDIM = 2; + Tensor indices = CreateTensor(TensorShape({N, NDIM}), {0, 0, 1, 1}); + Tensor values = CreateTensor(TensorShape({N}), {888, 999}); + Tensor dense_shape = CreateTensor(TensorShape({NDIM}), {5, 5}); + gtl::InlinedVector inputs = {&indices, &values, &dense_shape}; + + std::unique_ptr dataset_kernel; + TF_ASSERT_OK(CreateSparseTensorSliceDatasetKernel(DT_INT32, &dataset_kernel)); + std::unique_ptr dataset_kernel_ctx; + TF_ASSERT_OK(CreateSparseTensorSliceDatasetContext( + dataset_kernel.get(), &inputs, &dataset_kernel_ctx)); + DatasetBase *dataset; + TF_ASSERT_OK( + CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); + core::ScopedUnref scoped_unref(dataset); + + std::unique_ptr serialization_ctx; + TF_ASSERT_OK(CreateSerializationContext(&serialization_ctx)); + VariantTensorData data; + VariantTensorDataWriter writer(&data); + TF_ASSERT_OK(dataset->Save(serialization_ctx.get(), &writer)); + TF_ASSERT_OK(writer.Flush()); +} + +struct IteratorOutputDtypesTest : SparseTensorSliceDatasetOpTest, + ::testing::WithParamInterface {}; + +TEST_P(IteratorOutputDtypesTest, IteratorOutputDtypes) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + SparseTensorParam input_sparse_tensor = GetParam().input_sparse_tensor; + std::vector expected_outputs = GetParam().expected_outputs; + DataType tvalues = input_sparse_tensor.values.dtype(); + gtl::InlinedVector inputs = { + &input_sparse_tensor.indices, &input_sparse_tensor.values, + &input_sparse_tensor.dense_shape}; + + std::unique_ptr dataset_kernel; + TF_ASSERT_OK(CreateSparseTensorSliceDatasetKernel(tvalues, &dataset_kernel)); + std::unique_ptr dataset_kernel_ctx; + TF_ASSERT_OK(CreateSparseTensorSliceDatasetContext( + dataset_kernel.get(), &inputs, &dataset_kernel_ctx)); + DatasetBase *dataset; + TF_ASSERT_OK( + CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); + core::ScopedUnref scoped_unref(dataset); + + std::unique_ptr iterator_ctx; + TF_ASSERT_OK(CreateIteratorContext(dataset_kernel_ctx.get(), &iterator_ctx)); + std::unique_ptr iterator; + TF_ASSERT_OK( + dataset->MakeIterator(iterator_ctx.get(), "Iterator", &iterator)); + DataTypeVector expected_output_dtypes = { + expected_outputs[0].indices.dtype(), expected_outputs[0].values.dtype(), + expected_outputs[0].dense_shape.dtype()}; + TF_EXPECT_OK( + VerifyTypesMatch(iterator->output_dtypes(), expected_output_dtypes)); +} + +INSTANTIATE_TEST_CASE_P(SparseTensorSliceDatasetOpTest, + IteratorOutputDtypesTest, + ::testing::ValuesIn(TestCases)); + +struct IteratorOutputShapesTest : SparseTensorSliceDatasetOpTest, + ::testing::WithParamInterface {}; + +TEST_P(IteratorOutputShapesTest, IteratorOutputShapes) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + SparseTensorParam input_sparse_tensor = GetParam().input_sparse_tensor; + std::vector expected_outputs = GetParam().expected_outputs; + DataType tvalues = input_sparse_tensor.values.dtype(); + gtl::InlinedVector inputs = { + &input_sparse_tensor.indices, &input_sparse_tensor.values, + &input_sparse_tensor.dense_shape}; + + std::unique_ptr dataset_kernel; + TF_ASSERT_OK(CreateSparseTensorSliceDatasetKernel(tvalues, &dataset_kernel)); + std::unique_ptr dataset_kernel_ctx; + TF_ASSERT_OK(CreateSparseTensorSliceDatasetContext( + dataset_kernel.get(), &inputs, &dataset_kernel_ctx)); + DatasetBase *dataset; + TF_ASSERT_OK( + CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); + core::ScopedUnref scoped_unref(dataset); + + std::unique_ptr iterator_ctx; + TF_ASSERT_OK(CreateIteratorContext(dataset_kernel_ctx.get(), &iterator_ctx)); + std::unique_ptr iterator; + TF_ASSERT_OK( + dataset->MakeIterator(iterator_ctx.get(), "Iterator", &iterator)); + std::vector expected_output_shapes = { + expected_outputs[0].indices.shape(), expected_outputs[0].values.shape(), + expected_outputs[0].dense_shape.shape()}; + TF_EXPECT_OK(VerifyShapesCompatible(iterator->output_shapes(), + expected_output_shapes)); +} + +INSTANTIATE_TEST_CASE_P(SparseTensorSliceDatasetOpTest, + IteratorOutputShapesTest, + ::testing::ValuesIn(TestCases)); + +TEST_F(SparseTensorSliceDatasetOpTest, IteratorOutputPrefix) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + int N = 2; + const int NDIM = 2; + Tensor indices = CreateTensor(TensorShape({N, NDIM}), {0, 0, 1, 1}); + Tensor values = CreateTensor(TensorShape({N}), {888, 999}); + Tensor dense_shape = CreateTensor(TensorShape({NDIM}), {5, 5}); + gtl::InlinedVector inputs = {&indices, &values, &dense_shape}; + + std::unique_ptr dataset_kernel; + TF_ASSERT_OK(CreateSparseTensorSliceDatasetKernel(DT_INT32, &dataset_kernel)); + std::unique_ptr dataset_kernel_ctx; + TF_ASSERT_OK(CreateSparseTensorSliceDatasetContext( + dataset_kernel.get(), &inputs, &dataset_kernel_ctx)); + DatasetBase *dataset; + TF_ASSERT_OK( + CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); + core::ScopedUnref scoped_unref(dataset); + + std::unique_ptr iterator_ctx; + TF_ASSERT_OK(CreateIteratorContext(dataset_kernel_ctx.get(), &iterator_ctx)); + std::unique_ptr iterator; + TF_ASSERT_OK( + dataset->MakeIterator(iterator_ctx.get(), "Iterator", &iterator)); + EXPECT_EQ(iterator->prefix(), strings::StrCat("Iterator::SparseTensorSlice")); +} + +struct IteratorRoundtripTest : SparseTensorSliceDatasetOpTest, + ::testing::WithParamInterface {}; + +TEST_P(IteratorRoundtripTest, Roundtrip) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + SparseTensorParam input_sparse_tensor = GetParam().input_sparse_tensor; + std::vector expected_outputs = GetParam().expected_outputs; + std::vector breakpoints = GetParam().breakpoints; + DataType tvalues = input_sparse_tensor.values.dtype(); + gtl::InlinedVector inputs = { + &input_sparse_tensor.indices, &input_sparse_tensor.values, + &input_sparse_tensor.dense_shape}; + + std::unique_ptr dataset_kernel; + TF_ASSERT_OK(CreateSparseTensorSliceDatasetKernel(tvalues, &dataset_kernel)); + std::unique_ptr dataset_kernel_ctx; + TF_ASSERT_OK(CreateSparseTensorSliceDatasetContext( + dataset_kernel.get(), &inputs, &dataset_kernel_ctx)); + DatasetBase *dataset; + TF_ASSERT_OK( + CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); + core::ScopedUnref scoped_unref(dataset); + + std::unique_ptr iterator_ctx; + TF_ASSERT_OK(CreateIteratorContext(dataset_kernel_ctx.get(), &iterator_ctx)); + std::unique_ptr iterator; + TF_ASSERT_OK( + dataset->MakeIterator(iterator_ctx.get(), "Iterator", &iterator)); + + std::unique_ptr serialization_ctx; + TF_ASSERT_OK(CreateSerializationContext(&serialization_ctx)); + + int cur_iteration = 0; + bool end_of_sequence = false; + int64 num_slices = input_sparse_tensor.dense_shape.dim_size(0); + std::vector out_tensors; + + for (int breakpoint : breakpoints) { + while (cur_iteration < breakpoint) { + TF_EXPECT_OK(iterator->GetNext(iterator_ctx.get(), &out_tensors, + &end_of_sequence)); + cur_iteration++; + } + + if (breakpoint == 0) { + EXPECT_FALSE(end_of_sequence); + } else if (breakpoint <= num_slices) { + for (int i = 0; i < out_tensors.size(); ++i) { + TF_EXPECT_OK(ExpectEqual(out_tensors[0], + expected_outputs[cur_iteration - 1].indices)); + TF_EXPECT_OK(ExpectEqual(out_tensors[1], + expected_outputs[cur_iteration - 1].values)); + TF_EXPECT_OK(ExpectEqual( + out_tensors[2], expected_outputs[cur_iteration - 1].dense_shape)); + } + } else { + EXPECT_TRUE(end_of_sequence); + } + + VariantTensorData data; + VariantTensorDataWriter writer(&data); + TF_ASSERT_OK(iterator->Save(serialization_ctx.get(), &writer)); + TF_ASSERT_OK(writer.Flush()); + VariantTensorDataReader reader(&data); + TF_ASSERT_OK(iterator->Restore(iterator_ctx.get(), &reader)); + } +} + +INSTANTIATE_TEST_CASE_P(SparseTensorSliceDatasetOpTest, IteratorRoundtripTest, + ::testing::ValuesIn(TestCases)); + +} // namespace +} // namespace data +} // namespace tensorflow \ No newline at end of file -- GitLab From 71727aefe3a378c6ec21c85ee73cfb1fbc116712 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Thu, 7 Feb 2019 14:11:04 -0800 Subject: [PATCH 073/766] Add the shape and dtype validation for TensorSliceDataset --- tensorflow/core/kernels/data/dataset_test_base.cc | 2 +- .../core/kernels/data/tensor_slice_dataset_op.cc | 13 ++++++++++++- .../kernels/data/tensor_slice_dataset_op_test.cc | 8 ++++---- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/tensorflow/core/kernels/data/dataset_test_base.cc b/tensorflow/core/kernels/data/dataset_test_base.cc index e8cf529652..e163dbaa3d 100644 --- a/tensorflow/core/kernels/data/dataset_test_base.cc +++ b/tensorflow/core/kernels/data/dataset_test_base.cc @@ -18,7 +18,7 @@ limitations under the License. namespace tensorflow { namespace data { -Status DatasetOpsTestBase::ExpectEqual(const Tensor &a, const Tensor &b) { +Status DatasetOpsTestBase::ExpectEqual(const Tensor& a, const Tensor& b) { EXPECT_EQ(a.dtype(), b.dtype()); switch (a.dtype()) { #define CASE(type) \ diff --git a/tensorflow/core/kernels/data/tensor_slice_dataset_op.cc b/tensorflow/core/kernels/data/tensor_slice_dataset_op.cc index 54dcd7eb7d..97a1ec402f 100644 --- a/tensorflow/core/kernels/data/tensor_slice_dataset_op.cc +++ b/tensorflow/core/kernels/data/tensor_slice_dataset_op.cc @@ -16,6 +16,7 @@ limitations under the License. #include "tensorflow/core/framework/partial_tensor_shape.h" #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/graph/graph.h" +#include "tensorflow/core/kernels/data/dataset_utils.h" #include "tensorflow/core/util/batch_util.h" namespace tensorflow { @@ -28,7 +29,10 @@ namespace { class TensorSliceDatasetOp : public DatasetOpKernel { public: explicit TensorSliceDatasetOp(OpKernelConstruction* ctx) - : DatasetOpKernel(ctx) {} + : DatasetOpKernel(ctx) { + OP_REQUIRES_OK(ctx, ctx->GetAttr("Toutput_types", &output_types_)); + OP_REQUIRES_OK(ctx, ctx->GetAttr("output_shapes", &output_shapes_)); + } void MakeDataset(OpKernelContext* ctx, DatasetBase** output) override { OpInputList inputs; @@ -50,6 +54,10 @@ class TensorSliceDatasetOp : public DatasetOpKernel { "All components must have the same size in the 0th dimension")); } *output = new Dataset(ctx, std::move(components)); + OP_REQUIRES_OK(ctx, + VerifyTypesMatch((*output)->output_dtypes(), output_types_)); + OP_REQUIRES_OK(ctx, VerifyShapesCompatible((*output)->output_shapes(), + output_shapes_)); } private: @@ -170,6 +178,9 @@ class TensorSliceDatasetOp : public DatasetOpKernel { DataTypeVector dtypes_; std::vector shapes_; }; + + DataTypeVector output_types_; + std::vector output_shapes_; }; REGISTER_KERNEL_BUILDER(Name("TensorSliceDataset").Device(DEVICE_CPU), diff --git a/tensorflow/core/kernels/data/tensor_slice_dataset_op_test.cc b/tensorflow/core/kernels/data/tensor_slice_dataset_op_test.cc index 09336bc6db..67e86dc04f 100644 --- a/tensorflow/core/kernels/data/tensor_slice_dataset_op_test.cc +++ b/tensorflow/core/kernels/data/tensor_slice_dataset_op_test.cc @@ -107,17 +107,17 @@ struct TestParam { TensorShape({2, 3}), {1, 2, 3, 4, 5, 6})}}, // components {{DatasetOpsTestBase::CreateTensor( TensorShape({1}), {DatasetOpsTestBase::CreateTensor( - TensorShape({2, 2}), {1.0, 2.0, 3.0, 4.0})}), + TensorShape({2, 2}), {1.0, 2.0, 3.0, 4.0})}), DatasetOpsTestBase::CreateTensor( TensorShape({1}), {DatasetOpsTestBase::CreateTensor( - TensorShape({1, 2}), {"a", "b"})}), + TensorShape({1, 2}), {"a", "b"})}), DatasetOpsTestBase::CreateTensor(TensorShape({3}), {1, 2, 3}), DatasetOpsTestBase::CreateTensor( TensorShape({1}), {DatasetOpsTestBase::CreateTensor( - TensorShape({2, 2}), {5.0, 6.0, 7.0, 8.0})}), + TensorShape({2, 2}), {5.0, 6.0, 7.0, 8.0})}), DatasetOpsTestBase::CreateTensor( TensorShape({1}), {DatasetOpsTestBase::CreateTensor( - TensorShape({1, 2}), {"c", "d"})}), + TensorShape({1, 2}), {"c", "d"})}), DatasetOpsTestBase::CreateTensor( TensorShape({3}), {4, 5, 6})}}, // expected_outputs {{0, 1, 2}}} // breakpoints -- GitLab From cfc04307d316bcd158377b5e768613b89927f74b Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 15:34:38 -0800 Subject: [PATCH 074/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 30070bf008..56d8796ef8 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -16,6 +16,9 @@ detection model, takes up a total of 22KB. * [Building for "Hifive1" SiFive FE310 development board using Make](#building-for-hifive1-sifive-fe310-development-board-using-make) * [Building for Ambiq Micro Apollo3Blue EVB using Make](#building-for-ambiq-micro-apollo3blue-evb-using-make) * [Additional Apollo3 Instructions](#additional-apollo3-instructions) + * [Building for the ECM3531 Eta Compute EVB using Make](#building-for-eta_compute-ecm3531-evb-using-make) + * [Additional ECM3531 EVB Instructions](#additional-ecm3531-evb-instructions) + - [Goals](#goals) - [Generating Project Files](#generating-project-#files) - [How to Port TensorFlow Lite Micro to a New Platform](#how-to-port-tensorflow-lite-micro-to-a-new-platform) @@ -45,6 +48,7 @@ project files that you can download for the following platforms: ["Blue Pill" STM32F103-compatible development board](https://github.com/google/stm32_bare_lib) | - | - | [Instructions](#building-for-the-blue-pill-stm32f103-using-make) [Ambiq Micro Apollo3Blue EVB using Make](https://ambiqmicro.com/apollo-ultra-low-power-mcus/) | - | - | [Instructions](#building-for-ambiq-micro-apollo3blue-evb-using-make) [Generic Keil uVision Projects](http://www2.keil.com/mdk5/uvision/) | - | [Download](https://drive.google.com/open?id=1Lw9rsdquNKObozClLPoE5CTJLuhfh5mV) | - +[Eta Compute ECM3531 EVB](https://www.etacompute.com/products/) | - | - | [Instructions] (#building-for-eta-compute-ecm3531-evb-using-make) If your device is not yet supported, it may not be too hard to add support. You can learn about that process -- GitLab From 009bbb0155f70220c9b88acd2c2bd2073d736fe9 Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 15:42:28 -0800 Subject: [PATCH 075/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 56d8796ef8..4a8ee68cba 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -17,7 +17,6 @@ detection model, takes up a total of 22KB. * [Building for Ambiq Micro Apollo3Blue EVB using Make](#building-for-ambiq-micro-apollo3blue-evb-using-make) * [Additional Apollo3 Instructions](#additional-apollo3-instructions) * [Building for the ECM3531 Eta Compute EVB using Make](#building-for-eta_compute-ecm3531-evb-using-make) - * [Additional ECM3531 EVB Instructions](#additional-ecm3531-evb-instructions) - [Goals](#goals) - [Generating Project Files](#generating-project-#files) @@ -48,7 +47,7 @@ project files that you can download for the following platforms: ["Blue Pill" STM32F103-compatible development board](https://github.com/google/stm32_bare_lib) | - | - | [Instructions](#building-for-the-blue-pill-stm32f103-using-make) [Ambiq Micro Apollo3Blue EVB using Make](https://ambiqmicro.com/apollo-ultra-low-power-mcus/) | - | - | [Instructions](#building-for-ambiq-micro-apollo3blue-evb-using-make) [Generic Keil uVision Projects](http://www2.keil.com/mdk5/uvision/) | - | [Download](https://drive.google.com/open?id=1Lw9rsdquNKObozClLPoE5CTJLuhfh5mV) | - -[Eta Compute ECM3531 EVB](https://www.etacompute.com/products/) | - | - | [Instructions] (#building-for-eta-compute-ecm3531-evb-using-make) +[Eta Compute ECM3531 EVB](https://www.etacompute.com/products/) | - | - | [Instructions] (#building-for-eta_compute-ecm3531-evb-using-make) If your device is not yet supported, it may not be too hard to add support. You can learn about that process -- GitLab From d57230ba2ad9f63e961f892e20c9132ab59fd231 Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 15:55:25 -0800 Subject: [PATCH 076/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 4a8ee68cba..3dc0eca3a2 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -16,7 +16,7 @@ detection model, takes up a total of 22KB. * [Building for "Hifive1" SiFive FE310 development board using Make](#building-for-hifive1-sifive-fe310-development-board-using-make) * [Building for Ambiq Micro Apollo3Blue EVB using Make](#building-for-ambiq-micro-apollo3blue-evb-using-make) * [Additional Apollo3 Instructions](#additional-apollo3-instructions) - * [Building for the ECM3531 Eta Compute EVB using Make](#building-for-eta_compute-ecm3531-evb-using-make) + * [Building for the ECM3531 Eta Compute EVB using Make](##building-for-the-eta-compute-ecm3531-evb-using-make) - [Goals](#goals) - [Generating Project Files](#generating-project-#files) @@ -47,7 +47,7 @@ project files that you can download for the following platforms: ["Blue Pill" STM32F103-compatible development board](https://github.com/google/stm32_bare_lib) | - | - | [Instructions](#building-for-the-blue-pill-stm32f103-using-make) [Ambiq Micro Apollo3Blue EVB using Make](https://ambiqmicro.com/apollo-ultra-low-power-mcus/) | - | - | [Instructions](#building-for-ambiq-micro-apollo3blue-evb-using-make) [Generic Keil uVision Projects](http://www2.keil.com/mdk5/uvision/) | - | [Download](https://drive.google.com/open?id=1Lw9rsdquNKObozClLPoE5CTJLuhfh5mV) | - -[Eta Compute ECM3531 EVB](https://www.etacompute.com/products/) | - | - | [Instructions] (#building-for-eta_compute-ecm3531-evb-using-make) +[Eta Compute ECM3531 EVB](https://www.etacompute.com/products/) | - | - | [Instructions] (#building-for-the-eta-compute-ecm3531-evb-using-make) If your device is not yet supported, it may not be too hard to add support. You can learn about that process @@ -318,6 +318,8 @@ To flash a part with JFlash Lite, do the following: 4. Data file = `tensorflow/lite/experimental/micro/tools/make/gen/apollo3evb_cortex-m4/bin/pushbutton_cmsis_speech_test.bin` 5. Prog Addr = 0x0000C000 +## Building for the Eta Compute ECM3531 EVB using Make + ## Goals The design goals are for the framework to be: -- GitLab From 231f3c741d06f4f62a77d710b95211126b1067cf Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 16:01:51 -0800 Subject: [PATCH 077/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 3dc0eca3a2..1a526caa2f 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -47,7 +47,7 @@ project files that you can download for the following platforms: ["Blue Pill" STM32F103-compatible development board](https://github.com/google/stm32_bare_lib) | - | - | [Instructions](#building-for-the-blue-pill-stm32f103-using-make) [Ambiq Micro Apollo3Blue EVB using Make](https://ambiqmicro.com/apollo-ultra-low-power-mcus/) | - | - | [Instructions](#building-for-ambiq-micro-apollo3blue-evb-using-make) [Generic Keil uVision Projects](http://www2.keil.com/mdk5/uvision/) | - | [Download](https://drive.google.com/open?id=1Lw9rsdquNKObozClLPoE5CTJLuhfh5mV) | - -[Eta Compute ECM3531 EVB](https://www.etacompute.com/products/) | - | - | [Instructions] (#building-for-the-eta-compute-ecm3531-evb-using-make) +[Eta Compute ECM3531 EVB](https://www.etacompute.com/products/) | - | - | [Instructions](##building-for-the-eta-compute-ecm3531-evb-using-make) If your device is not yet supported, it may not be too hard to add support. You can learn about that process @@ -318,7 +318,7 @@ To flash a part with JFlash Lite, do the following: 4. Data file = `tensorflow/lite/experimental/micro/tools/make/gen/apollo3evb_cortex-m4/bin/pushbutton_cmsis_speech_test.bin` 5. Prog Addr = 0x0000C000 -## Building for the Eta Compute ECM3531 EVB using Make +## Building For Fhe Eta Compute ECM3531 EVB using make ## Goals -- GitLab From 7af1732830fc5cc8eb3656fa299efb283b1023c9 Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 16:07:56 -0800 Subject: [PATCH 078/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 1a526caa2f..e41c94d801 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -16,7 +16,7 @@ detection model, takes up a total of 22KB. * [Building for "Hifive1" SiFive FE310 development board using Make](#building-for-hifive1-sifive-fe310-development-board-using-make) * [Building for Ambiq Micro Apollo3Blue EVB using Make](#building-for-ambiq-micro-apollo3blue-evb-using-make) * [Additional Apollo3 Instructions](#additional-apollo3-instructions) - * [Building for the ECM3531 Eta Compute EVB using Make](##building-for-the-eta-compute-ecm3531-evb-using-make) + * [Building for the ECM3531 Eta Compute EVB using Make](# building-for-the-eta-compute-ecm3531-evb-using-make) - [Goals](#goals) - [Generating Project Files](#generating-project-#files) @@ -47,7 +47,7 @@ project files that you can download for the following platforms: ["Blue Pill" STM32F103-compatible development board](https://github.com/google/stm32_bare_lib) | - | - | [Instructions](#building-for-the-blue-pill-stm32f103-using-make) [Ambiq Micro Apollo3Blue EVB using Make](https://ambiqmicro.com/apollo-ultra-low-power-mcus/) | - | - | [Instructions](#building-for-ambiq-micro-apollo3blue-evb-using-make) [Generic Keil uVision Projects](http://www2.keil.com/mdk5/uvision/) | - | [Download](https://drive.google.com/open?id=1Lw9rsdquNKObozClLPoE5CTJLuhfh5mV) | - -[Eta Compute ECM3531 EVB](https://www.etacompute.com/products/) | - | - | [Instructions](##building-for-the-eta-compute-ecm3531-evb-using-make) +[Eta Compute ECM3531 EVB](https://www.etacompute.com/products/) | - | - | [Instructions](## building-for-the-eta-compute-ecm3531-evb-using-make) If your device is not yet supported, it may not be too hard to add support. You can learn about that process @@ -318,7 +318,7 @@ To flash a part with JFlash Lite, do the following: 4. Data file = `tensorflow/lite/experimental/micro/tools/make/gen/apollo3evb_cortex-m4/bin/pushbutton_cmsis_speech_test.bin` 5. Prog Addr = 0x0000C000 -## Building For Fhe Eta Compute ECM3531 EVB using make +## Building For The Eta Compute ECM3531 EVB using make ## Goals -- GitLab From f0a987129596a968a3e20f3ce75d5962f471557c Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 16:10:43 -0800 Subject: [PATCH 079/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index e41c94d801..3d6cd085d9 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -16,7 +16,7 @@ detection model, takes up a total of 22KB. * [Building for "Hifive1" SiFive FE310 development board using Make](#building-for-hifive1-sifive-fe310-development-board-using-make) * [Building for Ambiq Micro Apollo3Blue EVB using Make](#building-for-ambiq-micro-apollo3blue-evb-using-make) * [Additional Apollo3 Instructions](#additional-apollo3-instructions) - * [Building for the ECM3531 Eta Compute EVB using Make](# building-for-the-eta-compute-ecm3531-evb-using-make) + * [Building for the ECM3531 Eta Compute EVB using Make](#building-for-the-eta-compute-ecm3531-evb-using-make) - [Goals](#goals) - [Generating Project Files](#generating-project-#files) @@ -47,7 +47,7 @@ project files that you can download for the following platforms: ["Blue Pill" STM32F103-compatible development board](https://github.com/google/stm32_bare_lib) | - | - | [Instructions](#building-for-the-blue-pill-stm32f103-using-make) [Ambiq Micro Apollo3Blue EVB using Make](https://ambiqmicro.com/apollo-ultra-low-power-mcus/) | - | - | [Instructions](#building-for-ambiq-micro-apollo3blue-evb-using-make) [Generic Keil uVision Projects](http://www2.keil.com/mdk5/uvision/) | - | [Download](https://drive.google.com/open?id=1Lw9rsdquNKObozClLPoE5CTJLuhfh5mV) | - -[Eta Compute ECM3531 EVB](https://www.etacompute.com/products/) | - | - | [Instructions](## building-for-the-eta-compute-ecm3531-evb-using-make) +[Eta Compute ECM3531 EVB](https://www.etacompute.com/products/) | - | - | [Instructions](#building-for-the-eta-compute-ecm3531-evb-using-make) If your device is not yet supported, it may not be too hard to add support. You can learn about that process @@ -318,7 +318,7 @@ To flash a part with JFlash Lite, do the following: 4. Data file = `tensorflow/lite/experimental/micro/tools/make/gen/apollo3evb_cortex-m4/bin/pushbutton_cmsis_speech_test.bin` 5. Prog Addr = 0x0000C000 -## Building For The Eta Compute ECM3531 EVB using make +# building-for-the-eta-compute-ecm3531-evb-using-make ## Goals -- GitLab From 3070539b305e3769e715b32d050a4e18c0c3e671 Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 16:15:15 -0800 Subject: [PATCH 080/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 3d6cd085d9..4dbe1d9619 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -318,7 +318,7 @@ To flash a part with JFlash Lite, do the following: 4. Data file = `tensorflow/lite/experimental/micro/tools/make/gen/apollo3evb_cortex-m4/bin/pushbutton_cmsis_speech_test.bin` 5. Prog Addr = 0x0000C000 -# building-for-the-eta-compute-ecm3531-evb-using-make +# Building for the Eta Compute ECM3531 EVB using Make ## Goals -- GitLab From d802c44c24ff373969daa6eecbee1ccbafa94495 Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 16:19:08 -0800 Subject: [PATCH 081/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 4dbe1d9619..56dcf921eb 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -318,7 +318,9 @@ To flash a part with JFlash Lite, do the following: 4. Data file = `tensorflow/lite/experimental/micro/tools/make/gen/apollo3evb_cortex-m4/bin/pushbutton_cmsis_speech_test.bin` 5. Prog Addr = 0x0000C000 -# Building for the Eta Compute ECM3531 EVB using Make +## Building for the Eta Compute ECM3531 EVB using Make + +Follow these steps to build and run the tests and examples ## Goals -- GitLab From dfe1a432687675603bde5e7b731ea477d6e875ec Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 16:33:18 -0800 Subject: [PATCH 082/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 56dcf921eb..0ff090981c 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -16,7 +16,7 @@ detection model, takes up a total of 22KB. * [Building for "Hifive1" SiFive FE310 development board using Make](#building-for-hifive1-sifive-fe310-development-board-using-make) * [Building for Ambiq Micro Apollo3Blue EVB using Make](#building-for-ambiq-micro-apollo3blue-evb-using-make) * [Additional Apollo3 Instructions](#additional-apollo3-instructions) - * [Building for the ECM3531 Eta Compute EVB using Make](#building-for-the-eta-compute-ecm3531-evb-using-make) + * [Building for the ECM3531 Eta Compute EVB using Make](#building-for-hifive1-sifive-fe310-development-board-using-make) - [Goals](#goals) - [Generating Project Files](#generating-project-#files) @@ -47,7 +47,7 @@ project files that you can download for the following platforms: ["Blue Pill" STM32F103-compatible development board](https://github.com/google/stm32_bare_lib) | - | - | [Instructions](#building-for-the-blue-pill-stm32f103-using-make) [Ambiq Micro Apollo3Blue EVB using Make](https://ambiqmicro.com/apollo-ultra-low-power-mcus/) | - | - | [Instructions](#building-for-ambiq-micro-apollo3blue-evb-using-make) [Generic Keil uVision Projects](http://www2.keil.com/mdk5/uvision/) | - | [Download](https://drive.google.com/open?id=1Lw9rsdquNKObozClLPoE5CTJLuhfh5mV) | - -[Eta Compute ECM3531 EVB](https://www.etacompute.com/products/) | - | - | [Instructions](#building-for-the-eta-compute-ecm3531-evb-using-make) +[Eta Compute ECM3531 EVB](https://www.etacompute.com/products/) | - | - | [Instructions](#building-for-hifive1-sifive-fe310-development-board-using-make) If your device is not yet supported, it may not be too hard to add support. You can learn about that process -- GitLab From ec8c365d45b28ecb00c917940ace5fe5219febf7 Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 16:35:04 -0800 Subject: [PATCH 083/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 0ff090981c..533b8dab03 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -16,7 +16,7 @@ detection model, takes up a total of 22KB. * [Building for "Hifive1" SiFive FE310 development board using Make](#building-for-hifive1-sifive-fe310-development-board-using-make) * [Building for Ambiq Micro Apollo3Blue EVB using Make](#building-for-ambiq-micro-apollo3blue-evb-using-make) * [Additional Apollo3 Instructions](#additional-apollo3-instructions) - * [Building for the ECM3531 Eta Compute EVB using Make](#building-for-hifive1-sifive-fe310-development-board-using-make) + * [Getting Started with Portable Reference Code](#getting-started-with-portable-reference-code) - [Goals](#goals) - [Generating Project Files](#generating-project-#files) -- GitLab From ece0fa670f578197c8aead7558ca2b35eddbc6b3 Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 16:35:53 -0800 Subject: [PATCH 084/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 533b8dab03..93a87d8cb1 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -16,7 +16,7 @@ detection model, takes up a total of 22KB. * [Building for "Hifive1" SiFive FE310 development board using Make](#building-for-hifive1-sifive-fe310-development-board-using-make) * [Building for Ambiq Micro Apollo3Blue EVB using Make](#building-for-ambiq-micro-apollo3blue-evb-using-make) * [Additional Apollo3 Instructions](#additional-apollo3-instructions) - * [Getting Started with Portable Reference Code](#getting-started-with-portable-reference-code) + * [Building for the Eta Compute ECM3531 EVB using Make](#getting-started-with-portable-reference-code) - [Goals](#goals) - [Generating Project Files](#generating-project-#files) -- GitLab From 525530e9bf5c2df15400ca818044ade128bff97a Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 16:38:18 -0800 Subject: [PATCH 085/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 93a87d8cb1..0c56fad9ab 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -16,7 +16,7 @@ detection model, takes up a total of 22KB. * [Building for "Hifive1" SiFive FE310 development board using Make](#building-for-hifive1-sifive-fe310-development-board-using-make) * [Building for Ambiq Micro Apollo3Blue EVB using Make](#building-for-ambiq-micro-apollo3blue-evb-using-make) * [Additional Apollo3 Instructions](#additional-apollo3-instructions) - * [Building for the Eta Compute ECM3531 EVB using Make](#getting-started-with-portable-reference-code) + * [Building for the Eta Compute ECM3531 EVB using Make](#building-for-the-eta-compute-ecm3531-evb-using-make) - [Goals](#goals) - [Generating Project Files](#generating-project-#files) @@ -318,7 +318,7 @@ To flash a part with JFlash Lite, do the following: 4. Data file = `tensorflow/lite/experimental/micro/tools/make/gen/apollo3evb_cortex-m4/bin/pushbutton_cmsis_speech_test.bin` 5. Prog Addr = 0x0000C000 -## Building for the Eta Compute ECM3531 EVB using Make +## building for the eta compute ecm3531 evb using make Follow these steps to build and run the tests and examples -- GitLab From 28f779c7fdce6a4a541894ed878ff90580f8a54b Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 16:41:27 -0800 Subject: [PATCH 086/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 0c56fad9ab..6ef78750e7 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -318,7 +318,7 @@ To flash a part with JFlash Lite, do the following: 4. Data file = `tensorflow/lite/experimental/micro/tools/make/gen/apollo3evb_cortex-m4/bin/pushbutton_cmsis_speech_test.bin` 5. Prog Addr = 0x0000C000 -## building for the eta compute ecm3531 evb using make +## Building For The Eta Compute ECM3531 EVB using Make Follow these steps to build and run the tests and examples -- GitLab From 08d855eabdf8e87d94ced02b8aba1088b640e1b3 Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 16:42:22 -0800 Subject: [PATCH 087/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 6ef78750e7..be2379a427 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -47,8 +47,7 @@ project files that you can download for the following platforms: ["Blue Pill" STM32F103-compatible development board](https://github.com/google/stm32_bare_lib) | - | - | [Instructions](#building-for-the-blue-pill-stm32f103-using-make) [Ambiq Micro Apollo3Blue EVB using Make](https://ambiqmicro.com/apollo-ultra-low-power-mcus/) | - | - | [Instructions](#building-for-ambiq-micro-apollo3blue-evb-using-make) [Generic Keil uVision Projects](http://www2.keil.com/mdk5/uvision/) | - | [Download](https://drive.google.com/open?id=1Lw9rsdquNKObozClLPoE5CTJLuhfh5mV) | - -[Eta Compute ECM3531 EVB](https://www.etacompute.com/products/) | - | - | [Instructions](#building-for-hifive1-sifive-fe310-development-board-using-make) - +[Eta Compute ECM3531 EVB](https://www.etacompute.com/products/) | - | - | [Instructions](#building-for-the-eta-compute-ecm3531-evb-using-make) If your device is not yet supported, it may not be too hard to add support. You can learn about that process [here](#how-to-port-tensorflow-lite-micro-to-a-new-platform). We're looking -- GitLab From 7ac2a823fd94e032af37802491c019d1d19d2438 Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 16:43:53 -0800 Subject: [PATCH 088/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index be2379a427..28b494b18c 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -48,6 +48,7 @@ project files that you can download for the following platforms: [Ambiq Micro Apollo3Blue EVB using Make](https://ambiqmicro.com/apollo-ultra-low-power-mcus/) | - | - | [Instructions](#building-for-ambiq-micro-apollo3blue-evb-using-make) [Generic Keil uVision Projects](http://www2.keil.com/mdk5/uvision/) | - | [Download](https://drive.google.com/open?id=1Lw9rsdquNKObozClLPoE5CTJLuhfh5mV) | - [Eta Compute ECM3531 EVB](https://www.etacompute.com/products/) | - | - | [Instructions](#building-for-the-eta-compute-ecm3531-evb-using-make) + If your device is not yet supported, it may not be too hard to add support. You can learn about that process [here](#how-to-port-tensorflow-lite-micro-to-a-new-platform). We're looking -- GitLab From 91eddfbe594d497b320105bcb553bbe50222601a Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 17:27:35 -0800 Subject: [PATCH 089/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 23 ++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 28b494b18c..23cc5f2fad 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -322,6 +322,29 @@ To flash a part with JFlash Lite, do the following: Follow these steps to build and run the tests and examples +This code will enable you to compile and execute the Tensorflow Lite Micro Speech Example on Eta Computes's low power ECM3531 chip. + + +1. Download the Tensorflow code from Github and follow instructions there to download other dependencies. + +2. Download the Eta Compute SDK, version 0.0.17. + +3. Install the Arm compiler arm-none-eabi-gcc, version = arm-none-eabi-gcc (GNU Tools for Arm Embedded Processors 7-2018-q2-update) 7.3.1 20180622 (release) [ARM/embedded-7-branch revision 261907] + +4. Edit the file tensorflow/lite/experimental/micro/tools/make/targets/ecm3531_makefile.inc so that the variable ETA_SDK points to the location where the Eta Compute SDK is installed, and the variable GCC_ARM points to the Arm compiler. + +5. Compile the code with the command "make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=ecm3531 test". This will create the executable tensorflow/lite/experimental/micro/tools/make/gen/ecm3531_cortex-m3/bin/micro_speech_test. + +6. Connect the board to the host computer, start PuTTY (Connection type = Serial, Speed = 11520, Data bits = 8, Stop bits = 1, Parity = None), and load the executable with ocd. A sample script for loading the image is provided in tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program. + +The following will be printed on the Uart: + +Testing TestInvoke +Ran successfully + +/ tests passed +~~~ALL TESTS PASSED~~~ + ## Goals The design goals are for the framework to be: -- GitLab From 0f589f0940534b82b5218b9f0c340fd9eb498115 Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 18:01:51 -0800 Subject: [PATCH 090/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 23cc5f2fad..4bd7846554 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -320,18 +320,15 @@ To flash a part with JFlash Lite, do the following: ## Building For The Eta Compute ECM3531 EVB using Make -Follow these steps to build and run the tests and examples +Follow these steps to build and run the Tensorflow Micro Speech tests and example. -This code will enable you to compile and execute the Tensorflow Lite Micro Speech Example on Eta Computes's low power ECM3531 chip. +1. Follow the procedure outlined in the [Micro Speech Getting Started section] (https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro/examples/micro_speech#getting-started) to clone the Tensorflow code to your machine. - -1. Download the Tensorflow code from Github and follow instructions there to download other dependencies. - -2. Download the Eta Compute SDK, version 0.0.17. +2. Download the Eta Compute SDK, version 0.0.17 (Contact info@etacompute.com for more information on obtaining the Eta Compute SDK and ECM3531 evalution board). 3. Install the Arm compiler arm-none-eabi-gcc, version = arm-none-eabi-gcc (GNU Tools for Arm Embedded Processors 7-2018-q2-update) 7.3.1 20180622 (release) [ARM/embedded-7-branch revision 261907] -4. Edit the file tensorflow/lite/experimental/micro/tools/make/targets/ecm3531_makefile.inc so that the variable ETA_SDK points to the location where the Eta Compute SDK is installed, and the variable GCC_ARM points to the Arm compiler. +4. Edit the file tensorflow/lite/experimental/micro/tools/make/targets/ecm3531_makefile.inc so that the variable ETA_SDK points to the location where the Eta Compute SDK is installed, and the variable GCC_ARM points to the Arm compiler (arm-none-eabi-gcc, version = arm-none-eabi-gcc (GNU Tools for Arm Embedded Processors 7-2018-q2-update) 7.3.1 20180622 (release) [ARM/embedded-7-branch revision 261907] ). The compiler is also downloaded when you ran the tensorflow/lite/experimental/micro/tools/make/download_dependencies.sh script. 5. Compile the code with the command "make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=ecm3531 test". This will create the executable tensorflow/lite/experimental/micro/tools/make/gen/ecm3531_cortex-m3/bin/micro_speech_test. -- GitLab From 533243c4044891efbf0707d5ad3ae54d14db21c6 Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 18:09:31 -0800 Subject: [PATCH 091/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 4bd7846554..0aa5cbac01 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -322,17 +322,15 @@ To flash a part with JFlash Lite, do the following: Follow these steps to build and run the Tensorflow Micro Speech tests and example. -1. Follow the procedure outlined in the [Micro Speech Getting Started section] (https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro/examples/micro_speech#getting-started) to clone the Tensorflow code to your machine. +1. Follow the procedure outlined in the [Micro Speech Getting Started section](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro/examples/micro_speech#getting-started) to download the Tensorflow source code and the support libraries. 2. Download the Eta Compute SDK, version 0.0.17 (Contact info@etacompute.com for more information on obtaining the Eta Compute SDK and ECM3531 evalution board). -3. Install the Arm compiler arm-none-eabi-gcc, version = arm-none-eabi-gcc (GNU Tools for Arm Embedded Processors 7-2018-q2-update) 7.3.1 20180622 (release) [ARM/embedded-7-branch revision 261907] +3. Edit the file tensorflow/lite/experimental/micro/tools/make/targets/ecm3531_makefile.inc so that the variable ETA_SDK points to the location where the Eta Compute SDK is installed, and the variable GCC_ARM points to the Arm compiler (arm-none-eabi-gcc, version = arm-none-eabi-gcc (GNU Tools for Arm Embedded Processors 7-2018-q2-update) 7.3.1 20180622 (release) [ARM/embedded-7-branch revision 261907] ). The compiler is also downloaded when you ran the tensorflow/lite/experimental/micro/tools/make/download_dependencies.sh script. -4. Edit the file tensorflow/lite/experimental/micro/tools/make/targets/ecm3531_makefile.inc so that the variable ETA_SDK points to the location where the Eta Compute SDK is installed, and the variable GCC_ARM points to the Arm compiler (arm-none-eabi-gcc, version = arm-none-eabi-gcc (GNU Tools for Arm Embedded Processors 7-2018-q2-update) 7.3.1 20180622 (release) [ARM/embedded-7-branch revision 261907] ). The compiler is also downloaded when you ran the tensorflow/lite/experimental/micro/tools/make/download_dependencies.sh script. +4. Compile the code with the command make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=ecm3531 TAGS="CMSIS" test. This will create the executable tensorflow/lite/experimental/micro/tools/make/gen/ecm3531_cortex-m3/bin/micro_speech_test. -5. Compile the code with the command "make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=ecm3531 test". This will create the executable tensorflow/lite/experimental/micro/tools/make/gen/ecm3531_cortex-m3/bin/micro_speech_test. - -6. Connect the board to the host computer, start PuTTY (Connection type = Serial, Speed = 11520, Data bits = 8, Stop bits = 1, Parity = None), and load the executable with ocd. A sample script for loading the image is provided in tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program. +5. Connect the board to the host computer, start PuTTY (Connection type = Serial, Speed = 11520, Data bits = 8, Stop bits = 1, Parity = None), and load the executable with ocd. A sample script for loading the image is provided in tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program. The following will be printed on the Uart: -- GitLab From dd0d79389336e1389dfdac0f00440d7fb65d273d Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 18:28:25 -0800 Subject: [PATCH 092/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 0aa5cbac01..1e1c0ffff1 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -320,15 +320,17 @@ To flash a part with JFlash Lite, do the following: ## Building For The Eta Compute ECM3531 EVB using Make -Follow these steps to build and run the Tensorflow Micro Speech tests and example. +Follow these steps to build and run the Tensorflow Micro Speech tests. 1. Follow the procedure outlined in the [Micro Speech Getting Started section](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro/examples/micro_speech#getting-started) to download the Tensorflow source code and the support libraries. 2. Download the Eta Compute SDK, version 0.0.17 (Contact info@etacompute.com for more information on obtaining the Eta Compute SDK and ECM3531 evalution board). -3. Edit the file tensorflow/lite/experimental/micro/tools/make/targets/ecm3531_makefile.inc so that the variable ETA_SDK points to the location where the Eta Compute SDK is installed, and the variable GCC_ARM points to the Arm compiler (arm-none-eabi-gcc, version = arm-none-eabi-gcc (GNU Tools for Arm Embedded Processors 7-2018-q2-update) 7.3.1 20180622 (release) [ARM/embedded-7-branch revision 261907] ). The compiler is also downloaded when you ran the tensorflow/lite/experimental/micro/tools/make/download_dependencies.sh script. +3. Edit the file tensorflow/lite/experimental/micro/tools/make/targets/ecm3531_makefile.inc so that the variable ETA_SDK points to the location where the Eta Compute SDK is installed, and the variable GCC_ARM points to the Arm compiler (arm-none-eabi-gcc, version = arm-none-eabi-gcc (gcc version 7.3.1 20180622, ARM/embedded-7-branch revision 261907, 7-2018-q2-update). The compiler is also downloaded when you run the tensorflow/lite/experimental/micro/tools/make/download_dependencies.sh script. -4. Compile the code with the command make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=ecm3531 TAGS="CMSIS" test. This will create the executable tensorflow/lite/experimental/micro/tools/make/gen/ecm3531_cortex-m3/bin/micro_speech_test. +4. Compile the code with the command +make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=ecm3531 TAGS="CMSIS" test. +This will create a set of executables in tensorflow/lite/experimental/micro/tools/make/gen/ecm3531_cortex-m3/bin/. 5. Connect the board to the host computer, start PuTTY (Connection type = Serial, Speed = 11520, Data bits = 8, Stop bits = 1, Parity = None), and load the executable with ocd. A sample script for loading the image is provided in tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program. -- GitLab From 0011263dc1339c03f5628e2cb3d958c8dd913064 Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 18:32:59 -0800 Subject: [PATCH 093/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 1e1c0ffff1..fb8fc65d57 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -328,9 +328,11 @@ Follow these steps to build and run the Tensorflow Micro Speech tests. 3. Edit the file tensorflow/lite/experimental/micro/tools/make/targets/ecm3531_makefile.inc so that the variable ETA_SDK points to the location where the Eta Compute SDK is installed, and the variable GCC_ARM points to the Arm compiler (arm-none-eabi-gcc, version = arm-none-eabi-gcc (gcc version 7.3.1 20180622, ARM/embedded-7-branch revision 261907, 7-2018-q2-update). The compiler is also downloaded when you run the tensorflow/lite/experimental/micro/tools/make/download_dependencies.sh script. -4. Compile the code with the command -make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=ecm3531 TAGS="CMSIS" test. -This will create a set of executables in tensorflow/lite/experimental/micro/tools/make/gen/ecm3531_cortex-m3/bin/. +4. Compile the code with the command + +make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=ecm3531 TAGS="CMSIS" test + +This will create a set of executables in tensorflow/lite/experimental/micro/tools/make/gen/ecm3531_cortex-m3/bin/ 5. Connect the board to the host computer, start PuTTY (Connection type = Serial, Speed = 11520, Data bits = 8, Stop bits = 1, Parity = None), and load the executable with ocd. A sample script for loading the image is provided in tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program. -- GitLab From 9ecd3ae146ea1cd83f3189e6c99cf77417ff902b Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 18:34:38 -0800 Subject: [PATCH 094/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index fb8fc65d57..35db214c59 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -324,7 +324,7 @@ Follow these steps to build and run the Tensorflow Micro Speech tests. 1. Follow the procedure outlined in the [Micro Speech Getting Started section](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro/examples/micro_speech#getting-started) to download the Tensorflow source code and the support libraries. -2. Download the Eta Compute SDK, version 0.0.17 (Contact info@etacompute.com for more information on obtaining the Eta Compute SDK and ECM3531 evalution board). +2. Download the Eta Compute SDK, version 0.0.17 (Contact info@etacompute.com). 3. Edit the file tensorflow/lite/experimental/micro/tools/make/targets/ecm3531_makefile.inc so that the variable ETA_SDK points to the location where the Eta Compute SDK is installed, and the variable GCC_ARM points to the Arm compiler (arm-none-eabi-gcc, version = arm-none-eabi-gcc (gcc version 7.3.1 20180622, ARM/embedded-7-branch revision 261907, 7-2018-q2-update). The compiler is also downloaded when you run the tensorflow/lite/experimental/micro/tools/make/download_dependencies.sh script. -- GitLab From 03a9acb73330aa08b8dece413fc830d4d9dd5496 Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 18:36:38 -0800 Subject: [PATCH 095/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 35db214c59..4a7818531b 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -322,9 +322,9 @@ To flash a part with JFlash Lite, do the following: Follow these steps to build and run the Tensorflow Micro Speech tests. -1. Follow the procedure outlined in the [Micro Speech Getting Started section](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro/examples/micro_speech#getting-started) to download the Tensorflow source code and the support libraries. +1. Follow the procedure outlined in the [Micro Speech Getting Started section](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro/examples/micro_speech#getting-started) to download the Tensorflow source code and the support libraries (but not the make command) -2. Download the Eta Compute SDK, version 0.0.17 (Contact info@etacompute.com). +2. Download the Eta Compute SDK, version 0.0.17 (contact info@etacompute.com). 3. Edit the file tensorflow/lite/experimental/micro/tools/make/targets/ecm3531_makefile.inc so that the variable ETA_SDK points to the location where the Eta Compute SDK is installed, and the variable GCC_ARM points to the Arm compiler (arm-none-eabi-gcc, version = arm-none-eabi-gcc (gcc version 7.3.1 20180622, ARM/embedded-7-branch revision 261907, 7-2018-q2-update). The compiler is also downloaded when you run the tensorflow/lite/experimental/micro/tools/make/download_dependencies.sh script. -- GitLab From d322aca75bc27f7a87051e6b4c1a8b650fdd2c1e Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 18:40:16 -0800 Subject: [PATCH 096/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 4a7818531b..892995636a 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -322,17 +322,13 @@ To flash a part with JFlash Lite, do the following: Follow these steps to build and run the Tensorflow Micro Speech tests. -1. Follow the procedure outlined in the [Micro Speech Getting Started section](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro/examples/micro_speech#getting-started) to download the Tensorflow source code and the support libraries (but not the make command) +1. Follow the procedure outlined in the [Micro Speech Getting Started section](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro/examples/micro_speech#getting-started) to download the Tensorflow source code and the support libraries \(but not the make command\) 2. Download the Eta Compute SDK, version 0.0.17 (contact info@etacompute.com). 3. Edit the file tensorflow/lite/experimental/micro/tools/make/targets/ecm3531_makefile.inc so that the variable ETA_SDK points to the location where the Eta Compute SDK is installed, and the variable GCC_ARM points to the Arm compiler (arm-none-eabi-gcc, version = arm-none-eabi-gcc (gcc version 7.3.1 20180622, ARM/embedded-7-branch revision 261907, 7-2018-q2-update). The compiler is also downloaded when you run the tensorflow/lite/experimental/micro/tools/make/download_dependencies.sh script. -4. Compile the code with the command - -make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=ecm3531 TAGS="CMSIS" test - -This will create a set of executables in tensorflow/lite/experimental/micro/tools/make/gen/ecm3531_cortex-m3/bin/ +4. Compile the code with the command make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=ecm3531 TAGS="CMSIS" test . This will create a set of executables in tensorflow/lite/experimental/micro/tools/make/gen/ecm3531_cortex-m3/bin/ 5. Connect the board to the host computer, start PuTTY (Connection type = Serial, Speed = 11520, Data bits = 8, Stop bits = 1, Parity = None), and load the executable with ocd. A sample script for loading the image is provided in tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program. -- GitLab From 66df2b04a010413db40af26aa7a859022c112467 Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 18:42:11 -0800 Subject: [PATCH 097/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 892995636a..aa6918121f 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -326,7 +326,7 @@ Follow these steps to build and run the Tensorflow Micro Speech tests. 2. Download the Eta Compute SDK, version 0.0.17 (contact info@etacompute.com). -3. Edit the file tensorflow/lite/experimental/micro/tools/make/targets/ecm3531_makefile.inc so that the variable ETA_SDK points to the location where the Eta Compute SDK is installed, and the variable GCC_ARM points to the Arm compiler (arm-none-eabi-gcc, version = arm-none-eabi-gcc (gcc version 7.3.1 20180622, ARM/embedded-7-branch revision 261907, 7-2018-q2-update). The compiler is also downloaded when you run the tensorflow/lite/experimental/micro/tools/make/download_dependencies.sh script. +3. Edit the file tensorflow/lite/experimental/micro/tools/make/targets/ecm3531_makefile.inc so that the variable ETA_SDK points to the location where the Eta Compute SDK is installed, and the variable GCC_ARM points to the Arm compiler arm-none-eabi-gcc, \(version = arm-none-eabi-gcc, gcc version 7.3.1 20180622, ARM/embedded-7-branch revision 261907, 7-2018-q2-update\). The compiler is also downloaded when you run the tensorflow/lite/experimental/micro/tools/make/download_dependencies.sh script. 4. Compile the code with the command make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=ecm3531 TAGS="CMSIS" test . This will create a set of executables in tensorflow/lite/experimental/micro/tools/make/gen/ecm3531_cortex-m3/bin/ -- GitLab From 3786ca6a94a941254ba909c083a2164fd33280a1 Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 19:04:55 -0800 Subject: [PATCH 098/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index aa6918121f..9bd8cf6406 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -326,19 +326,19 @@ Follow these steps to build and run the Tensorflow Micro Speech tests. 2. Download the Eta Compute SDK, version 0.0.17 (contact info@etacompute.com). -3. Edit the file tensorflow/lite/experimental/micro/tools/make/targets/ecm3531_makefile.inc so that the variable ETA_SDK points to the location where the Eta Compute SDK is installed, and the variable GCC_ARM points to the Arm compiler arm-none-eabi-gcc, \(version = arm-none-eabi-gcc, gcc version 7.3.1 20180622, ARM/embedded-7-branch revision 261907, 7-2018-q2-update\). The compiler is also downloaded when you run the tensorflow/lite/experimental/micro/tools/make/download_dependencies.sh script. +3. Edit the file tensorflow/lite/experimental/micro/tools/make/targets/ecm3531_makefile.inc so that the variable ETA_SDK points to the location where the Eta Compute SDK is installed, and the variable GCC_ARM points to the Arm compiler arm-none-eabi-gcc \(version = arm-none-eabi-gcc, gcc version 7.3.1 20180622, ARM/embedded-7-branch revision 261907, 7-2018-q2-update\). The compiler is also downloaded when you run the tensorflow/lite/experimental/micro/tools/make/download_dependencies.sh script. 4. Compile the code with the command make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=ecm3531 TAGS="CMSIS" test . This will create a set of executables in tensorflow/lite/experimental/micro/tools/make/gen/ecm3531_cortex-m3/bin/ -5. Connect the board to the host computer, start PuTTY (Connection type = Serial, Speed = 11520, Data bits = 8, Stop bits = 1, Parity = None), and load the executable with ocd. A sample script for loading the image is provided in tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program. +5. Connect the board to the host computer, start PuTTY (Connection type = Serial, Speed = 11520, Data bits = 8, Stop bits = 1, Parity = None), and load the executable with ocd. A sample script for loading the image is provided in tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program \(.e.g load_program audio_provider_test\) to load and run from SRAM. -The following will be printed on the Uart: - -Testing TestInvoke -Ran successfully - -/ tests passed +The following output will be printed: +Testing TestAudioProvider +TestingTestTimer +2/2 tests passed ~~~ALL TESTS PASSED~~~ +Execution time (msec) = 8 + ## Goals -- GitLab From 3d6e1130809ae53a9ee566264839532d03732836 Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 19:12:12 -0800 Subject: [PATCH 099/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 9bd8cf6406..035e19e995 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -330,13 +330,13 @@ Follow these steps to build and run the Tensorflow Micro Speech tests. 4. Compile the code with the command make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=ecm3531 TAGS="CMSIS" test . This will create a set of executables in tensorflow/lite/experimental/micro/tools/make/gen/ecm3531_cortex-m3/bin/ -5. Connect the board to the host computer, start PuTTY (Connection type = Serial, Speed = 11520, Data bits = 8, Stop bits = 1, Parity = None), and load the executable with ocd. A sample script for loading the image is provided in tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program \(.e.g load_program audio_provider_test\) to load and run from SRAM. +5. Connect the board to the host computer, start PuTTY (Connection type = Serial, Speed = 11520, Data bits = 8, Stop bits = 1, Parity = None), and load the executable with ocd. A sample script is provided in tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program \(.e.g load_program audio_provider_test\) to load and run from SRAM. The following output will be printed: Testing TestAudioProvider TestingTestTimer 2/2 tests passed -~~~ALL TESTS PASSED~~~ +\~\~\~ALL TESTS PASSED\~\~\~ Execution time (msec) = 8 -- GitLab From fb37bf8dc36cd4bada86641e26d233a547be15e4 Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 19:15:08 -0800 Subject: [PATCH 100/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 035e19e995..24500cf717 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -330,13 +330,11 @@ Follow these steps to build and run the Tensorflow Micro Speech tests. 4. Compile the code with the command make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=ecm3531 TAGS="CMSIS" test . This will create a set of executables in tensorflow/lite/experimental/micro/tools/make/gen/ecm3531_cortex-m3/bin/ -5. Connect the board to the host computer, start PuTTY (Connection type = Serial, Speed = 11520, Data bits = 8, Stop bits = 1, Parity = None), and load the executable with ocd. A sample script is provided in tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program \(.e.g load_program audio_provider_test\) to load and run from SRAM. - -The following output will be printed: -Testing TestAudioProvider -TestingTestTimer -2/2 tests passed -\~\~\~ALL TESTS PASSED\~\~\~ +5. Connect the board to the host computer, start PuTTY (Connection type = Serial, Speed = 11520, Data bits = 8, Stop bits = 1, Parity = None), and load the executable with ocd. A sample script is provided in tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program \(.e.g load_program audio_provider_test\) to load and run from SRAM. \s\s The following output will be printed: \s\s +Testing TestAudioProvider s\s\s +TestingTestTimer \s\s +2/2 tests passed \s\s +\~\~\~ALL TESTS PASSED\~\~\~ \s\s Execution time (msec) = 8 -- GitLab From 214287fa95fd22d03b2c5a942186c56a7b076bf2 Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 19:16:22 -0800 Subject: [PATCH 101/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 24500cf717..b47f2819bc 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -330,11 +330,11 @@ Follow these steps to build and run the Tensorflow Micro Speech tests. 4. Compile the code with the command make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=ecm3531 TAGS="CMSIS" test . This will create a set of executables in tensorflow/lite/experimental/micro/tools/make/gen/ecm3531_cortex-m3/bin/ -5. Connect the board to the host computer, start PuTTY (Connection type = Serial, Speed = 11520, Data bits = 8, Stop bits = 1, Parity = None), and load the executable with ocd. A sample script is provided in tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program \(.e.g load_program audio_provider_test\) to load and run from SRAM. \s\s The following output will be printed: \s\s -Testing TestAudioProvider s\s\s -TestingTestTimer \s\s -2/2 tests passed \s\s -\~\~\~ALL TESTS PASSED\~\~\~ \s\s +5. Connect the board to the host computer, start PuTTY (Connection type = Serial, Speed = 11520, Data bits = 8, Stop bits = 1, Parity = None), and load the executable with ocd. A sample script is provided in tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program \(.e.g load_program audio_provider_test\) to load and run from SRAM.\s\s The following output will be printed:\s\s +Testing TestAudioProvider\s\s +TestingTestTimer\s\s +2/2 tests passed\s\s +\~\~\~ALL TESTS PASSED\~\~\~\s\s Execution time (msec) = 8 -- GitLab From a38913d449da8c7c1cdd019afd0ab8cef9cf903a Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 19:20:41 -0800 Subject: [PATCH 102/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index b47f2819bc..6096a4099c 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -330,11 +330,12 @@ Follow these steps to build and run the Tensorflow Micro Speech tests. 4. Compile the code with the command make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=ecm3531 TAGS="CMSIS" test . This will create a set of executables in tensorflow/lite/experimental/micro/tools/make/gen/ecm3531_cortex-m3/bin/ -5. Connect the board to the host computer, start PuTTY (Connection type = Serial, Speed = 11520, Data bits = 8, Stop bits = 1, Parity = None), and load the executable with ocd. A sample script is provided in tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program \(.e.g load_program audio_provider_test\) to load and run from SRAM.\s\s The following output will be printed:\s\s -Testing TestAudioProvider\s\s -TestingTestTimer\s\s -2/2 tests passed\s\s -\~\~\~ALL TESTS PASSED\~\~\~\s\s +5. Connect the board to the host computer, start PuTTY (Connection type = Serial, Speed = 11520, Data bits = 8, Stop bits = 1, Parity = None), and load the executable with ocd. A sample script is provided in tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program \(.e.g load_program audio_provider_test\) to load and run from SRAM. +The following output will be printed: +Testing TestAudioProvider +TestingTestTimer +2/2 tests passed +\~\~\~ALL TESTS PASSED\~\~\~ Execution time (msec) = 8 -- GitLab From 3d8f649d4135ae2ab4faf5cc1f390c1c20d4a416 Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 19:21:37 -0800 Subject: [PATCH 103/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 6096a4099c..0728a51d21 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -328,7 +328,9 @@ Follow these steps to build and run the Tensorflow Micro Speech tests. 3. Edit the file tensorflow/lite/experimental/micro/tools/make/targets/ecm3531_makefile.inc so that the variable ETA_SDK points to the location where the Eta Compute SDK is installed, and the variable GCC_ARM points to the Arm compiler arm-none-eabi-gcc \(version = arm-none-eabi-gcc, gcc version 7.3.1 20180622, ARM/embedded-7-branch revision 261907, 7-2018-q2-update\). The compiler is also downloaded when you run the tensorflow/lite/experimental/micro/tools/make/download_dependencies.sh script. -4. Compile the code with the command make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=ecm3531 TAGS="CMSIS" test . This will create a set of executables in tensorflow/lite/experimental/micro/tools/make/gen/ecm3531_cortex-m3/bin/ +4. Compile the code with the command make +-f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=ecm3531 TAGS="CMSIS" test +This will create a set of executables in tensorflow/lite/experimental/micro/tools/make/gen/ecm3531_cortex-m3/bin/ 5. Connect the board to the host computer, start PuTTY (Connection type = Serial, Speed = 11520, Data bits = 8, Stop bits = 1, Parity = None), and load the executable with ocd. A sample script is provided in tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program \(.e.g load_program audio_provider_test\) to load and run from SRAM. The following output will be printed: -- GitLab From c30fca730ade17df2152584ab1bacb67b13bd587 Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 19:22:29 -0800 Subject: [PATCH 104/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 0728a51d21..2a12824822 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -328,8 +328,8 @@ Follow these steps to build and run the Tensorflow Micro Speech tests. 3. Edit the file tensorflow/lite/experimental/micro/tools/make/targets/ecm3531_makefile.inc so that the variable ETA_SDK points to the location where the Eta Compute SDK is installed, and the variable GCC_ARM points to the Arm compiler arm-none-eabi-gcc \(version = arm-none-eabi-gcc, gcc version 7.3.1 20180622, ARM/embedded-7-branch revision 261907, 7-2018-q2-update\). The compiler is also downloaded when you run the tensorflow/lite/experimental/micro/tools/make/download_dependencies.sh script. -4. Compile the code with the command make --f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=ecm3531 TAGS="CMSIS" test +4. Compile the code with the command +make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=ecm3531 TAGS="CMSIS" test This will create a set of executables in tensorflow/lite/experimental/micro/tools/make/gen/ecm3531_cortex-m3/bin/ 5. Connect the board to the host computer, start PuTTY (Connection type = Serial, Speed = 11520, Data bits = 8, Stop bits = 1, Parity = None), and load the executable with ocd. A sample script is provided in tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program \(.e.g load_program audio_provider_test\) to load and run from SRAM. -- GitLab From 7424bdfb0c0173f1b7afb1308f4a93c6188e3adf Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 19:24:41 -0800 Subject: [PATCH 105/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 2a12824822..faf8aca8bb 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -332,7 +332,7 @@ Follow these steps to build and run the Tensorflow Micro Speech tests. make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=ecm3531 TAGS="CMSIS" test This will create a set of executables in tensorflow/lite/experimental/micro/tools/make/gen/ecm3531_cortex-m3/bin/ -5. Connect the board to the host computer, start PuTTY (Connection type = Serial, Speed = 11520, Data bits = 8, Stop bits = 1, Parity = None), and load the executable with ocd. A sample script is provided in tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program \(.e.g load_program audio_provider_test\) to load and run from SRAM. +5. Connect the board to the host computer, start PuTTY (Connection type = Serial, Speed = 11520, Data bits = 8, Stop bits = 1, Parity = None), and load the executable with ocd. A sample script is provided in tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program \(e.g. lad_program audio_provider_test\) to load and run from SRAM. The following output will be printed: Testing TestAudioProvider TestingTestTimer -- GitLab From fcc1ed6f0ed06e7e7fdf0b1da9bb084462dc8cfd Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 19:25:09 -0800 Subject: [PATCH 106/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index faf8aca8bb..eda9ae27a4 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -332,7 +332,7 @@ Follow these steps to build and run the Tensorflow Micro Speech tests. make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=ecm3531 TAGS="CMSIS" test This will create a set of executables in tensorflow/lite/experimental/micro/tools/make/gen/ecm3531_cortex-m3/bin/ -5. Connect the board to the host computer, start PuTTY (Connection type = Serial, Speed = 11520, Data bits = 8, Stop bits = 1, Parity = None), and load the executable with ocd. A sample script is provided in tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program \(e.g. lad_program audio_provider_test\) to load and run from SRAM. +5. Connect the board to the host computer, start PuTTY (Connection type = Serial, Speed = 11520, Data bits = 8, Stop bits = 1, Parity = None), and load the executable with ocd. A sample script is provided in tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program \(e.g. load_program audio_provider_test\) to load and run from SRAM. The following output will be printed: Testing TestAudioProvider TestingTestTimer -- GitLab From 0f2456f2715fd4d7d52c713200beab6ee11b2433 Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 19:25:48 -0800 Subject: [PATCH 107/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index eda9ae27a4..a95d363346 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -332,7 +332,7 @@ Follow these steps to build and run the Tensorflow Micro Speech tests. make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=ecm3531 TAGS="CMSIS" test This will create a set of executables in tensorflow/lite/experimental/micro/tools/make/gen/ecm3531_cortex-m3/bin/ -5. Connect the board to the host computer, start PuTTY (Connection type = Serial, Speed = 11520, Data bits = 8, Stop bits = 1, Parity = None), and load the executable with ocd. A sample script is provided in tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program \(e.g. load_program audio_provider_test\) to load and run from SRAM. +5. Connect the board to the host computer, start PuTTY (Connection type = Serial, Speed = 11520, Data bits = 8, Stop bits = 1, Parity = None), and load the executable with ocd. A sample script is provided in tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program \(e.g., load_program audio_provider_test\) to load and run from SRAM. The following output will be printed: Testing TestAudioProvider TestingTestTimer -- GitLab From b2506970e71873ea4ee37bbee06a3a4055309482 Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 19:33:07 -0800 Subject: [PATCH 108/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index a95d363346..987a6c94a2 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -340,6 +340,8 @@ TestingTestTimer \~\~\~ALL TESTS PASSED\~\~\~ Execution time (msec) = 8 +6. To load and run from flash, edit the file tensorflow/lite/experimental/micro/tools/make/targets/ecm3531_makefile.inc so that the variable ETA_LDS_FILE := ecm3531_flash.lds. Compile the code and use the script flash_program, for e.g., flash_program audio_provider_test . + ## Goals -- GitLab From 8ad0b8f5e81fa22e89f920b0435169165d3800a4 Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Thu, 7 Feb 2019 19:35:34 -0800 Subject: [PATCH 109/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 987a6c94a2..dbaecf62d6 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -332,7 +332,7 @@ Follow these steps to build and run the Tensorflow Micro Speech tests. make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=ecm3531 TAGS="CMSIS" test This will create a set of executables in tensorflow/lite/experimental/micro/tools/make/gen/ecm3531_cortex-m3/bin/ -5. Connect the board to the host computer, start PuTTY (Connection type = Serial, Speed = 11520, Data bits = 8, Stop bits = 1, Parity = None), and load the executable with ocd. A sample script is provided in tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program \(e.g., load_program audio_provider_test\) to load and run from SRAM. +5. Connect the board to the host computer, start PuTTY (Connection type = Serial, Speed = 11520, Data bits = 8, Stop bits = 1, Parity = None), and load the executable with ocd. A sample script is provided in tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program to load and run from SRAM, \(e.g., load_program audio_provider_test\) . The following output will be printed: Testing TestAudioProvider TestingTestTimer -- GitLab From 1f711cc6f11fdf0d528f4d20c70fd7b9713c72bf Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Thu, 7 Feb 2019 21:10:11 -0800 Subject: [PATCH 110/766] Replace TF_CHECK_OK with TF_RETURN_IF_ERROR and add the missing new line --- .../core/kernels/data/sparse_tensor_slice_dataset_op_test.cc | 4 ++-- tensorflow/core/kernels/data/tensor_slice_dataset_op_test.cc | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tensorflow/core/kernels/data/sparse_tensor_slice_dataset_op_test.cc b/tensorflow/core/kernels/data/sparse_tensor_slice_dataset_op_test.cc index d3b5ab66bc..d1d01f792f 100644 --- a/tensorflow/core/kernels/data/sparse_tensor_slice_dataset_op_test.cc +++ b/tensorflow/core/kernels/data/sparse_tensor_slice_dataset_op_test.cc @@ -42,7 +42,7 @@ class SparseTensorSliceDatasetOpTest : public DatasetOpsTestBase { node_def_ = test::function::NDef(kNodeName, kOpName, {"indices", "values", "dense_shape"}, {{"Tvalues", tvalues}}); - TF_CHECK_OK(CreateOpKernel(node_def_, op_kernel)); + TF_RETURN_IF_ERROR(CreateOpKernel(node_def_, op_kernel)); return Status::OK(); } @@ -516,4 +516,4 @@ INSTANTIATE_TEST_CASE_P(SparseTensorSliceDatasetOpTest, IteratorRoundtripTest, } // namespace } // namespace data -} // namespace tensorflow \ No newline at end of file +} // namespace tensorflow diff --git a/tensorflow/core/kernels/data/tensor_slice_dataset_op_test.cc b/tensorflow/core/kernels/data/tensor_slice_dataset_op_test.cc index 67e86dc04f..b0e09b4545 100644 --- a/tensorflow/core/kernels/data/tensor_slice_dataset_op_test.cc +++ b/tensorflow/core/kernels/data/tensor_slice_dataset_op_test.cc @@ -49,7 +49,7 @@ class TensorSliceDatasetOpTest : public DatasetOpsTestBase { node_def_ = test::function::NDef( kNodeName, kOpName, components, {{"Toutput_types", dtypes}, {"output_shapes", shapes}}); - TF_CHECK_OK(CreateOpKernel(node_def_, tensor_dataset_kernel)); + TF_RETURN_IF_ERROR(CreateOpKernel(node_def_, tensor_dataset_kernel)); return Status::OK(); } @@ -627,4 +627,4 @@ INSTANTIATE_TEST_CASE_P(TensorDatasetSliceOpTest, IteratorRoundtripTest, } // namespace } // namespace data -} // namespace tensorflow \ No newline at end of file +} // namespace tensorflow -- GitLab From 6f0adc2cf3a1a41fa5fa52ae86349f1e93826985 Mon Sep 17 00:00:00 2001 From: Albin Joy Date: Fri, 18 Jan 2019 15:16:40 +0530 Subject: [PATCH 111/766] Typo error fixed in execute.h --- tensorflow/core/common_runtime/eager/execute.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/core/common_runtime/eager/execute.h b/tensorflow/core/common_runtime/eager/execute.h index 80d381cecd..4945688900 100644 --- a/tensorflow/core/common_runtime/eager/execute.h +++ b/tensorflow/core/common_runtime/eager/execute.h @@ -41,8 +41,8 @@ Status EagerExecute( tensorflow::gtl::InlinedVector* retvals, int* num_retvals); -// Low-level utility to execute the kernel specified by kernel on device device, -// with the inputs op_inputs, in the context ctx. +// Low-level utility to execute the kernel specified by kernel on device +// 'device', with the inputs op_inputs, in the context 'ctx'. Status EagerKernelExecute(EagerContext* ctx, Device* device, const gtl::InlinedVector& op_inputs, KernelAndDevice* kernel, NodeExecStats* maybe_stats, -- GitLab From a1f69b47a5f0cd18b3d918a72efb3323a6b2227e Mon Sep 17 00:00:00 2001 From: Lukas Geiger Date: Fri, 8 Feb 2019 18:08:46 +0100 Subject: [PATCH 112/766] Remove unnecessary use of six.iterkeys This PR replaces unnecessary use of `for key in six.iterkeys(d)` with `for key in d` to improve readability. --- .../python/estimator_integration_test.py | 3 +-- .../estimator/python/gan_estimator_test.py | 5 ++--- .../python/stargan_estimator_test.py | 5 ++--- .../python/tpu_gan_estimator_test.py | 7 +++---- .../python/learn/estimators/head_test.py | 20 +++++++++---------- tensorflow/python/data/util/nest.py | 2 +- tensorflow/python/util/nest.py | 2 +- 7 files changed, 20 insertions(+), 24 deletions(-) diff --git a/tensorflow/contrib/distribute/python/estimator_integration_test.py b/tensorflow/contrib/distribute/python/estimator_integration_test.py index e17085628b..1ff1e7c1d2 100644 --- a/tensorflow/contrib/distribute/python/estimator_integration_test.py +++ b/tensorflow/contrib/distribute/python/estimator_integration_test.py @@ -22,7 +22,6 @@ import shutil import tempfile from absl.testing import parameterized import numpy as np -import six from tensorflow.contrib.distribute.python import combinations from tensorflow.contrib.optimizer_v2 import adagrad @@ -117,7 +116,7 @@ class DNNLinearCombinedClassifierIntegrationTest(test.TestCase, scores = estimator.evaluate(eval_input_fn) self.assertEqual(num_steps, scores[ops.GraphKeys.GLOBAL_STEP]) - self.assertIn('loss', six.iterkeys(scores)) + self.assertIn('loss', scores) predictions = np.array([ x[prediction_keys.PredictionKeys.PREDICTIONS] diff --git a/tensorflow/contrib/gan/python/estimator/python/gan_estimator_test.py b/tensorflow/contrib/gan/python/estimator/python/gan_estimator_test.py index 5b9c54e43a..66af79d1e8 100644 --- a/tensorflow/contrib/gan/python/estimator/python/gan_estimator_test.py +++ b/tensorflow/contrib/gan/python/estimator/python/gan_estimator_test.py @@ -23,7 +23,6 @@ import tempfile from absl.testing import parameterized import numpy as np -import six from tensorflow.contrib import layers from tensorflow.contrib.gan.python import namedtuples as tfgan_tuples @@ -238,10 +237,10 @@ class GANEstimatorIntegrationTest(test.TestCase): # Evaluate. scores = est.evaluate(eval_input_fn) self.assertEqual(num_steps, scores[ops.GraphKeys.GLOBAL_STEP]) - self.assertIn('loss', six.iterkeys(scores)) + self.assertIn('loss', scores) self.assertEqual(scores['discriminator_loss'] + scores['generator_loss'], scores['loss']) - self.assertIn('mse_custom_metric', six.iterkeys(scores)) + self.assertIn('mse_custom_metric', scores) # Predict. predictions = np.array([x for x in est.predict(predict_input_fn)]) diff --git a/tensorflow/contrib/gan/python/estimator/python/stargan_estimator_test.py b/tensorflow/contrib/gan/python/estimator/python/stargan_estimator_test.py index c00ff43997..0fcd1b7924 100644 --- a/tensorflow/contrib/gan/python/estimator/python/stargan_estimator_test.py +++ b/tensorflow/contrib/gan/python/estimator/python/stargan_estimator_test.py @@ -23,7 +23,6 @@ import tempfile from absl.testing import parameterized import numpy as np -import six from tensorflow.contrib import layers from tensorflow.contrib.gan.python import namedtuples as tfgan_tuples @@ -235,10 +234,10 @@ class StarGANEstimatorIntegrationTest(test.TestCase): # EVALUTE scores = est.evaluate(eval_input_fn) self.assertEqual(num_steps, scores[ops.GraphKeys.GLOBAL_STEP]) - self.assertIn('loss', six.iterkeys(scores)) + self.assertIn('loss', scores) self.assertEqual(scores['discriminator_loss'] + scores['generator_loss'], scores['loss']) - self.assertIn('mse_custom_metric', six.iterkeys(scores)) + self.assertIn('mse_custom_metric', scores) # PREDICT predictions = np.array([x for x in est.predict(predict_input_fn)]) diff --git a/tensorflow/contrib/gan/python/estimator/python/tpu_gan_estimator_test.py b/tensorflow/contrib/gan/python/estimator/python/tpu_gan_estimator_test.py index 0d9e6489bd..baf2c28df4 100644 --- a/tensorflow/contrib/gan/python/estimator/python/tpu_gan_estimator_test.py +++ b/tensorflow/contrib/gan/python/estimator/python/tpu_gan_estimator_test.py @@ -23,7 +23,6 @@ import tempfile from absl.testing import parameterized import numpy as np -import six from tensorflow.contrib import layers from tensorflow.contrib.gan.python import namedtuples as tfgan_tuples @@ -184,11 +183,11 @@ class TPUGANEstimatorIntegrationTest(test.TestCase, parameterized.TestCase): # Evaluate. num_steps_eval = 2 scores = est.evaluate(eval_input_fn, steps=num_steps_eval) - self.assertIn(ops.GraphKeys.GLOBAL_STEP, six.iterkeys(scores)) - self.assertIn('loss', six.iterkeys(scores)) + self.assertIn(ops.GraphKeys.GLOBAL_STEP, scores) + self.assertIn('loss', scores) self.assertEqual(scores['discriminator_loss'] + scores['generator_loss'], scores['loss']) - self.assertIn('mse_custom_metric', six.iterkeys(scores)) + self.assertIn('mse_custom_metric', scores) # Predict. predictions = np.array([x['generated_data'] for x in diff --git a/tensorflow/contrib/learn/python/learn/estimators/head_test.py b/tensorflow/contrib/learn/python/learn/estimators/head_test.py index 7c2d9bb076..1c4e6f4f1f 100644 --- a/tensorflow/contrib/learn/python/learn/estimators/head_test.py +++ b/tensorflow/contrib/learn/python/learn/estimators/head_test.py @@ -62,8 +62,8 @@ def _assert_no_variables(test_case): def _assert_metrics(test_case, expected_loss, expected_eval_metrics, model_fn_ops): test_case.assertAlmostEqual(expected_loss, model_fn_ops.loss.eval(), places=4) - for k in six.iterkeys(expected_eval_metrics): - test_case.assertIn(k, six.iterkeys(model_fn_ops.eval_metric_ops)) + for k in expected_eval_metrics: + test_case.assertIn(k, model_fn_ops.eval_metric_ops) variables.initialize_local_variables().run() for key, expected_value in six.iteritems(expected_eval_metrics): value_tensor, update_tensor = model_fn_ops.eval_metric_ops[key] @@ -553,11 +553,11 @@ class MultiLabelHeadTest(test.TestCase): predictions_for_serving = ( model_fn_ops.output_alternatives["head_name"][1]) - self.assertIn("classes", six.iterkeys(predictions_for_serving)) + self.assertIn("classes", predictions_for_serving) self.assertAllEqual( [[b"0", b"1", b"2"], [b"0", b"1", b"2"]], predictions_for_serving["classes"].eval()) - self.assertIn("probabilities", six.iterkeys(predictions_for_serving)) + self.assertIn("probabilities", predictions_for_serving) self.assertAllClose( [[0.731059, 0.5, 0.5], [0.5, 0.5, 0.731059,]], @@ -857,11 +857,11 @@ class BinaryClassificationHeadTest(test.TestCase): model_fn_ops.output_alternatives["head_name"][0]) predictions_for_serving = ( model_fn_ops.output_alternatives["head_name"][1]) - self.assertIn("classes", six.iterkeys(predictions_for_serving)) + self.assertIn("classes", predictions_for_serving) predicted_classes = predictions_for_serving["classes"].eval().tolist() self.assertListEqual( [b"0", b"1"], predicted_classes[0]) - self.assertIn("probabilities", six.iterkeys(predictions_for_serving)) + self.assertIn("probabilities", predictions_for_serving) def testBinaryClassificationInferMode_withWeightColumn(self): n_classes = 2 @@ -1356,11 +1356,11 @@ class MultiClassHeadTest(test.TestCase): model_fn_ops.output_alternatives["head_name"][0]) predictions_for_serving = ( model_fn_ops.output_alternatives["head_name"][1]) - self.assertIn("classes", six.iterkeys(predictions_for_serving)) + self.assertIn("classes", predictions_for_serving) self.assertAllEqual( [[b"0", b"1", b"2"], [b"0", b"1", b"2"]], predictions_for_serving["classes"].eval()) - self.assertIn("probabilities", six.iterkeys(predictions_for_serving)) + self.assertIn("probabilities", predictions_for_serving) self.assertAllClose( [[0.576117, 0.2119416, 0.2119416], [0.2119416, 0.2119416, 0.576117]], @@ -1408,11 +1408,11 @@ class MultiClassHeadTest(test.TestCase): model_fn_ops.output_alternatives["head_name"][0]) predictions_for_serving = ( model_fn_ops.output_alternatives["head_name"][1]) - self.assertIn("classes", six.iterkeys(predictions_for_serving)) + self.assertIn("classes", predictions_for_serving) self.assertAllEqual( [[b"key0", b"key1", b"key2"], [b"key0", b"key1", b"key2"]], predictions_for_serving["classes"].eval()) - self.assertIn("probabilities", six.iterkeys(predictions_for_serving)) + self.assertIn("probabilities", predictions_for_serving) self.assertAllClose( [[0.576117, 0.2119416, 0.2119416], [0.2119416, 0.2119416, 0.576117]], diff --git a/tensorflow/python/data/util/nest.py b/tensorflow/python/data/util/nest.py index e5abc654da..35743fdb38 100644 --- a/tensorflow/python/data/util/nest.py +++ b/tensorflow/python/data/util/nest.py @@ -68,7 +68,7 @@ def _sequence_like(instance, args): # ordered and plain dicts (e.g., flattening a dict but using a # corresponding `OrderedDict` to pack it back). result = dict(zip(_sorted(instance), args)) - return type(instance)((key, result[key]) for key in _six.iterkeys(instance)) + return type(instance)((key, result[key]) for key in instance) elif (isinstance(instance, tuple) and hasattr(instance, "_fields") and isinstance(instance._fields, _collections.Sequence) and diff --git a/tensorflow/python/util/nest.py b/tensorflow/python/util/nest.py index 2435694631..76bec98911 100644 --- a/tensorflow/python/util/nest.py +++ b/tensorflow/python/util/nest.py @@ -124,7 +124,7 @@ def _sequence_like(instance, args): # ordered and plain dicts (e.g., flattening a dict but using a # corresponding `OrderedDict` to pack it back). result = dict(zip(_sorted(instance), args)) - return type(instance)((key, result[key]) for key in _six.iterkeys(instance)) + return type(instance)((key, result[key]) for key in instance) elif _is_namedtuple(instance) or _is_attrs(instance): return type(instance)(*args) elif _is_composite_tensor(instance): -- GitLab From 062cf16091d974195becc4edd109b92ac69c30ca Mon Sep 17 00:00:00 2001 From: Lukas Geiger Date: Fri, 8 Feb 2019 18:26:30 +0100 Subject: [PATCH 113/766] list(iterkeys(dict)) -> list(dict) sorted(iterkeys(dict)) -> sorted(dict) --- tensorflow/contrib/graph_editor/transform.py | 3 +-- .../contrib/learn/python/learn/estimators/head_test.py | 8 ++++---- tensorflow/python/data/util/nest.py | 5 ++--- tensorflow/python/ops/control_flow_ops.py | 2 +- tensorflow/python/ops/gradients_util.py | 2 +- tensorflow/python/util/nest.py | 2 +- 6 files changed, 10 insertions(+), 12 deletions(-) diff --git a/tensorflow/contrib/graph_editor/transform.py b/tensorflow/contrib/graph_editor/transform.py index e79ccd8da1..178be2c194 100644 --- a/tensorflow/contrib/graph_editor/transform.py +++ b/tensorflow/contrib/graph_editor/transform.py @@ -22,7 +22,6 @@ from __future__ import print_function from copy import deepcopy from functools import partial from six import iteritems -from six import iterkeys from six import string_types from six import StringIO from tensorflow.contrib.graph_editor import reroute @@ -735,7 +734,7 @@ def graph_replace(target_ts, replacement_ts, dst_scope="", # control dependencies. graph = util.get_unique_graph(flatten_target_ts, check_types=(tf_ops.Tensor)) control_ios = util.ControlOutputs(graph) - ops = select.get_walks_intersection_ops(list(iterkeys(replacement_ts)), + ops = select.get_walks_intersection_ops(list(replacement_ts), flatten_target_ts, control_ios=control_ios) if not ops: diff --git a/tensorflow/contrib/learn/python/learn/estimators/head_test.py b/tensorflow/contrib/learn/python/learn/estimators/head_test.py index 1c4e6f4f1f..f203ee3d69 100644 --- a/tensorflow/contrib/learn/python/learn/estimators/head_test.py +++ b/tensorflow/contrib/learn/python/learn/estimators/head_test.py @@ -546,7 +546,7 @@ class MultiLabelHeadTest(test.TestCase): self.assertListEqual( [1, 0, 0], model_fn_ops.predictions["classes"].eval().tolist()[0]) self.assertItemsEqual( - ["head_name"], six.iterkeys(model_fn_ops.output_alternatives)) + ["head_name"], list(model_fn_ops.output_alternatives)) self.assertEqual( constants.ProblemType.CLASSIFICATION, model_fn_ops.output_alternatives["head_name"][0]) @@ -851,7 +851,7 @@ class BinaryClassificationHeadTest(test.TestCase): self.assertListEqual( [1, 1], list(model_fn_ops.predictions["classes"].eval())) self.assertItemsEqual( - ["head_name"], six.iterkeys(model_fn_ops.output_alternatives)) + ["head_name"], list(model_fn_ops.output_alternatives)) self.assertEqual( constants.ProblemType.LOGISTIC_REGRESSION, model_fn_ops.output_alternatives["head_name"][0]) @@ -1350,7 +1350,7 @@ class MultiClassHeadTest(test.TestCase): [0, 2], model_fn_ops.predictions["classes"].eval()) self.assertItemsEqual( - ["head_name"], six.iterkeys(model_fn_ops.output_alternatives)) + ["head_name"], list(model_fn_ops.output_alternatives)) self.assertEqual( constants.ProblemType.CLASSIFICATION, model_fn_ops.output_alternatives["head_name"][0]) @@ -1402,7 +1402,7 @@ class MultiClassHeadTest(test.TestCase): [b"key0", b"key2"], model_fn_ops.predictions["classes"].eval()) self.assertItemsEqual( - ["head_name"], six.iterkeys(model_fn_ops.output_alternatives)) + ["head_name"], list(model_fn_ops.output_alternatives)) self.assertEqual( constants.ProblemType.CLASSIFICATION, model_fn_ops.output_alternatives["head_name"][0]) diff --git a/tensorflow/python/data/util/nest.py b/tensorflow/python/data/util/nest.py index 35743fdb38..ebfd8af342 100644 --- a/tensorflow/python/data/util/nest.py +++ b/tensorflow/python/data/util/nest.py @@ -46,7 +46,7 @@ from tensorflow.python.framework import sparse_tensor as _sparse_tensor def _sorted(dict_): """Returns a sorted list of the dict keys, with error if keys not sortable.""" try: - return sorted(_six.iterkeys(dict_)) + return sorted(list(dict_)) except TypeError: raise TypeError("nest only supports dicts with sortable keys.") @@ -317,8 +317,7 @@ def assert_shallow_structure(shallow_tree, input_tree, check_types=True): raise ValueError( "The two structures don't have the same keys. Input " "structure has keys %s, while shallow structure has keys %s." % - (list(_six.iterkeys(input_tree)), - list(_six.iterkeys(shallow_tree)))) + (list(input_tree), list(shallow_tree))) input_tree = list(sorted(_six.iteritems(input_tree))) shallow_tree = list(sorted(_six.iteritems(shallow_tree))) diff --git a/tensorflow/python/ops/control_flow_ops.py b/tensorflow/python/ops/control_flow_ops.py index e0b83c490c..42afea6614 100644 --- a/tensorflow/python/ops/control_flow_ops.py +++ b/tensorflow/python/ops/control_flow_ops.py @@ -3619,7 +3619,7 @@ def group(*inputs, **kwargs): """A sort key that allows None to be compared to strings.""" return "" if dev is None else dev - for dev in sorted(six.iterkeys(ops_on_device), key=device_key): + for dev in sorted(ops_on_device, key=device_key): deps.append(_GroupControlDeps(dev, ops_on_device[dev])) with ops.control_dependencies(deps): diff --git a/tensorflow/python/ops/gradients_util.py b/tensorflow/python/ops/gradients_util.py index 4f73423d75..777549dbcf 100644 --- a/tensorflow/python/ops/gradients_util.py +++ b/tensorflow/python/ops/gradients_util.py @@ -912,7 +912,7 @@ def _MultiDeviceAddN(tensor_list, gradient_uid): def DeviceKey(dev): return "" if dev is None else dev - for dev in sorted(six.iterkeys(tensors_on_device), key=DeviceKey): + for dev in sorted(tensors_on_device, key=DeviceKey): tensors = tensors_on_device[dev] with ops._colocate_with_for_gradient( # pylint: disable=protected-access tensors[0].op, diff --git a/tensorflow/python/util/nest.py b/tensorflow/python/util/nest.py index 76bec98911..644c7556ee 100644 --- a/tensorflow/python/util/nest.py +++ b/tensorflow/python/util/nest.py @@ -79,7 +79,7 @@ def _get_attrs_items(obj): def _sorted(dict_): """Returns a sorted list of the dict keys, with error if keys not sortable.""" try: - return sorted(_six.iterkeys(dict_)) + return sorted(dict_) except TypeError: raise TypeError("nest only supports dicts with sortable keys.") -- GitLab From 5144f9e40504be5a9c4f16d3fc0748d7e975d733 Mon Sep 17 00:00:00 2001 From: hari Date: Fri, 8 Feb 2019 10:08:50 -0800 Subject: [PATCH 114/766] increased stack size to 16 KB --- .../experimental/micro/tools/make/targets/ecm3531/ecm3531.lds | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/ecm3531.lds b/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/ecm3531.lds index af34f988f2..383b7f9244 100644 --- a/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/ecm3531.lds +++ b/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/ecm3531.lds @@ -55,7 +55,7 @@ SECTIONS { _datax = .; KEEP(*(.mainStack)) - . += 12288; + . += 16384; _edatax = .; _stack_top = .; . += 4; -- GitLab From d968bd5de19678de5d0b44b95bad583f523596b0 Mon Sep 17 00:00:00 2001 From: hari Date: Fri, 8 Feb 2019 10:11:19 -0800 Subject: [PATCH 115/766] load and run from flash --- .../make/targets/ecm3531/ecm3531_flash.lds | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/ecm3531_flash.lds diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/ecm3531_flash.lds b/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/ecm3531_flash.lds new file mode 100644 index 0000000000..9cbbea3569 --- /dev/null +++ b/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/ecm3531_flash.lds @@ -0,0 +1,85 @@ +/* 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. +==============================================================================*/ + + +/* + * linker script for use with ECM3531 chip. + * .text and .ro map to FLASH all else to SRAM. + * + */ + + /* + * Indicate to the linker the entry point. + */ +ENTRY(ResetISR) + +/* + * FLASH is at 0x01000000 of length 0x00080000 512KB + * SRAM is at 0x10000000 of length 0x00020000 128KB + */ +MEMORY +{ + FLASH (RX) : ORIGIN = 0x01000000, LENGTH = 0x00080000 + SRAM (RWX) : ORIGIN = 0x10000000, LENGTH = 0x00020000 +} + +SECTIONS +{ + .text : + { + _text = .; + KEEP(*(.vectors)) + . = ALIGN(0x4); + *(.text*) + . = ALIGN(0x4); + *(.rodata*) + . = ALIGN(0x4); + _etext = .; + } > FLASH= 0 + .dummy : + { + . = ALIGN(0x4); + _eftext = .; + } > FLASH +/* put the stack at the bottom of SRAM*/ + .datax (NOLOAD) : + { + _datax = .; + KEEP(*(.mainStack)) + . = ALIGN(0x4); + . += 16384; + _edatax = .; + _stack_top = .; + } > SRAM + .data : + { + _data = .; + *(.data*) + KEEP(*(.mainHeap)) + _edata = .; + } > SRAM AT > FLASH + + .bss (NOLOAD) : + { + _bss = .; + *(.bss*) + *(COMMON) + _ebss = .; + } > SRAM + + + +} + -- GitLab From 876b0befc6ec677c5912a44db35d13be48d23c0d Mon Sep 17 00:00:00 2001 From: hari Date: Fri, 8 Feb 2019 10:12:43 -0800 Subject: [PATCH 116/766] new file --- .../micro/testing/test_ecm3531_binary.sh | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100755 tensorflow/lite/experimental/micro/testing/test_ecm3531_binary.sh diff --git a/tensorflow/lite/experimental/micro/testing/test_ecm3531_binary.sh b/tensorflow/lite/experimental/micro/testing/test_ecm3531_binary.sh new file mode 100755 index 0000000000..c4978b95b5 --- /dev/null +++ b/tensorflow/lite/experimental/micro/testing/test_ecm3531_binary.sh @@ -0,0 +1,15 @@ +/* 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. +==============================================================================*/ + -- GitLab From c1f51e8dde0bb65836923a571161192becf3bc00 Mon Sep 17 00:00:00 2001 From: hari Date: Fri, 8 Feb 2019 10:22:28 -0800 Subject: [PATCH 117/766] new file --- .../tools/make/targets/ecm3531/flash_program | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100755 tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/flash_program diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/flash_program b/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/flash_program new file mode 100755 index 0000000000..bc3fe5cb21 --- /dev/null +++ b/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/flash_program @@ -0,0 +1,53 @@ +#!/usr/bin/python3 +#Usage: cd to the directory tensorflow/lite/experimental/micro/tools/make/targets/ecm3531 and type ./flash_program executable_name to load an executable from the directory tensorflow/lite/experimental/micro/tools/make/gen/ecm3531_cortex-m3/bin/ into flash +# +# +# 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. +#============================================================================== + + +import sys, getopt +import os +import telnetlib + +def send_ocd_cmd(line): + ocd_sock.write(bytes(line,encoding = 'utf-8')) + print(ocd_sock.read_until(b'> ').decode('utf-8'), end='') + +def get_ocd_response(): + print(ocd_sock.read_until(b'> ').decode('utf-8'), end='') + +#get hooked up to openocd daemon +ocd_sock = telnetlib.Telnet(host='localhost', port=4444) +get_ocd_response() # clean it out + +# git path to project elf file +cur_dir = os.getcwd() +#elf_file = cur_dir + '/../../gen/ecm3531_cortex-m3/bin/' + 'micro_speech' +elf_file = cur_dir + '/../../gen/ecm3531_cortex-m3/bin/' + sys.argv[1] +print("elf_file = ",elf_file) + + +# use these to download and run the elf fle +ocd_commands = ["halt\n", + "flash erase_sector 0 0 127\n", + "flash write_image {}\n".format(elf_file), + "mww 0x1001fff8 0\n", + "reset\n"] + +# OK now do what we came here for!!! +for x in ocd_commands: + print(x) + send_ocd_cmd(x) -- GitLab From 14d57ea334274d2056105e71844387674afc75b9 Mon Sep 17 00:00:00 2001 From: hari Date: Fri, 8 Feb 2019 10:22:45 -0800 Subject: [PATCH 118/766] new file --- .../tools/make/targets/ecm3531/flash_erase | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100755 tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/flash_erase diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/flash_erase b/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/flash_erase new file mode 100755 index 0000000000..5395b3d996 --- /dev/null +++ b/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/flash_erase @@ -0,0 +1,47 @@ +#!/usr/bin/python3 +#Usage: cd to the directory tensorflow/lite/experimental/micro/tools/make/targets/ecm3531 and type ./flash_erase to erase the flash. +# +# +# 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. +#============================================================================== + + +import os +import telnetlib + +def send_ocd_cmd(line): + ocd_sock.write(bytes(line,encoding = 'utf-8')) + print(ocd_sock.read_until(b'> ').decode('utf-8'), end='') + +def get_ocd_response(): + print(ocd_sock.read_until(b'> ').decode('utf-8'), end='') + +#get hooked up to openocd daemon +ocd_sock = telnetlib.Telnet(host='localhost', port=4444) +get_ocd_response() # clean it out + +#ocd comand +ocd_commands = ["halt\n", + "flash erase_sector 0 0 127\n", + "mww 0x1001fff8 0\n", + "mdw 0x01000000 16\n", + "reset\n"] + +# OK now do what we came here for!!! +for x in ocd_commands: + print(x) + send_ocd_cmd(x) + + -- GitLab From 8b71a241ff9ec9dab4d5edefce42fb55416c1ab6 Mon Sep 17 00:00:00 2001 From: hari Date: Fri, 8 Feb 2019 10:23:39 -0800 Subject: [PATCH 119/766] edited comments --- .../micro/tools/make/targets/ecm3531/load_program | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program b/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program index ac1f49962a..781231480a 100755 --- a/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program +++ b/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program @@ -1,5 +1,5 @@ #!/usr/bin/python3 -#Usage: cd to the directory tensorflow/lite/experimental/micro/tools/make/targets/ecm3531 and type ./load_prgram to load the executable tensorflow/lite/experimental/micro/tools/make/gen/ecm3531_cortex-m3/bin/micro_speech_test into SRAM +#Usage: cd to the directory tensorflow/lite/experimental/micro/tools/make/targets/ecm3531 and type ./load_prgram executable_name to load an executable from the directory tensorflow/lite/experimental/micro/tools/make/gen/ecm3531_cortex-m3/bin/ # # # Copyright 2015 The TensorFlow Authors. All Rights Reserved. @@ -18,6 +18,7 @@ #============================================================================== +import sys, getopt import os import telnetlib @@ -34,7 +35,8 @@ get_ocd_response() # clean it out # git path to project elf file cur_dir = os.getcwd() -elf_file = cur_dir + '/../../gen/ecm3531_cortex-m3/bin/' + 'micro_speech_test' +#elf_file = cur_dir + '/../../gen/ecm3531_cortex-m3/bin/' + 'preprocessor_test' +elf_file = cur_dir + '/../../gen/ecm3531_cortex-m3/bin/' + sys.argv[1] print("elf_file = ",elf_file) -- GitLab From a0143d8a731c2b4d2ddc18c260cec30fa01d9aa5 Mon Sep 17 00:00:00 2001 From: hari Date: Fri, 8 Feb 2019 10:25:19 -0800 Subject: [PATCH 120/766] added ETA_LDS_FILE variable --- .../micro/tools/make/targets/ecm3531_makefile.inc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531_makefile.inc b/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531_makefile.inc index 4ce2f69bc9..897a2b66d2 100644 --- a/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531_makefile.inc +++ b/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531_makefile.inc @@ -5,6 +5,10 @@ ifeq ($(TARGET), ecm3531) ETA_SDK := /home/hari/TensaiSDK-v0.0.17/soc/ GCC_ARM := /home/hari/Downloads/gcc-arm-none-eabi-7-2018-q2-update/ +#Pick the appropriate lds file depending whether you are running frof SRAM of flash + ETA_LDS_FILE := ecm3531.lds +# ETA_LDS_FILE := ecm3531_flash.lds + ifeq ($(wildcard $(ETA_SDK)),) $(error Path to ETA SDK is not set (ETA_SDK)) endif @@ -59,7 +63,7 @@ ifeq ($(TARGET), ecm3531) -Wl,--start-group -lm -lc -lgcc -Wl,--end-group \ -fno-exceptions \ -nostdlib --specs=nano.specs -t -lstdc++ -lc -lnosys -lm \ - -Wl,-T,$(MAKEFILE_DIR)/targets/ecm3531/ecm3531.lds \ + -Wl,-T,$(MAKEFILE_DIR)/targets/ecm3531/$(ETA_LDS_FILE) \ -Wl,-Map=$(MAKEFILE_DIR)/targets/ecm3531/ecm3531.map,--cref BUILD_TYPE := micro MICROLITE_LIBS := \ @@ -92,7 +96,7 @@ ifeq ($(TARGET), ecm3531) # directory for generated projects. This is similar to the behavior needed # for headers. MICROLITE_CC_HDRS += \ - $(MAKEFILE_DIR)/targets/ecm3531/ecm3531.lds + $(MAKEFILE_DIR)/targets/ecm3531/$(ETA_LDS_FILE) TEST_SCRIPT := tensorflow/lite/experimental/micro/testing/test_ecm3531_binary.sh # These are tests that don't currently work on the blue pill. -- GitLab From 5a2200c3d24a11febe861524239492f95d515fef Mon Sep 17 00:00:00 2001 From: hari Date: Fri, 8 Feb 2019 10:26:08 -0800 Subject: [PATCH 121/766] added text for Eta Compute --- tensorflow/lite/experimental/micro/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 30070bf008..32712f3b71 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -16,6 +16,8 @@ detection model, takes up a total of 22KB. * [Building for "Hifive1" SiFive FE310 development board using Make](#building-for-hifive1-sifive-fe310-development-board-using-make) * [Building for Ambiq Micro Apollo3Blue EVB using Make](#building-for-ambiq-micro-apollo3blue-evb-using-make) * [Additional Apollo3 Instructions](#additional-apollo3-instructions) + * [Building for the Eta Compute EVB using Make](#building-for-eta_compute-micro-ECM3531-evb-using-make) + * [Additional Eta Compute Instructions](#additional-apollo3-instructions) - [Goals](#goals) - [Generating Project Files](#generating-project-#files) - [How to Port TensorFlow Lite Micro to a New Platform](#how-to-port-tensorflow-lite-micro-to-a-new-platform) -- GitLab From fd6b83eb9631048f80670a4eb655e4fd7819598c Mon Sep 17 00:00:00 2001 From: hari Date: Fri, 8 Feb 2019 11:22:19 -0800 Subject: [PATCH 122/766] adding text for Eta Compute ECM3531 EVB --- .../tools/make/targets/ecm3531/README.txt | 31 ++----------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/README.txt b/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/README.txt index 3cb74a7243..7e592da350 100644 --- a/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/README.txt +++ b/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/README.txt @@ -1,32 +1,5 @@ -Running The Micro Speech Example On Eta Compute's ECM3531EVB - -This code will enable you to compile and execute the Tensorflow Lite Micro Speech Example on Eta Computes's low power ECM3531 chip. - - -GETTING STARTED: - -1. Download the Tensorflow code from Github and follow instructions there to download other dependencies. - -2. Download the Eta Compute SDK, version 0.0.17. - -3. Install the Arm compiler arm-none-eabi-gcc, version = arm-none-eabi-gcc (GNU Tools for Arm Embedded Processors 7-2018-q2-update) 7.3.1 20180622 (release) [ARM/embedded-7-branch revision 261907] - -4. Edit the file tensorflow/lite/experimental/micro/tools/make/targets/ecm3531_makefile.inc so that the variable ETA_SDK points to the location where the Eta Compute SDK is installed, and the variable GCC_ARM points to the Arm compiler. - -5. Compile the code with the command "make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=ecm3531 test". This will create the executable tensorflow/lite/experimental/micro/tools/make/gen/ecm3531_cortex-m3/bin/micro_speech_test. - -6. Connect the board to the host computer, start PuTTY (Connection type = Serial, Speed = 11520, Data bits = 8, Stop bits = 1, Parity = None), and load the executable with ocd. A sample script for loading the image is provided in tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program. - -The following will be printed on the Uart: - -Testing TestInvoke -Ran successfully - -/ tests passed -~~~ALL TESTS PASSED~~~ - - - CONTACT INFORMATION: Contact info@etacompute.com for more information on obtaining the Eta Compute SDK and evalution board. + +www.etacompute.com -- GitLab From 0b43a91d5bee33161f62d3e2dd96b01efae8f9be Mon Sep 17 00:00:00 2001 From: hari Date: Fri, 8 Feb 2019 11:26:25 -0800 Subject: [PATCH 123/766] added text for ECM3531 EVB --- tensorflow/lite/experimental/micro/README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 32712f3b71..7d4b390070 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -16,8 +16,8 @@ detection model, takes up a total of 22KB. * [Building for "Hifive1" SiFive FE310 development board using Make](#building-for-hifive1-sifive-fe310-development-board-using-make) * [Building for Ambiq Micro Apollo3Blue EVB using Make](#building-for-ambiq-micro-apollo3blue-evb-using-make) * [Additional Apollo3 Instructions](#additional-apollo3-instructions) - * [Building for the Eta Compute EVB using Make](#building-for-eta_compute-micro-ECM3531-evb-using-make) - * [Additional Eta Compute Instructions](#additional-apollo3-instructions) + * [Building for the Eta Compute ECM3531 EVB using Make](#building-for-the-eta-compute-ecm31-evb-using-make) + - [Goals](#goals) - [Generating Project Files](#generating-project-#files) - [How to Port TensorFlow Lite Micro to a New Platform](#how-to-port-tensorflow-lite-micro-to-a-new-platform) @@ -47,6 +47,7 @@ project files that you can download for the following platforms: ["Blue Pill" STM32F103-compatible development board](https://github.com/google/stm32_bare_lib) | - | - | [Instructions](#building-for-the-blue-pill-stm32f103-using-make) [Ambiq Micro Apollo3Blue EVB using Make](https://ambiqmicro.com/apollo-ultra-low-power-mcus/) | - | - | [Instructions](#building-for-ambiq-micro-apollo3blue-evb-using-make) [Generic Keil uVision Projects](http://www2.keil.com/mdk5/uvision/) | - | [Download](https://drive.google.com/open?id=1Lw9rsdquNKObozClLPoE5CTJLuhfh5mV) | - +[Eta Compute ECM3531 EVB](http://https://etacompute.com/) | - | - | [Instructions](#building-for-the-eta-compute-ecm31-evb-using-make) If your device is not yet supported, it may not be too hard to add support. You can learn about that process @@ -317,6 +318,9 @@ To flash a part with JFlash Lite, do the following: 4. Data file = `tensorflow/lite/experimental/micro/tools/make/gen/apollo3evb_cortex-m4/bin/pushbutton_cmsis_speech_test.bin` 5. Prog Addr = 0x0000C000 +## Building for the Eta Compute ECM3531 EVB using Make + + ## Goals The design goals are for the framework to be: -- GitLab From 1576ec8082f1fe7da308c9765019a1f4d3ee3f0c Mon Sep 17 00:00:00 2001 From: hari Date: Fri, 8 Feb 2019 11:29:34 -0800 Subject: [PATCH 124/766] edits --- tensorflow/lite/experimental/micro/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 7d4b390070..c54cbad49d 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -47,7 +47,7 @@ project files that you can download for the following platforms: ["Blue Pill" STM32F103-compatible development board](https://github.com/google/stm32_bare_lib) | - | - | [Instructions](#building-for-the-blue-pill-stm32f103-using-make) [Ambiq Micro Apollo3Blue EVB using Make](https://ambiqmicro.com/apollo-ultra-low-power-mcus/) | - | - | [Instructions](#building-for-ambiq-micro-apollo3blue-evb-using-make) [Generic Keil uVision Projects](http://www2.keil.com/mdk5/uvision/) | - | [Download](https://drive.google.com/open?id=1Lw9rsdquNKObozClLPoE5CTJLuhfh5mV) | - -[Eta Compute ECM3531 EVB](http://https://etacompute.com/) | - | - | [Instructions](#building-for-the-eta-compute-ecm31-evb-using-make) +[Eta Compute ECM3531 EVB](//https://etacompute.com/) | - | - | [Instructions](#building-for-the-eta-compute-ecm31-evb-using-make) If your device is not yet supported, it may not be too hard to add support. You can learn about that process -- GitLab From 4953ae297ab3d3fdbe951bdb8bc606334edc3336 Mon Sep 17 00:00:00 2001 From: hari Date: Fri, 8 Feb 2019 11:32:21 -0800 Subject: [PATCH 125/766] edits --- tensorflow/lite/experimental/micro/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index c54cbad49d..aba5090037 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -47,7 +47,7 @@ project files that you can download for the following platforms: ["Blue Pill" STM32F103-compatible development board](https://github.com/google/stm32_bare_lib) | - | - | [Instructions](#building-for-the-blue-pill-stm32f103-using-make) [Ambiq Micro Apollo3Blue EVB using Make](https://ambiqmicro.com/apollo-ultra-low-power-mcus/) | - | - | [Instructions](#building-for-ambiq-micro-apollo3blue-evb-using-make) [Generic Keil uVision Projects](http://www2.keil.com/mdk5/uvision/) | - | [Download](https://drive.google.com/open?id=1Lw9rsdquNKObozClLPoE5CTJLuhfh5mV) | - -[Eta Compute ECM3531 EVB](//https://etacompute.com/) | - | - | [Instructions](#building-for-the-eta-compute-ecm31-evb-using-make) +[Eta Compute ECM3531 EVB](https://etacompute.com/) | - | - | [Instructions](#building-for-the-eta-compute-ecm31-evb-using-make) If your device is not yet supported, it may not be too hard to add support. You can learn about that process -- GitLab From f98173ccbeaf3a019ddd8152c8520aa68f744d4f Mon Sep 17 00:00:00 2001 From: Jonathan Kyl Date: Fri, 8 Feb 2019 12:47:47 -0800 Subject: [PATCH 126/766] Allow functions of `DistributedVariable`s to implement `custom_gradient` Previously, variables created by functions decorated with `custom_gradient` had to pass: ```python isinstance(v, resource_variable_ops.ResourceVariable) ``` Now, they must pass: ```python resource_variable_ops.is_resource_variable(v) ``` This allows functions of `DistributedVariables` (which already have the method `_should_act_as_resource_variable`) to implement `custom_gradient`, which is necessary for cross-replica batch normalization, among other things. --- tensorflow/python/ops/custom_gradient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/ops/custom_gradient.py b/tensorflow/python/ops/custom_gradient.py index 5fc3fdcc3e..a2b7767dcd 100644 --- a/tensorflow/python/ops/custom_gradient.py +++ b/tensorflow/python/ops/custom_gradient.py @@ -185,7 +185,7 @@ def _graph_mode_decorator(f, *args, **kwargs): current_var_scope.local_variables()) new_vars = after_vars - before_vars for v in new_vars: - if not isinstance(v, resource_variable_ops.ResourceVariable): + if not resource_variable_ops.is_resource_variable(v): raise TypeError( "All variables used by a function wrapped with @custom_gradient must " "be `ResourceVariable`s. Ensure that no `variable_scope` is created " -- GitLab From d211b649e482b36dfaf6f0bdc4603e63fe05a08b Mon Sep 17 00:00:00 2001 From: hari Date: Fri, 8 Feb 2019 15:10:58 -0800 Subject: [PATCH 127/766] edit --- tensorflow/lite/experimental/micro/README.md | 23 +++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index aba5090037..8c25ad76c5 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -16,7 +16,7 @@ detection model, takes up a total of 22KB. * [Building for "Hifive1" SiFive FE310 development board using Make](#building-for-hifive1-sifive-fe310-development-board-using-make) * [Building for Ambiq Micro Apollo3Blue EVB using Make](#building-for-ambiq-micro-apollo3blue-evb-using-make) * [Additional Apollo3 Instructions](#additional-apollo3-instructions) - * [Building for the Eta Compute ECM3531 EVB using Make](#building-for-the-eta-compute-ecm31-evb-using-make) + * [Building for the Eta Compute ECM3531 EVB using Make](#Building-for-the-Eta-Compute-ECM3531-EVB-using-Make) - [Goals](#goals) - [Generating Project Files](#generating-project-#files) @@ -47,7 +47,7 @@ project files that you can download for the following platforms: ["Blue Pill" STM32F103-compatible development board](https://github.com/google/stm32_bare_lib) | - | - | [Instructions](#building-for-the-blue-pill-stm32f103-using-make) [Ambiq Micro Apollo3Blue EVB using Make](https://ambiqmicro.com/apollo-ultra-low-power-mcus/) | - | - | [Instructions](#building-for-ambiq-micro-apollo3blue-evb-using-make) [Generic Keil uVision Projects](http://www2.keil.com/mdk5/uvision/) | - | [Download](https://drive.google.com/open?id=1Lw9rsdquNKObozClLPoE5CTJLuhfh5mV) | - -[Eta Compute ECM3531 EVB](https://etacompute.com/) | - | - | [Instructions](#building-for-the-eta-compute-ecm31-evb-using-make) +[Eta Compute ECM3531 EVB](https://etacompute.com/) | - | - | [Instructions](#Building-for-the-Eta-Compute-ECM3531-EVB-using-Make) If your device is not yet supported, it may not be too hard to add support. You can learn about that process @@ -319,7 +319,24 @@ To flash a part with JFlash Lite, do the following: 5. Prog Addr = 0x0000C000 ## Building for the Eta Compute ECM3531 EVB using Make - +1. Follow the instructions at [Tensorflow Micro Speech](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro/examples/micro_speech#getting-started) to down load the Tensorflow source code and the support libraries \(but do not run the make command shown there.\) +2. Download the Eta Compute SDK. Contact info@etacompute.com +3. You will need the the Arm compiler arm-none-eabi-gcc, version = 7.3.1 20180622, release ARM/embedded-7-branch revision 261907. This compiler is downloaded when you run the tensorflow/lite/experimental/micro/tools/make/download_dependencies.sh script. +4. Edit the file tensorflow/lite/experimental/micro/tools/make/targets/ecm3531_makefile.inc so that the variables ETA_SDK and GCC_ARM point to the correct directories. +5. Compile the code with the command +  make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=ecm3531 TAGS="CMSIS" test +This will produce a set of executables in the tensorflow/lite/experimental/micro/tools/make/gen/ecm3531_cortex-m3/bin directory. +6. To load an executable into SRAM +  start ocd +  cd tensorflow/lite/experimental/micro/tools/make/targets/ecm3531 +  ./load_program name_of_executable, for e.g., ./load_program audio_provider_test +  start PuTTY \(Connection type = Serial, Speed = 11520, Data bits = 8, Stop bits = 1, Parity = None\) +The following output should appear +7. To load into flash +  edit the variable ETA_LDS_FILE in tensorflow/lite/experimental/micro/tools/  make/targets/ecm3531_makefile.inc to point to the ecm3531_flash.lds file +  Recompile \( make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=ecm3531 TAGS="CMSIS" test\) +  cd tensorflow/lite/experimental/micro/tools/make/targets/ecm3531 +  ./flash_program executable_name to load into flash. ## Goals -- GitLab From 33a64e52765d4b26431b2719ed2fe5c1a2a80c87 Mon Sep 17 00:00:00 2001 From: 1e100 <38598618+1e100@users.noreply.github.com> Date: Fri, 8 Feb 2019 15:29:57 -0800 Subject: [PATCH 128/766] Make build script executable --- tensorflow/lite/tools/make/build_aarch64_lib.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 tensorflow/lite/tools/make/build_aarch64_lib.sh diff --git a/tensorflow/lite/tools/make/build_aarch64_lib.sh b/tensorflow/lite/tools/make/build_aarch64_lib.sh old mode 100644 new mode 100755 -- GitLab From f97e10cd350c3d4effe72548a98ffb65d193ebd0 Mon Sep 17 00:00:00 2001 From: hari Date: Fri, 8 Feb 2019 15:32:43 -0800 Subject: [PATCH 129/766] edits --- tensorflow/lite/experimental/micro/README.md | 29 ++++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 8c25ad76c5..3029e2d705 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -320,23 +320,28 @@ To flash a part with JFlash Lite, do the following: ## Building for the Eta Compute ECM3531 EVB using Make 1. Follow the instructions at [Tensorflow Micro Speech](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro/examples/micro_speech#getting-started) to down load the Tensorflow source code and the support libraries \(but do not run the make command shown there.\) -2. Download the Eta Compute SDK. Contact info@etacompute.com -3. You will need the the Arm compiler arm-none-eabi-gcc, version = 7.3.1 20180622, release ARM/embedded-7-branch revision 261907. This compiler is downloaded when you run the tensorflow/lite/experimental/micro/tools/make/download_dependencies.sh script. +2. Download the Eta Compute SDK, version 0.0.17. Contact info@etacompute.com +3. You will need the the Arm compiler arm-none-eabi-gcc, version 7.3.1 20180622, release ARM/embedded-7-branch revision 261907, 7-2018-q2-update. This compiler is downloaded when you run the tensorflow/lite/experimental/micro/tools/make/download_dependencies.sh script. 4. Edit the file tensorflow/lite/experimental/micro/tools/make/targets/ecm3531_makefile.inc so that the variables ETA_SDK and GCC_ARM point to the correct directories. 5. Compile the code with the command -  make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=ecm3531 TAGS="CMSIS" test +    make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=ecm3531 TAGS="CMSIS" test This will produce a set of executables in the tensorflow/lite/experimental/micro/tools/make/gen/ecm3531_cortex-m3/bin directory. 6. To load an executable into SRAM -  start ocd -  cd tensorflow/lite/experimental/micro/tools/make/targets/ecm3531 -  ./load_program name_of_executable, for e.g., ./load_program audio_provider_test -  start PuTTY \(Connection type = Serial, Speed = 11520, Data bits = 8, Stop bits = 1, Parity = None\) -The following output should appear +    start ocd +    cd tensorflow/lite/experimental/micro/tools/make/targets/ecm3531 +    ./load_program name_of_executable, for e.g., ./load_program audio_provider_test +    start PuTTY \(Connection type = Serial, Speed = 11520, Data bits = 8, Stop bits = 1, Parity = None\) +The following output should appear: +Testing TestAudioProvider +Testing TestTimer +2/2 tests passed +\~\~\~ALL TESTS PASSED\~\~\~ +Execution time \(msec\) = 7 7. To load into flash -  edit the variable ETA_LDS_FILE in tensorflow/lite/experimental/micro/tools/  make/targets/ecm3531_makefile.inc to point to the ecm3531_flash.lds file -  Recompile \( make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=ecm3531 TAGS="CMSIS" test\) -  cd tensorflow/lite/experimental/micro/tools/make/targets/ecm3531 -  ./flash_program executable_name to load into flash. +    edit the variable ETA_LDS_FILE in tensorflow/lite/experimental/micro/tools/  make/targets/ecm3531_makefile.inc to point to the ecm3531_flash.lds file +    Recompile \( make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=ecm3531 TAGS="CMSIS" test\) +    cd tensorflow/lite/experimental/micro/tools/make/targets/ecm3531 +    ./flash_program executable_name to load into flash. ## Goals -- GitLab From 2f59fcaef0f6a17ed10397e685ecafc697b6ace3 Mon Sep 17 00:00:00 2001 From: hari Date: Fri, 8 Feb 2019 15:33:07 -0800 Subject: [PATCH 130/766] edits --- .../micro/testing/test_ecm3531_binary.sh | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/tensorflow/lite/experimental/micro/testing/test_ecm3531_binary.sh b/tensorflow/lite/experimental/micro/testing/test_ecm3531_binary.sh index c4978b95b5..1647cf82a2 100755 --- a/tensorflow/lite/experimental/micro/testing/test_ecm3531_binary.sh +++ b/tensorflow/lite/experimental/micro/testing/test_ecm3531_binary.sh @@ -1,15 +1,16 @@ -/* 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. -==============================================================================*/ +#!/bin/bash -e +# 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. +# ============================================================================== -- GitLab From 301393aa9db16a6384eca4260dba50abfcd26dca Mon Sep 17 00:00:00 2001 From: hari Date: Fri, 8 Feb 2019 15:37:34 -0800 Subject: [PATCH 131/766] edits --- tensorflow/lite/experimental/micro/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 3029e2d705..6892e5d70b 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -327,10 +327,10 @@ To flash a part with JFlash Lite, do the following:     make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=ecm3531 TAGS="CMSIS" test This will produce a set of executables in the tensorflow/lite/experimental/micro/tools/make/gen/ecm3531_cortex-m3/bin directory. 6. To load an executable into SRAM -    start ocd +    Start ocd     cd tensorflow/lite/experimental/micro/tools/make/targets/ecm3531     ./load_program name_of_executable, for e.g., ./load_program audio_provider_test -    start PuTTY \(Connection type = Serial, Speed = 11520, Data bits = 8, Stop bits = 1, Parity = None\) +    Start PuTTY \(Connection type = Serial, Speed = 11520, Data bits = 8, Stop bits = 1, Parity = None\) The following output should appear: Testing TestAudioProvider Testing TestTimer @@ -338,7 +338,7 @@ Testing TestTimer \~\~\~ALL TESTS PASSED\~\~\~ Execution time \(msec\) = 7 7. To load into flash -    edit the variable ETA_LDS_FILE in tensorflow/lite/experimental/micro/tools/  make/targets/ecm3531_makefile.inc to point to the ecm3531_flash.lds file +    Edit the variable ETA_LDS_FILE in tensorflow/lite/experimental/micro/tools/  make/targets/ecm3531_makefile.inc to point to the ecm3531_flash.lds file     Recompile \( make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=ecm3531 TAGS="CMSIS" test\)     cd tensorflow/lite/experimental/micro/tools/make/targets/ecm3531     ./flash_program executable_name to load into flash. -- GitLab From 7916f3e7c0ed73eecb793ea5f187373691d56cc0 Mon Sep 17 00:00:00 2001 From: hari Date: Fri, 8 Feb 2019 15:45:20 -0800 Subject: [PATCH 132/766] new file --- .../micro/tools/make/targets/ecm3531/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/README.md diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/README.md b/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/README.md new file mode 100644 index 0000000000..7bb92fb2b3 --- /dev/null +++ b/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/README.md @@ -0,0 +1,8 @@ +Compiling instructions here(https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro) + + +CONTACT INFORMATION: + +Contact info@etacompute.com for more information on obtaining the Eta Compute SDK and evalution board. + +www.etacompute.com -- GitLab From 22051ef40591054525aeda678cbcba953793fb33 Mon Sep 17 00:00:00 2001 From: HARI SHANKAR Date: Fri, 8 Feb 2019 15:46:20 -0800 Subject: [PATCH 133/766] Delete README.txt --- .../experimental/micro/tools/make/targets/ecm3531/README.txt | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/README.txt diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/README.txt b/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/README.txt deleted file mode 100644 index 7e592da350..0000000000 --- a/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/README.txt +++ /dev/null @@ -1,5 +0,0 @@ -CONTACT INFORMATION: - -Contact info@etacompute.com for more information on obtaining the Eta Compute SDK and evalution board. - -www.etacompute.com -- GitLab From 753f3755662cf998c27bfacc3dcaabbb66f08d4b Mon Sep 17 00:00:00 2001 From: hari Date: Fri, 8 Feb 2019 15:48:42 -0800 Subject: [PATCH 134/766] edits --- .../experimental/micro/tools/make/targets/ecm3531/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/README.md b/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/README.md index 7bb92fb2b3..77ea4e0610 100644 --- a/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/README.md +++ b/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/README.md @@ -1,4 +1,4 @@ -Compiling instructions here(https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro) +Compiling instructions here https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro CONTACT INFORMATION: -- GitLab From f08ed0b0e9ddd729325d514337f7c13308447066 Mon Sep 17 00:00:00 2001 From: jwu Date: Sun, 10 Feb 2019 22:43:49 +0100 Subject: [PATCH 135/766] remove np.asarray condition, avoid raggedtensor conflict --- tensorflow/python/ops/array_ops.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tensorflow/python/ops/array_ops.py b/tensorflow/python/ops/array_ops.py index 131f356361..c227e81b15 100644 --- a/tensorflow/python/ops/array_ops.py +++ b/tensorflow/python/ops/array_ops.py @@ -581,9 +581,7 @@ def _slice_helper(tensor, slice_spec, var=None): """ if isinstance(slice_spec, bool) or \ (isinstance(slice_spec, ops.Tensor) and slice_spec.dtype == dtypes.bool) or \ - (isinstance(slice_spec, np.ndarray) and slice_spec.dtype == bool) or \ - (isinstance(slice_spec, (list, tuple)) and - np.asarray(slice_spec).dtype == bool): + (isinstance(slice_spec, np.ndarray) and slice_spec.dtype == bool): return boolean_mask(tensor=tensor, mask=slice_spec) if not isinstance(slice_spec, (list, tuple)): -- GitLab From 841350de1ef317ca7df4cfca438ca1daf3d93200 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Mon, 11 Feb 2019 18:51:39 +0000 Subject: [PATCH 136/766] Remove duplicated file of kafka_ops.cc The kafka_ops.cc is the same as dataset_ops.cc and is not being used by BUILD file. Signed-off-by: Yong Tang --- tensorflow/contrib/kafka/ops/kafka_ops.cc | 44 ----------------------- 1 file changed, 44 deletions(-) delete mode 100644 tensorflow/contrib/kafka/ops/kafka_ops.cc diff --git a/tensorflow/contrib/kafka/ops/kafka_ops.cc b/tensorflow/contrib/kafka/ops/kafka_ops.cc deleted file mode 100644 index 8cdf16103b..0000000000 --- a/tensorflow/contrib/kafka/ops/kafka_ops.cc +++ /dev/null @@ -1,44 +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. -==============================================================================*/ - -#include "tensorflow/core/framework/common_shape_fns.h" -#include "tensorflow/core/framework/op.h" -#include "tensorflow/core/framework/shape_inference.h" - -namespace tensorflow { - -REGISTER_OP("KafkaDataset") - .Input("topics: string") - .Input("servers: string") - .Input("group: string") - .Input("eof: bool") - .Input("timeout: int64") - .Output("handle: variant") - .SetIsStateful() - .SetShapeFn(shape_inference::ScalarShape) - .Doc(R"doc( -Creates a dataset that emits the messages of one or more Kafka topics. - -topics: A `tf.string` tensor containing one or more subscriptions, - in the format of [topic:partition:offset:length], - by default length is -1 for unlimited. -servers: A list of bootstrap servers. -group: The consumer group id. -eof: If True, the kafka reader will stop on EOF. -timeout: The timeout value for the Kafka Consumer to wait - (in millisecond). -)doc"); - -} // namespace tensorflow -- GitLab From 18cab473fd76c114b713f595c0952dcba3c05450 Mon Sep 17 00:00:00 2001 From: Sergii Khomenko Date: Tue, 12 Feb 2019 00:47:01 +0100 Subject: [PATCH 137/766] Add an initial version of ipynb support Adapted from original https://github.com/lc0/tf2-ipynb --- tensorflow/tools/compatibility/ipynb.py | 138 ++++++++++++++++++ .../tools/compatibility/tf_upgrade_v2_main.py | 4 +- 2 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 tensorflow/tools/compatibility/ipynb.py diff --git a/tensorflow/tools/compatibility/ipynb.py b/tensorflow/tools/compatibility/ipynb.py new file mode 100644 index 0000000000..4e4c922b75 --- /dev/null +++ b/tensorflow/tools/compatibility/ipynb.py @@ -0,0 +1,138 @@ +# Copyright 2019 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 module to support operation on ipynb files""" + +from __future__ import print_function + +import collections +import copy +import json +import shutil +import tempfile + + +from tensorflow.tools.compatibility import ast_edits +from tensorflow.tools.compatibility import tf_upgrade_v2 + + +CodeLine = collections.namedtuple('CodeLine', ['cell_number', 'code']) + + +def process_file(in_filename, out_filename): + """The function where we inject the support for ipynb upgrade.""" + upgrader = ast_edits.ASTCodeUpgrader(tf_upgrade_v2.TFAPIChangeSpec()) + + if in_filename.endswith('.py'): + files_processed, report_text, errors = \ + upgrader.process_file(in_filename, out_filename) + + elif in_filename.endswith('.ipynb'): + print("Extracting code lines from original notebook") + raw_code, notebook = _get_code(in_filename) + raw_lines = [cl.code for cl in raw_code] + + # The function follows the original flow from `upgrader.process_fil` + with tempfile.NamedTemporaryFile("w", delete=False) as temp_file: + + processed_file, new_file_content, log, process_errors = ( + upgrader.update_string_pasta("\n".join(raw_lines), in_filename)) + + if temp_file and processed_file: + new_notebook = _update_notebook( + notebook, raw_code, new_file_content.split("\n")) + json.dump(new_notebook, temp_file) + else: + raise SyntaxError( + "Was not able to process the file: \n%s\n" % ''.join(log)) + + files_processed = processed_file + report_text = upgrader._format_log(log, in_filename, out_filename) + errors = process_errors + + shutil.move(temp_file.name, out_filename) + + else: + raise NotImplementedError( + "Currently converter only supports python or ipynb") + + return files_processed, report_text, errors + + +def _get_code(input_file): + """Load the ipynb file and return a list of CodeLines.""" + + raw_code = [] + + with open(input_file) as in_file: + notebook = json.load(in_file) + + cell_index = 0 + for cell in notebook['cells']: + if cell['cell_type'] == 'code': + cell_lines = cell['source'] + + for line_idx, code_line in enumerate(cell_lines): + + # Sometimes, jupyter has more than python code + # Idea is to comment these lines, for upgrade time + if code_line.startswith("%") or code_line.startswith("!") \ + or code_line.startswith("?"): + # Found a special character, need to "encode" + code_line = "###!!!" + code_line + + # Sometimes, people leave \n at the end of cell + # in order to migrate only related things, and make the diff + # the smallest -> here is another hack + if (line_idx == len(cell_lines) - 1) and code_line.endswith("\n"): + code_line = code_line.replace("\n", "###===") + + # sometimes a line would start with `\n` and content after + # that's the hack for this + raw_code.append( + CodeLine(cell_index, + code_line.rstrip().replace('\n', "###==="))) + + cell_index += 1 + + return raw_code, notebook + + +def _update_notebook(original_notebook, original_raw_lines, updated_code_lines): + """Update notebook, once migration is done.""" + + new_notebook = copy.deepcopy(original_notebook) + + # validate that the number of lines is the same + assert len(original_raw_lines) == len(updated_code_lines), \ + ("The lengths of input and converted files are not the same: " + "{} vs {}".format(len(original_raw_lines), len(updated_code_lines))) + + code_cell_idx = 0 + for cell in new_notebook['cells']: + if cell['cell_type'] != 'code': + continue + + applicable_lines = [ + idx for idx, code_line in enumerate(original_raw_lines) + if code_line.cell_number == code_cell_idx] + + new_code = [updated_code_lines[idx] for idx in applicable_lines] + + cell['source'] = "\n".join(new_code).replace( + '###!!!', '').replace("###===", "\n") + code_cell_idx += 1 + + return new_notebook diff --git a/tensorflow/tools/compatibility/tf_upgrade_v2_main.py b/tensorflow/tools/compatibility/tf_upgrade_v2_main.py index c34d659eeb..cf92b06da4 100644 --- a/tensorflow/tools/compatibility/tf_upgrade_v2_main.py +++ b/tensorflow/tools/compatibility/tf_upgrade_v2_main.py @@ -22,6 +22,7 @@ import argparse from tensorflow.tools.compatibility import ast_edits from tensorflow.tools.compatibility import tf_upgrade_v2 +from tensorflow.tools.compatibility import ipynb def main(): @@ -31,6 +32,7 @@ def main(): Simple usage: tf_upgrade_v2.py --infile foo.py --outfile bar.py + tf_upgrade_v2.py --infile foo.ipynb --outfile bar.ipynb tf_upgrade_v2.py --intree ~/code/old --outtree ~/code/new """) parser.add_argument( @@ -85,7 +87,7 @@ Simple usage: raise ValueError( "--outfile= argument is required when converting a " "single file.") - files_processed, report_text, errors = upgrade.process_file( + files_processed, report_text, errors = ipynb.process_file( args.input_file, args.output_file) errors = {args.input_file: errors} files_processed = 1 -- GitLab From 38dddb1c720cba9057993d05978084c8a4847562 Mon Sep 17 00:00:00 2001 From: Amit Srivastava Date: Mon, 11 Feb 2019 08:38:15 +0530 Subject: [PATCH 138/766] Addded missing scenarios for the tensor_content Added the TCs for validation of content set via tensor_content() This is also a TODO item in the TC mentioned --- .../lite/toco/import_tensorflow_test.cc | 122 +++++++++++++++++- 1 file changed, 121 insertions(+), 1 deletion(-) diff --git a/tensorflow/lite/toco/import_tensorflow_test.cc b/tensorflow/lite/toco/import_tensorflow_test.cc index 8ff3f7733a..4a1f4de184 100644 --- a/tensorflow/lite/toco/import_tensorflow_test.cc +++ b/tensorflow/lite/toco/import_tensorflow_test.cc @@ -115,7 +115,6 @@ void BuildConstNode(std::initializer_list shape, s->add_dim()->set_size(d); } - // TODO(ahentz): also need to test via tensor_content() switch (dtype) { case DT_FLOAT: for (int64_t i = 0; i < num_elements; ++i) { @@ -385,6 +384,127 @@ std::vector> UnaryTestTypes() { {DT_INT64, ArrayDataType::kInt64}}; } +class TensorContentTest : public ::testing::Test { + public: + template + std::vector> ImportAndGetData(const NodeDef& node) { + Model model; + auto status = ImportNode(node, &model); + CHECK(status.ok()) << status.error_message(); + const auto& nodearray = model.GetArray("Node1"); + return nodearray.GetBuffer().data; + } + template + void NodeWithTensorContent(std::initializer_list shape, + tensorflow::DataType dtype, int64_t num_elements, + NodeDef* node) { + node->set_op("Const"); + node->set_name("Node1"); + + // An attribute describing the type of this const node. + AttrValue dtype_attr; + SetAttrValue(dtype, &dtype_attr); + (*node->mutable_attr())["dtype"] = dtype_attr; + + auto allocated_content = absl::make_unique(num_elements); + + // An attribute describing the content of this const node. + tensorflow::TensorProto t; + t.set_dtype(dtype); + auto* s = t.mutable_tensor_shape(); + for (const auto & d : shape) { + s->add_dim()->set_size(d); + } + + switch (dtype) { + case DT_FLOAT: + for (int64_t i = 0; i < num_elements; ++i) { + allocated_content[i] = i / 10000.0 + 1; + } + break; + case DT_INT32: + for (int64_t i = 0; i < num_elements; ++i) { + allocated_content[i] = i % std::numeric_limits::max() + 1; + } + break; + case DT_QUINT8: + for (int64_t i = 0; i < num_elements; ++i) { + allocated_content[i] = i % std::numeric_limits::max() + 1; + } + break; + case DT_INT64: + for (int64_t i = 0; i < num_elements; ++i) { + allocated_content[i] = i + 1; + } + break; + case DT_STRING: + break; + case DT_BOOL: + for (int64_t i = 0; i < num_elements; ++i) { + allocated_content[i] = ((i % 2) == 0); + } + break; + default: + break; + } + t.set_tensor_content( + string(reinterpret_cast(allocated_content.get()), + num_elements * sizeof(T))); + + AttrValue value_attr; + SetAttrValue(t, &value_attr); + (*node->mutable_attr())["value"] = value_attr; + + allocated_content.reset(); + } +}; + +TEST_F(TensorContentTest, Int64) { + constexpr ArrayDataType kType = ArrayDataType::kInt64; + + NodeDef node; + NodeWithTensorContent({1, 2, 3}, DT_INT64, 6, &node); + + EXPECT_THAT(ImportAndGetData(node), ElementsAre(1, 2, 3, 4, 5, 6)); +} + +TEST_F(TensorContentTest, Int32) { + constexpr ArrayDataType kType = ArrayDataType::kInt32; + + NodeDef node; + NodeWithTensorContent({1, 2, 3}, DT_INT32, 6, &node); + + EXPECT_THAT(ImportAndGetData(node), ElementsAre(1, 2, 3, 4, 5, 6)); +} + +TEST_F(TensorContentTest, Float) { + constexpr ArrayDataType kType = ArrayDataType::kFloat; + + NodeDef node; + NodeWithTensorContent({1, 2, 3}, DT_FLOAT, 6, &node); + + EXPECT_THAT(ImportAndGetData(node), + ElementsAre(1.0000, 1.0001, 1.0002, 1.0003, 1.0004, 1.0005)); +} + +TEST_F(TensorContentTest, Quint8) { + constexpr ArrayDataType kType = ArrayDataType::kUint8; + + NodeDef node; + NodeWithTensorContent({1, 2, 3}, DT_QUINT8, 6, &node); + + EXPECT_THAT(ImportAndGetData(node), ElementsAre(1, 2, 3, 4, 5, 6)); +} + +TEST_F(TensorContentTest, Bool) { + constexpr ArrayDataType kType = ArrayDataType::kBool; + + NodeDef node; + NodeWithTensorContent({1, 2, 3}, DT_BOOL, 6, &node); + + EXPECT_THAT(ImportAndGetData(node), ElementsAre(1, 0, 1, 0, 1, 0)); +} + class TypeImportTest : public ::testing::TestWithParam< std::pair> { protected: -- GitLab From fbd56732738cb74a8d4afc19c9d1a6cfb6712bce Mon Sep 17 00:00:00 2001 From: Dayananda-V Date: Tue, 12 Feb 2019 11:35:05 +0530 Subject: [PATCH 139/766] TF Android Example compile warning fixes 1. The minSdk and targetSdk version should not be declared in the android manifest file. You can move the version from the manifest to the defaultConfig in the build.gradle file. Reference : https://developer.android.com/guide/topics/manifest/manifest-intro#uses-sdk 2. Configuration 'compile' is obsolete and has been replaced with 'implementation' and 'api'. Reference : http://d.android.com/r/tools/update-dependency-configurations.html 3. Caused by: java.io.FileNotFoundException: imagenet_comp_graph_label_strings.txt this error reported after application compile and run first time using Studio, this fix solves the exist problem. --- .../examples/android/AndroidManifest.xml | 4 ---- tensorflow/examples/android/build.gradle | 20 +++++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tensorflow/examples/android/AndroidManifest.xml b/tensorflow/examples/android/AndroidManifest.xml index 5c47ce6b67..a3b53da6a3 100644 --- a/tensorflow/examples/android/AndroidManifest.xml +++ b/tensorflow/examples/android/AndroidManifest.xml @@ -24,10 +24,6 @@ - - } } -// Download default models; if you wish to use your own models then -// place them in the "assets" directory and comment out this line. -apply from: "download-models.gradle" - - dependencies { if (nativeBuildSystem == 'cmake' || nativeBuildSystem == 'none') { - compile 'org.tensorflow:tensorflow-android:+' + implementation 'org.tensorflow:tensorflow-android:+' } } -- GitLab From 40e1acd4fc8937ea9690d65857c933c7d555bd1f Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Tue, 12 Feb 2019 21:02:12 +0000 Subject: [PATCH 140/766] Remove the limitation of int max for dim_size in SparseDenseCwise ops This fix tries to address the issue raised in 25701 where the dim_size in SparseDenseCwise ops was set to `std::numeric_limits::max`. The restriction is likely unnecesary, due to historical reasons. This fix remove this limitation. This fix fixes 25701. Signed-off-by: Yong Tang --- tensorflow/core/kernels/sparse_dense_binary_op_shared.cc | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tensorflow/core/kernels/sparse_dense_binary_op_shared.cc b/tensorflow/core/kernels/sparse_dense_binary_op_shared.cc index a4e89f439e..af69ae9ebd 100644 --- a/tensorflow/core/kernels/sparse_dense_binary_op_shared.cc +++ b/tensorflow/core/kernels/sparse_dense_binary_op_shared.cc @@ -78,9 +78,6 @@ class SparseDenseBinaryOpShared : public OpKernel { "but received shapes: ", values_t->shape().DebugString(), " and ", shape_t->shape().DebugString())); - OP_REQUIRES(ctx, indices_t->dim_size(0) < std::numeric_limits::max(), - errors::InvalidArgument( - "Number of non-zero elements exceeds int32 range")); const auto indices_mat = indices_t->matrix(); const auto shape_vec = shape_t->vec(); @@ -106,7 +103,7 @@ class SparseDenseBinaryOpShared : public OpKernel { Tensor *output_values = nullptr; Tensor dense_gathered; - const int nnz = static_cast(indices_t->dim_size(0)); + const int64 nnz = indices_t->dim_size(0); OP_REQUIRES_OK(ctx, ctx->allocate_output(0, TensorShape({nnz}), &output_values)); OP_REQUIRES_OK( -- GitLab From bae887e18dada914c3719bc1dacbb3a4c09eb60d Mon Sep 17 00:00:00 2001 From: Sergii Khomenko Date: Tue, 12 Feb 2019 22:36:28 +0100 Subject: [PATCH 141/766] Move dispatch logic out of the module --- tensorflow/tools/compatibility/ipynb.py | 54 +++++++------------ .../tools/compatibility/tf_upgrade_v2_main.py | 25 +++++++-- 2 files changed, 40 insertions(+), 39 deletions(-) diff --git a/tensorflow/tools/compatibility/ipynb.py b/tensorflow/tools/compatibility/ipynb.py index 4e4c922b75..633a0d523e 100644 --- a/tensorflow/tools/compatibility/ipynb.py +++ b/tensorflow/tools/compatibility/ipynb.py @@ -23,50 +23,34 @@ import json import shutil import tempfile - -from tensorflow.tools.compatibility import ast_edits -from tensorflow.tools.compatibility import tf_upgrade_v2 - - CodeLine = collections.namedtuple('CodeLine', ['cell_number', 'code']) -def process_file(in_filename, out_filename): +def process_file(in_filename, out_filename, upgrader): """The function where we inject the support for ipynb upgrade.""" - upgrader = ast_edits.ASTCodeUpgrader(tf_upgrade_v2.TFAPIChangeSpec()) - - if in_filename.endswith('.py'): - files_processed, report_text, errors = \ - upgrader.process_file(in_filename, out_filename) - - elif in_filename.endswith('.ipynb'): - print("Extracting code lines from original notebook") - raw_code, notebook = _get_code(in_filename) - raw_lines = [cl.code for cl in raw_code] - - # The function follows the original flow from `upgrader.process_fil` - with tempfile.NamedTemporaryFile("w", delete=False) as temp_file: + print("Extracting code lines from original notebook") + raw_code, notebook = _get_code(in_filename) + raw_lines = [cl.code for cl in raw_code] - processed_file, new_file_content, log, process_errors = ( - upgrader.update_string_pasta("\n".join(raw_lines), in_filename)) + # The function follows the original flow from `upgrader.process_fil` + with tempfile.NamedTemporaryFile("w", delete=False) as temp_file: - if temp_file and processed_file: - new_notebook = _update_notebook( - notebook, raw_code, new_file_content.split("\n")) - json.dump(new_notebook, temp_file) - else: - raise SyntaxError( - "Was not able to process the file: \n%s\n" % ''.join(log)) + processed_file, new_file_content, log, process_errors = ( + upgrader.update_string_pasta("\n".join(raw_lines), in_filename)) - files_processed = processed_file - report_text = upgrader._format_log(log, in_filename, out_filename) - errors = process_errors + if temp_file and processed_file: + new_notebook = _update_notebook( + notebook, raw_code, new_file_content.split("\n")) + json.dump(new_notebook, temp_file) + else: + raise SyntaxError( + "Was not able to process the file: \n%s\n" % ''.join(log)) - shutil.move(temp_file.name, out_filename) + files_processed = processed_file + report_text = upgrader._format_log(log, in_filename, out_filename) + errors = process_errors - else: - raise NotImplementedError( - "Currently converter only supports python or ipynb") + shutil.move(temp_file.name, out_filename) return files_processed, report_text, errors diff --git a/tensorflow/tools/compatibility/tf_upgrade_v2_main.py b/tensorflow/tools/compatibility/tf_upgrade_v2_main.py index cf92b06da4..cd0f204ae9 100644 --- a/tensorflow/tools/compatibility/tf_upgrade_v2_main.py +++ b/tensorflow/tools/compatibility/tf_upgrade_v2_main.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Upgrader for Python scripts from 1.* TensorFlow to 2.0 TensorFlow.""" +"""Upgrader for Python scripts from 1.x TensorFlow to 2.0 TensorFlow.""" from __future__ import absolute_import from __future__ import division @@ -25,10 +25,27 @@ from tensorflow.tools.compatibility import tf_upgrade_v2 from tensorflow.tools.compatibility import ipynb + +def process_file(in_filename, out_filename, upgrader): + """Process a file of type `.py` or `.ipynb`.""" + + if in_filename.endswith('.py'): + files_processed, report_text, errors = \ + upgrader.process_file(in_filename, out_filename) + elif in_filename.endswith('.ipynb'): + files_processed, report_text, errors = \ + ipynb.process_file(in_filename, out_filename, upgrader) + else: + raise NotImplementedError( + "Currently converter only supports python or ipynb") + + return files_processed, report_text, errors + + def main(): parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, - description="""Convert a TensorFlow Python file from 1.* to 2.0 + description="""Convert a TensorFlow Python file from 1.x to 2.0 Simple usage: tf_upgrade_v2.py --infile foo.py --outfile bar.py @@ -87,8 +104,8 @@ Simple usage: raise ValueError( "--outfile= argument is required when converting a " "single file.") - files_processed, report_text, errors = ipynb.process_file( - args.input_file, args.output_file) + files_processed, report_text, errors = process_file( + args.input_file, args.output_file, upgrade) errors = {args.input_file: errors} files_processed = 1 elif args.input_tree: -- GitLab From 6617f31d511aa1ed1d868d96bd4c3da47e0e78ae Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Tue, 12 Feb 2019 22:09:37 +0000 Subject: [PATCH 142/766] Fix infinite loop when ignore_errors is used with TFRecordDataset This fix tries to address the issue raised in 25700 where ignore_errors combined with TFRecordDataset will have a infinite loop. The issue was that when ignore errors happen, the index of the file names are not moved forward with error returned. So the same file is repeated. This fix is a short solution to fix it. This fix fixes 25700. Signed-off-by: Yong Tang --- tensorflow/core/kernels/data/reader_dataset_ops.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tensorflow/core/kernels/data/reader_dataset_ops.cc b/tensorflow/core/kernels/data/reader_dataset_ops.cc index 789f9c859a..8e9b6f164e 100644 --- a/tensorflow/core/kernels/data/reader_dataset_ops.cc +++ b/tensorflow/core/kernels/data/reader_dataset_ops.cc @@ -821,6 +821,11 @@ class TFRecordDatasetOp : public DatasetOpKernel { } out_tensors->pop_back(); if (!errors::IsOutOfRange(s)) { + // In case of other errors e.g., DataLoss, we still move forward + // the file index so that it works with ignore_errors. + // Otherwise the same file will repeat. + ResetStreamsLocked(); + ++current_file_index_; return s; } -- GitLab From 59178ef084aa8c97049d29d292aca4f1ddd350a5 Mon Sep 17 00:00:00 2001 From: Yuxin Wu Date: Wed, 13 Feb 2019 11:50:34 -0800 Subject: [PATCH 143/766] Update clip_ops.py --- tensorflow/python/ops/clip_ops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/ops/clip_ops.py b/tensorflow/python/ops/clip_ops.py index a237cfff82..7e24c59269 100644 --- a/tensorflow/python/ops/clip_ops.py +++ b/tensorflow/python/ops/clip_ops.py @@ -306,7 +306,7 @@ def clip_by_global_norm(t_list, clip_norm, use_norm=None, name=None): date=None, instructions= "clip_by_average_norm is deprecated in TensorFlow 2.0. Please use " - "clip_by_norm(t, clip_norm * tf.to_float(tf.size(t), name)) instead.") + "clip_by_norm(t, clip_norm * tf.cast(tf.size(t), tf.float32), name) instead.") @tf_export(v1=["clip_by_average_norm"]) def clip_by_average_norm(t, clip_norm, name=None): """Clips tensor values to a maximum average L2-norm. -- GitLab From e60d57a43a3a10fec02d5b6faa002a02008884d6 Mon Sep 17 00:00:00 2001 From: Siju Date: Thu, 14 Feb 2019 08:14:35 +0530 Subject: [PATCH 144/766] Scalar shape added in testing of make_floor_tests --- tensorflow/lite/testing/generate_examples.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/testing/generate_examples.py b/tensorflow/lite/testing/generate_examples.py index fd1a60a8fc..d2d47dddef 100644 --- a/tensorflow/lite/testing/generate_examples.py +++ b/tensorflow/lite/testing/generate_examples.py @@ -3282,7 +3282,7 @@ def make_floor_tests(zip_path): test_parameters = [{ "input_dtype": [tf.float32], - "input_shape": [[1], [1, 2], [5, 6, 7, 8], [3, 4, 5, 6]], + "input_shape": [[], [1], [1, 2], [5, 6, 7, 8], [3, 4, 5, 6]], }] def build_graph(parameters): -- GitLab From beb770112a09bd2645d789df1f82a9b9b8081987 Mon Sep 17 00:00:00 2001 From: Siju Date: Thu, 14 Feb 2019 08:16:54 +0530 Subject: [PATCH 145/766] Scalar shape added in testing of make_ceil_tests --- tensorflow/lite/testing/generate_examples.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/testing/generate_examples.py b/tensorflow/lite/testing/generate_examples.py index d2d47dddef..cb8c79724e 100644 --- a/tensorflow/lite/testing/generate_examples.py +++ b/tensorflow/lite/testing/generate_examples.py @@ -3307,7 +3307,7 @@ def make_ceil_tests(zip_path): test_parameters = [{ "input_dtype": [tf.float32], - "input_shape": [[1], [1, 2], [5, 6, 7, 8], [3, 4, 5, 6]], + "input_shape": [[], [1], [1, 2], [5, 6, 7, 8], [3, 4, 5, 6]], }] def build_graph(parameters): -- GitLab From 6bc4cdf8a7f829bc9e57cef5e8dab613593f3d67 Mon Sep 17 00:00:00 2001 From: Pariksheet Date: Thu, 14 Feb 2019 12:12:24 +0530 Subject: [PATCH 146/766] Negative number test case added --- tensorflow/python/keras/activations_test.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tensorflow/python/keras/activations_test.py b/tensorflow/python/keras/activations_test.py index 9d2195480d..a23b13bb5c 100644 --- a/tensorflow/python/keras/activations_test.py +++ b/tensorflow/python/keras/activations_test.py @@ -145,10 +145,14 @@ class KerasActivationsTest(test.TestCase): def test_relu(self): x = keras.backend.placeholder(ndim=2) f = keras.backend.function([x], [keras.activations.relu(x)]) - test_values = np.random.random((2, 5)) - result = f([test_values])[0] - # No negative values in test values... - self.assertAllClose(result, test_values, rtol=1e-05) + positive_values = np.random.random((2, 5)) + result = f([positive_values])[0] + self.assertAllClose(result, positive_values, rtol=1e-05) + + negative_values = np.random.uniform(-1, 0, (2, 5)) + result = f([negative_values])[0] + expected = np.zeros((2, 5)) + self.assertAllClose(result, expected, rtol=1e-05) def test_elu(self): x = keras.backend.placeholder(ndim=2) -- GitLab From 618269e6f4ce5d5f47305e06c24576bf5aaaf56d Mon Sep 17 00:00:00 2001 From: Bhavani Subramanian Date: Thu, 14 Feb 2019 11:17:57 -0800 Subject: [PATCH 147/766] Enabled quantized pad fusion. --- tensorflow/core/graph/mkl_layout_pass.cc | 9 +- tensorflow/core/kernels/BUILD | 23 + tensorflow/core/kernels/mkl_conv_ops.cc | 111 +++-- tensorflow/core/kernels/mkl_conv_ops.h | 5 +- .../kernels/mkl_quantized_conv_ops_test.cc | 454 ++++++++++++++++++ tensorflow/core/ops/mkl_nn_ops.cc | 11 + tensorflow/core/ops/nn_ops.cc | 23 +- 7 files changed, 577 insertions(+), 59 deletions(-) create mode 100644 tensorflow/core/kernels/mkl_quantized_conv_ops_test.cc diff --git a/tensorflow/core/graph/mkl_layout_pass.cc b/tensorflow/core/graph/mkl_layout_pass.cc index e934978e76..59cdc4afe4 100644 --- a/tensorflow/core/graph/mkl_layout_pass.cc +++ b/tensorflow/core/graph/mkl_layout_pass.cc @@ -2400,7 +2400,8 @@ void MklLayoutRewritePass::CopyAttrsQuantizedConv2D(const Node* orig_node, DataType Tinput, Tfilter, out_type; string padding; string data_format("NHWC"); - std::vector strides, dilations; + std::vector strides, dilations, padding_list; + bool has_padding_list = HasNodeAttr(orig_node->def(), "padding_list"); // Get all attributes from old node. TF_CHECK_OK(GetNodeAttr(orig_node->def(), "Tinput", &Tinput)); @@ -2409,6 +2410,9 @@ void MklLayoutRewritePass::CopyAttrsQuantizedConv2D(const Node* orig_node, TF_CHECK_OK(GetNodeAttr(orig_node->def(), "padding", &padding)); TF_CHECK_OK(GetNodeAttr(orig_node->def(), "strides", &strides)); TF_CHECK_OK(GetNodeAttr(orig_node->def(), "dilations", &dilations)); + if (has_padding_list) { + TF_CHECK_OK(GetNodeAttr(orig_node->def(), "padding_list", &padding_list)); + } Node* filter_node = nullptr; orig_node->input_node(1, &filter_node); @@ -2423,6 +2427,9 @@ void MklLayoutRewritePass::CopyAttrsQuantizedConv2D(const Node* orig_node, nb->Attr("dilations", dilations); nb->Attr("T", out_type); // added "T" for facilitating MklToTf conversion. nb->Attr("data_format", data_format); + if (has_padding_list) { + nb->Attr("padding_list", padding_list); + } // Requantization attr Tbias. DataType Tbias; diff --git a/tensorflow/core/kernels/BUILD b/tensorflow/core/kernels/BUILD index 445fde84f1..cf59fb005c 100644 --- a/tensorflow/core/kernels/BUILD +++ b/tensorflow/core/kernels/BUILD @@ -6388,6 +6388,29 @@ tf_cc_test( ], ) +tf_cc_test_mkl( + name = "mkl_quantized_conv_ops_test", + size = "small", + srcs = ["mkl_quantized_conv_ops_test.cc"], + tags = ["nomsan"], # http://b/32242946 + deps = [ + ":mkl_conv_op", + ":mkl_input_conversion_op", + ":ops_testutil", + ":ops_util", + ":quantization_utils", + ":quantized_ops", + "//tensorflow/core:array_ops_op_lib", + "//tensorflow/core:framework", + "//tensorflow/core:math_ops_op_lib", + "//tensorflow/core:nn_ops_op_lib", + "//tensorflow/core:protos_all_cc", + "//tensorflow/core:test", + "//tensorflow/core:test_main", + "//tensorflow/core:testlib", + ], +) + tf_cc_test( name = "quantize_op_test", size = "small", diff --git a/tensorflow/core/kernels/mkl_conv_ops.cc b/tensorflow/core/kernels/mkl_conv_ops.cc index 0134cc2235..aeb198c145 100644 --- a/tensorflow/core/kernels/mkl_conv_ops.cc +++ b/tensorflow/core/kernels/mkl_conv_ops.cc @@ -467,19 +467,18 @@ class MklConvOp : public OpKernel { filter.shape().DebugString())); for (int i = 0; i < 3; i++) { - OP_REQUIRES( - context, - FastBoundsCheck(filter.dim_size(i), std::numeric_limits::max()), - errors::InvalidArgument("filter too large")); + OP_REQUIRES(context, FastBoundsCheck(filter.dim_size(i), + std::numeric_limits::max()), + errors::InvalidArgument("filter too large")); } const int64 input_depth = input_in_mkl_format ? GetMklTensorDim(mkl_context.input_shape, 'C') : GetTensorDim(input, data_format_, 'C'); - OP_REQUIRES(context, input_depth == filter.dim_size(2), - errors::InvalidArgument( - "input and filter must have the same depth: ", input_depth, - " vs ", filter.dim_size(2))); + OP_REQUIRES( + context, input_depth == filter.dim_size(2), + errors::InvalidArgument("input and filter must have the same depth: ", + input_depth, " vs ", filter.dim_size(2))); // The last dimension for filter is out_depth. const int out_depth = static_cast(filter.dim_size(3)); @@ -488,10 +487,9 @@ class MklConvOp : public OpKernel { const int64 input_rows_raw = input_in_mkl_format ? GetMklTensorDim(mkl_context.input_shape, 'H') : GetTensorDim(input, data_format_, 'H'); - OP_REQUIRES( - context, - FastBoundsCheck(input_rows_raw, std::numeric_limits::max()), - errors::InvalidArgument("Input rows too large")); + OP_REQUIRES(context, FastBoundsCheck(input_rows_raw, + std::numeric_limits::max()), + errors::InvalidArgument("Input rows too large")); const int input_rows = static_cast(input_rows_raw); const int filter_rows = static_cast(filter.dim_size(0)); @@ -500,10 +498,9 @@ class MklConvOp : public OpKernel { const int64 input_cols_raw = input_in_mkl_format ? GetMklTensorDim(mkl_context.input_shape, 'W') : GetTensorDim(input, data_format_, 'W'); - OP_REQUIRES( - context, - FastBoundsCheck(input_cols_raw, std::numeric_limits::max()), - errors::InvalidArgument("Input cols too large")); + OP_REQUIRES(context, FastBoundsCheck(input_cols_raw, + std::numeric_limits::max()), + errors::InvalidArgument("Input cols too large")); const int input_cols = static_cast(input_cols_raw); const int filter_cols = static_cast(filter.dim_size(1)); @@ -511,10 +508,9 @@ class MklConvOp : public OpKernel { const int64 input_batch_raw = input_in_mkl_format ? GetMklTensorDim(mkl_context.input_shape, 'N') : GetTensorDim(input, data_format_, 'N'); - OP_REQUIRES( - context, - FastBoundsCheck(input_batch_raw, std::numeric_limits::max()), - errors::InvalidArgument("batch is too large")); + OP_REQUIRES(context, FastBoundsCheck(input_batch_raw, + std::numeric_limits::max()), + errors::InvalidArgument("batch is too large")); const int batch = static_cast(input_batch_raw); // For now we take the stride from the second and third dimensions only (we @@ -860,6 +856,9 @@ class MklConvOp : public OpKernel { explicit MklConvOp(OpKernelConstruction* context) : OpKernel(context) { OP_REQUIRES_OK(context, context->GetAttr("dilations", &dilations_)); + if (context->HasAttr("padding_list")) { + OP_REQUIRES_OK(context, context->GetAttr("padding_list", &padding_list_)); + } OP_REQUIRES_OK(context, context->GetAttr("strides", &strides_)); string data_format; OP_REQUIRES_OK(context, context->GetAttr("data_format", &data_format)); @@ -899,17 +898,15 @@ class MklConvOp : public OpKernel { OP_REQUIRES(context, dilations_.size() == 5, errors::InvalidArgument("Dilation rates field must " "specify 5 dimensions")); - OP_REQUIRES(context, - (GetTensorDim(dilations_, data_format_, 'N') == 1 && - GetTensorDim(dilations_, data_format_, 'C') == 1), + OP_REQUIRES(context, (GetTensorDim(dilations_, data_format_, 'N') == 1 && + GetTensorDim(dilations_, data_format_, 'C') == 1), errors::InvalidArgument( "Current implementation does not yet support " "dilations rates in the batch and depth dimensions.")); OP_REQUIRES( - context, - (GetTensorDim(dilations_, data_format_, '0') > 0 && - GetTensorDim(dilations_, data_format_, '1') > 0 && - GetTensorDim(dilations_, data_format_, '2') > 0), + context, (GetTensorDim(dilations_, data_format_, '0') > 0 && + GetTensorDim(dilations_, data_format_, '1') > 0 && + GetTensorDim(dilations_, data_format_, '2') > 0), errors::InvalidArgument("Dilated rates should be larger than 0.")); } } @@ -938,9 +935,18 @@ class MklConvOp : public OpKernel { dilations, strides; memory::dims dst_dims_tf_order, dst_dims_mkl_order; - // If pad with conv2d fusion is enabled - if (fuse_pad_) { - PadWithConvFusion(context, padding_left, padding_right); + // For Quantized-Conv2D and Pad fusion, we get padding from the + // `padding_list` attribute. Otherwise, we get it from one of the inputs. + bool quantized_pad_enabled = false; + for (auto const& padding_val : padding_list_) { + if (!padding_val) continue; + quantized_pad_enabled = true; + break; + } + + if (fuse_pad_ || quantized_pad_enabled) { + PadWithConvFusion(context, padding_left, padding_right, + quantized_pad_enabled); } // Get shapes of input tensors in MKL-DNN order @@ -951,7 +957,8 @@ class MklConvOp : public OpKernel { conv_utl.GetConvFwdSizesInMklOrder( src_tf_shape, filter_tf_shape, &src_dims, &filter_dims, &strides, &dilations, &dst_dims_tf_order, &dst_dims_mkl_order, &padding_left, - &padding_right, fuse_pad_, is_depthwise); + &padding_right, (fuse_pad_ || quantized_pad_enabled), is_depthwise); + if (!context->status().ok()) return; // Check for corner case - if there is nothing to compute, return. @@ -1151,16 +1158,20 @@ class MklConvOp : public OpKernel { } void PadWithConvFusion(OpKernelContext* context, memory::dims& padding_left, - memory::dims& padding_right) { + memory::dims& padding_right, + bool quantized_pad_enabled) { const Tensor& paddings_tf = MklGetInput(context, input_index_pad_); - OP_REQUIRES(context, paddings_tf.dims() == 2, - errors::InvalidArgument("paddings must be 2-dimensional: ", - paddings_tf.shape().DebugString())); - - // Flatten tensor to get individual paddings. - Tpadding* paddings = static_cast( - const_cast(paddings_tf.flat().data())); - + Tpadding* paddings = nullptr; + if (quantized_pad_enabled) { + paddings = padding_list_.data(); + } else { + OP_REQUIRES(context, paddings_tf.dims() == 2, + errors::InvalidArgument("paddings must be 2-dimensional: ", + paddings_tf.shape().DebugString())); + // Flatten tensor to get individual paddings. + paddings = static_cast( + const_cast(paddings_tf.flat().data())); + } // If the data format is NHWC, indices 0, 1, 6 and 7 of paddings(_tf) // will be zero. // Example: @@ -1186,8 +1197,7 @@ class MklConvOp : public OpKernel { pad_left = paddings[6]; pad_right = paddings[7]; } - - // Create padding arrays for MKL DNN convolutions. + // Create padding arrays for MKL-DNN convolutions. // MKL-DNN uses asymetric padding. padding_left = {static_cast(pad_top), static_cast(pad_left)}; padding_right = {static_cast(pad_bottom), static_cast(pad_right)}; @@ -1264,6 +1274,7 @@ class MklConvOp : public OpKernel { private: std::vector strides_; std::vector dilations_; + std::vector padding_list_; bool is_filter_const_; mutex mu_; Padding padding_; @@ -1792,8 +1803,8 @@ class MklQuantizedConv2DSumReluOp const float max_filter = context->input(5 + bias_index_offset).flat()(0); - reorder_sum_scale = 255.0 * 127.0 / - (std::max(std::abs(max_input), std::abs(min_input)) * + reorder_sum_scale = + 255.0 * 127.0 / (std::max(std::abs(max_input), std::abs(min_input)) * std::max(std::abs(max_filter), std::abs(min_filter))); std::vector scales; scales.push_back(reorder_sum_scale); @@ -1825,7 +1836,7 @@ class MklQuantizedConv2DSumReluOp }; // INT8 kernel registration -// Register NoOp kernel for QunatizedConv2D for qint8 filter +// Register NoOp kernel for QuantizedConv2D for qint8 filter REGISTER_KERNEL_BUILDER(Name("QuantizedConv2D") .Device(DEVICE_CPU) .TypeConstraint("Tinput") @@ -1840,7 +1851,7 @@ REGISTER_KERNEL_BUILDER(Name("QuantizedConv2DAndRequantize") .TypeConstraint("out_type"), NoOp); -// Register a templatized implementation of MklQuntizedConv2D. +// Register a templatized implementation of MklQuantizedConv2D. REGISTER_KERNEL_BUILDER( Name("_MklQuantizedConv2D") .Device(DEVICE_CPU) @@ -2029,17 +2040,21 @@ REGISTER_KERNEL_BUILDER( .Device(DEVICE_CPU) .TypeConstraint("Tinput") .TypeConstraint("Tfilter") + .TypeConstraint("Tbias") .TypeConstraint("out_type") .Label(mkl_op_registry::kMklQuantizedOpLabel), MklQuantizedConv2DSumReluOp); + REGISTER_KERNEL_BUILDER( Name("_MklQuantizedConv2DWithBiasSignedSumAndReluAndRequantize") .Device(DEVICE_CPU) .TypeConstraint("Tinput") .TypeConstraint("Tfilter") + .TypeConstraint("Tbias") .TypeConstraint("out_type") .Label(mkl_op_registry::kMklQuantizedOpLabel), MklQuantizedConv2DSumReluOp); + #endif // INTEL_MKL_ML // Register 2D operations @@ -2074,7 +2089,7 @@ REGISTER_KERNEL_BUILDER( .TypeConstraint("Tpaddings") \ .Label(mkl_op_registry::kMklOpLabel), \ MklConvOp); \ + float, int32, false, true, false>); \ REGISTER_KERNEL_BUILDER(Name("__MklDummyPadWithConv2D") \ .Device(DEVICE_CPU) \ .TypeConstraint("T") \ @@ -2116,7 +2131,7 @@ TF_CALL_float(REGISTER_MKL_CPU_2D_DEPTHWISE); .TypeConstraint("T") \ .TypeConstraint("Tpaddings") \ .Label(mkl_op_registry::kMklOpLabel), \ - MklFusedConvOp); \ + MklFusedConvOp); \ REGISTER_KERNEL_BUILDER(Name("__MklDummyPadWithFusedConv2D") \ .Device(DEVICE_CPU) \ .TypeConstraint("T") \ diff --git a/tensorflow/core/kernels/mkl_conv_ops.h b/tensorflow/core/kernels/mkl_conv_ops.h index c12a4ff0f0..228397c712 100644 --- a/tensorflow/core/kernels/mkl_conv_ops.h +++ b/tensorflow/core/kernels/mkl_conv_ops.h @@ -196,9 +196,8 @@ class MklDnnConvUtil { filter_shape.DebugString())); for (int i = 0; i < ((strides_.size() == 4) ? 3 : 5); i++) { - OP_REQUIRES(context_, - FastBoundsCheck(filter_shape.dim_size(i), - std::numeric_limits::max()), + OP_REQUIRES(context_, FastBoundsCheck(filter_shape.dim_size(i), + std::numeric_limits::max()), errors::InvalidArgument("filter too large")); } diff --git a/tensorflow/core/kernels/mkl_quantized_conv_ops_test.cc b/tensorflow/core/kernels/mkl_quantized_conv_ops_test.cc new file mode 100644 index 0000000000..1d41b25193 --- /dev/null +++ b/tensorflow/core/kernels/mkl_quantized_conv_ops_test.cc @@ -0,0 +1,454 @@ +/* Copyright 2019 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. +==============================================================================*/ + +#ifdef INTEL_MKL +#define EIGEN_USE_THREADS + +#include +#include +#include + +#include "tensorflow/core/framework/allocator.h" +#include "tensorflow/core/framework/fake_input.h" +#include "tensorflow/core/framework/node_def_builder.h" +#include "tensorflow/core/framework/op_kernel.h" +#include "tensorflow/core/framework/tensor.h" +#include "tensorflow/core/framework/tensor_testutil.h" +#include "tensorflow/core/framework/types.h" +#include "tensorflow/core/framework/types.pb.h" +#include "tensorflow/core/kernels/ops_testutil.h" +#include "tensorflow/core/kernels/ops_util.h" +#include "tensorflow/core/kernels/quantization_utils.h" +#include "tensorflow/core/lib/core/status_test_util.h" +#include "tensorflow/core/platform/test.h" + +namespace tensorflow { + +// TODO(bhavanis): Move ConvMklToTF to mkl_test_util.h as it is used by +// most unit tests. + +// Helper class for converting MKL tensors to TF tensors and comparing to +// expected values + +static const uint8 dummy_tensor[] = {0, 0, 0, 0, 0, 0, 0, 0}; +static const TensorShape dummy_shape({8}); + +class ConvMklToTF : public OpsTestBase { + public: + template + void ConvertMklToTF(DataType dtype, const Tensor& input, + const Tensor& input_metadata_tensor, Tensor& output) { + // Create an MKL to TF conversion node and execute it + TF_EXPECT_OK(NodeDefBuilder("mkl_to_tf_op", "_MklToTf") + .Input(FakeInput(dtype)) // Input + .Input(FakeInput(DT_UINT8)) // MKL metadata tensor + .Attr("T", dtype) + .Attr("_kernel", "MklOp") + .Finalize(node_def())); + TF_EXPECT_OK(InitOp()); + AddInputFromArray(input.shape(), input.flat()); + AddInputFromArray(input_metadata_tensor.shape(), + input_metadata_tensor.flat()); + TF_ASSERT_OK(RunOpKernel()); + + output = *GetOutput(0); + } + void TestBody() {} +}; + +class QuantizedConv2DTest : public OpsTestBase { + protected: + void ConfigureQuantizedConv2D(const int& stride = 1) { + TF_ASSERT_OK(NodeDefBuilder("quantized_conv_op", "_MklQuantizedConv2D") + .Input(FakeInput(DT_QUINT8)) // Input + .Input(FakeInput(DT_QINT8)) // Filter + .Input(FakeInput(DT_FLOAT)) // Min input + .Input(FakeInput(DT_FLOAT)) // Max input + .Input(FakeInput(DT_FLOAT)) // Min filter + .Input(FakeInput(DT_FLOAT)) // Max filter + // MKL metadata tensors // + .Input(FakeInput(DT_UINT8)) + .Input(FakeInput(DT_UINT8)) + .Input(FakeInput(DT_UINT8)) + .Input(FakeInput(DT_UINT8)) + .Input(FakeInput(DT_UINT8)) + .Input(FakeInput(DT_UINT8)) + /////////////////////////// + .Attr("Tinput", DataTypeToEnum::v()) + .Attr("Tfilter", DataTypeToEnum::v()) + .Attr("T", DataTypeToEnum::v()) + .Attr("out_type", DataTypeToEnum::v()) + .Attr("strides", {1, stride, stride, 1}) + .Attr("padding", "SAME") + .Attr("_kernel", "QuantizedMklOp") + .Finalize(node_def())); + TF_ASSERT_OK(InitOp()); + } +}; + +// Output -> float +TEST_F(QuantizedConv2DTest, Small) { + const int stride = 1; + ConfigureQuantizedConv2D(stride); + + const int depth = 1; + const int image_width = 4; + const int image_height = 3; + const int image_batch_count = 1; + + // Image -> uint8 + const float image_min = 0.0f; + const float image_max = 255.0f; + + // The image matrix is: + // | 1 | 2 | 3 | 4 | + // | 5 | 6 | 7 | 8 | + // | 9 | 10 | 11 | 12 | + Tensor image_float(DT_FLOAT, + {image_batch_count, image_height, image_width, depth}); + test::FillValues(&image_float, + {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}); + Tensor image_quantized = + FloatTensorToQuantized(image_float, image_min, image_max); + + const int filter_size = 3; + const int filter_count = 1; + + // Filter -> int8 with symmetric range + const float filter_min = -127.0f; + const float filter_max = 127.0f; + + // The filter matrix is: + // | 1 | 4 | 7 | + // | 2 | 5 | 8 | + // | 3 | 6 | 9 | + Tensor filter_float(DT_FLOAT, + {filter_size, filter_size, depth, filter_count}); + test::FillValues(&filter_float, {1, 4, 7, 2, 5, 8, 3, 6, 9}); + Tensor filter_quantized = + FloatTensorToQuantized(filter_float, filter_min, filter_max); + + AddInputFromArray(image_quantized.shape(), + image_quantized.flat()); + AddInputFromArray(filter_quantized.shape(), + filter_quantized.flat()); + AddInputFromArray(TensorShape({1}), {image_min}); + AddInputFromArray(TensorShape({1}), {image_max}); + AddInputFromArray(TensorShape({1}), {filter_min}); + AddInputFromArray(TensorShape({1}), {filter_max}); + + AddInputFromArray(dummy_shape, dummy_tensor); + AddInputFromArray(dummy_shape, dummy_tensor); + AddInputFromArray(dummy_shape, dummy_tensor); + AddInputFromArray(dummy_shape, dummy_tensor); + AddInputFromArray(dummy_shape, dummy_tensor); + AddInputFromArray(dummy_shape, dummy_tensor); + + TF_ASSERT_OK(RunOpKernel()); + + // We're sliding the 3x3 filter across the 3x4 image, with accesses outside + // the input set to zero because we're using the 'SAME' padding mode. + // The calculations behind the expected output are: + // (1*0)+(4*0)+(7*0)+(2*0)+(5*1)+(8*2)+(3*0)+(6*5)+(9*6)=105 + // (1*0)+(4*0)+(7*0)+(2*1)+(5*2)+(8*3)+(3*5)+(6*6)+(9*7)=150 + // (1*0)+(4*0)+(7*0)+(2*2)+(5*3)+(8*4)+(3*6)+(6*7)+(9*8)=183 + // (1*0)+(4*0)+(7*0)+(2*3)+(5*4)+(8*0)+(3*7)+(6*8)+(9*0)=95 + // (1*0)+(4*1)+(7*2)+(2*0)+(5*5)+(8*6)+(3*0)+(6*9)+(9*10)=235 + // (1*1)+(4*2)+(7*3)+(2*5)+(5*6)+(8*7)+(3*9)+(6*10)+(9*11)=312 + // (1*2)+(4*3)+(7*4)+(2*6)+(5*7)+(8*8)+(3*10)+(6*11)+(9*12)=357 + // (1*3)+(4*4)+(7*0)+(2*7)+(5*8)+(8*0)+(3*11)+(6*12)+(9*0)=178 + // (1*0)+(4*5)+(7*6)+(2*0)+(5*9)+(8*10)+(3*0)+(6*0)+(9*0)=187 + // (1*5)+(4*6)+(7*7)+(2*9)+(5*10)+(8*11)+(3*0)+(6*0)+(9*0)=234 + // (1*6)+(4*7)+(7*8)+(2*10)+(5*11)+(8*12)+(3*0)+(6*0)+(9*0)=261 + // (1*7)+(4*8)+(7*0)+(2*11)+(5*12)+(8*0)+(3*0)+(6*0)+(9*0)=121 + // This means we should end up with this matrix: + // | 105 | 150 | 183 | 95 | + // | 235 | 312 | 357 | 178 | + // | 187 | 234 | 261 | 121 | + + // Output -> float + const int expected_width = image_width; + const int expected_height = image_height * filter_count; + Tensor expected_float( + DT_FLOAT, TensorShape({image_batch_count, expected_height, expected_width, + filter_count})); + test::FillValues(&expected_float, {105, 150, 183, 95, 235, 312, 357, + 178, 187, 234, 261, 121}); + + const Tensor& output = *GetOutput(0); + const Tensor& output_mkl_metadata = *GetOutput(3); + + ConvMklToTF conv_comp; + Tensor output_quantized; + conv_comp.ConvertMklToTF(DT_QINT32, output, output_mkl_metadata, + output_quantized); + + const float output_min = GetOutput(1)->flat()(0); + const float output_max = GetOutput(2)->flat()(0); + Tensor output_float = + QuantizedTensorToFloat(output_quantized, output_min, output_max); + + test::ExpectTensorNear(expected_float, output_float, 1.0); +} + +// Output -> int32 +TEST_F(QuantizedConv2DTest, Small32Bit) { + const int stride = 1; + ConfigureQuantizedConv2D(stride); + + const int depth = 1; + const int image_width = 4; + const int image_height = 3; + const int image_batch_count = 1; + AddInputFromArray( + TensorShape({image_batch_count, image_height, image_width, depth}), + {10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120}); + + const int filter_size = 3; + const int filter_count = 1; + AddInputFromArray( + TensorShape({filter_size, filter_size, depth, filter_count}), + {10, 40, 70, 20, 50, 80, 30, 60, 90}); + + // Image -> uint8 + AddInputFromArray(TensorShape({1}), {0.0f}); + AddInputFromArray(TensorShape({1}), {255.0f}); + + // Filter -> int8 with symmetric range + AddInputFromArray(TensorShape({1}), {-127.0f}); + AddInputFromArray(TensorShape({1}), {127.0f}); + + AddInputFromArray(dummy_shape, dummy_tensor); + AddInputFromArray(dummy_shape, dummy_tensor); + AddInputFromArray(dummy_shape, dummy_tensor); + AddInputFromArray(dummy_shape, dummy_tensor); + AddInputFromArray(dummy_shape, dummy_tensor); + AddInputFromArray(dummy_shape, dummy_tensor); + + TF_ASSERT_OK(RunOpKernel()); + + // Output -> int32 + const int expected_width = image_width; + const int expected_height = image_height * filter_count; + Tensor expected(DT_QINT32, TensorShape({image_batch_count, expected_height, + expected_width, filter_count})); + test::FillValues( + &expected, {10500, 15000, 18300, 9500, 23500, 31200, 35700, 17800, 18700, + 23400, 26100, 12100}); + + const Tensor& output = *GetOutput(0); + const Tensor& output_mkl_metadata = *GetOutput(3); + + ConvMklToTF conv_comp; + Tensor output_quantized; + conv_comp.ConvertMklToTF(DT_QINT32, output, output_mkl_metadata, + output_quantized); + + test::ExpectTensorEqual(expected, output_quantized); +} + +// Output -> int32 +TEST_F(QuantizedConv2DTest, Small32BitWithPadding) { + const int stride = 1; + TF_ASSERT_OK(NodeDefBuilder("quantized_conv_op", "_MklQuantizedConv2D") + .Input(FakeInput(DT_QUINT8)) // Input + .Input(FakeInput(DT_QINT8)) // Filter + .Input(FakeInput(DT_FLOAT)) // Min input + .Input(FakeInput(DT_FLOAT)) // Max input + .Input(FakeInput(DT_FLOAT)) // Min filter + .Input(FakeInput(DT_FLOAT)) // Max filter + // MKL metadata tensors // + .Input(FakeInput(DT_UINT8)) + .Input(FakeInput(DT_UINT8)) + .Input(FakeInput(DT_UINT8)) + .Input(FakeInput(DT_UINT8)) + .Input(FakeInput(DT_UINT8)) + .Input(FakeInput(DT_UINT8)) + /////////////////////////// + .Attr("Tinput", DataTypeToEnum::v()) + .Attr("Tfilter", DataTypeToEnum::v()) + .Attr("T", DataTypeToEnum::v()) + .Attr("out_type", DataTypeToEnum::v()) + .Attr("strides", {1, stride, stride, 1}) + .Attr("padding", "SAME") + .Attr("padding_list", {0, 0, 1, 1, 1, 1, 0, 0}) + .Attr("_kernel", "QuantizedMklOp") + .Finalize(node_def())); + TF_ASSERT_OK(InitOp()); + + const int depth = 1; + const int image_width = 4; + const int image_height = 3; + const int image_batch_count = 1; + AddInputFromArray( + TensorShape({image_batch_count, image_height, image_width, depth}), + {10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120}); + + const int filter_size = 3; + const int filter_count = 1; + AddInputFromArray( + TensorShape({filter_size, filter_size, depth, filter_count}), + {10, 40, 70, 20, 50, 80, 30, 60, 90}); + + // Image -> uint8 + AddInputFromArray(TensorShape({1}), {0.0f}); + AddInputFromArray(TensorShape({1}), {255.0f}); + + // Filter -> int8 with symmetric range + AddInputFromArray(TensorShape({1}), {-127.0f}); + AddInputFromArray(TensorShape({1}), {127.0f}); + + AddInputFromArray(dummy_shape, dummy_tensor); + AddInputFromArray(dummy_shape, dummy_tensor); + AddInputFromArray(dummy_shape, dummy_tensor); + AddInputFromArray(dummy_shape, dummy_tensor); + AddInputFromArray(dummy_shape, dummy_tensor); + AddInputFromArray(dummy_shape, dummy_tensor); + + TF_ASSERT_OK(RunOpKernel()); + + // Output -> int32 + const int expected_width = image_width; + const int expected_height = image_height * filter_count; + Tensor expected(DT_QINT32, TensorShape({image_batch_count, expected_height, + expected_width, filter_count})); + test::FillValues( + &expected, {10500, 15000, 18300, 9500, 23500, 31200, 35700, 17800, 18700, + 23400, 26100, 12100}); + + const Tensor& output = *GetOutput(0); + const Tensor& output_mkl_metadata = *GetOutput(3); + + ConvMklToTF conv_comp; + Tensor output_quantized; + conv_comp.ConvertMklToTF(DT_QINT32, output, output_mkl_metadata, + output_quantized); + + test::ExpectTensorEqual(expected, output_quantized); +} + +// Output -> int32 +TEST_F(QuantizedConv2DTest, OddPadding) { + const int stride = 2; + ConfigureQuantizedConv2D(stride); + + const int depth = 1; + const int image_width = 4; + const int image_height = 4; + const int image_batch_count = 1; + AddInputFromArray( + TensorShape({image_batch_count, image_height, image_width, depth}), + {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}); + + const int filter_size = 3; + const int filter_count = 1; + AddInputFromArray( + TensorShape({filter_size, filter_size, depth, filter_count}), + {1, 2, 3, 4, 5, 6, 7, 8, 9}); + + // Image -> uint8 + AddInputFromArray(TensorShape({1}), {0.0f}); + AddInputFromArray(TensorShape({1}), {255.0f}); + + // Filter -> int8 with symmetric range + AddInputFromArray(TensorShape({1}), {-127.0f}); + AddInputFromArray(TensorShape({1}), {127.0f}); + + AddInputFromArray(dummy_shape, dummy_tensor); + AddInputFromArray(dummy_shape, dummy_tensor); + AddInputFromArray(dummy_shape, dummy_tensor); + AddInputFromArray(dummy_shape, dummy_tensor); + AddInputFromArray(dummy_shape, dummy_tensor); + AddInputFromArray(dummy_shape, dummy_tensor); + + TF_ASSERT_OK(RunOpKernel()); + + // Output -> int32 + const int expected_width = image_width / stride; + const int expected_height = (image_height * filter_count) / stride; + Tensor expected(DT_QINT32, TensorShape({image_batch_count, expected_height, + expected_width, filter_count})); + test::FillValues(&expected, {348, 252, 274, 175}); + + const Tensor& output = *GetOutput(0); + const Tensor& output_mkl_metadata = *GetOutput(3); + + ConvMklToTF conv_comp; + Tensor output_quantized; + conv_comp.ConvertMklToTF(DT_QINT32, output, output_mkl_metadata, + output_quantized); + + test::ExpectTensorEqual(expected, output_quantized); +} + +// Output -> int32 +TEST_F(QuantizedConv2DTest, OddPaddingBatch) { + const int stride = 2; + ConfigureQuantizedConv2D(stride); + + const int depth = 1; + const int image_width = 4; + const int image_height = 4; + const int image_batch_count = 3; + AddInputFromArray( + TensorShape({image_batch_count, image_height, image_width, depth}), + {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}); + + const int filter_size = 3; + const int filter_count = 1; + AddInputFromArray( + TensorShape({filter_size, filter_size, depth, filter_count}), + {1, 2, 3, 4, 5, 6, 7, 8, 9}); + + // Image -> uint8 + AddInputFromArray(TensorShape({1}), {0.0f}); + AddInputFromArray(TensorShape({1}), {255.0f}); + + // Filter -> int8 with symmetric range + AddInputFromArray(TensorShape({1}), {-127.0f}); + AddInputFromArray(TensorShape({1}), {127.0f}); + + AddInputFromArray(dummy_shape, dummy_tensor); + AddInputFromArray(dummy_shape, dummy_tensor); + AddInputFromArray(dummy_shape, dummy_tensor); + AddInputFromArray(dummy_shape, dummy_tensor); + AddInputFromArray(dummy_shape, dummy_tensor); + AddInputFromArray(dummy_shape, dummy_tensor); + + TF_ASSERT_OK(RunOpKernel()); + + // Output -> int32 + const int expected_width = image_width / stride; + const int expected_height = (image_height * filter_count) / stride; + Tensor expected(DT_QINT32, TensorShape({image_batch_count, expected_height, + expected_width, filter_count})); + test::FillValues( + &expected, {348, 252, 274, 175, 348, 252, 274, 175, 348, 252, 274, 175}); + + const Tensor& output = *GetOutput(0); + const Tensor& output_mkl_metadata = *GetOutput(3); + + ConvMklToTF conv_comp; + Tensor output_quantized; + conv_comp.ConvertMklToTF(DT_QINT32, output, output_mkl_metadata, + output_quantized); + + test::ExpectTensorEqual(expected, output_quantized); +} + +} // namespace tensorflow +#endif // INTEL_MKL diff --git a/tensorflow/core/ops/mkl_nn_ops.cc b/tensorflow/core/ops/mkl_nn_ops.cc index b23c373566..0e6ad9162a 100644 --- a/tensorflow/core/ops/mkl_nn_ops.cc +++ b/tensorflow/core/ops/mkl_nn_ops.cc @@ -206,6 +206,7 @@ REGISTER_OP("_MklQuantizedConv2D") .Attr("is_filter_const: bool = true") .Attr(GetPaddingAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") + .Attr("padding_list: list(int) = []") .SetShapeFn([](InferenceContext* c) { TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); ShapeHandle unused; @@ -251,6 +252,7 @@ REGISTER_OP("_MklQuantizedConv2DAndRequantize") .Attr("is_filter_const: bool = true") .Attr(GetPaddingAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") + .Attr("padding_list: list(int) = []") .SetShapeFn([](InferenceContext* c) { TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); ShapeHandle unused; @@ -296,6 +298,7 @@ REGISTER_OP("_MklQuantizedConv2DWithBias") .Attr("is_filter_const: bool = true") .Attr(GetPaddingAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") + .Attr("padding_list: list(int) = []") .SetShapeFn([](InferenceContext* c) { TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); ShapeHandle unused; @@ -345,6 +348,7 @@ REGISTER_OP("_MklQuantizedConv2DWithBiasAndRequantize") .Attr("is_filter_const: bool = true") .Attr(GetPaddingAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") + .Attr("padding_list: list(int) = []") .SetShapeFn([](InferenceContext* c) { TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); ShapeHandle unused; @@ -387,6 +391,7 @@ REGISTER_OP("_MklQuantizedConv2DAndRelu") .Attr("is_filter_const: bool = true") .Attr(GetPaddingAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") + .Attr("padding_list: list(int) = []") .SetShapeFn([](InferenceContext* c) { TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); ShapeHandle unused; @@ -432,6 +437,7 @@ REGISTER_OP("_MklQuantizedConv2DAndReluAndRequantize") .Attr("is_filter_const: bool = true") .Attr(GetPaddingAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") + .Attr("padding_list: list(int) = []") .SetShapeFn([](InferenceContext* c) { TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); ShapeHandle unused; @@ -477,6 +483,7 @@ REGISTER_OP("_MklQuantizedConv2DWithBiasAndRelu") .Attr("is_filter_const: bool = true") .Attr(GetPaddingAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") + .Attr("padding_list: list(int) = []") .SetShapeFn([](InferenceContext* c) { TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); ShapeHandle unused; @@ -526,6 +533,7 @@ REGISTER_OP("_MklQuantizedConv2DWithBiasAndReluAndRequantize") .Attr("is_filter_const: bool = true") .Attr(GetPaddingAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") + .Attr("padding_list: list(int) = []") .SetShapeFn([](InferenceContext* c) { TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); ShapeHandle unused; @@ -574,6 +582,7 @@ REGISTER_OP("_MklQuantizedConv2DWithBiasSumAndRelu") .Attr("is_filter_const: bool = true") .Attr(GetPaddingAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") + .Attr("padding_list: list(int) = []") .SetShapeFn([](InferenceContext* c) { TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); ShapeHandle unused; @@ -630,6 +639,7 @@ REGISTER_OP("_MklQuantizedConv2DWithBiasSumAndReluAndRequantize") .Attr("is_filter_const: bool = true") .Attr(GetPaddingAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") + .Attr("padding_list: list(int) = []") .SetShapeFn([](InferenceContext* c) { TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); ShapeHandle unused; @@ -688,6 +698,7 @@ REGISTER_OP("_MklQuantizedConv2DWithBiasSignedSumAndReluAndRequantize") .Attr("is_filter_const: bool = true") .Attr(GetPaddingAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") + .Attr("padding_list: list(int) = []") .SetShapeFn([](InferenceContext* c) { TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); ShapeHandle unused; diff --git a/tensorflow/core/ops/nn_ops.cc b/tensorflow/core/ops/nn_ops.cc index ef7a65c011..7546e96e0f 100644 --- a/tensorflow/core/ops/nn_ops.cc +++ b/tensorflow/core/ops/nn_ops.cc @@ -1213,9 +1213,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. @@ -1269,9 +1269,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 @@ -2560,6 +2560,7 @@ REGISTER_OP("QuantizedConv2DAndRequantize") .Attr("strides: list(int)") .Attr(GetPaddingAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") + .Attr("padding_list: list(int) = []") .SetShapeFn([](InferenceContext* c) { TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); ShapeHandle unused; @@ -2592,6 +2593,7 @@ REGISTER_OP("QuantizedConv2DWithBias") .Attr("strides: list(int)") .Attr(GetPaddingAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") + .Attr("padding_list: list(int) = []") .SetShapeFn([](InferenceContext* c) { TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); ShapeHandle unused; @@ -2625,6 +2627,7 @@ REGISTER_OP("QuantizedConv2DWithBiasAndRequantize") .Attr("strides: list(int)") .Attr(GetPaddingAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") + .Attr("padding_list: list(int) = []") .SetShapeFn([](InferenceContext* c) { TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); ShapeHandle unused; @@ -2657,6 +2660,7 @@ REGISTER_OP("QuantizedConv2DAndRelu") .Attr("strides: list(int)") .Attr(GetPaddingAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") + .Attr("padding_list: list(int) = []") .SetShapeFn([](InferenceContext* c) { TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); ShapeHandle unused; @@ -2687,6 +2691,7 @@ REGISTER_OP("QuantizedConv2DAndReluAndRequantize") .Attr("strides: list(int)") .Attr(GetPaddingAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") + .Attr("padding_list: list(int) = []") .SetShapeFn([](InferenceContext* c) { TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); ShapeHandle unused; @@ -2719,6 +2724,7 @@ REGISTER_OP("QuantizedConv2DWithBiasAndRelu") .Attr("strides: list(int)") .Attr(GetPaddingAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") + .Attr("padding_list: list(int) = []") .SetShapeFn([](InferenceContext* c) { TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); ShapeHandle unused; @@ -2753,6 +2759,7 @@ REGISTER_OP("QuantizedConv2DWithBiasAndReluAndRequantize") .Attr("strides: list(int)") .Attr(GetPaddingAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") + .Attr("padding_list: list(int) = []") .SetShapeFn([](InferenceContext* c) { TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); ShapeHandle unused; @@ -2787,6 +2794,7 @@ REGISTER_OP("QuantizedConv2DWithBiasSumAndRelu") .Attr("strides: list(int)") .Attr(GetPaddingAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") + .Attr("padding_list: list(int) = []") .SetShapeFn([](InferenceContext* c) { TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); ShapeHandle unused; @@ -2824,6 +2832,7 @@ REGISTER_OP("QuantizedConv2DWithBiasSumAndReluAndRequantize") .Attr("strides: list(int)") .Attr(GetPaddingAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") + .Attr("padding_list: list(int) = []") .SetShapeFn([](InferenceContext* c) { TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); ShapeHandle unused; @@ -2863,6 +2872,7 @@ REGISTER_OP("QuantizedConv2DWithBiasSignedSumAndReluAndRequantize") .Attr("strides: list(int)") .Attr(GetPaddingAttrString()) .Attr("dilations: list(int) = [1, 1, 1, 1]") + .Attr("padding_list: list(int) = []") .SetShapeFn([](InferenceContext* c) { TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); ShapeHandle unused; @@ -2878,5 +2888,4 @@ REGISTER_OP("QuantizedConv2DWithBiasSignedSumAndReluAndRequantize") return Status::OK(); }); - } // namespace tensorflow -- GitLab From 7b363770b8e2468d36e7e4c514dbd5c4a372ea39 Mon Sep 17 00:00:00 2001 From: Amit Srivastava Date: Wed, 13 Feb 2019 18:28:53 +0530 Subject: [PATCH 148/766] Fixed the warning in slide.cc file Fixed the warning from the slide.cc file --- tensorflow/lite/kernels/slice.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tensorflow/lite/kernels/slice.cc b/tensorflow/lite/kernels/slice.cc index 5fca7a3ea7..0ced51cc02 100644 --- a/tensorflow/lite/kernels/slice.cc +++ b/tensorflow/lite/kernels/slice.cc @@ -117,7 +117,8 @@ TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { begin->type == kTfLiteInt32 || begin->type == kTfLiteInt64); TF_LITE_ENSURE(context, size->type == kTfLiteInt32 || size->type == kTfLiteInt64); - TF_LITE_ENSURE(context, NumDimensions(begin) == NumDimensions(size) == 1); + TF_LITE_ENSURE_EQ(context, NumDimensions(begin), 1); + TF_LITE_ENSURE_EQ(context, NumDimensions(size), 1); TF_LITE_ENSURE_MSG(context, NumDimensions(input) <= kMaxDim, "Slice op only supports 1D-4D input arrays."); -- GitLab From 7ebb033bf0e6114b9e50dbf3bf4eed934ddd58a2 Mon Sep 17 00:00:00 2001 From: Lucas Hendren Date: Fri, 15 Feb 2019 01:32:17 -0500 Subject: [PATCH 149/766] Updated test cases for einsum tests --- tensorflow/python/ops/special_math_ops.py | 1 + tensorflow/python/ops/special_math_ops_test.py | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/ops/special_math_ops.py b/tensorflow/python/ops/special_math_ops.py index 5a0f0782dd..d6bae7da29 100644 --- a/tensorflow/python/ops/special_math_ops.py +++ b/tensorflow/python/ops/special_math_ops.py @@ -237,6 +237,7 @@ def einsum(equation, *inputs, **kwargs): output_axis_labels = ''.join( sorted(ax for ax in indices if counts[ax] == 1)) + print('TEST: ',axis_labels,input_axis_labels,inputs,equation) for a in axis_labels: for input_labels in input_axis_labels: if (len(input_axis_labels) == 1 and input_labels.count(a) == 2 diff --git a/tensorflow/python/ops/special_math_ops_test.py b/tensorflow/python/ops/special_math_ops_test.py index 34f891edea..5e46b2ea50 100644 --- a/tensorflow/python/ops/special_math_ops_test.py +++ b/tensorflow/python/ops/special_math_ops_test.py @@ -250,8 +250,7 @@ class EinsumTest(test.TestCase): 'abc, bac', 'iJ, Ki -> JK', 'iJk, Jklm -> Jk', - 'ii', - 'ijji' + 'ii' ] long_cases = [ -- GitLab From dfabf756e53024bca89e07624738bd61deb89994 Mon Sep 17 00:00:00 2001 From: Lucas Hendren Date: Fri, 15 Feb 2019 01:33:29 -0500 Subject: [PATCH 150/766] Revert "Fixed formatting error for einsum" This reverts commit fa8b0f37f39449fea13259b6e954753c5b613128. --- tensorflow/python/ops/special_math_ops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/ops/special_math_ops.py b/tensorflow/python/ops/special_math_ops.py index d6bae7da29..f2add28d61 100644 --- a/tensorflow/python/ops/special_math_ops.py +++ b/tensorflow/python/ops/special_math_ops.py @@ -241,7 +241,7 @@ def einsum(equation, *inputs, **kwargs): for a in axis_labels: for input_labels in input_axis_labels: if (len(input_axis_labels) == 1 and input_labels.count(a) == 2 - and input_labels == input_labels[::-1] and '->' not in equation): + and input_labels == input_labels[::-1]): return math_ops.trace(inputs[0]) if input_labels.count(a) > 1: raise ValueError( -- GitLab From 9b051a4cf92bcc7d658fecfc848eaf8d44aaf249 Mon Sep 17 00:00:00 2001 From: Lucas Hendren Date: Fri, 15 Feb 2019 01:37:47 -0500 Subject: [PATCH 151/766] Updated test cases --- tensorflow/python/ops/special_math_ops.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tensorflow/python/ops/special_math_ops.py b/tensorflow/python/ops/special_math_ops.py index f2add28d61..5a0f0782dd 100644 --- a/tensorflow/python/ops/special_math_ops.py +++ b/tensorflow/python/ops/special_math_ops.py @@ -237,11 +237,10 @@ def einsum(equation, *inputs, **kwargs): output_axis_labels = ''.join( sorted(ax for ax in indices if counts[ax] == 1)) - print('TEST: ',axis_labels,input_axis_labels,inputs,equation) for a in axis_labels: for input_labels in input_axis_labels: if (len(input_axis_labels) == 1 and input_labels.count(a) == 2 - and input_labels == input_labels[::-1]): + and input_labels == input_labels[::-1] and '->' not in equation): return math_ops.trace(inputs[0]) if input_labels.count(a) > 1: raise ValueError( -- GitLab From aa26dce3be923a3833f52384ec194c2cfac76d43 Mon Sep 17 00:00:00 2001 From: Deven Desai Date: Thu, 31 Jan 2019 15:16:57 +0000 Subject: [PATCH 152/766] proper fix the earlier compile error for --config=rocm, + some minor changes --- tensorflow/stream_executor/rocm/BUILD | 2 +- .../stream_executor/rocm/rocm_diagnostics.cc | 24 +++++++---- .../stream_executor/rocm/rocm_diagnostics.h | 41 +++++++++++++++++++ .../stream_executor/rocm/rocm_gpu_executor.cc | 19 ++++----- .../stream_executor/rocm/rocm_platform.cc | 2 +- .../stream_executor/rocm/rocm_platform_id.cc | 2 +- .../stream_executor/rocm/rocm_platform_id.h | 6 +-- tensorflow/stream_executor/rocm/rocm_rng.cc | 4 +- 8 files changed, 72 insertions(+), 28 deletions(-) create mode 100644 tensorflow/stream_executor/rocm/rocm_diagnostics.h diff --git a/tensorflow/stream_executor/rocm/BUILD b/tensorflow/stream_executor/rocm/BUILD index dd08b36308..38cd19b3cb 100644 --- a/tensorflow/stream_executor/rocm/BUILD +++ b/tensorflow/stream_executor/rocm/BUILD @@ -33,7 +33,7 @@ filegroup( cc_library( name = "rocm_diagnostics", srcs = if_rocm_is_configured(["rocm_diagnostics.cc"]), - hdrs = [], + hdrs = if_rocm_is_configured(["rocm_diagnostics.h"]), deps = if_rocm_is_configured([ "@com_google_absl//absl/container:inlined_vector", "@com_google_absl//absl/strings", diff --git a/tensorflow/stream_executor/rocm/rocm_diagnostics.cc b/tensorflow/stream_executor/rocm/rocm_diagnostics.cc index c6da7f9e3d..01492a8ef9 100644 --- a/tensorflow/stream_executor/rocm/rocm_diagnostics.cc +++ b/tensorflow/stream_executor/rocm/rocm_diagnostics.cc @@ -30,7 +30,7 @@ limitations under the License. #include "absl/container/inlined_vector.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" -#include "tensorflow/stream_executor/gpu/gpu_diagnostics.h" +#include "tensorflow/stream_executor/rocm/rocm_diagnostics.h" #include "tensorflow/stream_executor/lib/error.h" #include "tensorflow/stream_executor/lib/numbers.h" #include "tensorflow/stream_executor/lib/process_state.h" @@ -40,7 +40,7 @@ limitations under the License. #include "tensorflow/stream_executor/platform/logging.h" namespace stream_executor { -namespace gpu { +namespace rocm { string DriverVersionToString(DriverVersion version) { return absl::StrFormat("%d.%d.%d", std::get<0>(version), std::get<1>(version), @@ -95,6 +95,12 @@ port::StatusOr StringToDriverVersion(const string& value) { return result; } +} // namespace rocm +} // namespace stream_executor + +namespace stream_executor { +namespace gpu { + // -- class Diagnostician string Diagnostician::GetDevNodePath(int dev_node_ordinal) { @@ -133,11 +139,11 @@ void Diagnostician::LogDiagnosticInformation() { } port::StatusOr dso_version = FindDsoVersion(); LOG(INFO) << "librocm reported version is: " - << DriverVersionStatusToString(dso_version); + << rocm::DriverVersionStatusToString(dso_version); port::StatusOr kernel_version = FindKernelDriverVersion(); LOG(INFO) << "kernel reported version is: " - << DriverVersionStatusToString(kernel_version); + << rocm::DriverVersionStatusToString(kernel_version); if (kernel_version.ok() && dso_version.ok()) { WarnOnDsoKernelMismatch(dso_version, kernel_version); @@ -175,7 +181,7 @@ port::StatusOr Diagnostician::FindDsoVersion() { // TODO(b/22689637): Eliminate the explicit namespace if possible. auto stripped_dso_version = port::StripSuffixString(dso_version, ".ld64"); auto result = static_cast*>(data); - *result = StringToDriverVersion(stripped_dso_version); + *result = rocm::StringToDriverVersion(stripped_dso_version); return 1; } return 0; @@ -205,7 +211,7 @@ port::StatusOr Diagnostician::FindKernelModuleVersion( // TODO(b/22689637): Eliminate the explicit namespace if possible. auto stripped_kernel_version = port::StripSuffixString(kernel_version, ".ld64"); - return StringToDriverVersion(stripped_kernel_version); + return rocm::StringToDriverVersion(stripped_kernel_version); } void Diagnostician::WarnOnDsoKernelMismatch( @@ -214,12 +220,12 @@ void Diagnostician::WarnOnDsoKernelMismatch( if (kernel_version.ok() && dso_version.ok() && dso_version.ValueOrDie() == kernel_version.ValueOrDie()) { LOG(INFO) << "kernel version seems to match DSO: " - << DriverVersionToString(kernel_version.ValueOrDie()); + << rocm::DriverVersionToString(kernel_version.ValueOrDie()); } else { LOG(ERROR) << "kernel version " - << DriverVersionStatusToString(kernel_version) + << rocm::DriverVersionStatusToString(kernel_version) << " does not match DSO version " - << DriverVersionStatusToString(dso_version) + << rocm::DriverVersionStatusToString(dso_version) << " -- cannot find working devices in this configuration"; } } diff --git a/tensorflow/stream_executor/rocm/rocm_diagnostics.h b/tensorflow/stream_executor/rocm/rocm_diagnostics.h new file mode 100644 index 0000000000..233c6bdade --- /dev/null +++ b/tensorflow/stream_executor/rocm/rocm_diagnostics.h @@ -0,0 +1,41 @@ +/* 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_STREAM_EXECUTOR_ROCM_ROCM_DIAGNOSTICS_H_ +#define TENSORFLOW_STREAM_EXECUTOR_ROCM_ROCM_DIAGNOSTICS_H_ + +#include "tensorflow/stream_executor/gpu/gpu_diagnostics.h" + +namespace stream_executor { +namespace rocm { + +// e.g. DriverVersion{346, 3, 4} +using DriverVersion = gpu::DriverVersion; + +// Converts a parsed driver version to string form. +string DriverVersionToString(DriverVersion version); + +// Converts a parsed driver version or status value to natural string form. +string DriverVersionStatusToString(port::StatusOr version); + +// Converts a string of a form like "331.79" to a DriverVersion{331, 79}. +port::StatusOr StringToDriverVersion(const string& value); + +using Diagnostician = gpu::Diagnostician; + +} // namespace rocm +} // namespace stream_executor + +#endif // TENSORFLOW_STREAM_EXECUTOR_ROCM_ROCM_DIAGNOSTICS_H_ diff --git a/tensorflow/stream_executor/rocm/rocm_gpu_executor.cc b/tensorflow/stream_executor/rocm/rocm_gpu_executor.cc index 0e38556ee7..684172cfb5 100644 --- a/tensorflow/stream_executor/rocm/rocm_gpu_executor.cc +++ b/tensorflow/stream_executor/rocm/rocm_gpu_executor.cc @@ -18,7 +18,6 @@ limitations under the License. #include "absl/base/casts.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" -#include "tensorflow/stream_executor/gpu/gpu_diagnostics.h" #include "tensorflow/stream_executor/gpu/gpu_driver.h" #include "tensorflow/stream_executor/gpu/gpu_event.h" #include "tensorflow/stream_executor/gpu/gpu_executor.h" @@ -41,6 +40,7 @@ limitations under the License. #include "tensorflow/stream_executor/platform/logging.h" #include "tensorflow/stream_executor/platform/port.h" #include "tensorflow/stream_executor/plugin_registry.h" +#include "tensorflow/stream_executor/rocm/rocm_diagnostics.h" #include "tensorflow/stream_executor/rocm/rocm_platform_id.h" #include "tensorflow/stream_executor/stream.h" #include "tensorflow/stream_executor/stream_executor_internal.h" @@ -655,7 +655,7 @@ port::Status GpuExecutor::BlockHostUntilDone(Stream* stream) { blas::BlasSupport* GpuExecutor::CreateBlas() { PluginRegistry* registry = PluginRegistry::Instance(); port::StatusOr status = - registry->GetFactory(kROCmPlatformId, + registry->GetFactory(rocm::kROCmPlatformId, plugin_config_.blas()); if (!status.ok()) { LOG(ERROR) << "Unable to retrieve BLAS factory: " @@ -669,7 +669,7 @@ blas::BlasSupport* GpuExecutor::CreateBlas() { dnn::DnnSupport* GpuExecutor::CreateDnn() { PluginRegistry* registry = PluginRegistry::Instance(); port::StatusOr status = - registry->GetFactory(kROCmPlatformId, + registry->GetFactory(rocm::kROCmPlatformId, plugin_config_.dnn()); if (!status.ok()) { LOG(ERROR) << "Unable to retrieve DNN factory: " @@ -683,7 +683,7 @@ dnn::DnnSupport* GpuExecutor::CreateDnn() { fft::FftSupport* GpuExecutor::CreateFft() { PluginRegistry* registry = PluginRegistry::Instance(); port::StatusOr status = - registry->GetFactory(kROCmPlatformId, + registry->GetFactory(rocm::kROCmPlatformId, plugin_config_.fft()); if (!status.ok()) { LOG(ERROR) << "Unable to retrieve FFT factory: " @@ -697,7 +697,7 @@ fft::FftSupport* GpuExecutor::CreateFft() { rng::RngSupport* GpuExecutor::CreateRng() { PluginRegistry* registry = PluginRegistry::Instance(); port::StatusOr status = - registry->GetFactory(kROCmPlatformId, + registry->GetFactory(rocm::kROCmPlatformId, plugin_config_.rng()); if (!status.ok()) { LOG(ERROR) << "Unable to retrieve RNG factory: " @@ -878,12 +878,9 @@ DeviceDescription* GpuExecutor::PopulateDeviceDescription() const { { int driver_version = 0; (void)GpuDriver::GetDriverVersion(&driver_version); - string augmented_driver_version = - absl::StrFormat("%d (%s)", driver_version, "__FIXME__"); - // FIXME: - // uncomment the line below once the "DriverVersionStatusToString" - // routine is moved from the "cuda" namespace to the "gpu" naemspace - // DriverVersionStatusToString(Diagnostician::FindDsoVersion()).c_str()); + string augmented_driver_version = absl::StrFormat( + "%d (%s)", driver_version, + rocm::DriverVersionStatusToString(Diagnostician::FindDsoVersion()).c_str()); builder.set_driver_version(augmented_driver_version); } diff --git a/tensorflow/stream_executor/rocm/rocm_platform.cc b/tensorflow/stream_executor/rocm/rocm_platform.cc index 113371dd55..ce091658da 100644 --- a/tensorflow/stream_executor/rocm/rocm_platform.cc +++ b/tensorflow/stream_executor/rocm/rocm_platform.cc @@ -94,7 +94,7 @@ port::StatusOr ROCmPlatform::FirstExecutorForBus( absl::StrFormat("Executor for bus %d not found.", bus_ordinal)}; } -Platform::Id ROCmPlatform::id() const { return kROCmPlatformId; } +Platform::Id ROCmPlatform::id() const { return rocm::kROCmPlatformId; } int ROCmPlatform::VisibleDeviceCount() const { // Throw away the result - it logs internally, and this [containing] function diff --git a/tensorflow/stream_executor/rocm/rocm_platform_id.cc b/tensorflow/stream_executor/rocm/rocm_platform_id.cc index daa42ab022..16f48bf12d 100644 --- a/tensorflow/stream_executor/rocm/rocm_platform_id.cc +++ b/tensorflow/stream_executor/rocm/rocm_platform_id.cc @@ -16,7 +16,7 @@ limitations under the License. #include "tensorflow/stream_executor/rocm/rocm_platform_id.h" namespace stream_executor { -namespace gpu { +namespace rocm { PLATFORM_DEFINE_ID(kROCmPlatformId); diff --git a/tensorflow/stream_executor/rocm/rocm_platform_id.h b/tensorflow/stream_executor/rocm/rocm_platform_id.h index 71c760b827..a17d4f97bb 100644 --- a/tensorflow/stream_executor/rocm/rocm_platform_id.h +++ b/tensorflow/stream_executor/rocm/rocm_platform_id.h @@ -19,16 +19,16 @@ limitations under the License. #include "tensorflow/stream_executor/platform.h" namespace stream_executor { -namespace gpu { +namespace rocm { // Opaque and unique identifier for the ROCm platform. // This is needed so that plugins can refer to/identify this platform without // instantiating a ROCmPlatform object. // This is broken out here to avoid a circular dependency between ROCmPlatform -// and GpuExecutor. +// and ROCmExecutor. extern const Platform::Id kROCmPlatformId; -} // namespace gpu +} // namespace rocm } // namespace stream_executor #endif // TENSORFLOW_STREAM_EXECUTOR_ROCM_ROCM_PLATFORM_ID_H_ diff --git a/tensorflow/stream_executor/rocm/rocm_rng.cc b/tensorflow/stream_executor/rocm/rocm_rng.cc index 2048c8ff64..65acd03c92 100644 --- a/tensorflow/stream_executor/rocm/rocm_rng.cc +++ b/tensorflow/stream_executor/rocm/rocm_rng.cc @@ -253,7 +253,7 @@ REGISTER_MODULE_INITIALIZER(register_hiprand, { se::port::Status status = se::PluginRegistry::Instance() ->RegisterFactory( - se::gpu::kROCmPlatformId, se::gpu::kGpuRandPlugin, "hipRAND", + se::rocm::kROCmPlatformId, se::gpu::kGpuRandPlugin, "hipRAND", [](se::internal::StreamExecutorInterface* parent) -> se::rng::RngSupport* { se::gpu::GpuExecutor* rocm_executor = @@ -280,5 +280,5 @@ REGISTER_MODULE_INITIALIZER(register_hiprand, { } se::PluginRegistry::Instance()->SetDefaultFactory( - se::gpu::kROCmPlatformId, se::PluginKind::kRng, se::gpu::kGpuRandPlugin); + se::rocm::kROCmPlatformId, se::PluginKind::kRng, se::gpu::kGpuRandPlugin); }); -- GitLab From 298383d00b5d385d0f58e5ca5bd860fc4508d37a Mon Sep 17 00:00:00 2001 From: Deven Desai Date: Thu, 31 Jan 2019 19:23:21 +0000 Subject: [PATCH 153/766] adding code for rocblas plugin --- tensorflow/stream_executor/rocm/BUILD | 64 +- tensorflow/stream_executor/rocm/rocm_blas.cc | 2324 ++++++++++++++++++ tensorflow/stream_executor/rocm/rocm_blas.h | 159 ++ 3 files changed, 2518 insertions(+), 29 deletions(-) create mode 100644 tensorflow/stream_executor/rocm/rocm_blas.cc create mode 100644 tensorflow/stream_executor/rocm/rocm_blas.h diff --git a/tensorflow/stream_executor/rocm/BUILD b/tensorflow/stream_executor/rocm/BUILD index 38cd19b3cb..737a442946 100644 --- a/tensorflow/stream_executor/rocm/BUILD +++ b/tensorflow/stream_executor/rocm/BUILD @@ -140,34 +140,40 @@ cc_library( deps = ["//tensorflow/stream_executor:platform"], ) -# FIXME: enable in future PRs -#cc_library( -# name = "rocblas_plugin", -# srcs = ["rocm_blas.cc"], -# hdrs = ["rocm_blas.h"], -# visibility = ["//visibility:public"], -# deps = [ -# ":rocm_gpu_executor", -# ":rocm_platform_id", -# "//third_party/eigen3", -# "//tensorflow/core:lib_internal", -# "//tensorflow/stream_executor", -# "//tensorflow/stream_executor:event", -# "//tensorflow/stream_executor:host_or_device_scalar", -# "//tensorflow/stream_executor:plugin_registry", -# "//tensorflow/stream_executor:scratch_allocator", -# "//tensorflow/stream_executor:timer", -# "//tenosrflow/stream_executor/gpu:gpu_activation_header", -# "//tenosrflow/stream_executor/gpu:gpu_stream_header", -# "//tenosrflow/stream_executor/gpu:gpu_timer_header", -# "//tensorflow/stream_executor/lib", -# "//tensorflow/stream_executor/platform", -# "//tensorflow/stream_executor/platform:dso_loader", -# "@com_google_absl//absl/strings", -# "@local_config_rocm//rocm:rocm_headers", -# ] + if_static(["@local_config_rocm//rocm:rocblas"]), -# alwayslink = True, -#) +cc_library( + name = "rocblas_plugin", + srcs = if_rocm_is_configured(["rocm_blas.cc"]), + hdrs = if_rocm_is_configured(["rocm_blas.h"]), + visibility = ["//visibility:public"], + deps = if_rocm_is_configured([ + ":rocm_gpu_executor", + ":rocm_platform_id", + "//third_party/eigen3", + "//tensorflow/core:lib_internal", + "//tensorflow/stream_executor", + "//tensorflow/stream_executor:event", + "//tensorflow/stream_executor:host_or_device_scalar", + "//tensorflow/stream_executor:plugin_registry", + "//tensorflow/stream_executor:scratch_allocator", + "//tensorflow/stream_executor:timer", + "//tensorflow/stream_executor/gpu:gpu_activation", + "//tensorflow/stream_executor/gpu:gpu_helpers_header", + "//tensorflow/stream_executor/gpu:gpu_stream_header", + "//tensorflow/stream_executor/gpu:gpu_timer_header", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + "//tensorflow/stream_executor/platform:dso_loader", + "@com_google_absl//absl/strings", + "@local_config_rocm//rocm:rocm_headers", + ] + if_static([ + "@local_config_rocm//rocm:rocblas" + # Delete the following line once we switch the rocblas library from + # being dynamically linked (current behaviour) to being dynamically + # loaded (future behaviour) + ], ["@local_config_rocm//rocm:rocblas" + ])), + alwayslink = True, +) # FIXME: enable in future PRs #cc_library( @@ -258,7 +264,7 @@ cc_library( # FIXME: enable in future PRs #":miopen_plugin", #":rocfft_plugin", - #":rocblas_plugin", + ":rocblas_plugin", #":rocrand_plugin", ":rocm_driver", ":rocm_platform", diff --git a/tensorflow/stream_executor/rocm/rocm_blas.cc b/tensorflow/stream_executor/rocm/rocm_blas.cc new file mode 100644 index 0000000000..b2e225433e --- /dev/null +++ b/tensorflow/stream_executor/rocm/rocm_blas.cc @@ -0,0 +1,2324 @@ +/* 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 "rocm/include/rocblas.h" + +#include "tensorflow/stream_executor/rocm/rocm_blas.h" + +#define EIGEN_USE_GPU +#include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor" + +#include +#include + +#include "absl/strings/str_cat.h" +#include "tensorflow/stream_executor/gpu/gpu_activation.h" +#include "tensorflow/stream_executor/gpu/gpu_executor.h" +#include "tensorflow/stream_executor/gpu/gpu_helpers.h" +#include "tensorflow/stream_executor/gpu/gpu_stream.h" +#include "tensorflow/stream_executor/gpu/gpu_timer.h" +#include "tensorflow/stream_executor/rocm/rocm_platform_id.h" +#include "tensorflow/stream_executor/device_memory.h" +#include "tensorflow/stream_executor/lib/env.h" +#include "tensorflow/stream_executor/lib/initialize.h" +#include "tensorflow/stream_executor/lib/status.h" +#include "tensorflow/stream_executor/lib/status_macros.h" +#include "tensorflow/stream_executor/lib/stringprintf.h" +#include "tensorflow/stream_executor/platform/logging.h" +#include "tensorflow/stream_executor/platform/port.h" +#include "tensorflow/stream_executor/plugin_registry.h" +#include "tensorflow/stream_executor/scratch_allocator.h" +#include "tensorflow/stream_executor/stream_executor.h" + +namespace stream_executor { +namespace gpu { + +PLUGIN_REGISTRY_DEFINE_PLUGIN_ID(kRocBlasPlugin); + +namespace wrap { + +#define STREAM_EXECUTOR_ROCBLAS_WRAP(__name) \ + struct WrapperShim__##__name { \ + static const char* kName; \ + template \ + rocblas_status operator()(GpuExecutor* parent, Args... args) { \ + gpu::ScopedActivateExecutorContext sac{parent}; \ + return ::__name(args...); \ + } \ + } __name; \ + const char* WrapperShim__##__name::kName = #__name; + +#define STREAM_EXECUTOR_ROCBLAS_V2_WRAP(__name) \ + STREAM_EXECUTOR_ROCBLAS_WRAP(__name) + +#define ROCBLAS_BLAS_ROUTINE_EACH(__macro) \ + __macro(rocblas_snrm2) __macro(rocblas_dnrm2) /* __macro(rocblas_scnrm2) \ + __macro(rocblas_dznrm2) */ \ + __macro(rocblas_sdot) \ + __macro(rocblas_ddot) /* __macro(rocblas_cdotu) \ + __macro(rocblas_cdotc) \ + __macro(rocblas_zdotu) \ + __macro(rocblas_zdotc) */ \ + __macro(rocblas_sscal) \ + __macro(rocblas_dscal) /* __macro(rocblas_cscal) \ + __macro(rocblas_csscal) \ + __macro(rocblas_zscal) \ + __macro(rocblas_zdscal) */ \ + __macro(rocblas_saxpy) \ + __macro(rocblas_daxpy) /* __macro(rocblas_caxpy) \ + __macro(rocblas_zaxpy) */ \ + __macro(rocblas_scopy) \ + __macro(rocblas_dcopy) /* __macro(rocblas_ccopy) \ + __macro(rocblas_zcopy) */ \ + __macro(rocblas_sswap) \ + __macro(rocblas_dswap) /* __macro(rocblas_cswap) \ + __macro(rocblas_zswap) */ \ + __macro(rocblas_isamax) \ + __macro(rocblas_idamax) /* __macro(rocblas_icamax) \ + __macro(rocblas_izamax) */ \ + __macro(rocblas_isamin) \ + __macro(rocblas_idamin) /* __macro(rocblas_icamin) \ + __macro(rocblas_izamin) */ \ + __macro(rocblas_sasum) \ + __macro(rocblas_dasum) /* __macro(rocblas_scasum) \ + __macro(rocblas_dzasum) \ + __macro(rocblas_srot) \ + __macro(rocblas_drot) \ + __macro(rocblas_crot) \ + __macro(rocblas_csrot) \ + __macro(rocblas_zrot) \ + __macro(rocblas_zdrot) \ + __macro(rocblas_srotg) \ + __macro(rocblas_drotg) \ + __macro(rocblas_Crotg) \ + __macro(rocblas_crotg) \ + __macro(rocblas_zrotm) \ + __macro(rocblas_drotm) \ + __macro(rocblas_srotmg) \ + __macro(rocblas_drotmg) */ \ + __macro(rocblas_sgemv) \ + __macro(rocblas_dgemv) /* __macro(rocblas_cgemv) \ + __macro(rocblas_zgemv) \ + __macro(rocblas_sgbmv) \ + __macro(rocblas_dgbmv) \ + __macro(rocblas_cgbmv) \ + __macro(rocblas_zgbmv) \ + __macro(rocblas_strmv) \ + __macro(rocblas_dtrmv) \ + __macro(rocblas_ctrmv) \ + __macro(rocblas_ztrmv) \ + __macro(rocblas_stbmv) \ + __macro(rocblas_dtbmv) \ + __macro(rocblas_ctbmv) \ + __macro(rocblas_ztbmv) \ + __macro(rocblas_stpmv) \ + __macro(rocblas_dtpmv) \ + __macro(rocblas_ctpmv) \ + __macro(rocblas_ztpmv) \ + __macro(rocblas_strsv) \ + __macro(rocblas_dtrsv) \ + __macro(rocblas_ctrsv) \ + __macro(rocblas_ztrsv) \ + __macro(rocblas_stpsv) \ + __macro(rocblas_dtpsv) \ + __macro(rocblas_ctpsv) \ + __macro(rocblas_ztpsv) \ + __macro(rocblas_stbsv) \ + __macro(rocblas_dtbsv) \ + __macro(rocblas_ctbsv) \ + __macro(rocblas_ztbsv) \ + __macro(rocblas_ssymv) \ + __macro(rocblas_dsymv) \ + __macro(rocblas_csymv) \ + __macro(rocblas_zsymv) \ + __macro(rocblas_chemv) \ + __macro(rocblas_zhemv) \ + __macro(rocblas_ssbmv) \ + __macro(rocblas_dsbmv) \ + __macro(rocblas_chbmv) \ + __macro(rocblas_zhbmv) \ + __macro(rocblas_sspmv) \ + __macro(rocblas_dspmv) \ + __macro(rocblas_chpmv) \ + __macro(rocblas_zhpmv) */ \ + __macro(rocblas_sger) \ + __macro(rocblas_dger) /* __macro(rocblas_cgeru) \ + __macro(rocblas_cgerc) \ + __macro(rocblas_zgeru) \ + __macro(rocblas_zgerc) */ \ + __macro(rocblas_ssyr) \ + __macro(rocblas_dsyr) /* __macro(rocblas_csyr) \ + __macro(rocblas_zsyr) \ + __macro(rocblas_cher) \ + __macro(rocblas_zher) \ + __macro(rocblas_sspr) \ + __macro(rocblas_dspr) \ + __macro(rocblas_chpr) \ + __macro(rocblas_zhpr) \ + __macro(rocblas_ssyr2) \ + __macro(rocblas_dsyr2) \ + __macro(rocblas_csyr2) \ + __macro(rocblas_zsyr2) \ + __macro(rocblas_cher2) \ + __macro(rocblas_zher2) \ + __macro(rocblas_sspr2) \ + __macro(rocblas_dspr2) \ + __macro(rocblas_chpr2) \ + __macro(rocblas_zhpr2) */ \ + __macro(rocblas_sgemm) __macro(rocblas_dgemm) \ + __macro(rocblas_hgemm) /* __macro(rocblas_cgemm) \ + __macro(rocblas_zgemm) \ + __macro(rocblas_ssyrk) \ + __macro(rocblas_dsyrk) \ + __macro(rocblas_csyrk) \ + __macro(rocblas_zsyrk) \ + __macro(rocblas_cherk) \ + __macro(rocblas_zherk) \ + __macro(rocblas_ssyr2k) \ + __macro(rocblas_dsyr2k) \ + __macro(rocblas_csyr2k) \ + __macro(rocblas_zsyr2k) \ + __macro(rocblas_cher2k) \ + __macro(rocblas_zher2k) \ + __macro(rocblas_ssyrkx) \ + __macro(rocblas_dsyrkx) \ + __macro(rocblas_csyrkx) \ + __macro(rocblas_zsyrkx) \ + __macro(rocblas_cherkx) \ + __macro(rocblas_zherkx) \ + __macro(rocblas_ssymm) \ + __macro(rocblas_dsymm) \ + __macro(rocblas_csymm) \ + __macro(rocblas_zsymm) \ + __macro(rocblas_chemm) \ + __macro(rocblas_zhemm) */ \ + __macro(rocblas_strsm) \ + __macro(rocblas_dtrsm) /* __macro(rocblas_ctrsm) \ + __macro(rocblas_ztrsm) \ + __macro(rocblas_strmm) \ + __macro(rocblas_dtrmm) \ + __macro(rocblas_ctrmm) \ + __macro(rocblas_ztrmm) */ \ + __macro(rocblas_sgeam) \ + __macro(rocblas_dgeam) /* __macro(rocblas_cgeam) \ + __macro(rocblas_zgeam) \ + __macro(rocblas_sdgmm) \ + __macro(rocblas_ddgmm) \ + __macro(rocblas_cdgmm) \ + __macro(rocblas_zdgmm) */ + +STREAM_EXECUTOR_ROCBLAS_V2_WRAP(rocblas_create_handle) +STREAM_EXECUTOR_ROCBLAS_V2_WRAP(rocblas_destroy_handle) +STREAM_EXECUTOR_ROCBLAS_V2_WRAP(rocblas_set_stream) +// STREAM_EXECUTOR_ROCBLAS_V2_WRAP(rocblas_set_pointer_mode) +// STREAM_EXECUTOR_ROCBLAS_V2_WRAP(rocblas_get_pointer_mode) +// STREAM_EXECUTOR_ROCBLAS_WRAP(rocblas_sgemm_batched) +STREAM_EXECUTOR_ROCBLAS_WRAP(rocblas_hgemm_strided_batched) +STREAM_EXECUTOR_ROCBLAS_WRAP(rocblas_sgemm_strided_batched) +// STREAM_EXECUTOR_ROCBLAS_WRAP(rocblas_dgemm_batched) +STREAM_EXECUTOR_ROCBLAS_WRAP(rocblas_dgemm_strided_batched) +// STREAM_EXECUTOR_ROCBLAS_WRAP(rocblas_cgemm_batched) +// STREAM_EXECUTOR_ROCBLAS_WRAP(rocblas_zgemm_batched) +ROCBLAS_BLAS_ROUTINE_EACH(STREAM_EXECUTOR_ROCBLAS_V2_WRAP) + +} // namespace wrap + +static string ToString(rocblas_status status) { + switch (status) { + case rocblas_status_success: + return "rocblas_status_success"; + case rocblas_status_invalid_handle: + return "rocblas_status_invalid_handle"; + case rocblas_status_not_implemented: + return "rocblas_status_not_implemented"; + case rocblas_status_invalid_pointer: + return "rocblas_status_invalid_pointer"; + case rocblas_status_invalid_size: + return "rocblas_status_invalid_size"; + case rocblas_status_memory_error: + return "rocblas_status_memory_error"; + case rocblas_status_internal_error: + return "rocblas_status_internal_error"; + default: + return absl::StrCat(""); + } +} + +bool ROCMBlas::Init() { + rocblas_status ret = wrap::rocblas_create_handle(parent_, &blas_); + if (ret != rocblas_status_success) { + LOG(ERROR) << "failed to create rocBLAS handle: " << ToString(ret); + return false; + } + + return true; +} + +ROCMBlas::ROCMBlas(gpu::GpuExecutor* parent) + : parent_(CHECK_NOTNULL(parent)), blas_(nullptr) {} + +ROCMBlas::~ROCMBlas() { + if (blas_ != nullptr) { + wrap::rocblas_destroy_handle(parent_, blas_); + } +} + +bool ROCMBlas::SetStream(Stream *stream) { + CHECK(stream != nullptr); + CHECK(AsGpuStreamValue(stream) != nullptr); + CHECK(blas_ != nullptr); + rocblas_status ret = + wrap::rocblas_set_stream(parent_, blas_, AsGpuStreamValue(stream)); + if (ret != rocblas_status_success) { + LOG(ERROR) << "failed to set stream for rocBLAS calls: " << ToString(ret); + return false; + } + + return true; +} + +namespace { + +// Helper functions transforming blas arguments into rocBLAS arguments. + +rocblas_operation ROCMBlasTranspose(blas::Transpose trans) { + switch (trans) { + case blas::Transpose::kNoTranspose: + return rocblas_operation_none; + case blas::Transpose::kTranspose: + return rocblas_operation_transpose; + case blas::Transpose::kConjugateTranspose: + return rocblas_operation_conjugate_transpose; + default: + LOG(FATAL) << "Invalid value of blas::Transpose."; + } +} + +rocblas_fill ROCMBlasUpperLower(blas::UpperLower uplo) { + switch (uplo) { + case blas::UpperLower::kUpper: + return rocblas_fill_upper; + case blas::UpperLower::kLower: + return rocblas_fill_lower; + default: + LOG(FATAL) << "Invalid value of blas::UpperLower."; + } +} + +rocblas_diagonal ROCMBlasDiagonal(blas::Diagonal diag) { + switch (diag) { + case blas::Diagonal::kUnit: + return rocblas_diagonal_unit; + case blas::Diagonal::kNonUnit: + return rocblas_diagonal_non_unit; + default: + LOG(FATAL) << "Invalid value of blas::Diagonal."; + } +} + +rocblas_side ROCMBlasSide(blas::Side side) { + switch (side) { + case blas::Side::kLeft: + return rocblas_side_left; + case blas::Side::kRight: + return rocblas_side_right; + default: + LOG(FATAL) << "Invalid value of blas::Side."; + } +} + +} // namespace + +template +bool ROCMBlas::DoBlasInternalImpl(FuncT rocblas_func, Stream *stream, + bool pointer_mode_host, bool err_on_failure, + Args... args) { + mutex_lock lock{mu_}; + + CHECK(blas_ != nullptr); + if (!SetStream(stream)) { + return false; + } + + rocblas_status ret = rocblas_func(parent_, blas_, args...); + if (err_on_failure && ret != rocblas_status_success) { + LOG(ERROR) << "failed to run ROCBLAS routine " << rocblas_func.kName << ": " + << ToString(ret); + } + return ret == rocblas_status_success; +} + +bool ROCMBlas::DoBlasAsum(Stream *stream, uint64 elem_count, + const DeviceMemory &x, int incx, + DeviceMemory *result) { + return DoBlasInternal(wrap::rocblas_sasum, stream, + false /* = pointer_mode_host */, elem_count, + GpuMemory(x), incx, GpuMemoryMutable(result)); +} + +bool ROCMBlas::DoBlasAsum(Stream *stream, uint64 elem_count, + const DeviceMemory &x, int incx, + DeviceMemory *result) { + return DoBlasInternal(wrap::rocblas_dasum, stream, + false /* = pointer_mode_host */, elem_count, + GpuMemory(x), incx, GpuMemoryMutable(result)); +} + +bool ROCMBlas::DoBlasAsum(Stream *stream, uint64 elem_count, + const DeviceMemory> &x, int incx, + DeviceMemory *result) { + LOG(ERROR) << "rocBLAS does not currently support the ASUM operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasAsum(Stream *stream, uint64 elem_count, + const DeviceMemory> &x, int incx, + DeviceMemory *result) { + LOG(ERROR) << "rocBLAS does not currently support the ASUM operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasAxpy(Stream *stream, uint64 elem_count, float alpha, + const DeviceMemory &x, int incx, + DeviceMemory *y, int incy) { + return DoBlasInternal(wrap::rocblas_saxpy, stream, + true /* = pointer_mode_host */, elem_count, &alpha, + GpuMemory(x), incx, GpuMemoryMutable(y), incy); +} + +bool ROCMBlas::DoBlasAxpy(Stream *stream, uint64 elem_count, double alpha, + const DeviceMemory &x, int incx, + DeviceMemory *y, int incy) { + return DoBlasInternal(wrap::rocblas_daxpy, stream, + true /* = pointer_mode_host */, elem_count, &alpha, + GpuMemory(x), incx, GpuMemoryMutable(y), incy); +} + +bool ROCMBlas::DoBlasAxpy(Stream *stream, uint64 elem_count, + std::complex alpha, + const DeviceMemory> &x, int incx, + DeviceMemory> *y, int incy) { + LOG(ERROR) << "rocBLAS does not currently support the AXPY operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasAxpy(Stream *stream, uint64 elem_count, + std::complex alpha, + const DeviceMemory> &x, int incx, + DeviceMemory> *y, int incy) { + LOG(ERROR) << "rocBLAS does not currently support the AXPY operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasCopy(Stream *stream, uint64 elem_count, + const DeviceMemory &x, int incx, + DeviceMemory *y, int incy) { + return DoBlasInternal(wrap::rocblas_scopy, stream, + true /* = pointer_mode_host */, elem_count, + GpuMemory(x), incx, GpuMemoryMutable(y), incy); +} + +bool ROCMBlas::DoBlasCopy(Stream *stream, uint64 elem_count, + const DeviceMemory &x, int incx, + DeviceMemory *y, int incy) { + return DoBlasInternal(wrap::rocblas_dcopy, stream, + true /* = pointer_mode_host */, elem_count, + GpuMemory(x), incx, GpuMemoryMutable(y), incy); +} + +bool ROCMBlas::DoBlasCopy(Stream *stream, uint64 elem_count, + const DeviceMemory> &x, int incx, + DeviceMemory> *y, int incy) { + LOG(ERROR) << "rocBLAS does not currently support the COPY operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasCopy(Stream *stream, uint64 elem_count, + const DeviceMemory> &x, int incx, + DeviceMemory> *y, int incy) { + LOG(ERROR) << "rocBLAS does not currently support the COPY operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasDot(Stream *stream, uint64 elem_count, + const DeviceMemory &x, int incx, + const DeviceMemory &y, int incy, + DeviceMemory *result) { + return DoBlasInternal( + wrap::rocblas_sdot, stream, false /* = pointer_mode_host */, elem_count, + GpuMemory(x), incx, GpuMemory(y), incy, GpuMemoryMutable(result)); +} + +bool ROCMBlas::DoBlasDot(Stream *stream, uint64 elem_count, + const DeviceMemory &x, int incx, + const DeviceMemory &y, int incy, + DeviceMemory *result) { + return DoBlasInternal( + wrap::rocblas_ddot, stream, false /* = pointer_mode_host */, elem_count, + GpuMemory(x), incx, GpuMemory(y), incy, GpuMemoryMutable(result)); +} + +bool ROCMBlas::DoBlasDotc(Stream *stream, uint64 elem_count, + const DeviceMemory> &x, int incx, + const DeviceMemory> &y, int incy, + DeviceMemory> *result) { + LOG(ERROR) << "rocBLAS does not currently support the DOT operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasDotc(Stream *stream, uint64 elem_count, + const DeviceMemory> &x, int incx, + const DeviceMemory> &y, int incy, + DeviceMemory> *result) { + LOG(ERROR) << "rocBLAS does not currently support the DOT operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasDotu(Stream *stream, uint64 elem_count, + const DeviceMemory> &x, int incx, + const DeviceMemory> &y, int incy, + DeviceMemory> *result) { + LOG(ERROR) << "rocBLAS does not currently support the DOT operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasDotu(Stream *stream, uint64 elem_count, + const DeviceMemory> &x, int incx, + const DeviceMemory> &y, int incy, + DeviceMemory> *result) { + LOG(ERROR) << "rocBLAS does not currently support the DOT operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasNrm2(Stream *stream, uint64 elem_count, + const DeviceMemory &x, int incx, + DeviceMemory *result) { + return DoBlasInternal(wrap::rocblas_snrm2, stream, + false /* = pointer_mode_host */, elem_count, + GpuMemory(x), incx, GpuMemoryMutable(result)); +} + +bool ROCMBlas::DoBlasNrm2(Stream *stream, uint64 elem_count, + const DeviceMemory &x, int incx, + DeviceMemory *result) { + return DoBlasInternal(wrap::rocblas_dnrm2, stream, + false /* = pointer_mode_host */, elem_count, + GpuMemory(x), incx, GpuMemoryMutable(result)); +} + +bool ROCMBlas::DoBlasNrm2(Stream *stream, uint64 elem_count, + const DeviceMemory> &x, int incx, + DeviceMemory *result) { + LOG(ERROR) << "rocBLAS does not currently support the NRM2 operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasNrm2(Stream *stream, uint64 elem_count, + const DeviceMemory> &x, int incx, + DeviceMemory *result) { + LOG(ERROR) << "rocBLAS does not currently support the NRM2 operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasRot(Stream *stream, uint64 elem_count, + DeviceMemory *x, int incx, + DeviceMemory *y, int incy, float c, float s) { + LOG(ERROR) << "rocBLAS does not currently support the ROT operation " + << "for the \"float\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasRot(Stream *stream, uint64 elem_count, + DeviceMemory *x, int incx, + DeviceMemory *y, int incy, double c, + double s) { + LOG(ERROR) << "rocBLAS does not currently support the ROT operation " + << "for the \"double\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasRot(Stream *stream, uint64 elem_count, + DeviceMemory> *x, int incx, + DeviceMemory> *y, int incy, + float c, float s) { + LOG(ERROR) << "rocBLAS does not currently support the ROT operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasRot(Stream *stream, uint64 elem_count, + DeviceMemory> *x, int incx, + DeviceMemory> *y, int incy, + double c, double s) { + LOG(ERROR) << "rocBLAS does not currently support the ROT operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasRotg(Stream *stream, DeviceMemory *a, + DeviceMemory *b, DeviceMemory *c, + DeviceMemory *s) { + LOG(ERROR) << "rocBLAS does not currently support the ROTG operation " + << "for the \"float\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasRotg(Stream *stream, DeviceMemory *a, + DeviceMemory *b, DeviceMemory *c, + DeviceMemory *s) { + LOG(ERROR) << "rocBLAS does not currently support the ROTG operation " + << "for the \"double\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasRotg(Stream *stream, DeviceMemory> *a, + DeviceMemory> *b, + DeviceMemory *c, + DeviceMemory> *s) { + LOG(ERROR) << "rocBLAS does not currently support the ROTG operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasRotg(Stream *stream, DeviceMemory> *a, + DeviceMemory> *b, + DeviceMemory *c, + DeviceMemory> *s) { + LOG(ERROR) << "rocBLAS does not currently support the ROTG operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasRotm(Stream *stream, uint64 elem_count, + DeviceMemory *x, int incx, + DeviceMemory *y, int incy, + const DeviceMemory ¶m) { + LOG(ERROR) << "rocBLAS does not currently support the ROTM operation " + << "for the \"float\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasRotm(Stream *stream, uint64 elem_count, + DeviceMemory *x, int incx, + DeviceMemory *y, int incy, + const DeviceMemory ¶m) { + LOG(ERROR) << "rocBLAS does not currently support the ROTM operation " + << "for the \"double\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasRotmg(Stream *stream, DeviceMemory *d1, + DeviceMemory *d2, DeviceMemory *x1, + const DeviceMemory &y1, + DeviceMemory *param) { + LOG(ERROR) << "rocBLAS does not currently support the ROTMG operation " + << "for the \"float\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasRotmg(Stream *stream, DeviceMemory *d1, + DeviceMemory *d2, DeviceMemory *x1, + const DeviceMemory &y1, + DeviceMemory *param) { + LOG(ERROR) << "rocBLAS does not currently support the ROTMG operation " + << "for the \"double\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasScal(Stream *stream, uint64 elem_count, float alpha, + DeviceMemory *x, int incx) { + return DoBlasInternal(wrap::rocblas_sscal, stream, + true /* = pointer_mode_host */, elem_count, &alpha, + GpuMemoryMutable(x), incx); +} + +bool ROCMBlas::DoBlasScal(Stream *stream, uint64 elem_count, double alpha, + DeviceMemory *x, int incx) { + return DoBlasInternal(wrap::rocblas_dscal, stream, + true /* = pointer_mode_host */, elem_count, &alpha, + GpuMemoryMutable(x), incx); +} + +bool ROCMBlas::DoBlasScal(Stream *stream, uint64 elem_count, float alpha, + DeviceMemory> *x, int incx) { + LOG(ERROR) << "rocBLAS does not currently support the SCAL operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasScal(Stream *stream, uint64 elem_count, double alpha, + DeviceMemory> *x, int incx) { + LOG(ERROR) << "rocBLAS does not currently support the SCAL operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasScal(Stream *stream, uint64 elem_count, + std::complex alpha, + DeviceMemory> *x, int incx) { + LOG(ERROR) << "rocBLAS does not currently support the SCAL operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasScal(Stream *stream, uint64 elem_count, + std::complex alpha, + DeviceMemory> *x, int incx) { + LOG(ERROR) << "rocBLAS does not currently support the SCAL operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasSwap(Stream *stream, uint64 elem_count, + DeviceMemory *x, int incx, + DeviceMemory *y, int incy) { + return DoBlasInternal(wrap::rocblas_sswap, stream, + true /* = pointer_mode_host */, elem_count, + GpuMemoryMutable(x), incx, GpuMemoryMutable(y), incy); +} + +bool ROCMBlas::DoBlasSwap(Stream *stream, uint64 elem_count, + DeviceMemory *x, int incx, + DeviceMemory *y, int incy) { + return DoBlasInternal(wrap::rocblas_dswap, stream, + true /* = pointer_mode_host */, elem_count, + GpuMemoryMutable(x), incx, GpuMemoryMutable(y), incy); +} + +bool ROCMBlas::DoBlasSwap(Stream *stream, uint64 elem_count, + DeviceMemory> *x, int incx, + DeviceMemory> *y, int incy) { + LOG(ERROR) << "rocBLAS does not currently support the SWAP operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasSwap(Stream *stream, uint64 elem_count, + DeviceMemory> *x, int incx, + DeviceMemory> *y, int incy) { + LOG(ERROR) << "rocBLAS does not currently support the SWAP operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasIamax(Stream *stream, uint64 elem_count, + const DeviceMemory &x, int incx, + DeviceMemory *result) { + return DoBlasInternal(wrap::rocblas_isamax, stream, + false /* = pointer_mode_host */, elem_count, + GpuMemory(x), incx, GpuMemoryMutable(result)); +} + +bool ROCMBlas::DoBlasIamax(Stream *stream, uint64 elem_count, + const DeviceMemory &x, int incx, + DeviceMemory *result) { + return DoBlasInternal(wrap::rocblas_idamax, stream, + false /* = pointer_mode_host */, elem_count, + GpuMemory(x), incx, GpuMemoryMutable(result)); +} + +bool ROCMBlas::DoBlasIamax(Stream *stream, uint64 elem_count, + const DeviceMemory> &x, int incx, + DeviceMemory *result) { + LOG(ERROR) << "rocBLAS does not currently support the AMAX operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasIamax(Stream *stream, uint64 elem_count, + const DeviceMemory> &x, + int incx, DeviceMemory *result) { + LOG(ERROR) << "rocBLAS does not currently support the AMAX operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasIamin(Stream *stream, uint64 elem_count, + const DeviceMemory &x, int incx, + DeviceMemory *result) { + return DoBlasInternal( + wrap::rocblas_isamin, stream, false /* = pointer_mode_host */, elem_count, + GpuComplex(GpuMemory(x)), incx, GpuMemoryMutable(result)); +} + +bool ROCMBlas::DoBlasIamin(Stream *stream, uint64 elem_count, + const DeviceMemory &x, int incx, + DeviceMemory *result) { + return DoBlasInternal( + wrap::rocblas_idamin, stream, false /* = pointer_mode_host */, elem_count, + GpuComplex(GpuMemory(x)), incx, GpuMemoryMutable(result)); +} + +bool ROCMBlas::DoBlasIamin(Stream *stream, uint64 elem_count, + const DeviceMemory> &x, int incx, + DeviceMemory *result) { + LOG(ERROR) << "rocBLAS does not currently support the AMIN operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasIamin(Stream *stream, uint64 elem_count, + const DeviceMemory> &x, + int incx, DeviceMemory *result) { + LOG(ERROR) << "rocBLAS does not currently support the AMIN operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasGbmv(Stream *stream, blas::Transpose trans, uint64 m, + uint64 n, uint64 kl, uint64 ku, float alpha, + const DeviceMemory &a, int lda, + const DeviceMemory &x, int incx, float beta, + DeviceMemory *y, int incy) { + LOG(ERROR) << "rocBLAS does not currently support the GBMV operation " + << "for the \"float\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasGbmv(Stream *stream, blas::Transpose trans, uint64 m, + uint64 n, uint64 kl, uint64 ku, double alpha, + const DeviceMemory &a, int lda, + const DeviceMemory &x, int incx, double beta, + DeviceMemory *y, int incy) { + LOG(ERROR) << "rocBLAS does not currently support the GBMV operation " + << "for the \"double\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasGbmv(Stream *stream, blas::Transpose trans, uint64 m, + uint64 n, uint64 kl, uint64 ku, + std::complex alpha, + const DeviceMemory> &a, int lda, + const DeviceMemory> &x, int incx, + std::complex beta, + DeviceMemory> *y, int incy) { + LOG(ERROR) << "rocBLAS does not currently support the GBMV operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasGbmv(Stream *stream, blas::Transpose trans, uint64 m, + uint64 n, uint64 kl, uint64 ku, + std::complex alpha, + const DeviceMemory> &a, int lda, + const DeviceMemory> &x, int incx, + std::complex beta, + DeviceMemory> *y, int incy) { + LOG(ERROR) << "rocBLAS does not currently support the GBMV operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasGemv(Stream *stream, blas::Transpose trans, uint64 m, + uint64 n, float alpha, const DeviceMemory &a, + int lda, const DeviceMemory &x, int incx, + float beta, DeviceMemory *y, int incy) { + return DoBlasInternal( + wrap::rocblas_sgemv, stream, true /* = pointer_mode_host */, + ROCMBlasTranspose(trans), m, n, &alpha, GpuMemory(a), lda, GpuMemory(x), + incx, &beta, GpuMemoryMutable(y), incy); +} + +bool ROCMBlas::DoBlasGemv(Stream *stream, blas::Transpose trans, uint64 m, + uint64 n, double alpha, const DeviceMemory &a, + int lda, const DeviceMemory &x, int incx, + double beta, DeviceMemory *y, int incy) { + return DoBlasInternal( + wrap::rocblas_dgemv, stream, true /* = pointer_mode_host */, + ROCMBlasTranspose(trans), m, n, &alpha, GpuMemory(a), lda, GpuMemory(x), + incx, &beta, GpuMemoryMutable(y), incy); +} + +bool ROCMBlas::DoBlasGemv(Stream *stream, blas::Transpose trans, uint64 m, + uint64 n, std::complex alpha, + const DeviceMemory> &a, int lda, + const DeviceMemory> &x, int incx, + std::complex beta, + DeviceMemory> *y, int incy) { + LOG(ERROR) << "rocBLAS does not currently support the GEMV operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasGemv(Stream *stream, blas::Transpose trans, uint64 m, + uint64 n, std::complex alpha, + const DeviceMemory> &a, int lda, + const DeviceMemory> &x, int incx, + std::complex beta, + DeviceMemory> *y, int incy) { + LOG(ERROR) << "rocBLAS does not currently support the GEMV operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasGer(Stream *stream, uint64 m, uint64 n, float alpha, + const DeviceMemory &x, int incx, + const DeviceMemory &y, int incy, + DeviceMemory *a, int lda) { + return DoBlasInternal( + wrap::rocblas_sger, stream, true /* = pointer_mode_host */, m, n, &alpha, + GpuMemory(x), incx, GpuMemory(y), incy, GpuMemoryMutable(a), lda); +} + +bool ROCMBlas::DoBlasGer(Stream *stream, uint64 m, uint64 n, double alpha, + const DeviceMemory &x, int incx, + const DeviceMemory &y, int incy, + DeviceMemory *a, int lda) { + return DoBlasInternal( + wrap::rocblas_dger, stream, true /* = pointer_mode_host */, m, n, &alpha, + GpuMemory(x), incx, GpuMemory(y), incy, GpuMemoryMutable(a), lda); +} + +bool ROCMBlas::DoBlasGerc(Stream *stream, uint64 m, uint64 n, + std::complex alpha, + const DeviceMemory> &x, int incx, + const DeviceMemory> &y, int incy, + DeviceMemory> *a, int lda) { + LOG(ERROR) << "rocBLAS does not currently support the GER operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasGerc(Stream *stream, uint64 m, uint64 n, + std::complex alpha, + const DeviceMemory> &x, int incx, + const DeviceMemory> &y, int incy, + DeviceMemory> *a, int lda) { + LOG(ERROR) << "rocBLAS does not currently support the GER operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasGeru(Stream *stream, uint64 m, uint64 n, + std::complex alpha, + const DeviceMemory> &x, int incx, + const DeviceMemory> &y, int incy, + DeviceMemory> *a, int lda) { + LOG(ERROR) << "rocBLAS does not currently support the GERU operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasGeru(Stream *stream, uint64 m, uint64 n, + std::complex alpha, + const DeviceMemory> &x, int incx, + const DeviceMemory> &y, int incy, + DeviceMemory> *a, int lda) { + LOG(ERROR) << "rocBLAS does not currently support the GERU operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasHbmv(Stream *stream, blas::UpperLower uplo, uint64 n, + uint64 k, std::complex alpha, + const DeviceMemory> &a, int lda, + const DeviceMemory> &x, int incx, + std::complex beta, + DeviceMemory> *y, int incy) { + LOG(ERROR) << "rocBLAS does not currently support the HBMV operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasHbmv(Stream *stream, blas::UpperLower uplo, uint64 n, + uint64 k, std::complex alpha, + const DeviceMemory> &a, int lda, + const DeviceMemory> &x, int incx, + std::complex beta, + DeviceMemory> *y, int incy) { + LOG(ERROR) << "rocBLAS does not currently support the HBMV operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasHemv(Stream *stream, blas::UpperLower uplo, uint64 n, + std::complex alpha, + const DeviceMemory> &a, int lda, + const DeviceMemory> &x, int incx, + std::complex beta, + DeviceMemory> *y, int incy) { + LOG(ERROR) << "rocBLAS does not currently support the HEMV operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasHemv(Stream *stream, blas::UpperLower uplo, uint64 n, + std::complex alpha, + const DeviceMemory> &a, int lda, + const DeviceMemory> &x, int incx, + std::complex beta, + DeviceMemory> *y, int incy) { + LOG(ERROR) << "rocBLAS does not currently support the HEMV operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasHer(Stream *stream, blas::UpperLower uplo, uint64 n, + float alpha, + const DeviceMemory> &x, int incx, + DeviceMemory> *a, int lda) { + LOG(ERROR) << "rocBLAS does not currently support the HER operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasHer(Stream *stream, blas::UpperLower uplo, uint64 n, + double alpha, + const DeviceMemory> &x, int incx, + DeviceMemory> *a, int lda) { + LOG(ERROR) << "rocBLAS does not currently support the HER operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasHer2(Stream *stream, blas::UpperLower uplo, uint64 n, + std::complex alpha, + const DeviceMemory> &x, int incx, + const DeviceMemory> &y, int incy, + DeviceMemory> *a, int lda) { + LOG(ERROR) << "rocBLAS does not currently support the HER2 operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasHer2(Stream *stream, blas::UpperLower uplo, uint64 n, + std::complex alpha, + const DeviceMemory> &x, int incx, + const DeviceMemory> &y, int incy, + DeviceMemory> *a, int lda) { + LOG(ERROR) << "rocBLAS does not currently support the HER2 operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasHpmv(Stream *stream, blas::UpperLower uplo, uint64 n, + std::complex alpha, + const DeviceMemory> &ap, + const DeviceMemory> &x, int incx, + std::complex beta, + DeviceMemory> *y, int incy) { + LOG(ERROR) << "rocBLAS does not currently support the HPMV operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasHpmv(Stream *stream, blas::UpperLower uplo, uint64 n, + std::complex alpha, + const DeviceMemory> &ap, + const DeviceMemory> &x, int incx, + std::complex beta, + DeviceMemory> *y, int incy) { + LOG(ERROR) << "rocBLAS does not currently support the HPMV operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasHpr(Stream *stream, blas::UpperLower uplo, uint64 n, + float alpha, + const DeviceMemory> &x, int incx, + DeviceMemory> *ap) { + LOG(ERROR) << "rocBLAS does not currently support the HPR operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasHpr(Stream *stream, blas::UpperLower uplo, uint64 n, + double alpha, + const DeviceMemory> &x, int incx, + DeviceMemory> *ap) { + LOG(ERROR) << "rocBLAS does not currently support the HPR operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasHpr2(Stream *stream, blas::UpperLower uplo, uint64 n, + std::complex alpha, + const DeviceMemory> &x, int incx, + const DeviceMemory> &y, int incy, + DeviceMemory> *ap) { + LOG(ERROR) << "rocBLAS does not currently support the HPR2 operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasHpr2(Stream *stream, blas::UpperLower uplo, uint64 n, + std::complex alpha, + const DeviceMemory> &x, int incx, + const DeviceMemory> &y, int incy, + DeviceMemory> *ap) { + LOG(ERROR) << "rocBLAS does not currently support the HPR2 operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasSbmv(Stream *stream, blas::UpperLower uplo, uint64 n, + uint64 k, float alpha, const DeviceMemory &a, + int lda, const DeviceMemory &x, int incx, + float beta, DeviceMemory *y, int incy) { + LOG(ERROR) << "rocBLAS does not currently support the SBMV operation " + << "for the \"complex\" dataype" ; + + return false; +} + +bool ROCMBlas::DoBlasSbmv(Stream *stream, blas::UpperLower uplo, uint64 n, + uint64 k, double alpha, const DeviceMemory &a, + int lda, const DeviceMemory &x, int incx, + double beta, DeviceMemory *y, int incy) { + LOG(ERROR) << "rocBLAS does not currently support the SBMV operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasSpmv(Stream *stream, blas::UpperLower uplo, uint64 n, + float alpha, const DeviceMemory &ap, + const DeviceMemory &x, int incx, float beta, + DeviceMemory *y, int incy) { + LOG(ERROR) << "rocBLAS does not currently support the SPMV operation " + << "for the \"float\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasSpmv(Stream *stream, blas::UpperLower uplo, uint64 n, + double alpha, const DeviceMemory &ap, + const DeviceMemory &x, int incx, double beta, + DeviceMemory *y, int incy) { + LOG(ERROR) << "rocBLAS does not currently support the SPMV operation " + << "for the \"double\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasSpr(Stream *stream, blas::UpperLower uplo, uint64 n, + float alpha, const DeviceMemory &x, int incx, + DeviceMemory *ap) { + LOG(ERROR) << "rocBLAS does not currently support the SPR operation " + << "for the \"float\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasSpr(Stream *stream, blas::UpperLower uplo, uint64 n, + double alpha, const DeviceMemory &x, int incx, + DeviceMemory *ap) { + LOG(ERROR) << "rocBLAS does not currently support the SPR operation " + << "for the \"double\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasSpr2(Stream *stream, blas::UpperLower uplo, uint64 n, + float alpha, const DeviceMemory &x, int incx, + const DeviceMemory &y, int incy, + DeviceMemory *ap) { + LOG(ERROR) << "rocBLAS does not currently support the SPR2 operation " + << "for the \"float\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasSpr2(Stream *stream, blas::UpperLower uplo, uint64 n, + double alpha, const DeviceMemory &x, int incx, + const DeviceMemory &y, int incy, + DeviceMemory *ap) { + LOG(ERROR) << "rocBLAS does not currently support the SPR2 operation " + << "for the \"double\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasSymv(Stream *stream, blas::UpperLower uplo, uint64 n, + float alpha, const DeviceMemory &a, int lda, + const DeviceMemory &x, int incx, float beta, + DeviceMemory *y, int incy) { + LOG(ERROR) << "rocBLAS does not currently support the SYMV operation " + << "for the \"float\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasSymv(Stream *stream, blas::UpperLower uplo, uint64 n, + double alpha, const DeviceMemory &a, int lda, + const DeviceMemory &x, int incx, double beta, + DeviceMemory *y, int incy) { + LOG(ERROR) << "rocBLAS does not currently support the SYMV operation " + << "for the \"double\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasSyr(Stream *stream, blas::UpperLower uplo, uint64 n, + float alpha, const DeviceMemory &x, int incx, + DeviceMemory *a, int lda) { + return DoBlasInternal(wrap::rocblas_ssyr, stream, + true /* = pointer_mode_host */, + ROCMBlasUpperLower(uplo), n, &alpha, GpuMemory(x), incx, + GpuMemoryMutable(a), lda); +} + +bool ROCMBlas::DoBlasSyr(Stream *stream, blas::UpperLower uplo, uint64 n, + double alpha, const DeviceMemory &x, int incx, + DeviceMemory *a, int lda) { + return DoBlasInternal(wrap::rocblas_dsyr, stream, + true /* = pointer_mode_host */, + ROCMBlasUpperLower(uplo), n, &alpha, GpuMemory(x), incx, + GpuMemoryMutable(a), lda); +} + +bool ROCMBlas::DoBlasSyr2(Stream *stream, blas::UpperLower uplo, uint64 n, + float alpha, const DeviceMemory &x, int incx, + const DeviceMemory &y, int incy, + DeviceMemory *a, int lda) { + LOG(ERROR) << "rocBLAS does not currently support the SYR2 operation " + << "for the \"float\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasSyr2(Stream *stream, blas::UpperLower uplo, uint64 n, + double alpha, const DeviceMemory &x, int incx, + const DeviceMemory &y, int incy, + DeviceMemory *a, int lda) { + LOG(ERROR) << "rocBLAS does not currently support the SYR2 operation " + << "for the \"double\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasTbmv(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, blas::Diagonal diag, uint64 n, + uint64 k, const DeviceMemory &a, int lda, + DeviceMemory *x, int incx) { + LOG(ERROR) << "rocBLAS does not currently support the TBMV operation " + << "for the \"float\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasTbmv(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, blas::Diagonal diag, uint64 n, + uint64 k, const DeviceMemory &a, int lda, + DeviceMemory *x, int incx) { + LOG(ERROR) << "rocBLAS does not currently support the TBMV operation " + << "for the \"double\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasTbmv(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, blas::Diagonal diag, uint64 n, + uint64 k, const DeviceMemory> &a, + int lda, DeviceMemory> *x, + int incx) { + LOG(ERROR) << "rocBLAS does not currently support the TBMV operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasTbmv(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, blas::Diagonal diag, uint64 n, + uint64 k, const DeviceMemory> &a, + int lda, DeviceMemory> *x, + int incx) { + LOG(ERROR) << "rocBLAS does not currently support the TBMV operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasTbsv(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, blas::Diagonal diag, uint64 n, + uint64 k, const DeviceMemory &a, int lda, + DeviceMemory *x, int incx) { + LOG(ERROR) << "rocBLAS does not currently support the TBSV operation " + << "for the \"float\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasTbsv(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, blas::Diagonal diag, uint64 n, + uint64 k, const DeviceMemory &a, int lda, + DeviceMemory *x, int incx) { + LOG(ERROR) << "rocBLAS does not currently support the TBSV operation " + << "for the \"double\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasTbsv(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, blas::Diagonal diag, uint64 n, + uint64 k, const DeviceMemory> &a, + int lda, DeviceMemory> *x, + int incx) { + LOG(ERROR) << "rocBLAS does not currently support the TBSV operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasTbsv(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, blas::Diagonal diag, uint64 n, + uint64 k, const DeviceMemory> &a, + int lda, DeviceMemory> *x, + int incx) { + LOG(ERROR) << "rocBLAS does not currently support the TBSV operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasTpmv(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, blas::Diagonal diag, uint64 n, + const DeviceMemory &ap, DeviceMemory *x, + int incx) { + LOG(ERROR) << "rocBLAS does not currently support the TPMV operation " + << "for the \"float\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasTpmv(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, blas::Diagonal diag, uint64 n, + const DeviceMemory &ap, + DeviceMemory *x, int incx) { + LOG(ERROR) << "rocBLAS does not currently support the TPMV operation " + << "for the \"double\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasTpmv(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, blas::Diagonal diag, uint64 n, + const DeviceMemory> &ap, + DeviceMemory> *x, int incx) { + LOG(ERROR) << "rocBLAS does not currently support the TPMV operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasTpmv(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, blas::Diagonal diag, uint64 n, + const DeviceMemory> &ap, + DeviceMemory> *x, int incx) { + LOG(ERROR) << "rocBLAS does not currently support the TPMV operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasTpsv(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, blas::Diagonal diag, uint64 n, + const DeviceMemory &ap, DeviceMemory *x, + int incx) { + LOG(ERROR) << "rocBLAS does not currently support the TPSV operation " + << "for the \"float\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasTpsv(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, blas::Diagonal diag, uint64 n, + const DeviceMemory &ap, + DeviceMemory *x, int incx) { + LOG(ERROR) << "rocBLAS does not currently support the TPSV operation " + << "for the \"double\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasTpsv(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, blas::Diagonal diag, uint64 n, + const DeviceMemory> &ap, + DeviceMemory> *x, int incx) { + LOG(ERROR) << "rocBLAS does not currently support the TPSV operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasTpsv(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, blas::Diagonal diag, uint64 n, + const DeviceMemory> &ap, + DeviceMemory> *x, int incx) { + LOG(ERROR) << "rocBLAS does not currently support the TPSV operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasTrmv(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, blas::Diagonal diag, uint64 n, + const DeviceMemory &a, int lda, + DeviceMemory *x, int incx) { + LOG(ERROR) << "rocBLAS does not currently support the TRMV operation " + << "for the \"float\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasTrmv(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, blas::Diagonal diag, uint64 n, + const DeviceMemory &a, int lda, + DeviceMemory *x, int incx) { + LOG(ERROR) << "rocBLAS does not currently support the TRMV operation " + << "for the \"double\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasTrmv(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, blas::Diagonal diag, uint64 n, + const DeviceMemory> &a, int lda, + DeviceMemory> *x, int incx) { + LOG(ERROR) << "rocBLAS does not currently support the TRMV operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasTrmv(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, blas::Diagonal diag, uint64 n, + const DeviceMemory> &a, int lda, + DeviceMemory> *x, int incx) { + LOG(ERROR) << "rocBLAS does not currently support the TRMV operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasTrsv(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, blas::Diagonal diag, uint64 n, + const DeviceMemory &a, int lda, + DeviceMemory *x, int incx) { + LOG(ERROR) << "rocBLAS does not currently support the TRSV operation " + << "for the \"float\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasTrsv(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, blas::Diagonal diag, uint64 n, + const DeviceMemory &a, int lda, + DeviceMemory *x, int incx) { + LOG(ERROR) << "rocBLAS does not currently support the TRSV operation " + << "for the \"double\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasTrsv(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, blas::Diagonal diag, uint64 n, + const DeviceMemory> &a, int lda, + DeviceMemory> *x, int incx) { + LOG(ERROR) << "rocBLAS does not currently support the TRSV operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasTrsv(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, blas::Diagonal diag, uint64 n, + const DeviceMemory> &a, int lda, + DeviceMemory> *x, int incx) { + LOG(ERROR) << "rocBLAS does not currently support the TRSV operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasGemm( + Stream *stream, blas::Transpose transa, + blas::Transpose transb, uint64 m, uint64 n, uint64 k, + float alpha, const DeviceMemory &a, int lda, + const DeviceMemory &b, int ldb, float beta, + DeviceMemory *c, int ldc) { + VLOG(1) << port::Printf( + "doing rocBLAS SGEMM: at=%d bt=%d m=%llu n=%llu " + "k=%llu alpha=%f a=%p lda=%d b=%p ldb=%d beta=%f " + "c=%p ldc=%d", + static_cast(transa), static_cast(transb), m, n, k, alpha, + a.opaque(), lda, b.opaque(), ldb, beta, c->opaque(), ldc); + if (transa == blas::Transpose::kNoTranspose) { + if (lda < static_cast(m)) { + LOG(WARNING) << "GEMM lda was smaller than m (no transpose case); " + "precondition violation"; + } + } else { + if (lda < static_cast(k)) { + LOG(WARNING) << "GEMM lda (" << lda << ") was smaller than k (" << k + << ") (transpose case); precondition violation"; + } + } + if (transb == blas::Transpose::kNoTranspose) { + if (ldb < static_cast(k)) { + LOG(WARNING) << "GEMM ldb (" << ldb << ") was smaller than k (" << k + << ") (no transpose case); precondition violation"; + } + } else { + if (ldb < static_cast(n)) { + LOG(WARNING) << "GEMM ldb was smaller than n (transpose case); " + "precondition violation"; + } + } + const Eigen::half alpha_half(alpha); + const Eigen::half beta_half(beta); + return DoBlasInternal( + wrap::rocblas_hgemm, stream, true /* = pointer_mode_host */, + ROCMBlasTranspose(transa), ROCMBlasTranspose(transb), m, n, k, + reinterpret_cast(&alpha_half), + reinterpret_cast(GpuMemory(a)), lda, + reinterpret_cast(GpuMemory(b)), ldb, + reinterpret_cast(&beta_half), + reinterpret_cast(GpuMemoryMutable(c)), ldc); +} + +bool ROCMBlas::DoBlasGemm(Stream *stream, blas::Transpose transa, + blas::Transpose transb, uint64 m, uint64 n, uint64 k, + float alpha, const DeviceMemory &a, int lda, + const DeviceMemory &b, int ldb, float beta, + DeviceMemory *c, int ldc) { + VLOG(1) << port::Printf( + "doing rocBLAS SGEMM: at=%d bt=%d m=%llu n=%llu " + "k=%llu alpha=%f a=%p lda=%d b=%p ldb=%d beta=%f " + "c=%p ldc=%d", + static_cast(transa), static_cast(transb), m, n, k, alpha, + a.opaque(), lda, b.opaque(), ldb, beta, c->opaque(), ldc); + if (transa == blas::Transpose::kNoTranspose) { + if (lda < static_cast(m)) { + LOG(WARNING) << "GEMM lda was smaller than m (no transpose case); " + "precondition violation"; + } + } else { + if (lda < static_cast(k)) { + LOG(WARNING) << "GEMM lda (" << lda << ") was smaller than k (" << k + << ") (transpose case); precondition violation"; + } + } + if (transb == blas::Transpose::kNoTranspose) { + if (ldb < static_cast(k)) { + LOG(WARNING) << "GEMM ldb (" << ldb << ") was smaller than k (" << k + << ") (no transpose case); precondition violation"; + } + } else { + if (ldb < static_cast(n)) { + LOG(WARNING) << "GEMM ldb was smaller than n (transpose case); " + "precondition violation"; + } + } + return DoBlasInternal( + wrap::rocblas_sgemm, stream, true /* = pointer_mode_host */, + ROCMBlasTranspose(transa), ROCMBlasTranspose(transb), m, n, k, &alpha, + GpuMemory(a), lda, GpuMemory(b), ldb, &beta, GpuMemoryMutable(c), ldc); +} + +bool ROCMBlas::DoBlasGemm(Stream *stream, blas::Transpose transa, + blas::Transpose transb, uint64 m, uint64 n, uint64 k, + double alpha, const DeviceMemory &a, int lda, + const DeviceMemory &b, int ldb, double beta, + DeviceMemory *c, int ldc) { + return DoBlasInternal( + wrap::rocblas_dgemm, stream, true /* = pointer_mode_host */, + ROCMBlasTranspose(transa), ROCMBlasTranspose(transb), m, n, k, &alpha, + GpuMemory(a), lda, GpuMemory(b), ldb, &beta, GpuMemoryMutable(c), ldc); +} + +bool ROCMBlas::DoBlasGemm(Stream *stream, blas::Transpose transa, + blas::Transpose transb, uint64 m, uint64 n, uint64 k, + std::complex alpha, + const DeviceMemory> &a, int lda, + const DeviceMemory> &b, int ldb, + std::complex beta, + DeviceMemory> *c, int ldc) { + LOG(ERROR) << "rocBLAS does not currently support the GEMM operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasGemm(Stream *stream, blas::Transpose transa, + blas::Transpose transb, uint64 m, uint64 n, uint64 k, + std::complex alpha, + const DeviceMemory> &a, int lda, + const DeviceMemory> &b, int ldb, + std::complex beta, + DeviceMemory> *c, int ldc) { + LOG(ERROR) << "rocBLAS does not currently support the GEMM operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasGemvWithProfiling( + Stream *stream, blas::Transpose trans, uint64 m, uint64 n, float alpha, + const DeviceMemory &a, int lda, const DeviceMemory &x, + int incx, float beta, DeviceMemory *y, int incy, + blas::ProfileResult *output_profile_result) { + return DoBlasGemvWithProfilingImpl(stream, trans, m, n, alpha, a, lda, x, + incx, beta, y, incy, + output_profile_result); +} + +bool ROCMBlas::DoBlasGemvWithProfiling( + Stream *stream, blas::Transpose trans, uint64 m, uint64 n, double alpha, + const DeviceMemory &a, int lda, const DeviceMemory &x, + int incx, double beta, DeviceMemory *y, int incy, + blas::ProfileResult *output_profile_result) { + return DoBlasGemvWithProfilingImpl(stream, trans, m, n, alpha, a, lda, x, + incx, beta, y, incy, + output_profile_result); +} + +bool ROCMBlas::DoBlasGemvWithProfiling( + Stream *stream, blas::Transpose trans, uint64 m, uint64 n, + std::complex alpha, const DeviceMemory> &a, + int lda, const DeviceMemory> &x, int incx, + std::complex beta, DeviceMemory> *y, int incy, + blas::ProfileResult *output_profile_result) { + return DoBlasGemvWithProfilingImpl(stream, trans, m, n, alpha, a, lda, x, + incx, beta, y, incy, + output_profile_result); +} + +bool ROCMBlas::DoBlasGemvWithProfiling( + Stream *stream, blas::Transpose trans, uint64 m, uint64 n, + std::complex alpha, const DeviceMemory> &a, + int lda, const DeviceMemory> &x, int incx, + std::complex beta, DeviceMemory> *y, int incy, + blas::ProfileResult *output_profile_result) { + return DoBlasGemvWithProfilingImpl(stream, trans, m, n, alpha, a, lda, x, + incx, beta, y, incy, + output_profile_result); +} + +bool ROCMBlas::DoBlasGemmWithProfiling( + Stream *stream, blas::Transpose transa, blas::Transpose transb, uint64 m, + uint64 n, uint64 k, float alpha, const DeviceMemory &a, + int lda, const DeviceMemory &b, int ldb, float beta, + DeviceMemory *c, int ldc, + blas::ProfileResult *output_profile_result) { + return DoBlasGemmWithProfilingImpl(stream, transa, transb, m, n, k, alpha, a, + lda, b, ldb, beta, c, ldc, + output_profile_result); +} + +bool ROCMBlas::DoBlasGemmWithProfiling( + Stream *stream, blas::Transpose transa, blas::Transpose transb, uint64 m, + uint64 n, uint64 k, float alpha, const DeviceMemory &a, int lda, + const DeviceMemory &b, int ldb, float beta, DeviceMemory *c, + int ldc, blas::ProfileResult *output_profile_result) { + return DoBlasGemmWithProfilingImpl(stream, transa, transb, m, n, k, alpha, a, + lda, b, ldb, beta, c, ldc, + output_profile_result); +} + +bool ROCMBlas::DoBlasGemmWithProfiling( + Stream *stream, blas::Transpose transa, blas::Transpose transb, uint64 m, + uint64 n, uint64 k, double alpha, const DeviceMemory &a, int lda, + const DeviceMemory &b, int ldb, double beta, + DeviceMemory *c, int ldc, + blas::ProfileResult *output_profile_result) { + return DoBlasGemmWithProfilingImpl(stream, transa, transb, m, n, k, alpha, a, + lda, b, ldb, beta, c, ldc, + output_profile_result); +} + +bool ROCMBlas::DoBlasGemmWithProfiling( + Stream *stream, blas::Transpose transa, blas::Transpose transb, uint64 m, + uint64 n, uint64 k, std::complex alpha, + const DeviceMemory> &a, int lda, + const DeviceMemory> &b, int ldb, + std::complex beta, DeviceMemory> *c, int ldc, + blas::ProfileResult *output_profile_result) { + return DoBlasGemmWithProfilingImpl(stream, transa, transb, m, n, k, alpha, a, + lda, b, ldb, beta, c, ldc, + output_profile_result); +} + +bool ROCMBlas::DoBlasGemmWithProfiling( + Stream *stream, blas::Transpose transa, blas::Transpose transb, uint64 m, + uint64 n, uint64 k, std::complex alpha, + const DeviceMemory> &a, int lda, + const DeviceMemory> &b, int ldb, + std::complex beta, DeviceMemory> *c, int ldc, + blas::ProfileResult *output_profile_result) { + return DoBlasGemmWithProfilingImpl(stream, transa, transb, m, n, k, alpha, a, + lda, b, ldb, beta, c, ldc, + output_profile_result); +} + +template +bool ROCMBlas::DoBlasGemvWithProfilingImpl( + Stream *stream, blas::Transpose trans, uint64 m, uint64 n, const T &alpha, + const DeviceMemory &a, int lda, const DeviceMemory &x, int incx, + const T &beta, DeviceMemory *y, int incy, + blas::ProfileResult *output_profile_result) { + // ROCM TODO: properly implement the interface + return false; +} + +template +bool ROCMBlas::DoBlasGemmWithProfilingImpl( + Stream *stream, blas::Transpose transa, blas::Transpose transb, uint64 m, + uint64 n, uint64 k, const ParamType &alpha, const DeviceMemory &a, + int lda, const DeviceMemory &b, int ldb, const ParamType &beta, + DeviceMemory *c, int ldc, blas::ProfileResult *output_profile_result) { + // ROCM TODO: properly implement the interface + return false; +} + +template +bool ROCMBlas::DoBlasGemmWithAlgorithmImpl( + Stream *stream, blas::Transpose transa, blas::Transpose transb, uint64 m, + uint64 n, uint64 k, const CompT &alpha, const DeviceMemory &a, int lda, + const DeviceMemory &b, int ldb, const CompT &beta, + DeviceMemory *c, int ldc, blas::ComputationType computation_type, + blas::AlgorithmType algorithm, blas::ProfileResult *output_profile_result) { + // ROCM TODO: properly implement the interface + return false; +} + +bool ROCMBlas::GetBlasGemmAlgorithms( + std::vector *out_algorithms) { + // ROCM TODO: properly implement the interface + return true; +} + +bool ROCMBlas::DoBlasGemmWithAlgorithm( + Stream *stream, blas::Transpose transa, blas::Transpose transb, uint64 m, + uint64 n, uint64 k, const HostOrDeviceScalar &alpha, + const DeviceMemory &a, int lda, const DeviceMemory &b, + int ldb, const HostOrDeviceScalar &beta, DeviceMemory *c, + int ldc, blas::ComputationType computation_type, blas::AlgorithmType algorithm, + blas::ProfileResult *output_profile_result) { + LOG(ERROR) << "rocBLAS does not currently support the GEMMwithAlgorithm operation " + << "for the \"int8\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasGemmWithAlgorithm( + Stream *stream, blas::Transpose transa, blas::Transpose transb, uint64 m, + uint64 n, uint64 k, const HostOrDeviceScalar &alpha, + const DeviceMemory &a, int lda, + const DeviceMemory &b, int ldb, + const HostOrDeviceScalar &beta, DeviceMemory *c, + int ldc, blas::ComputationType computation_type, blas::AlgorithmType algorithm, + blas::ProfileResult *output_profile_result) { + LOG(ERROR) << "rocBLAS does not currently support the GEMMwithAlgorithm operation " + << "for the \"half\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasGemmWithAlgorithm( + Stream *stream, blas::Transpose transa, blas::Transpose transb, uint64 m, + uint64 n, uint64 k, const HostOrDeviceScalar &alpha, + const DeviceMemory &a, int lda, const DeviceMemory &b, + int ldb, const HostOrDeviceScalar &beta, DeviceMemory *c, + int ldc, blas::ComputationType computation_type, blas::AlgorithmType algorithm, + blas::ProfileResult *output_profile_result) { + LOG(ERROR) << "rocBLAS does not currently support the GEMMwithAlgorithm operation " + << "for the \"float\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasGemmWithAlgorithm( + Stream *stream, blas::Transpose transa, blas::Transpose transb, uint64 m, + uint64 n, uint64 k, const HostOrDeviceScalar &alpha, + const DeviceMemory &a, int lda, const DeviceMemory &b, + int ldb, const HostOrDeviceScalar &beta, DeviceMemory *c, + int ldc, blas::ComputationType computation_type, blas::AlgorithmType algorithm, + blas::ProfileResult *output_profile_result) { + LOG(ERROR) << "rocBLAS does not currently support the GEMMwithAlgorithm operation " + << "for the \"double\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasGemmWithAlgorithm( + Stream *stream, blas::Transpose transa, blas::Transpose transb, uint64 m, + uint64 n, uint64 k, const HostOrDeviceScalar> &alpha, + const DeviceMemory> &a, int lda, + const DeviceMemory> &b, int ldb, + const HostOrDeviceScalar> &beta, + DeviceMemory> *c, int ldc, + blas::ComputationType computation_type, blas::AlgorithmType algorithm, + blas::ProfileResult *output_profile_result) { + LOG(ERROR) << "rocBLAS does not currently support the GEMMwithAlgorithm operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasGemmWithAlgorithm( + Stream *stream, blas::Transpose transa, blas::Transpose transb, uint64 m, + uint64 n, uint64 k, const HostOrDeviceScalar> &alpha, + const DeviceMemory> &a, int lda, + const DeviceMemory> &b, int ldb, + const HostOrDeviceScalar> &beta, + DeviceMemory> *c, int ldc, + blas::ComputationType computation_type, blas::AlgorithmType algorithm, + blas::ProfileResult *output_profile_result) { + LOG(ERROR) << "rocBLAS does not currently support the GEMMwithAlgorithm operation " + << "for the \"complex\" dataype" ; + return false; +} + +template +struct EigenHalfToRocBlasHalf { + using type = T; +}; + +template <> +struct EigenHalfToRocBlasHalf { + using type = rocblas_half; +}; + + template +port::Status ROCMBlas::DoBlasGemmBatchedInternal( + FuncT rocblas_func, Stream *stream, blas::Transpose transa, + blas::Transpose transb, uint64 m, uint64 n, uint64 k, T alpha, + const port::ArraySlice *> &a_ptrs_to_wrappers, int lda, + const port::ArraySlice *> &b_ptrs_to_wrappers, int ldb, + T beta, const port::ArraySlice *> &c_ptrs_to_wrappers, + int ldc, int batch_count, ScratchAllocator *scratch_allocator) { + + // MAPPED_T will be same as T for all types except Eigen::Half + // for T = Eigen::half, MAPPED_T = rocblas_half + using MAPPED_T = typename EigenHalfToRocBlasHalf::type; + + // Alocate local vectors to hold device pointers to matrices + std::vector a_raw_ptrs, b_raw_ptrs, c_raw_ptrs; + for (int i = 0; i < batch_count; ++i) { + // static_cast does work when converting Eigen::half* to rocblas_half*, + // hence the use od reinterpret_cast + a_raw_ptrs.push_back(reinterpret_cast(a_ptrs_to_wrappers[i]->opaque())); + b_raw_ptrs.push_back(reinterpret_cast(b_ptrs_to_wrappers[i]->opaque())); + c_raw_ptrs.push_back(reinterpret_cast(c_ptrs_to_wrappers[i]->opaque())); + } + + // batch_count <= 1 is base case, no definable matrix stride, set it same as ld* + long long bsa = lda; + long long bsb = ldb; + long long bsc = ldc; + bool bsa_is_constant = true; + bool bsb_is_constant = true; + bool bsc_is_constant = true; + + if( batch_count > 1 ) + { + // Remember first stride; if any other stride is different that this one, KABLAM + bsa = a_raw_ptrs[1] - a_raw_ptrs[0]; + bsb = b_raw_ptrs[1] - b_raw_ptrs[0]; + bsc = c_raw_ptrs[1] - c_raw_ptrs[0]; + + // Loop to verify that batched strides are constant + // All the test cases from batch_matmul_op_test.py seem to satisfy this requirement of a constant + // stride. If this can be proven globally, then this loop check can be safely removed + for( int i=1; i < batch_count-1; ++i ) + { + long long iterative_bsa = a_raw_ptrs[i+1] - a_raw_ptrs[i]; + if( iterative_bsa != bsa) + { + bsa_is_constant = false; + break; + } + + long long iterative_bsb = b_raw_ptrs[i+1] - b_raw_ptrs[i]; + if( iterative_bsb != bsb) + { + bsb_is_constant = false; + break; + } + + long long iterative_bsc = c_raw_ptrs[i+1] - c_raw_ptrs[i]; + if( iterative_bsc != bsc) + { + bsc_is_constant = false; + break; + } + } + } + + assert(!(ldc < m || bsc < ldc * n)); + + if (ROCMBlasTranspose(transa) == rocblas_operation_none) + assert(!(lda < m || bsa < lda * k)); + else + assert(!(lda < k || bsa < lda * m)); + + if (ROCMBlasTranspose(transb) == rocblas_operation_none) + assert(!(ldb < k || bsb < ldb * n)); + else + assert(!(ldb < n || bsc < ldc * k)); + + + MAPPED_T *alpha_ptr = reinterpret_cast(&alpha); + MAPPED_T *beta_ptr = reinterpret_cast(&beta); + + if(bsa_is_constant && bsb_is_constant && bsc_is_constant) + { + bool ok = DoBlasInternal( + rocblas_func, stream, true /* = pointer_mode_host */, + ROCMBlasTranspose(transa), ROCMBlasTranspose(transb), m, n, k, + GpuComplex(alpha_ptr), a_raw_ptrs[ 0 ], lda, bsa, + b_raw_ptrs[ 0 ], ldb, bsb, GpuComplex(beta_ptr), + c_raw_ptrs[ 0 ], ldc, bsc, batch_count); + + if (ok) { + return port::Status::OK(); + } + } + + return port::Status(port::error::INTERNAL, + "failed BLAS call, see log for details"); +} + +bool ROCMBlas::DoBlasGemmBatched( + Stream *stream, blas::Transpose transa, blas::Transpose transb, uint64 m, + uint64 n, uint64 k, float alpha, + const port::ArraySlice *> &a, int lda, + const port::ArraySlice *> &b, int ldb, + float beta, const port::ArraySlice *> &c, + int ldc, int batch_count, ScratchAllocator *scratch_allocator) { + + const Eigen::half alpha_half(alpha); + const Eigen::half beta_half(beta); + + port::Status status = DoBlasGemmBatchedInternal( + wrap::rocblas_hgemm_strided_batched, stream, transa, transb, m, n, k, + alpha_half, a, lda, b, ldb, beta_half, c, ldc, batch_count, + scratch_allocator); + if (!status.ok()) { + LOG(ERROR) << status; + } + + return status.ok(); +} + + +bool ROCMBlas::DoBlasGemmBatched( + Stream *stream, blas::Transpose transa, blas::Transpose transb, uint64 m, + uint64 n, uint64 k, float alpha, + const port::ArraySlice *> &a_array, int lda, + const port::ArraySlice *> &b_array, int ldb, float beta, + const port::ArraySlice *> &c_array, int ldc, + int batch_count, ScratchAllocator *scratch_allocator) { + port::Status status = DoBlasGemmBatchedInternal( + wrap::rocblas_sgemm_strided_batched, stream, transa, transb, m, n, k, alpha, + a_array, lda, b_array, ldb, beta, c_array, ldc, batch_count, + scratch_allocator); + if (!status.ok()) { + LOG(ERROR) << status; + } + return status.ok(); +} + +bool ROCMBlas::DoBlasGemmBatched( + Stream *stream, blas::Transpose transa, blas::Transpose transb, uint64 m, + uint64 n, uint64 k, double alpha, + const port::ArraySlice *> &a_array, int lda, + const port::ArraySlice *> &b_array, int ldb, + double beta, const port::ArraySlice *> &c_array, + int ldc, int batch_count, ScratchAllocator *scratch_allocator) { + port::Status status = DoBlasGemmBatchedInternal( + wrap::rocblas_dgemm_strided_batched, stream, transa, transb, m, n, k, alpha, + a_array, lda, b_array, ldb, beta, c_array, ldc, batch_count, + scratch_allocator); + if (!status.ok()) { + LOG(ERROR) << status; + } + return status.ok(); +} + +bool ROCMBlas::DoBlasGemmBatched( + Stream *stream, blas::Transpose transa, blas::Transpose transb, uint64 m, + uint64 n, uint64 k, std::complex alpha, + const port::ArraySlice> *> &a_array, + int lda, + const port::ArraySlice> *> &b_array, + int ldb, std::complex beta, + const port::ArraySlice> *> &c_array, + int ldc, int batch_count, ScratchAllocator *scratch_allocator) { + LOG(ERROR) << "rocBLAS does not currently support the GEMMBatched operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasGemmBatched( + Stream *stream, blas::Transpose transa, blas::Transpose transb, uint64 m, + uint64 n, uint64 k, std::complex alpha, + const port::ArraySlice> *> &a_array, + int lda, + const port::ArraySlice> *> &b_array, + int ldb, std::complex beta, + const port::ArraySlice> *> &c_array, + int ldc, int batch_count, ScratchAllocator *scratch_allocator) { + LOG(ERROR) << "rocBLAS does not currently support the GEMMBatched operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasHemm(Stream *stream, blas::Side side, + blas::UpperLower uplo, uint64 m, uint64 n, + std::complex alpha, + const DeviceMemory> &a, int lda, + const DeviceMemory> &b, int ldb, + std::complex beta, + DeviceMemory> *c, int ldc) { + LOG(ERROR) << "rocBLAS does not currently support the HEMM operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasHemm(Stream *stream, blas::Side side, + blas::UpperLower uplo, uint64 m, uint64 n, + std::complex alpha, + const DeviceMemory> &a, int lda, + const DeviceMemory> &b, int ldb, + std::complex beta, + DeviceMemory> *c, int ldc) { + LOG(ERROR) << "rocBLAS does not currently support the HEMM operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasHerk(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, uint64 n, uint64 k, + float alpha, + const DeviceMemory> &a, int lda, + float beta, DeviceMemory> *c, + int ldc) { + LOG(ERROR) << "rocBLAS does not currently support the HERK operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasHerk(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, uint64 n, uint64 k, + double alpha, + const DeviceMemory> &a, int lda, + double beta, DeviceMemory> *c, + int ldc) { + LOG(ERROR) << "rocBLAS does not currently support the HERK operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasHer2k(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, uint64 n, uint64 k, + std::complex alpha, + const DeviceMemory> &a, int lda, + const DeviceMemory> &b, int ldb, + float beta, DeviceMemory> *c, + int ldc) { + LOG(ERROR) << "rocBLAS does not currently support the HER2K operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasHer2k(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, uint64 n, uint64 k, + std::complex alpha, + const DeviceMemory> &a, int lda, + const DeviceMemory> &b, int ldb, + double beta, DeviceMemory> *c, + int ldc) { + LOG(ERROR) << "rocBLAS does not currently support the HER2K operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasSymm(Stream *stream, blas::Side side, + blas::UpperLower uplo, uint64 m, uint64 n, + float alpha, const DeviceMemory &a, int lda, + const DeviceMemory &b, int ldb, float beta, + DeviceMemory *c, int ldc) { + LOG(ERROR) << "rocBLAS does not currently support the SYMM operation " + << "for the \"float\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasSymm(Stream *stream, blas::Side side, + blas::UpperLower uplo, uint64 m, uint64 n, + double alpha, const DeviceMemory &a, int lda, + const DeviceMemory &b, int ldb, double beta, + DeviceMemory *c, int ldc) { + LOG(ERROR) << "rocBLAS does not currently support the SYMM operation " + << "for the \"double\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasSymm(Stream *stream, blas::Side side, + blas::UpperLower uplo, uint64 m, uint64 n, + std::complex alpha, + const DeviceMemory> &a, int lda, + const DeviceMemory> &b, int ldb, + std::complex beta, + DeviceMemory> *c, int ldc) { + LOG(ERROR) << "rocBLAS does not currently support the SYMM operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasSymm(Stream *stream, blas::Side side, + blas::UpperLower uplo, uint64 m, uint64 n, + std::complex alpha, + const DeviceMemory> &a, int lda, + const DeviceMemory> &b, int ldb, + std::complex beta, + DeviceMemory> *c, int ldc) { + LOG(ERROR) << "rocBLAS does not currently support the SYMM operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasSyrk(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, uint64 n, uint64 k, + float alpha, const DeviceMemory &a, int lda, + float beta, DeviceMemory *c, int ldc) { + LOG(ERROR) << "rocBLAS does not currently support the SYRK operation " + << "for the \"float\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasSyrk(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, uint64 n, uint64 k, + double alpha, const DeviceMemory &a, int lda, + double beta, DeviceMemory *c, int ldc) { + LOG(ERROR) << "rocBLAS does not currently support the SYRK operation " + << "for the \"double\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasSyrk(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, uint64 n, uint64 k, + std::complex alpha, + const DeviceMemory> &a, int lda, + std::complex beta, + DeviceMemory> *c, int ldc) { + LOG(ERROR) << "rocBLAS does not currently support the SYRK operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasSyrk(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, uint64 n, uint64 k, + std::complex alpha, + const DeviceMemory> &a, int lda, + std::complex beta, + DeviceMemory> *c, int ldc) { + LOG(ERROR) << "rocBLAS does not currently support the SYRK operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasSyr2k(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, uint64 n, uint64 k, + float alpha, const DeviceMemory &a, int lda, + const DeviceMemory &b, int ldb, float beta, + DeviceMemory *c, int ldc) { + LOG(ERROR) << "rocBLAS does not currently support the SYR2K operation " + << "for the \"float\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasSyr2k(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, uint64 n, uint64 k, + double alpha, const DeviceMemory &a, int lda, + const DeviceMemory &b, int ldb, double beta, + DeviceMemory *c, int ldc) { + LOG(ERROR) << "rocBLAS does not currently support the SYR2K operation " + << "for the \"double\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasSyr2k(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, uint64 n, uint64 k, + std::complex alpha, + const DeviceMemory> &a, int lda, + const DeviceMemory> &b, int ldb, + std::complex beta, + DeviceMemory> *c, int ldc) { + LOG(ERROR) << "rocBLAS does not currently support the SYR2K operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasSyr2k(Stream *stream, blas::UpperLower uplo, + blas::Transpose trans, uint64 n, uint64 k, + std::complex alpha, + const DeviceMemory> &a, int lda, + const DeviceMemory> &b, int ldb, + std::complex beta, + DeviceMemory> *c, int ldc) { + LOG(ERROR) << "rocBLAS does not currently support the SYR2K operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasTrmm(Stream *stream, blas::Side side, + blas::UpperLower uplo, blas::Transpose transa, + blas::Diagonal diag, uint64 m, uint64 n, float alpha, + const DeviceMemory &a, int lda, + DeviceMemory *b, int ldb) { + LOG(ERROR) << "rocBLAS does not currently support the TRMM operation " + << "for the \"float\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasTrmm(Stream *stream, blas::Side side, + blas::UpperLower uplo, blas::Transpose transa, + blas::Diagonal diag, uint64 m, uint64 n, double alpha, + const DeviceMemory &a, int lda, + DeviceMemory *b, int ldb) { + LOG(ERROR) << "rocBLAS does not currently support the TRMM operation " + << "for the \"double\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasTrmm(Stream *stream, blas::Side side, + blas::UpperLower uplo, blas::Transpose transa, + blas::Diagonal diag, uint64 m, uint64 n, + std::complex alpha, + const DeviceMemory> &a, int lda, + DeviceMemory> *b, int ldb) { + LOG(ERROR) << "rocBLAS does not currently support the TRMM operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasTrmm(Stream *stream, blas::Side side, + blas::UpperLower uplo, blas::Transpose transa, + blas::Diagonal diag, uint64 m, uint64 n, + std::complex alpha, + const DeviceMemory> &a, int lda, + DeviceMemory> *b, int ldb) { + LOG(ERROR) << "rocBLAS does not currently support the TRMM operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasTrsm(Stream *stream, blas::Side side, + blas::UpperLower uplo, blas::Transpose transa, + blas::Diagonal diag, uint64 m, uint64 n, float alpha, + const DeviceMemory &a, int lda, + DeviceMemory *b, int ldb) { + return DoBlasInternal( + wrap::rocblas_strsm, stream, true /* = pointer_mode_host */, + ROCMBlasSide(side), ROCMBlasUpperLower(uplo), ROCMBlasTranspose(transa), + ROCMBlasDiagonal(diag), m, n, &alpha, const_cast(GpuMemory(a)), + lda, GpuMemoryMutable(b), ldb); +} + +bool ROCMBlas::DoBlasTrsm(Stream *stream, blas::Side side, + blas::UpperLower uplo, blas::Transpose transa, + blas::Diagonal diag, uint64 m, uint64 n, double alpha, + const DeviceMemory &a, int lda, + DeviceMemory *b, int ldb) { + return DoBlasInternal( + wrap::rocblas_dtrsm, stream, true /* = pointer_mode_host */, + ROCMBlasSide(side), ROCMBlasUpperLower(uplo), ROCMBlasTranspose(transa), + ROCMBlasDiagonal(diag), m, n, &alpha, const_cast(GpuMemory(a)), + lda, GpuMemoryMutable(b), ldb); +} + +bool ROCMBlas::DoBlasTrsm(Stream *stream, blas::Side side, + blas::UpperLower uplo, blas::Transpose transa, + blas::Diagonal diag, uint64 m, uint64 n, + std::complex alpha, + const DeviceMemory> &a, int lda, + DeviceMemory> *b, int ldb) { + LOG(ERROR) << "rocBLAS does not currently support the TRSM operation " + << "for the \"complex\" dataype" ; + return false; +} + +bool ROCMBlas::DoBlasTrsm(Stream *stream, blas::Side side, + blas::UpperLower uplo, blas::Transpose transa, + blas::Diagonal diag, uint64 m, uint64 n, + std::complex alpha, + const DeviceMemory> &a, int lda, + DeviceMemory> *b, int ldb) { + LOG(ERROR) << "rocBLAS does not currently support the TRSM operation " + << "for the \"complex\" dataype" ; + return false; +} +bool ROCMBlas::DoBlasGemmStridedBatched( + Stream *stream, blas::Transpose transa, blas::Transpose transb, uint64 m, + uint64 n, uint64 k, float alpha, const DeviceMemory &a, + int lda, int64 stride_a, const DeviceMemory &b, int ldb, + int64 stride_b, float beta, DeviceMemory *c, int ldc, + int64 stride_c, int batch_count) { + LOG(ERROR) << "rocBLAS does not currently support the DoBlasGemmStridedBatched operation " + << "for the \"Eigen::half\" dataype" ; + return false; +} + bool ROCMBlas::DoBlasGemmStridedBatched( + Stream *stream, blas::Transpose transa, blas::Transpose transb, uint64 m, + uint64 n, uint64 k, float alpha, const DeviceMemory &a, int lda, + int64 stride_a, const DeviceMemory &b, int ldb, int64 stride_b, + float beta, DeviceMemory *c, int ldc, int64 stride_c, + int batch_count) { + LOG(ERROR) << "rocBLAS does not currently support the DoBlasGemmStridedBatched operation " + << "for the \"float\" dataype" ; + return false; +} + bool ROCMBlas::DoBlasGemmStridedBatched( + Stream *stream, blas::Transpose transa, blas::Transpose transb, uint64 m, + uint64 n, uint64 k, double alpha, const DeviceMemory &a, int lda, + int64 stride_a, const DeviceMemory &b, int ldb, int64 stride_b, + double beta, DeviceMemory *c, int ldc, int64 stride_c, + int batch_count) { + LOG(ERROR) << "rocBLAS does not currently support the DoBlasGemmStridedBatched operation " + << "for the \"double\" dataype" ; + return false; +} + bool ROCMBlas::DoBlasGemmStridedBatched( + Stream *stream, blas::Transpose transa, blas::Transpose transb, uint64 m, + uint64 n, uint64 k, std::complex alpha, + const DeviceMemory> &a, int lda, int64 stride_a, + const DeviceMemory> &b, int ldb, int64 stride_b, + std::complex beta, DeviceMemory> *c, int ldc, + int64 stride_c, int batch_count) { + LOG(ERROR) << "rocBLAS does not currently support the DoBlasGemmStridedBatched operation " + << "for the \"complex\" dataype" ; + return false; +} + bool ROCMBlas::DoBlasGemmStridedBatched( + Stream *stream, blas::Transpose transa, blas::Transpose transb, uint64 m, + uint64 n, uint64 k, std::complex alpha, + const DeviceMemory> &a, int lda, int64 stride_a, + const DeviceMemory> &b, int ldb, int64 stride_b, + std::complex beta, DeviceMemory> *c, int ldc, + int64 stride_c, int batch_count) { + LOG(ERROR) << "rocBLAS does not currently support the DoBlasGemmStridedBatched operation " + << "for the \"complex\" dataype" ; + return false; +} +} // namespace gpu + +void initialize_rocblas() { + port::Status status = + PluginRegistry::Instance()->RegisterFactory( + rocm::kROCmPlatformId, gpu::kRocBlasPlugin, "rocBLAS", + [](internal::StreamExecutorInterface* parent) -> blas::BlasSupport* { + gpu::GpuExecutor* rocm_executor = + dynamic_cast(parent); + if (rocm_executor == nullptr) { + LOG(ERROR) + << "Attempting to initialize an instance of the rocBLAS " + << "support library with a non-ROCM StreamExecutor"; + return nullptr; + } + + gpu::ROCMBlas* blas = new gpu::ROCMBlas(rocm_executor); + if (!blas->Init()) { + // Note: Init() will log a more specific error. + delete blas; + return nullptr; + } + return blas; + }); + + if (!status.ok()) { + LOG(ERROR) << "Unable to register rocBLAS factory: " + << status.error_message(); + } + + PluginRegistry::Instance()->SetDefaultFactory( + rocm::kROCmPlatformId, PluginKind::kBlas, gpu::kRocBlasPlugin); +} + +} // namespace stream_executor + +REGISTER_MODULE_INITIALIZER(register_rocblas, + { stream_executor::initialize_rocblas(); }); diff --git a/tensorflow/stream_executor/rocm/rocm_blas.h b/tensorflow/stream_executor/rocm/rocm_blas.h new file mode 100644 index 0000000000..8e57712745 --- /dev/null +++ b/tensorflow/stream_executor/rocm/rocm_blas.h @@ -0,0 +1,159 @@ +/* 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. +==============================================================================*/ + +// ROCM-specific support for BLAS functionality -- this wraps the rocBLAS library +// capabilities, and is only included into ROCM implementation code -- it will +// not introduce rocm headers into other code. + +#ifndef TENSORFLOW_STREAM_EXECUTOR_ROCM_ROCM_BLAS_H_ +#define TENSORFLOW_STREAM_EXECUTOR_ROCM_ROCM_BLAS_H_ + +#include "tensorflow/stream_executor/blas.h" +#include "tensorflow/stream_executor/platform/mutex.h" +#include "tensorflow/stream_executor/platform/port.h" +#include "tensorflow/stream_executor/platform/thread_annotations.h" +#include "tensorflow/stream_executor/plugin_registry.h" + +namespace stream_executor { + +class Stream; + +namespace gpu { + +// Opaque and unique identifier for the rocBLAS plugin. +extern const PluginId kRocBlasPlugin; + +class GpuExecutor; + +// BLAS plugin for ROCM platform via rocBLAS library. +// +// This satisfies the platform-agnostic BlasSupport interface. +// +// Note that the rocBLAS handle that this encapsulates is implicitly tied to the +// context (and, as a result, the device) that the parent GpuExecutor is tied +// to. This simply happens as an artifact of creating the rocBLAS handle when a +// ROCM context is active. +// +// Thread-safe post-initialization. +class ROCMBlas : public blas::BlasSupport { + public: + explicit ROCMBlas(GpuExecutor* parent); + + // Allocates a rocBLAS handle. + bool Init(); + + // Releases the rocBLAS handle, if present. + ~ROCMBlas() override; + + TENSORFLOW_STREAM_EXECUTOR_GPU_BLAS_SUPPORT_OVERRIDES + + private: + // Tells rocBLAS to enqueue the BLAS operation onto a particular Stream. + // + // rocBLAS is stateful, and only be associated with one stream (in order to + // enqueue dispatch) at a given time. As a result, this generally must be + // invoked before calling into rocBLAS. + bool SetStream(Stream *stream) EXCLUSIVE_LOCKS_REQUIRED(mu_); + + // A helper function that calls the real rocBLAS function together with error + // handling. + // + // rocblas_func: rocBLAS function pointer. + // rocblas_name: rocBLAS function name. + // stream: Stream to enqueue the BLAS operation onto. + // pointer_mode_host: Indicate if the pointer to a scalar value is from host + // (true) or device (false). + // err_on_failure: Whether to print an error if the rocBLAS function fails. + // args: Arguments of rocBLAS function. + template + bool DoBlasInternalImpl(FuncT rocblas_func, Stream *stream, + bool pointer_mode_host, bool err_on_failure, + Args... args); + + // Convenience functions that call DoBlasInternalImpl with different values + // for err_on_failure. + template + bool DoBlasInternal(FuncT rocblas_func, Stream *stream, bool pointer_mode_host, + Args... args) { + return DoBlasInternalImpl(rocblas_func, stream, pointer_mode_host, + /*err_on_failure=*/true, args...); + } + template + bool DoBlasInternalFailureOK(FuncT rocblas_func, Stream *stream, + bool pointer_mode_host, Args... args) { + return DoBlasInternalImpl(rocblas_func, stream, pointer_mode_host, + /*err_on_failure=*/false, args...); + } + + // A helper function to implement DoBlasGemmBatched interfaces for generic + // types. + template + port::Status DoBlasGemmBatchedInternal( + FuncT rocblas_func, Stream *stream, blas::Transpose transa, + blas::Transpose transb, uint64 m, uint64 n, uint64 k, T alpha, + const port::ArraySlice *> &a_array, int lda, + const port::ArraySlice *> &b_array, int ldb, T beta, + const port::ArraySlice *> &c_array, int ldc, + int batch_count, ScratchAllocator *scratch_allocator); + + // Helper function for implementing DoBlasGemmWithAlgorithm. + // + // We take alpha and beta by const reference because T might be Eigen::half, + // and we want to avoid pulling in a dependency on Eigen. When we pass the + // references to rocBLAS, we essentially reinterpret_cast to __half, which is + // safe because Eigen::half inherits from __half. + template + bool DoBlasGemmWithAlgorithmImpl( + Stream *stream, blas::Transpose transa, blas::Transpose transb, uint64 m, + uint64 n, uint64 k, const CompT &alpha, const DeviceMemory &a, + int lda, const DeviceMemory &b, int ldb, const CompT &beta, + DeviceMemory *c, int ldc, blas::ComputationType computation_type, + blas::AlgorithmType algorithm, + blas::ProfileResult *output_profile_result); + + // Helper function for implementing DoBlasGemmWithProfiling. + template + bool DoBlasGemmWithProfilingImpl( + Stream *stream, blas::Transpose transa, blas::Transpose transb, uint64 m, + uint64 n, uint64 k, const ParamType &alpha, const DeviceMemory &a, + int lda, const DeviceMemory &b, int ldb, const ParamType &beta, + DeviceMemory *c, int ldc, blas::ProfileResult *output_profile_result); + + // Helper function for implementing DoBlasGemvWithProfiling. + template + bool DoBlasGemvWithProfilingImpl(Stream *stream, blas::Transpose trans, + uint64 m, uint64 n, const T &alpha, + const DeviceMemory &a, int lda, + const DeviceMemory &x, int incx, + const T &beta, DeviceMemory *y, int incy, + blas::ProfileResult *output_profile_result); + + // mutex that guards the rocBLAS handle for this device. + mutex mu_; + + // GpuExecutor which instantiated this ROCMBlas. + // Immutable post-initialization. + GpuExecutor* parent_; + + // rocBLAS library handle on the device. + rocblas_handle blas_ GUARDED_BY(mu_); + + SE_DISALLOW_COPY_AND_ASSIGN(ROCMBlas); +}; + +} // namespace gpu +} // namespace stream_executor + +#endif // TENSORFLOW_STREAM_EXECUTOR_ROCM_ROCM_BLAS_H_ -- GitLab From 19d3ff647d2174184d49ebfe2e505ffd266d0f09 Mon Sep 17 00:00:00 2001 From: Deven Desai Date: Thu, 31 Jan 2019 19:49:46 +0000 Subject: [PATCH 154/766] adding code for rocfft plugin --- tensorflow/stream_executor/rocm/BUILD | 51 +- tensorflow/stream_executor/rocm/rocm_fft.cc | 588 ++++++++++++++++++++ tensorflow/stream_executor/rocm/rocm_fft.h | 132 +++++ 3 files changed, 750 insertions(+), 21 deletions(-) create mode 100644 tensorflow/stream_executor/rocm/rocm_fft.cc create mode 100644 tensorflow/stream_executor/rocm/rocm_fft.h diff --git a/tensorflow/stream_executor/rocm/BUILD b/tensorflow/stream_executor/rocm/BUILD index 737a442946..c0da35121f 100644 --- a/tensorflow/stream_executor/rocm/BUILD +++ b/tensorflow/stream_executor/rocm/BUILD @@ -175,26 +175,35 @@ cc_library( alwayslink = True, ) -# FIXME: enable in future PRs -#cc_library( -# name = "rocfft_plugin", -# srcs = ["rocm_fft.cc"], -# hdrs = [], -# visibility = ["//visibility:public"], -# deps = [ -# ":rocm_platform_id", -# "//tensorflow/stream_executor:event", -# "//tensorflow/stream_executor:fft", -# "//tensorflow/stream_executor:plugin_registry", -# "//tensorflow/stream_executor:scratch_allocator", -# "//tenosrflow/stream_executor/gpu:gpu_stream_header", -# "//tensorflow/stream_executor/lib", -# "//tensorflow/stream_executor/platform", -# "//tensorflow/stream_executor/platform:dso_loader", -# "@local_config_rocm//rocm:rocm_headers", -# ] + if_static(["@local_config_rocm//rocm:rocfft"]), -# alwayslink = True, -#) +cc_library( + name = "rocfft_plugin", + srcs = if_rocm_is_configured(["rocm_fft.cc"]), + hdrs = if_rocm_is_configured(["rocm_fft.h"]), + visibility = ["//visibility:public"], + deps = if_rocm_is_configured([ + ":rocm_platform_id", + "//tensorflow/stream_executor:event", + "//tensorflow/stream_executor:fft", + "//tensorflow/stream_executor:plugin_registry", + "//tensorflow/stream_executor:scratch_allocator", + "//tensorflow/stream_executor/gpu:gpu_activation", + "//tensorflow/stream_executor/gpu:gpu_helpers_header", + "//tensorflow/stream_executor/gpu:gpu_executor_header", + "//tensorflow/stream_executor/gpu:gpu_stream_header", + "//tensorflow/stream_executor/gpu:gpu_kernel_header", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + "//tensorflow/stream_executor/platform:dso_loader", + "@local_config_rocm//rocm:rocm_headers", + ] + if_static([ + "@local_config_rocm//rocm:rocfft" + # Delete the following line once we switch the rocfft library from + # being dynamically linked (current behaviour) to being dynamically + # loaded (future behaviour) + ], ["@local_config_rocm//rocm:rocfft" + ])), + alwayslink = True, +) # FIXME: enable in future PRs #cc_library( @@ -263,7 +272,7 @@ cc_library( deps = if_rocm_is_configured([ # FIXME: enable in future PRs #":miopen_plugin", - #":rocfft_plugin", + ":rocfft_plugin", ":rocblas_plugin", #":rocrand_plugin", ":rocm_driver", diff --git a/tensorflow/stream_executor/rocm/rocm_fft.cc b/tensorflow/stream_executor/rocm/rocm_fft.cc new file mode 100644 index 0000000000..dd30911ead --- /dev/null +++ b/tensorflow/stream_executor/rocm/rocm_fft.cc @@ -0,0 +1,588 @@ +/* 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/stream_executor/rocm/rocm_fft.h" + +#include + +#include "tensorflow/stream_executor/gpu/gpu_activation.h" +#include "tensorflow/stream_executor/gpu/gpu_executor.h" +#include "tensorflow/stream_executor/gpu/gpu_helpers.h" +#include "tensorflow/stream_executor/rocm/rocm_platform_id.h" +#include "tensorflow/stream_executor/gpu/gpu_stream.h" +#include "tensorflow/stream_executor/device_memory.h" +#include "tensorflow/stream_executor/lib/env.h" +#include "tensorflow/stream_executor/lib/initialize.h" +#include "tensorflow/stream_executor/lib/status.h" +#include "tensorflow/stream_executor/platform/logging.h" +#include "tensorflow/stream_executor/platform/port.h" +#include "tensorflow/stream_executor/plugin_registry.h" +#include "tensorflow/stream_executor/stream_executor_internal.h" + +namespace stream_executor { +namespace gpu { + +PLUGIN_REGISTRY_DEFINE_PLUGIN_ID(kRocFftPlugin); + +namespace wrap { + +// This macro wraps a global identifier, given by __name, in a callable +// structure that loads the DLL symbol out of the DSO handle in a thread-safe +// manner on first use. This dynamic loading technique is used to avoid DSO +// dependencies on vendor libraries which may or may not be available in the +// deployed binary environment. +#define STREAM_EXECUTOR_ROCFFT_WRAP(__name) \ + struct WrapperShim__##__name { \ + template \ + hipfftResult operator()(GpuExecutor* parent, Args... args) { \ + gpu::ScopedActivateExecutorContext sac{parent}; \ + return ::__name(args...); \ + } \ + } __name; + +#define ROCFFT_ROUTINE_EACH(__macro) \ + __macro(hipfftDestroy) \ + __macro(hipfftSetStream) \ + __macro(hipfftPlan1d) \ + __macro(hipfftPlan2d) \ + __macro(hipfftPlan3d) \ + __macro(hipfftPlanMany) \ + __macro(hipfftCreate) \ + __macro(hipfftSetAutoAllocation) \ + __macro(hipfftSetWorkArea) \ + __macro(hipfftGetSize1d) \ + __macro(hipfftMakePlan1d) \ + __macro(hipfftGetSize2d) \ + __macro(hipfftMakePlan2d) \ + __macro(hipfftGetSize3d) \ + __macro(hipfftMakePlan3d) \ + __macro(hipfftGetSizeMany) \ + __macro(hipfftMakePlanMany) \ + __macro(hipfftExecD2Z) \ + __macro(hipfftExecZ2D) \ + __macro(hipfftExecC2C) \ + __macro(hipfftExecC2R) \ + __macro(hipfftExecZ2Z) \ + __macro(hipfftExecR2C) \ + +ROCFFT_ROUTINE_EACH(STREAM_EXECUTOR_ROCFFT_WRAP) + +} // namespace wrap + +namespace { + +// A helper function transforming gpu_fft arguments into rocFFT arguments. +hipfftType ROCMFftType(fft::Type type) { + switch (type) { + case fft::Type::kC2CForward: + case fft::Type::kC2CInverse: + return HIPFFT_C2C; + case fft::Type::kC2R: + return HIPFFT_C2R; + case fft::Type::kR2C: + return HIPFFT_R2C; + case fft::Type::kZ2ZForward: + case fft::Type::kZ2ZInverse: + return HIPFFT_Z2Z; + case fft::Type::kZ2D: + return HIPFFT_Z2D; + case fft::Type::kD2Z: + return HIPFFT_D2Z; + default: + LOG(FATAL) << "Invalid value of fft::Type."; + } +} + +// Associates the given stream with the given rocFFT plan. +bool SetStream(GpuExecutor *parent, hipfftHandle plan, Stream *stream) { + auto ret = wrap::hipfftSetStream(parent, plan, AsGpuStreamValue(stream)); + if (ret != HIPFFT_SUCCESS) { + LOG(ERROR) << "failed to run rocFFT routine hipfftSetStream: " << ret; + return false; + } + return true; +} + +} // namespace + +port::Status ROCMFftPlan::Initialize( + GpuExecutor *parent, Stream *stream, int rank, uint64 *elem_count, + uint64 *input_embed, uint64 input_stride, uint64 input_distance, + uint64 *output_embed, uint64 output_stride, uint64 output_distance, + fft::Type type, int batch_count, ScratchAllocator *scratch_allocator) { + if (IsInitialized()) { + LOG(FATAL) << "Try to repeatedly initialize."; + } + is_initialized_ = true; + int elem_count_[3], input_embed_[3], output_embed_[3]; + for (int i = 0; i < rank; ++i) { + elem_count_[i] = elem_count[i]; + if (input_embed) { + input_embed_[i] = input_embed[i]; + } + if (output_embed) { + output_embed_[i] = output_embed[i]; + } + } + parent_ = parent; + fft_type_ = type; + if (batch_count == 1 && input_embed == nullptr && output_embed == nullptr) { + hipfftResult_t ret; + if (scratch_allocator == nullptr) { + switch (rank) { + case 1: + // hipfftPlan1d + ret = wrap::hipfftPlan1d(parent, &plan_, elem_count_[0], + ROCMFftType(type), 1 /* = batch */); + if (ret != HIPFFT_SUCCESS) { + LOG(ERROR) << "failed to create rocFFT 1d plan:" << ret; + return port::Status{port::error::INTERNAL, + "Failed to create rocFFT 1d plan."}; + } + return port::Status::OK(); + case 2: + // hipfftPlan2d + ret = wrap::hipfftPlan2d(parent, &plan_, elem_count_[0], + elem_count_[1], ROCMFftType(type)); + if (ret != HIPFFT_SUCCESS) { + LOG(ERROR) << "failed to create rocFFT 2d plan:" << ret; + return port::Status{port::error::INTERNAL, + "Failed to create rocFFT 2d plan."}; + } + return port::Status::OK(); + case 3: + // hipfftPlan3d + ret = + wrap::hipfftPlan3d(parent, &plan_, elem_count_[0], elem_count_[1], + elem_count_[2], ROCMFftType(type)); + if (ret != HIPFFT_SUCCESS) { + LOG(ERROR) << "failed to create rocFFT 3d plan:" << ret; + return port::Status{port::error::INTERNAL, + "Failed to create rocFFT 3d plan."}; + } + return port::Status::OK(); + default: + LOG(ERROR) << "Invalid rank value for hipfftPlan. " + "Requested 1, 2, or 3, given: " + << rank; + return port::Status{port::error::INVALID_ARGUMENT, + "hipfftPlan only takes rank 1, 2, or 3."}; + } + } else { + ret = wrap::hipfftCreate(parent, &plan_); + if (ret != HIPFFT_SUCCESS) { + LOG(ERROR) << "failed to create rocFFT plan:" << ret; + return port::Status{port::error::INTERNAL, + "Failed to create rocFFT plan."}; + } + ret = wrap::hipfftSetAutoAllocation(parent, plan_, 0); + if (ret != HIPFFT_SUCCESS) { + LOG(ERROR) << "failed to set auto allocation for rocFFT plan:" << ret; + return port::Status{port::error::INTERNAL, + "Failed to set auto allocation for rocFFT plan."}; + } + size_t size_in_bytes; + switch (rank) { + case 1: + ret = wrap::hipfftMakePlan1d(parent, plan_, elem_count_[0], + ROCMFftType(type), /*batch=*/1, + &size_in_bytes); + if (ret != HIPFFT_SUCCESS) { + LOG(ERROR) << "failed to make rocFFT 1d plan:" << ret; + return port::Status{port::error::INTERNAL, + "Failed to make rocFFT 1d plan."}; + } + break; + case 2: + ret = wrap::hipfftMakePlan2d(parent, plan_, elem_count_[0], + elem_count_[1], ROCMFftType(type), + &size_in_bytes); + if (ret != HIPFFT_SUCCESS) { + LOG(ERROR) << "failed to make rocFFT 2d plan:" << ret; + return port::Status{port::error::INTERNAL, + "Failed to make rocFFT 2d plan."}; + } + break; + case 3: + ret = wrap::hipfftMakePlan3d(parent, plan_, elem_count_[0], + elem_count_[1], elem_count_[2], + ROCMFftType(type), &size_in_bytes); + if (ret != HIPFFT_SUCCESS) { + LOG(ERROR) << "failed to make rocFFT 3d plan:" << ret; + return port::Status{port::error::INTERNAL, + "Failed to make rocFFT 3d plan."}; + } + break; + default: + LOG(ERROR) << "Invalid rank value for hipfftPlan. " + "Requested 1, 2, or 3, given: " + << rank; + return port::Status{port::error::INVALID_ARGUMENT, + "hipfftPlan only takes rank 1, 2, or 3."}; + } + // TODO(yangzihao): refactor this code and the one with the same function + // in the batch mode. + if (size_in_bytes != 0) { + auto allocated = + scratch_allocator->AllocateBytes(stream, size_in_bytes); + if (!allocated.ok() || (scratch_ = allocated.ValueOrDie()) == nullptr) { + LOG(ERROR) << "failed to allocate work area."; + return allocated.status(); + } + } + // Connect work area with allocated space. + ret = wrap::hipfftSetWorkArea(parent, plan_, scratch_.opaque()); + if (ret != HIPFFT_SUCCESS) { + LOG(ERROR) << "failed to set work area for rocFFT plan:" << ret; + return port::Status{port::error::INTERNAL, + "Failed to set work area for rocFFT plan."}; + } + return port::Status::OK(); + } + } else { + // For either multiple batches or rank higher than 3, use hipfftPlanMany(). + if (scratch_allocator == nullptr) { + auto ret = wrap::hipfftPlanMany( + parent, &plan_, rank, elem_count_, + input_embed ? input_embed_ : nullptr, input_stride, input_distance, + output_embed ? output_embed_ : nullptr, output_stride, + output_distance, ROCMFftType(type), batch_count); + if (ret != HIPFFT_SUCCESS) { + LOG(ERROR) << "failed to create rocFFT batched plan:" << ret; + return port::Status{port::error::INTERNAL, + "Failed to create rocFFT bacthed plan."}; + } + } else { + auto ret = wrap::hipfftCreate(parent, &plan_); + if (ret != HIPFFT_SUCCESS) { + LOG(ERROR) << "failed to create rocFFT batched plan:" << ret; + return port::Status{port::error::INTERNAL, + "Failed to create rocFFT bacthed plan."}; + } + ret = wrap::hipfftSetAutoAllocation(parent, plan_, 0); + if (ret != HIPFFT_SUCCESS) { + LOG(ERROR) << "failed to set auto allocation for rocFFT batched plan:" + << ret; + return port::Status{ + port::error::INTERNAL, + "Failed to set auto allocation for rocFFT bacthed plan."}; + } + size_t size_in_bytes; + ret = wrap::hipfftMakePlanMany( + parent, plan_, rank, elem_count_, + input_embed ? input_embed_ : nullptr, input_stride, input_distance, + output_embed ? output_embed_ : nullptr, output_stride, + output_distance, ROCMFftType(type), batch_count, &size_in_bytes); + if (ret != HIPFFT_SUCCESS) { + LOG(ERROR) << "failed to make rocFFT batched plan:" << ret; + return port::Status{port::error::INTERNAL, + "Failed to make rocFFT bacthed plan."}; + } + if (size_in_bytes != 0) { + auto allocated = + scratch_allocator->AllocateBytes(stream, size_in_bytes); + if (!allocated.ok() || (scratch_ = allocated.ValueOrDie()) == nullptr) { + LOG(ERROR) << "failed to allocate work area."; + return allocated.status(); + } + } + // Connect work area with allocated space. + ret = wrap::hipfftSetWorkArea(parent, plan_, scratch_.opaque()); + if (ret != HIPFFT_SUCCESS) { + LOG(ERROR) << "failed to set work area for rocFFT batched plan:" << ret; + return port::Status{port::error::INTERNAL, + "Failed to set work area for rocFFT bacthed plan."}; + } + } + } + return port::Status::OK(); +} + +port::Status ROCMFftPlan::Initialize(GpuExecutor *parent, Stream *stream, + int rank, uint64 *elem_count, + fft::Type type, + ScratchAllocator *scratch_allocator) { + return Initialize(parent_, stream, rank, elem_count, + /*input_embed=*/nullptr, /*input_stride=*/0, + /*input_distance=*/0, + /*output_embed=*/nullptr, /*output_stride=*/0, + /*output_distance=*/0, type, 1, scratch_allocator); +} + +ROCMFftPlan::~ROCMFftPlan() { wrap::hipfftDestroy(parent_, plan_); } + +int ROCMFftPlan::GetFftDirection() const { + if (!IsInitialized()) { + LOG(FATAL) << "Try to get fft direction before initialization."; + } else { + switch (fft_type_) { + case fft::Type::kC2CForward: + case fft::Type::kZ2ZForward: + case fft::Type::kR2C: + case fft::Type::kD2Z: + return HIPFFT_FORWARD; + case fft::Type::kC2CInverse: + case fft::Type::kZ2ZInverse: + case fft::Type::kC2R: + case fft::Type::kZ2D: + return HIPFFT_BACKWARD; + default: + LOG(FATAL) << "Invalid value of fft::Type."; + } + } +} + +std::unique_ptr ROCMFft::Create1dPlan(Stream *stream, uint64 num_x, + fft::Type type, + bool in_place_fft) { + std::unique_ptr fft_plan_ptr{new ROCMFftPlan()}; + uint64 elem_count[1] = {num_x}; + port::Status status = fft_plan_ptr->Initialize( + parent_, stream, 1, elem_count, type, /*scratch_allocator=*/nullptr); + // TODO(yangzihao): In the future, send error msg back to TensorFlow + // so it can fail gracefully, + if (!status.ok()) { + LOG(FATAL) << "failed to initialize hipfft 1d plan: " + << status.error_message(); + } + return std::move(fft_plan_ptr); +} + +std::unique_ptr ROCMFft::Create1dPlanWithScratchAllocator( + Stream *stream, uint64 num_x, fft::Type type, bool in_place_fft, + ScratchAllocator *scratch_allocator) { + std::unique_ptr fft_plan_ptr{new ROCMFftPlan()}; + uint64 elem_count[1] = {num_x}; + port::Status status = fft_plan_ptr->Initialize(parent_, stream, 1, elem_count, + type, scratch_allocator); + if (!status.ok()) { + LOG(FATAL) + << "failed to initialize hipfft 1d plan with customized allocator: " + << status.error_message(); + } + return std::move(fft_plan_ptr); +} + +std::unique_ptr ROCMFft::Create2dPlan(Stream *stream, uint64 num_x, + uint64 num_y, fft::Type type, + bool in_place_fft) { + std::unique_ptr fft_plan_ptr{new ROCMFftPlan()}; + uint64 elem_count[2] = {num_x, num_y}; + port::Status status = fft_plan_ptr->Initialize( + parent_, stream, 1, elem_count, type, /*scratch_allocator=*/nullptr); + if (!status.ok()) { + LOG(FATAL) << "failed to initialize hipfft 2d plan: " + << status.error_message(); + } + return std::move(fft_plan_ptr); +} + +std::unique_ptr ROCMFft::Create2dPlanWithScratchAllocator( + Stream *stream, uint64 num_x, uint64 num_y, fft::Type type, + bool in_place_fft, ScratchAllocator *scratch_allocator) { + std::unique_ptr fft_plan_ptr{new ROCMFftPlan()}; + uint64 elem_count[2] = {num_x, num_y}; + port::Status status = fft_plan_ptr->Initialize(parent_, stream, 2, elem_count, + type, scratch_allocator); + if (!status.ok()) { + LOG(FATAL) + << "failed to initialize hipfft 2d plan with customized allocator: " + << status.error_message(); + } + return std::move(fft_plan_ptr); +} + +std::unique_ptr ROCMFft::Create3dPlan(Stream *stream, uint64 num_x, + uint64 num_y, uint64 num_z, + fft::Type type, + bool in_place_fft) { + std::unique_ptr fft_plan_ptr{new ROCMFftPlan()}; + uint64 elem_count[3] = {num_x, num_y, num_z}; + port::Status status = fft_plan_ptr->Initialize( + parent_, stream, 3, elem_count, type, /*scratch_allocator=*/nullptr); + if (!status.ok()) { + LOG(FATAL) << "failed to initialize hipfft 3d plan: " + << status.error_message(); + } + return std::move(fft_plan_ptr); +} + +std::unique_ptr ROCMFft::Create3dPlanWithScratchAllocator( + Stream *stream, uint64 num_x, uint64 num_y, uint64 num_z, fft::Type type, + bool in_place_fft, ScratchAllocator *scratch_allocator) { + std::unique_ptr fft_plan_ptr{new ROCMFftPlan()}; + uint64 elem_count[3] = {num_x, num_y, num_z}; + port::Status status = fft_plan_ptr->Initialize(parent_, stream, 3, elem_count, + type, scratch_allocator); + if (!status.ok()) { + LOG(FATAL) + << "failed to initialize hipfft 3d plan with customized allocator: " + << status.error_message(); + } + return std::move(fft_plan_ptr); +} + +std::unique_ptr ROCMFft::CreateBatchedPlan( + Stream *stream, int rank, uint64 *elem_count, uint64 *input_embed, + uint64 input_stride, uint64 input_distance, uint64 *output_embed, + uint64 output_stride, uint64 output_distance, fft::Type type, + bool in_place_fft, int batch_count) { + std::unique_ptr fft_plan_ptr{new ROCMFftPlan()}; + port::Status status = fft_plan_ptr->Initialize( + parent_, stream, rank, elem_count, input_embed, input_stride, + input_distance, output_embed, output_stride, output_distance, type, + batch_count, /*scratch_allocator=*/nullptr); + if (!status.ok()) { + LOG(FATAL) << "failed to initialize batched hipfft plan: " + << status.error_message(); + } + + return std::move(fft_plan_ptr); +} + +std::unique_ptr ROCMFft::CreateBatchedPlanWithScratchAllocator( + Stream *stream, int rank, uint64 *elem_count, uint64 *input_embed, + uint64 input_stride, uint64 input_distance, uint64 *output_embed, + uint64 output_stride, uint64 output_distance, fft::Type type, + bool in_place_fft, int batch_count, ScratchAllocator *scratch_allocator) { + std::unique_ptr fft_plan_ptr{new ROCMFftPlan()}; + port::Status status = fft_plan_ptr->Initialize( + parent_, stream, rank, elem_count, input_embed, input_stride, + input_distance, output_embed, output_stride, output_distance, type, + batch_count, scratch_allocator); + if (!status.ok()) { + LOG(FATAL) + << "failed to initialize batched hipfft plan with customized allocator: " + << status.error_message(); + } + return std::move(fft_plan_ptr); +} + +void ROCMFft::UpdatePlanWithScratchAllocator( + Stream *stream, fft::Plan *plan, ScratchAllocator *scratch_allocator) { + LOG(ERROR) << "update plan with scratch allocator not implemented"; +} + +template +bool ROCMFft::DoFftInternal(Stream *stream, fft::Plan *plan, FuncT hipfftExec, + const DeviceMemory &input, + DeviceMemory *output) { + ROCMFftPlan *rocm_fft_plan = dynamic_cast(plan); + if (rocm_fft_plan == nullptr) { + LOG(ERROR) << "the passed-in plan is not a ROCMFftPlan object."; + return false; + } + + if (!SetStream(parent_, rocm_fft_plan->GetPlan(), stream)) { + return false; + } + + auto ret = hipfftExec(parent_, rocm_fft_plan->GetPlan(), + GpuComplex(const_cast(GpuMemory(input))), + GpuComplex(GpuMemoryMutable(output))); + + if (ret != HIPFFT_SUCCESS) { + LOG(ERROR) << "failed to run rocFFT routine: " << ret; + return false; + } + + return true; +} + +template +bool ROCMFft::DoFftWithDirectionInternal(Stream *stream, fft::Plan *plan, + FuncT hipfftExec, + const DeviceMemory &input, + DeviceMemory *output) { + ROCMFftPlan *rocm_fft_plan = dynamic_cast(plan); + if (rocm_fft_plan == nullptr) { + LOG(ERROR) << "the passed-in plan is not a ROCMFftPlan object."; + return false; + } + + if (!SetStream(parent_, rocm_fft_plan->GetPlan(), stream)) { + return false; + } + + auto ret = hipfftExec(parent_, rocm_fft_plan->GetPlan(), + GpuComplex(const_cast(GpuMemory(input))), + GpuComplex(GpuMemoryMutable(output)), + rocm_fft_plan->GetFftDirection()); + + if (ret != HIPFFT_SUCCESS) { + LOG(ERROR) << "failed to run rocFFT routine: " << ret; + return false; + } + + return true; +} + +#define STREAM_EXECUTOR_ROCM_DEFINE_FFT(__type, __fft_type1, __fft_type2, \ + __fft_type3) \ + bool ROCMFft::DoFft(Stream *stream, fft::Plan *plan, \ + const DeviceMemory> &input, \ + DeviceMemory> *output) { \ + return DoFftWithDirectionInternal( \ + stream, plan, wrap::hipfftExec##__fft_type1, input, output); \ + } \ + bool ROCMFft::DoFft(Stream *stream, fft::Plan *plan, \ + const DeviceMemory<__type> &input, \ + DeviceMemory> *output) { \ + return DoFftInternal(stream, plan, wrap::hipfftExec##__fft_type2, input, \ + output); \ + } \ + bool ROCMFft::DoFft(Stream *stream, fft::Plan *plan, \ + const DeviceMemory> &input, \ + DeviceMemory<__type> *output) { \ + return DoFftInternal(stream, plan, wrap::hipfftExec##__fft_type3, input, \ + output); \ + } + +STREAM_EXECUTOR_ROCM_DEFINE_FFT(float, C2C, R2C, C2R) +STREAM_EXECUTOR_ROCM_DEFINE_FFT(double, Z2Z, D2Z, Z2D) + +#undef STREAM_EXECUTOR_ROCM_DEFINE_FFT + +} // namespace gpu + +void initialize_rocfft() { + port::Status status = + PluginRegistry::Instance()->RegisterFactory( + rocm::kROCmPlatformId, gpu::kRocFftPlugin, "rocFFT", + [](internal::StreamExecutorInterface *parent) -> fft::FftSupport * { + gpu::GpuExecutor *rocm_executor = + dynamic_cast(parent); + if (rocm_executor == nullptr) { + LOG(ERROR) + << "Attempting to initialize an instance of the rocFFT " + << "support library with a non-ROCM StreamExecutor"; + return nullptr; + } + + return new gpu::ROCMFft(rocm_executor); + }); + if (!status.ok()) { + LOG(ERROR) << "Unable to register rocFFT factory: " + << status.error_message(); + } + + PluginRegistry::Instance()->SetDefaultFactory( + rocm::kROCmPlatformId, PluginKind::kFft, gpu::kRocFftPlugin); +} + +} // namespace stream_executor + +REGISTER_MODULE_INITIALIZER(register_rocfft, + { stream_executor::initialize_rocfft(); }); diff --git a/tensorflow/stream_executor/rocm/rocm_fft.h b/tensorflow/stream_executor/rocm/rocm_fft.h new file mode 100644 index 0000000000..3dbe5800b7 --- /dev/null +++ b/tensorflow/stream_executor/rocm/rocm_fft.h @@ -0,0 +1,132 @@ +/* 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. +==============================================================================*/ + +// ROCM-specific support for FFT functionality -- this wraps the rocFFT library +// capabilities, and is only included into ROCM implementation code -- it will +// not introduce rocm headers into other code. + +#ifndef TENSORFLOW_STREAM_EXECUTOR_ROCM_ROCM_FFT_H_ +#define TENSORFLOW_STREAM_EXECUTOR_ROCM_ROCM_FFT_H_ + +#include "tensorflow/stream_executor/fft.h" +#include "tensorflow/stream_executor/platform/port.h" +#include "tensorflow/stream_executor/plugin_registry.h" +#include "tensorflow/stream_executor/scratch_allocator.h" +#include "rocm/include/rocfft/hipfft.h" + +namespace stream_executor { + +class Stream; + +namespace gpu { + +class GpuExecutor; + +// Opaque and unique indentifier for the rocFFT plugin. +extern const PluginId kRocFftPlugin; + +// ROCMFftPlan uses deferred initialization. Only a single call of +// Initialize() is allowed to properly create hipfft plan and set member +// variable is_initialized_ to true. Newly added interface that uses member +// variables should first check is_initialized_ to make sure that the values of +// member variables are valid. +class ROCMFftPlan : public fft::Plan { + public: + ROCMFftPlan() + : parent_(nullptr), + plan_(), + fft_type_(fft::Type::kInvalid), + scratch_(nullptr), + is_initialized_(false) {} + ~ROCMFftPlan() override; + + // Get FFT direction in hipFFT based on FFT type. + int GetFftDirection() const; + hipfftHandle GetPlan() const { + if (IsInitialized()) { + return plan_; + } else { + LOG(FATAL) << "Try to get hipfftHandle value before initialization."; + } + } + + // Initialize function for batched plan + port::Status Initialize(GpuExecutor *parent, Stream *stream, int rank, + uint64 *elem_count, uint64 *input_embed, + uint64 input_stride, uint64 input_distance, + uint64 *output_embed, uint64 output_stride, + uint64 output_distance, fft::Type type, + int batch_count, ScratchAllocator *scratch_allocator); + + // Initialize function for 1d,2d, and 3d plan + port::Status Initialize(GpuExecutor *parent, Stream *stream, int rank, + uint64 *elem_count, fft::Type type, + ScratchAllocator *scratch_allocator); + + protected: + bool IsInitialized() const { return is_initialized_; } + + private: + GpuExecutor *parent_; + hipfftHandle plan_; + fft::Type fft_type_; + DeviceMemory scratch_; + bool is_initialized_; +}; + +// FFT support for ROCM platform via rocFFT library. +// +// This satisfies the platform-agnostic FftSupport interface. +// +// Note that the hipFFT handle that this encapsulates is implicitly tied to the +// context (and, as a result, the device) that the parent GpuExecutor is tied +// to. This simply happens as an artifact of creating the hipFFT handle when a +// ROCM context is active. +// +// Thread-safe. The ROCM context associated with all operations is the ROCM +// context of parent_, so all context is explicit. +class ROCMFft : public fft::FftSupport { + public: + explicit ROCMFft(GpuExecutor *parent) : parent_(parent) {} + ~ROCMFft() override {} + + TENSORFLOW_STREAM_EXECUTOR_GPU_FFT_SUPPORT_OVERRIDES + + private: + GpuExecutor *parent_; + + // Two helper functions that execute dynload::hipfftExec?2?. + + // This is for complex to complex FFT, when the direction is required. + template + bool DoFftWithDirectionInternal(Stream *stream, fft::Plan *plan, + FuncT hipfft_exec, + const DeviceMemory &input, + DeviceMemory *output); + + // This is for complex to real or real to complex FFT, when the direction + // is implied. + template + bool DoFftInternal(Stream *stream, fft::Plan *plan, FuncT hipfft_exec, + const DeviceMemory &input, + DeviceMemory *output); + + SE_DISALLOW_COPY_AND_ASSIGN(ROCMFft); +}; + +} // namespace gpu +} // namespace stream_executor + +#endif // TENSORFLOW_STREAM_EXECUTOR_ROCM_ROCM_FFT_H_ -- GitLab From 234f47031bdd5346b6a3dc29670c215c94311cc8 Mon Sep 17 00:00:00 2001 From: Deven Desai Date: Thu, 31 Jan 2019 20:09:53 +0000 Subject: [PATCH 155/766] adding code for hiprand plugin --- tensorflow/stream_executor/rocm/BUILD | 50 ++++++----- tensorflow/stream_executor/rocm/rocm_rng.cc | 91 +++++++++++---------- 2 files changed, 75 insertions(+), 66 deletions(-) diff --git a/tensorflow/stream_executor/rocm/BUILD b/tensorflow/stream_executor/rocm/BUILD index c0da35121f..85ab38d424 100644 --- a/tensorflow/stream_executor/rocm/BUILD +++ b/tensorflow/stream_executor/rocm/BUILD @@ -244,26 +244,34 @@ cc_library( # alwayslink = True, #) -# FIXME: enable in future PRs -#cc_library( -# name = "rocrand_plugin", -# srcs = ["rocm_rng.cc"], -# hdrs = [], -# deps = [ -# ":rocm_gpu_executor", -# ":rocm_platform_id", -# "@local_config_rocm//rocm:rocm_headers", -# "//tensorflow/stream_executor:event", -# "//tensorflow/stream_executor:plugin_registry", -# "//tensorflow/stream_executor:rng", -# "//tenosrflow/stream_executor/gpu:gpu_activation_header", -# "//tenosrflow/stream_executor/gpu:gpu_stream_header", -# "//tensorflow/stream_executor/lib", -# "//tensorflow/stream_executor/platform", -# "//tensorflow/stream_executor/platform:dso_loader", -# ] + if_static(["@local_config_rocm//rocm:curand"]), -# alwayslink = True, -#) +cc_library( + name = "rocrand_plugin", + srcs = if_rocm_is_configured(["rocm_rng.cc"]), + hdrs = if_rocm_is_configured([]), + deps = if_rocm_is_configured([ + ":rocm_gpu_executor", + ":rocm_platform_id", + "@local_config_rocm//rocm:rocm_headers", + "//tensorflow/stream_executor:event", + "//tensorflow/stream_executor:plugin_registry", + "//tensorflow/stream_executor:rng", + "//tensorflow/stream_executor/gpu:gpu_activation_header", + "//tensorflow/stream_executor/gpu:gpu_helpers_header", + "//tensorflow/stream_executor/gpu:gpu_executor_header", + "//tensorflow/stream_executor/gpu:gpu_rng_header", + "//tensorflow/stream_executor/gpu:gpu_stream_header", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + "//tensorflow/stream_executor/platform:dso_loader", + ] + if_static([ + "@local_config_rocm//rocm:hiprand" + # Delete the following line once we switch the hiprand library from + # being dynamically linked (current behaviour) to being dynamically + # loaded (future behaviour) + ], ["@local_config_rocm//rocm:hiprand" + ])), + alwayslink = True, +) cc_library( name = "all_runtime", @@ -274,7 +282,7 @@ cc_library( #":miopen_plugin", ":rocfft_plugin", ":rocblas_plugin", - #":rocrand_plugin", + ":rocrand_plugin", ":rocm_driver", ":rocm_platform", ]), diff --git a/tensorflow/stream_executor/rocm/rocm_rng.cc b/tensorflow/stream_executor/rocm/rocm_rng.cc index 65acd03c92..7925057908 100644 --- a/tensorflow/stream_executor/rocm/rocm_rng.cc +++ b/tensorflow/stream_executor/rocm/rocm_rng.cc @@ -14,21 +14,22 @@ limitations under the License. ==============================================================================*/ #include "rocm/include/hiprand/hiprand.h" -#include "tensorflow/stream_executor/device_memory.h" +#include "tensorflow/stream_executor/gpu/gpu_rng.h" + #include "tensorflow/stream_executor/gpu/gpu_activation.h" #include "tensorflow/stream_executor/gpu/gpu_executor.h" #include "tensorflow/stream_executor/gpu/gpu_helpers.h" -#include "tensorflow/stream_executor/gpu/gpu_rng.h" +#include "tensorflow/stream_executor/rocm/rocm_platform_id.h" #include "tensorflow/stream_executor/gpu/gpu_stream.h" +#include "tensorflow/stream_executor/device_memory.h" #include "tensorflow/stream_executor/lib/env.h" #include "tensorflow/stream_executor/lib/initialize.h" #include "tensorflow/stream_executor/lib/status.h" #include "tensorflow/stream_executor/platform/logging.h" #include "tensorflow/stream_executor/rng.h" -#include "tensorflow/stream_executor/rocm/rocm_platform_id.h" // Formats hiprandStatus_t to output prettified values into a log stream. -std::ostream& operator<<(std::ostream& in, const hiprandStatus_t& status) { +std::ostream &operator<<(std::ostream &in, const hiprandStatus_t &status) { #define OSTREAM_HIPRAND_STATUS(__name) \ case HIPRAND_STATUS_##__name: \ in << "HIPRAND_STATUS_" #__name; \ @@ -60,7 +61,7 @@ PLUGIN_REGISTRY_DEFINE_PLUGIN_ID(kGpuRandPlugin); namespace wrap { -#define PERFTOOLS_GPUTOOLS_HIPRAND_WRAP(__name) \ +#define STREAM_EXECUTOR_HIPRAND_WRAP(__name) \ struct WrapperShim__##__name { \ template \ hiprandStatus_t operator()(GpuExecutor* parent, Args... args) { \ @@ -69,15 +70,15 @@ namespace wrap { } \ } __name; -PERFTOOLS_GPUTOOLS_HIPRAND_WRAP(hiprandCreateGenerator); -PERFTOOLS_GPUTOOLS_HIPRAND_WRAP(hiprandDestroyGenerator); -PERFTOOLS_GPUTOOLS_HIPRAND_WRAP(hiprandSetStream); -PERFTOOLS_GPUTOOLS_HIPRAND_WRAP(hiprandGenerateUniform); -PERFTOOLS_GPUTOOLS_HIPRAND_WRAP(hiprandGenerateUniformDouble); -PERFTOOLS_GPUTOOLS_HIPRAND_WRAP(hiprandSetPseudoRandomGeneratorSeed); -PERFTOOLS_GPUTOOLS_HIPRAND_WRAP(hiprandSetGeneratorOffset); -PERFTOOLS_GPUTOOLS_HIPRAND_WRAP(hiprandGenerateNormal); -PERFTOOLS_GPUTOOLS_HIPRAND_WRAP(hiprandGenerateNormalDouble); +STREAM_EXECUTOR_HIPRAND_WRAP(hiprandCreateGenerator); +STREAM_EXECUTOR_HIPRAND_WRAP(hiprandDestroyGenerator); +STREAM_EXECUTOR_HIPRAND_WRAP(hiprandSetStream); +STREAM_EXECUTOR_HIPRAND_WRAP(hiprandGenerateUniform); +STREAM_EXECUTOR_HIPRAND_WRAP(hiprandGenerateUniformDouble); +STREAM_EXECUTOR_HIPRAND_WRAP(hiprandSetPseudoRandomGeneratorSeed); +STREAM_EXECUTOR_HIPRAND_WRAP(hiprandSetGeneratorOffset); +STREAM_EXECUTOR_HIPRAND_WRAP(hiprandGenerateNormal); +STREAM_EXECUTOR_HIPRAND_WRAP(hiprandGenerateNormalDouble); } // namespace wrap @@ -245,40 +246,40 @@ bool GpuRng::SetSeed(Stream* stream, const uint8* seed, uint64 seed_bytes) { } } // namespace gpu -} // namespace stream_executor -namespace se = ::stream_executor; - -REGISTER_MODULE_INITIALIZER(register_hiprand, { - se::port::Status status = - se::PluginRegistry::Instance() - ->RegisterFactory( - se::rocm::kROCmPlatformId, se::gpu::kGpuRandPlugin, "hipRAND", - [](se::internal::StreamExecutorInterface* parent) - -> se::rng::RngSupport* { - se::gpu::GpuExecutor* rocm_executor = - dynamic_cast(parent); - if (rocm_executor == nullptr) { - LOG(ERROR) - << "Attempting to initialize an instance of the hipRAND " - << "support library with a non-ROCM StreamExecutor"; - return nullptr; - } - - se::gpu::GpuRng* rng = new se::gpu::GpuRng(rocm_executor); - if (!rng->Init()) { - // Note: Init() will log a more specific error. - delete rng; - return nullptr; - } - return rng; - }); +void initialize_rocrand() { + port::Status status = + PluginRegistry::Instance()->RegisterFactory( + rocm::kROCmPlatformId, gpu::kGpuRandPlugin, "rocRAND", + [](internal::StreamExecutorInterface* parent) -> rng::RngSupport* { + gpu::GpuExecutor* rocm_executor = + dynamic_cast(parent); + if (rocm_executor == nullptr) { + LOG(ERROR) + << "Attempting to initialize an instance of the hipRAND " + << "support library with a non-ROCM StreamExecutor"; + return nullptr; + } + + gpu::GpuRng* rng = new gpu::GpuRng(rocm_executor); + if (!rng->Init()) { + // Note: Init() will log a more specific error. + delete rng; + return nullptr; + } + return rng; + }); if (!status.ok()) { - LOG(ERROR) << "Unable to register hipRAND factory: " + LOG(ERROR) << "Unable to register rocRAND factory: " << status.error_message(); } - se::PluginRegistry::Instance()->SetDefaultFactory( - se::rocm::kROCmPlatformId, se::PluginKind::kRng, se::gpu::kGpuRandPlugin); -}); + PluginRegistry::Instance()->SetDefaultFactory( + rocm::kROCmPlatformId, PluginKind::kRng, gpu::kGpuRandPlugin); +} + +} // namespace stream_executor + +REGISTER_MODULE_INITIALIZER(register_rocrand, + { stream_executor::initialize_rocrand(); }); -- GitLab From 07b3f341995ff0b012bf9297f500a8e75c682f31 Mon Sep 17 00:00:00 2001 From: Deven Desai Date: Fri, 1 Feb 2019 02:47:14 +0000 Subject: [PATCH 156/766] changing rocblas, rocfft and rocrand from being dynamically linked to being dynmically loaded --- .../platform/default/dso_loader.cc | 52 ++++++++++++++++++- .../platform/default/dso_loader.h | 13 +++++ tensorflow/stream_executor/rocm/BUILD | 12 ----- tensorflow/stream_executor/rocm/rocm_blas.cc | 37 +++++++++++++ tensorflow/stream_executor/rocm/rocm_fft.cc | 34 ++++++++++++ tensorflow/stream_executor/rocm/rocm_rng.cc | 35 +++++++++++++ 6 files changed, 169 insertions(+), 14 deletions(-) diff --git a/tensorflow/stream_executor/platform/default/dso_loader.cc b/tensorflow/stream_executor/platform/default/dso_loader.cc index ad8112b831..3f2bb5e4dd 100644 --- a/tensorflow/stream_executor/platform/default/dso_loader.cc +++ b/tensorflow/stream_executor/platform/default/dso_loader.cc @@ -39,7 +39,7 @@ port::StatusOr GetDsoHandle(const string& name, const string& version) { port::Status status = port::Env::Default()->LoadLibrary(filename.c_str(), &dso_handle); if (status.ok()) { - LOG(INFO) << "Successfully opened CUDA library " << filename; + LOG(INFO) << "Successfully opened dynamic library " << filename; return dso_handle; } @@ -54,6 +54,7 @@ port::StatusOr GetDsoHandle(const string& name, const string& version) { return port::Status(port::error::FAILED_PRECONDITION, message); } } // namespace + namespace DsoLoader { port::StatusOr GetCudaDriverDsoHandle() { @@ -99,6 +100,27 @@ port::StatusOr GetCuptiDsoHandle() { port::StatusOr GetCudnnDsoHandle() { return GetDsoHandle("cudnn", GetCudnnVersion()); } + +port::StatusOr GetRocblasDsoHandle() { + return GetDsoHandle("rocblas", ""); +} + +port::StatusOr GetMiopenDsoHandle() { + return GetDsoHandle("MIOpen", ""); +} + +port::StatusOr GetRocfftDsoHandle() { + return GetDsoHandle("rocfft", ""); +} + +port::StatusOr GetRocrandDsoHandle() { + return GetDsoHandle("rocrand", ""); +} + +port::StatusOr GetHipDsoHandle() { + return GetDsoHandle("hip_hcc", ""); +} + } // namespace DsoLoader namespace CachedDsoLoader { @@ -131,11 +153,37 @@ port::StatusOr GetCuptiDsoHandle() { static auto result = new auto(DsoLoader::GetCuptiDsoHandle()); return *result; } - + port::StatusOr GetCudnnDsoHandle() { static auto result = new auto(DsoLoader::GetCudnnDsoHandle()); return *result; } + +port::StatusOr GetRocblasDsoHandle() { + static auto result = new auto(DsoLoader::GetRocblasDsoHandle()); + return result; +} + +port::StatusOr GetMiopenDsoHandle() { + static auto result = new auto(DsoLoader::GetMiopenDsoHandle()); + return result; +} + +port::StatusOr GetRocfftDsoHandle() { + static auto result = new auto(DsoLoader::GetRocfftDsoHandle()); + return result; +} + +port::StatusOr GetRocrandDsoHandle() { + static auto result = new auto(DsoLoader::GetRocrandDsoHandle()); + return result; +} + +port::StatusOr GetHipDsoHandle() { + static auto result = new auto(DsoLoader::GetHipDsoHandle()); + return result; +} + } // namespace CachedDsoLoader } // namespace internal } // namespace stream_executor diff --git a/tensorflow/stream_executor/platform/default/dso_loader.h b/tensorflow/stream_executor/platform/default/dso_loader.h index 45a8315b43..8da8ea7be6 100644 --- a/tensorflow/stream_executor/platform/default/dso_loader.h +++ b/tensorflow/stream_executor/platform/default/dso_loader.h @@ -41,6 +41,12 @@ port::StatusOr GetCufftDsoHandle(); port::StatusOr GetCurandDsoHandle(); port::StatusOr GetCuptiDsoHandle(); port::StatusOr GetCudnnDsoHandle(); + +port::StatusOr GetRocblasDsoHandle(); +port::StatusOr GetMiopenDsoHandle(); +port::StatusOr GetRocfftDsoHandle(); +port::StatusOr GetRocrandDsoHandle(); +port::StatusOr GetHipDsoHandle(); } // namespace DsoLoader // Wrapper around the DsoLoader that prevents us from dlopen'ing any of the DSOs @@ -54,7 +60,14 @@ port::StatusOr GetCufftDsoHandle(); port::StatusOr GetCurandDsoHandle(); port::StatusOr GetCuptiDsoHandle(); port::StatusOr GetCudnnDsoHandle(); + +port::StatusOr GetRocblasDsoHandle(); +port::StatusOr GetMiopenDsoHandle(); +port::StatusOr GetRocfftDsoHandle(); +port::StatusOr GetRocrandDsoHandle(); +port::StatusOr GetHipDsoHandle(); } // namespace CachedDsoLoader + } // namespace internal } // namespace stream_executor diff --git a/tensorflow/stream_executor/rocm/BUILD b/tensorflow/stream_executor/rocm/BUILD index 85ab38d424..f0b0582270 100644 --- a/tensorflow/stream_executor/rocm/BUILD +++ b/tensorflow/stream_executor/rocm/BUILD @@ -167,10 +167,6 @@ cc_library( "@local_config_rocm//rocm:rocm_headers", ] + if_static([ "@local_config_rocm//rocm:rocblas" - # Delete the following line once we switch the rocblas library from - # being dynamically linked (current behaviour) to being dynamically - # loaded (future behaviour) - ], ["@local_config_rocm//rocm:rocblas" ])), alwayslink = True, ) @@ -197,10 +193,6 @@ cc_library( "@local_config_rocm//rocm:rocm_headers", ] + if_static([ "@local_config_rocm//rocm:rocfft" - # Delete the following line once we switch the rocfft library from - # being dynamically linked (current behaviour) to being dynamically - # loaded (future behaviour) - ], ["@local_config_rocm//rocm:rocfft" ])), alwayslink = True, ) @@ -265,10 +257,6 @@ cc_library( "//tensorflow/stream_executor/platform:dso_loader", ] + if_static([ "@local_config_rocm//rocm:hiprand" - # Delete the following line once we switch the hiprand library from - # being dynamically linked (current behaviour) to being dynamically - # loaded (future behaviour) - ], ["@local_config_rocm//rocm:hiprand" ])), alwayslink = True, ) diff --git a/tensorflow/stream_executor/rocm/rocm_blas.cc b/tensorflow/stream_executor/rocm/rocm_blas.cc index b2e225433e..a626d168c2 100644 --- a/tensorflow/stream_executor/rocm/rocm_blas.cc +++ b/tensorflow/stream_executor/rocm/rocm_blas.cc @@ -36,6 +36,7 @@ limitations under the License. #include "tensorflow/stream_executor/lib/status.h" #include "tensorflow/stream_executor/lib/status_macros.h" #include "tensorflow/stream_executor/lib/stringprintf.h" +#include "tensorflow/stream_executor/platform/dso_loader.h" #include "tensorflow/stream_executor/platform/logging.h" #include "tensorflow/stream_executor/platform/port.h" #include "tensorflow/stream_executor/plugin_registry.h" @@ -49,6 +50,7 @@ PLUGIN_REGISTRY_DEFINE_PLUGIN_ID(kRocBlasPlugin); namespace wrap { +#ifdef PLATFORM_GOOGLE #define STREAM_EXECUTOR_ROCBLAS_WRAP(__name) \ struct WrapperShim__##__name { \ static const char* kName; \ @@ -63,6 +65,41 @@ namespace wrap { #define STREAM_EXECUTOR_ROCBLAS_V2_WRAP(__name) \ STREAM_EXECUTOR_ROCBLAS_WRAP(__name) +#else + +#define STREAM_EXECUTOR_ROCBLAS_WRAP(__name) \ + struct DynLoadShim__##__name { \ + static const char* kName; \ + using FuncPtrT = std::add_pointer::type; \ + static void* GetDsoHandle() { \ + auto s = internal::CachedDsoLoader::GetRocblasDsoHandle(); \ + return s.ValueOrDie(); \ + } \ + static FuncPtrT LoadOrDie() { \ + void* f; \ + auto s = port::Env::Default()->GetSymbolFromLibrary(GetDsoHandle(), \ + kName, &f); \ + CHECK(s.ok()) << "could not find " << kName \ + << " in rocblas DSO; dlerror: " << s.error_message(); \ + return reinterpret_cast(f); \ + } \ + static FuncPtrT DynLoad() { \ + static FuncPtrT f = LoadOrDie(); \ + return f; \ + } \ + template \ + rocblas_status operator()(GpuExecutor* parent, Args... args) { \ + gpu::ScopedActivateExecutorContext sac{parent}; \ + return DynLoad()(args...); \ + } \ + } __name; \ + const char* DynLoadShim__##__name::kName = #__name; + +#define STREAM_EXECUTOR_ROCBLAS_V2_WRAP(__name) \ + STREAM_EXECUTOR_ROCBLAS_WRAP(__name) + +#endif + #define ROCBLAS_BLAS_ROUTINE_EACH(__macro) \ __macro(rocblas_snrm2) __macro(rocblas_dnrm2) /* __macro(rocblas_scnrm2) \ __macro(rocblas_dznrm2) */ \ diff --git a/tensorflow/stream_executor/rocm/rocm_fft.cc b/tensorflow/stream_executor/rocm/rocm_fft.cc index dd30911ead..e8a72f61d3 100644 --- a/tensorflow/stream_executor/rocm/rocm_fft.cc +++ b/tensorflow/stream_executor/rocm/rocm_fft.cc @@ -26,6 +26,7 @@ limitations under the License. #include "tensorflow/stream_executor/lib/env.h" #include "tensorflow/stream_executor/lib/initialize.h" #include "tensorflow/stream_executor/lib/status.h" +#include "tensorflow/stream_executor/platform/dso_loader.h" #include "tensorflow/stream_executor/platform/logging.h" #include "tensorflow/stream_executor/platform/port.h" #include "tensorflow/stream_executor/plugin_registry.h" @@ -38,6 +39,7 @@ PLUGIN_REGISTRY_DEFINE_PLUGIN_ID(kRocFftPlugin); namespace wrap { +#ifdef PLATFORM_GOOGLE // This macro wraps a global identifier, given by __name, in a callable // structure that loads the DLL symbol out of the DSO handle in a thread-safe // manner on first use. This dynamic loading technique is used to avoid DSO @@ -52,6 +54,38 @@ namespace wrap { } \ } __name; +#else + +#define STREAM_EXECUTOR_ROCFFT_WRAP(__name) \ + struct DynLoadShim__##__name { \ + static const char *kName; \ + using FuncPtrT = std::add_pointer::type; \ + static void *GetDsoHandle() { \ + auto s = internal::CachedDsoLoader::GetRocfftDsoHandle(); \ + return s.ValueOrDie(); \ + } \ + static FuncPtrT LoadOrDie() { \ + void *f; \ + auto s = port::Env::Default()->GetSymbolFromLibrary(GetDsoHandle(), \ + kName, &f); \ + CHECK(s.ok()) << "could not find " << kName \ + << " in rocfft DSO; dlerror: " << s.error_message(); \ + return reinterpret_cast(f); \ + } \ + static FuncPtrT DynLoad() { \ + static FuncPtrT f = LoadOrDie(); \ + return f; \ + } \ + template \ + hipfftResult operator()(GpuExecutor *parent, Args... args) { \ + gpu::ScopedActivateExecutorContext sac{parent}; \ + return DynLoad()(args...); \ + } \ + } __name; \ + const char *DynLoadShim__##__name::kName = #__name; + +#endif + #define ROCFFT_ROUTINE_EACH(__macro) \ __macro(hipfftDestroy) \ __macro(hipfftSetStream) \ diff --git a/tensorflow/stream_executor/rocm/rocm_rng.cc b/tensorflow/stream_executor/rocm/rocm_rng.cc index 7925057908..2779784570 100644 --- a/tensorflow/stream_executor/rocm/rocm_rng.cc +++ b/tensorflow/stream_executor/rocm/rocm_rng.cc @@ -25,6 +25,7 @@ limitations under the License. #include "tensorflow/stream_executor/lib/env.h" #include "tensorflow/stream_executor/lib/initialize.h" #include "tensorflow/stream_executor/lib/status.h" +#include "tensorflow/stream_executor/platform/dso_loader.h" #include "tensorflow/stream_executor/platform/logging.h" #include "tensorflow/stream_executor/rng.h" @@ -61,6 +62,8 @@ PLUGIN_REGISTRY_DEFINE_PLUGIN_ID(kGpuRandPlugin); namespace wrap { +#ifdef PLATFORM_GOOGLE + #define STREAM_EXECUTOR_HIPRAND_WRAP(__name) \ struct WrapperShim__##__name { \ template \ @@ -70,6 +73,38 @@ namespace wrap { } \ } __name; +#else + +#define STREAM_EXECUTOR_HIPRAND_WRAP(__name) \ + struct DynLoadShim__##__name { \ + static const char *kName; \ + using FuncPtrT = std::add_pointer::type; \ + static void *GetDsoHandle() { \ + auto s = internal::CachedDsoLoader::GetRocrandDsoHandle(); \ + return s.ValueOrDie(); \ + } \ + static FuncPtrT LoadOrDie() { \ + void *f; \ + auto s = port::Env::Default()->GetSymbolFromLibrary(GetDsoHandle(), \ + kName, &f); \ + CHECK(s.ok()) << "could not find " << kName \ + << " in rocrand DSO; dlerror: " << s.error_message(); \ + return reinterpret_cast(f); \ + } \ + static FuncPtrT DynLoad() { \ + static FuncPtrT f = LoadOrDie(); \ + return f; \ + } \ + template \ + hiprandStatus operator()(GpuExecutor *parent, Args... args) { \ + gpu::ScopedActivateExecutorContext sac{parent}; \ + return DynLoad()(args...); \ + } \ + } __name; \ + const char *DynLoadShim__##__name::kName = #__name; + +#endif + STREAM_EXECUTOR_HIPRAND_WRAP(hiprandCreateGenerator); STREAM_EXECUTOR_HIPRAND_WRAP(hiprandDestroyGenerator); STREAM_EXECUTOR_HIPRAND_WRAP(hiprandSetStream); -- GitLab From 834a3f7395a9db748349e0bf9dfff7af558cb4fb Mon Sep 17 00:00:00 2001 From: Deven Desai Date: Fri, 1 Feb 2019 17:59:02 +0000 Subject: [PATCH 157/766] changing the rocm_driver API from being dynamically linked to being dynamically loaded --- tensorflow/stream_executor/rocm/BUILD | 149 +++++++++--------- .../stream_executor/rocm/rocm_driver.cc | 125 +++++++-------- .../rocm/rocm_driver_wrapper.h | 147 +++++++++++++++++ 3 files changed, 285 insertions(+), 136 deletions(-) create mode 100644 tensorflow/stream_executor/rocm/rocm_driver_wrapper.h diff --git a/tensorflow/stream_executor/rocm/BUILD b/tensorflow/stream_executor/rocm/BUILD index f0b0582270..5190b551f8 100644 --- a/tensorflow/stream_executor/rocm/BUILD +++ b/tensorflow/stream_executor/rocm/BUILD @@ -47,7 +47,7 @@ cc_library( cc_library( name = "rocm_driver", srcs = if_rocm_is_configured(["rocm_driver.cc"]), - hdrs = [], + hdrs = if_rocm_is_configured(["rocm_driver_wrapper.h"]), deps = if_rocm_is_configured([ ":rocm_diagnostics", "@com_google_absl//absl/base", @@ -57,6 +57,7 @@ cc_library( "//tensorflow/stream_executor/gpu:gpu_driver_header", "//tensorflow/stream_executor/lib", "//tensorflow/stream_executor/platform", + "//tensorflow/stream_executor/platform:dso_loader", "@local_config_rocm//rocm:rocm_headers", ]), ) @@ -141,60 +142,60 @@ cc_library( ) cc_library( - name = "rocblas_plugin", - srcs = if_rocm_is_configured(["rocm_blas.cc"]), - hdrs = if_rocm_is_configured(["rocm_blas.h"]), - visibility = ["//visibility:public"], - deps = if_rocm_is_configured([ - ":rocm_gpu_executor", - ":rocm_platform_id", - "//third_party/eigen3", - "//tensorflow/core:lib_internal", - "//tensorflow/stream_executor", - "//tensorflow/stream_executor:event", - "//tensorflow/stream_executor:host_or_device_scalar", - "//tensorflow/stream_executor:plugin_registry", - "//tensorflow/stream_executor:scratch_allocator", - "//tensorflow/stream_executor:timer", - "//tensorflow/stream_executor/gpu:gpu_activation", - "//tensorflow/stream_executor/gpu:gpu_helpers_header", - "//tensorflow/stream_executor/gpu:gpu_stream_header", - "//tensorflow/stream_executor/gpu:gpu_timer_header", - "//tensorflow/stream_executor/lib", - "//tensorflow/stream_executor/platform", - "//tensorflow/stream_executor/platform:dso_loader", - "@com_google_absl//absl/strings", - "@local_config_rocm//rocm:rocm_headers", - ] + if_static([ - "@local_config_rocm//rocm:rocblas" - ])), - alwayslink = True, + name = "rocblas_plugin", + srcs = if_rocm_is_configured(["rocm_blas.cc"]), + hdrs = if_rocm_is_configured(["rocm_blas.h"]), + visibility = ["//visibility:public"], + deps = if_rocm_is_configured([ + ":rocm_gpu_executor", + ":rocm_platform_id", + "//third_party/eigen3", + "//tensorflow/core:lib_internal", + "//tensorflow/stream_executor", + "//tensorflow/stream_executor:event", + "//tensorflow/stream_executor:host_or_device_scalar", + "//tensorflow/stream_executor:plugin_registry", + "//tensorflow/stream_executor:scratch_allocator", + "//tensorflow/stream_executor:timer", + "//tensorflow/stream_executor/gpu:gpu_activation", + "//tensorflow/stream_executor/gpu:gpu_helpers_header", + "//tensorflow/stream_executor/gpu:gpu_stream_header", + "//tensorflow/stream_executor/gpu:gpu_timer_header", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + "//tensorflow/stream_executor/platform:dso_loader", + "@com_google_absl//absl/strings", + "@local_config_rocm//rocm:rocm_headers", + ] + if_static([ + "@local_config_rocm//rocm:rocblas", + ])), + alwayslink = True, ) cc_library( - name = "rocfft_plugin", - srcs = if_rocm_is_configured(["rocm_fft.cc"]), - hdrs = if_rocm_is_configured(["rocm_fft.h"]), - visibility = ["//visibility:public"], - deps = if_rocm_is_configured([ - ":rocm_platform_id", - "//tensorflow/stream_executor:event", - "//tensorflow/stream_executor:fft", - "//tensorflow/stream_executor:plugin_registry", - "//tensorflow/stream_executor:scratch_allocator", - "//tensorflow/stream_executor/gpu:gpu_activation", - "//tensorflow/stream_executor/gpu:gpu_helpers_header", - "//tensorflow/stream_executor/gpu:gpu_executor_header", - "//tensorflow/stream_executor/gpu:gpu_stream_header", - "//tensorflow/stream_executor/gpu:gpu_kernel_header", - "//tensorflow/stream_executor/lib", - "//tensorflow/stream_executor/platform", - "//tensorflow/stream_executor/platform:dso_loader", - "@local_config_rocm//rocm:rocm_headers", - ] + if_static([ - "@local_config_rocm//rocm:rocfft" - ])), - alwayslink = True, + name = "rocfft_plugin", + srcs = if_rocm_is_configured(["rocm_fft.cc"]), + hdrs = if_rocm_is_configured(["rocm_fft.h"]), + visibility = ["//visibility:public"], + deps = if_rocm_is_configured([ + ":rocm_platform_id", + "//tensorflow/stream_executor:event", + "//tensorflow/stream_executor:fft", + "//tensorflow/stream_executor:plugin_registry", + "//tensorflow/stream_executor:scratch_allocator", + "//tensorflow/stream_executor/gpu:gpu_activation", + "//tensorflow/stream_executor/gpu:gpu_helpers_header", + "//tensorflow/stream_executor/gpu:gpu_executor_header", + "//tensorflow/stream_executor/gpu:gpu_stream_header", + "//tensorflow/stream_executor/gpu:gpu_kernel_header", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + "//tensorflow/stream_executor/platform:dso_loader", + "@local_config_rocm//rocm:rocm_headers", + ] + if_static([ + "@local_config_rocm//rocm:rocfft", + ])), + alwayslink = True, ) # FIXME: enable in future PRs @@ -237,28 +238,28 @@ cc_library( #) cc_library( - name = "rocrand_plugin", - srcs = if_rocm_is_configured(["rocm_rng.cc"]), - hdrs = if_rocm_is_configured([]), - deps = if_rocm_is_configured([ - ":rocm_gpu_executor", - ":rocm_platform_id", - "@local_config_rocm//rocm:rocm_headers", - "//tensorflow/stream_executor:event", - "//tensorflow/stream_executor:plugin_registry", - "//tensorflow/stream_executor:rng", - "//tensorflow/stream_executor/gpu:gpu_activation_header", - "//tensorflow/stream_executor/gpu:gpu_helpers_header", - "//tensorflow/stream_executor/gpu:gpu_executor_header", - "//tensorflow/stream_executor/gpu:gpu_rng_header", - "//tensorflow/stream_executor/gpu:gpu_stream_header", - "//tensorflow/stream_executor/lib", - "//tensorflow/stream_executor/platform", - "//tensorflow/stream_executor/platform:dso_loader", - ] + if_static([ - "@local_config_rocm//rocm:hiprand" - ])), - alwayslink = True, + name = "rocrand_plugin", + srcs = if_rocm_is_configured(["rocm_rng.cc"]), + hdrs = if_rocm_is_configured([]), + deps = if_rocm_is_configured([ + ":rocm_gpu_executor", + ":rocm_platform_id", + "@local_config_rocm//rocm:rocm_headers", + "//tensorflow/stream_executor:event", + "//tensorflow/stream_executor:plugin_registry", + "//tensorflow/stream_executor:rng", + "//tensorflow/stream_executor/gpu:gpu_activation_header", + "//tensorflow/stream_executor/gpu:gpu_helpers_header", + "//tensorflow/stream_executor/gpu:gpu_executor_header", + "//tensorflow/stream_executor/gpu:gpu_rng_header", + "//tensorflow/stream_executor/gpu:gpu_stream_header", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + "//tensorflow/stream_executor/platform:dso_loader", + ] + if_static([ + "@local_config_rocm//rocm:hiprand", + ])), + alwayslink = True, ) cc_library( diff --git a/tensorflow/stream_executor/rocm/rocm_driver.cc b/tensorflow/stream_executor/rocm/rocm_driver.cc index 39d52d2830..73b1b350f7 100644 --- a/tensorflow/stream_executor/rocm/rocm_driver.cc +++ b/tensorflow/stream_executor/rocm/rocm_driver.cc @@ -36,6 +36,7 @@ limitations under the License. #include "tensorflow/stream_executor/platform/logging.h" #include "tensorflow/stream_executor/platform/mutex.h" #include "tensorflow/stream_executor/platform/port.h" +#include "tensorflow/stream_executor/rocm/rocm_driver_wrapper.h" bool FLAGS_gpuexec_rocm_driver_inject_init_error = false; bool FLAGS_gpuexec_rocm_sync_around_driver_calls = false; @@ -143,7 +144,7 @@ string MemorySpaceString(MemorySpace memory_space) { // HIP driver (e.g., this value is not our cached view of the current device). static int CurrentDeviceOrDie() { int current = -1; - hipError_t result = hipGetDevice(¤t); + hipError_t result = tensorflow::wrap::hipGetDevice(¤t); if (result != hipSuccess) { LOG(FATAL) << "failed to query current device: " << ToString(result); } @@ -154,7 +155,7 @@ namespace { // Call hipDeviceSynchronize and crash if it doesn't succeed. void SynchronizeOrDie() { - auto res = hipDeviceSynchronize(); + auto res = tensorflow::wrap::hipDeviceSynchronize(); if (res != hipSuccess) { LOG(FATAL) << "Synchronize found " << ToString(res) << " :: " << port::CurrentStackTrace(); @@ -197,7 +198,7 @@ ScopedActivateContext::ScopedActivateContext(GpuContext* context) { << tls->current_device_ordinal << " to " << context->device_ordinal(); // Set the device and update thread local. - CHECK_EQ(hipSuccess, hipSetDevice(context->device_ordinal())); + CHECK_EQ(hipSuccess, tensorflow::wrap::hipSetDevice(context->device_ordinal())); tls->current_device_ordinal = context->device_ordinal(); } @@ -225,7 +226,7 @@ ScopedActivateContext::~ScopedActivateContext() { << to_restore_->device_ordinal(); // Set context and update thread local. - CHECK_EQ(hipSuccess, hipSetDevice(to_restore_->device_ordinal())); + CHECK_EQ(hipSuccess, tensorflow::wrap::hipSetDevice(to_restore_->device_ordinal())); tls->current_device_ordinal = to_restore_->device_ordinal(); } @@ -261,7 +262,7 @@ string ROCMPointerToMemorySpaceString(hipDeviceptr_t pointer) { // in the process of querying. string ROCMPointersToCanAccessString(hipDeviceptr_t from, hipDeviceptr_t to) { hipPointerAttribute_t from_pointerAttributes; - hipError_t result = hipPointerGetAttributes(&from_pointerAttributes, from); + hipError_t result = tensorflow::wrap::hipPointerGetAttributes(&from_pointerAttributes, from); if (result != hipSuccess) { LOG(ERROR) << "could not retrieve source pointer's device: " << ToString(result); @@ -269,7 +270,7 @@ string ROCMPointersToCanAccessString(hipDeviceptr_t from, hipDeviceptr_t to) { } hipPointerAttribute_t to_pointerAttributes; - result = hipPointerGetAttributes(&to_pointerAttributes, to); + result = tensorflow::wrap::hipPointerGetAttributes(&to_pointerAttributes, to); if (result != hipSuccess) { LOG(ERROR) << "could not retrieve destination pointer's device: " << ToString(result); @@ -289,7 +290,7 @@ static port::Status InternalInit() { if (FLAGS_gpuexec_rocm_driver_inject_init_error) { LOG(ERROR) << "injecting ROCM init error; initialization will fail"; } else { - res = hipInit(0 /* = flags */); + res = tensorflow::wrap::hipInit(0 /* = flags */); } if (res == hipSuccess) { @@ -322,7 +323,7 @@ static port::Status InternalInit() { /* static */ port::Status GpuDriver::GetDevice(int device_ordinal, hipDevice_t* device) { - hipError_t res = hipDeviceGet(device, device_ordinal); + hipError_t res = tensorflow::wrap::hipDeviceGet(device, device_ordinal); if (res == hipSuccess) { return port::Status::OK(); } @@ -336,7 +337,7 @@ static port::Status InternalInit() { string* device_name) { static const size_t kCharLimit = 64; absl::InlinedVector chars(kCharLimit); - hipError_t res = hipDeviceGetName(chars.begin(), kCharLimit - 1, device); + hipError_t res = tensorflow::wrap::hipDeviceGetName(chars.begin(), kCharLimit - 1, device); if (res != hipSuccess) { LOG(ERROR) << "failed to get device name for " << device << ": " << ToString(res); @@ -382,7 +383,7 @@ bool DeviceOptionsToContextFlags(const DeviceOptions& device_options, /* static */ bool GpuDriver::FuncSetCacheConfig(hipFunction_t function, hipFuncCache_t cache_config) { - hipError_t res = hipFuncSetCacheConfig(function, cache_config); + hipError_t res = tensorflow::wrap::hipFuncSetCacheConfig(function, cache_config); if (res != hipSuccess) { LOG(ERROR) << "failed to set ROCM kernel cache config. kernel: " << function << ", config: " << cache_config << ", result: " << ToString(res); @@ -396,7 +397,7 @@ bool DeviceOptionsToContextFlags(const DeviceOptions& device_options, GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { hipSharedMemConfig shared_mem_config; ScopedActivateContext activation{context}; - hipError_t result = hipDeviceGetSharedMemConfig(&shared_mem_config); + hipError_t result = tensorflow::wrap::hipDeviceGetSharedMemConfig(&shared_mem_config); if (result != hipSuccess) { LOG(ERROR) << "failed to get ROCM device shared memory config. " << "Context device ID: " << context->device_ordinal() @@ -411,7 +412,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { /* static */ port::Status GpuDriver::ContextSetSharedMemConfig( GpuContext* context, hipSharedMemConfig shared_mem_config) { ScopedActivateContext activation{context}; - hipError_t result = hipDeviceSetSharedMemConfig(shared_mem_config); + hipError_t result = tensorflow::wrap::hipDeviceSetSharedMemConfig(shared_mem_config); if (result != hipSuccess) { LOG(ERROR) << "failed to set ROCM device shared memory config. " << "Context device ID: " << context->device_ordinal() @@ -435,7 +436,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { << " gdy: " << grid_dim_y << " gdz: " << grid_dim_z << " bdx: " << block_dim_x << " bdy: " << block_dim_y << " bdz: " << block_dim_z << " smem: " << shared_mem_bytes; - hipError_t res = hipModuleLaunchKernel( + hipError_t res = tensorflow::wrap::hipModuleLaunchKernel( function, grid_dim_x, grid_dim_y, grid_dim_z, block_dim_x, block_dim_y, block_dim_z, shared_mem_bytes, stream, kernel_params, extra); if (res != hipSuccess) { @@ -471,7 +472,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { ScopedActivateContext activation{context}; void* hsaco_data = const_cast(hsaco_contents); - hipError_t res = hipModuleLoadData(module, hsaco_data); + hipError_t res = tensorflow::wrap::hipModuleLoadData(module, hsaco_data); if (res != hipSuccess) { LOG(ERROR) << "failed to load HSACO: " << ToString(res); @@ -491,7 +492,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { hipDeviceptr_t location, uint8 value, size_t size) { ScopedActivateContext activation{context}; - hipError_t res = hipMemset(location, value, size); + hipError_t res = tensorflow::wrap::hipMemset(location, value, size); if (res != hipSuccess) { LOG(ERROR) << "failed to memset memory: " << ToString(res); return false; @@ -513,7 +514,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { return false; } hipError_t res = - hipMemset(pointer, static_cast(value), uint32_count * 4); + tensorflow::wrap::hipMemset(pointer, static_cast(value), uint32_count * 4); if (res != hipSuccess) { LOG(ERROR) << "failed to memset memory: " << ToString(res); return false; @@ -527,7 +528,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { size_t uint32_count, GpuStreamHandle stream) { ScopedActivateContext activation{context}; - hipError_t res = hipMemsetAsync(location, value, uint32_count, stream); + hipError_t res = tensorflow::wrap::hipMemsetAsync(location, value, uint32_count, stream); if (res != hipSuccess) { LOG(ERROR) << "failed to enqueue async memset operation: " << ToString(res); return false; @@ -552,7 +553,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { LOG(ERROR) << "failed to memset memory"; return false; } - hipError_t res = hipMemsetAsync(pointer, value, uint32_count * 4, stream); + hipError_t res = tensorflow::wrap::hipMemsetAsync(pointer, value, uint32_count * 4, stream); if (res != hipSuccess) { LOG(ERROR) << "failed to enqueue async memset operation: " << ToString(res); return false; @@ -565,7 +566,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { GpuStreamHandle stream, StreamCallback callback, void* data) { - hipError_t res = hipStreamAddCallback(stream, (hipStreamCallback_t)callback, + hipError_t res = tensorflow::wrap::hipStreamAddCallback(stream, (hipStreamCallback_t)callback, data, 0 /* = flags */); if (res != hipSuccess) { LOG(ERROR) << "unable to add host callback: " << ToString(res); @@ -580,7 +581,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { hipFunction_t* function) { ScopedActivateContext activated{context}; CHECK(module != nullptr && kernel_name != nullptr); - hipError_t res = hipModuleGetFunction(function, module, kernel_name); + hipError_t res = tensorflow::wrap::hipModuleGetFunction(function, module, kernel_name); if (res != hipSuccess) { LOG(ERROR) << "failed to get kernel \"" << kernel_name << "\" from module: " << ToString(res); @@ -598,7 +599,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { ScopedActivateContext activated{context}; CHECK(module != nullptr && symbol_name != nullptr && (dptr != nullptr || bytes != nullptr)); - hipError_t res = hipModuleGetGlobal(dptr, bytes, module, symbol_name); + hipError_t res = tensorflow::wrap::hipModuleGetGlobal(dptr, bytes, module, symbol_name); if (res != hipSuccess) { // symbol may not be found in the current module, but it may reside in // another module. @@ -613,7 +614,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { /* static */ void GpuDriver::UnloadModule(GpuContext* context, hipModule_t module) { ScopedActivateContext activated{context}; - hipError_t res = hipModuleUnload(module); + hipError_t res = tensorflow::wrap::hipModuleUnload(module); if (res != hipSuccess) { LOG(ERROR) << "failed to unload module " << module << "; leaking: " << ToString(res); @@ -623,7 +624,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { /* static */ bool GpuDriver::CreateStream(GpuContext* context, GpuStreamHandle* stream) { ScopedActivateContext activated{context}; - hipError_t res = hipStreamCreateWithFlags( + hipError_t res = tensorflow::wrap::hipStreamCreateWithFlags( stream, hipStreamDefault); // switch to hipStreamNonBlocking? if (res != hipSuccess) { LOG(ERROR) << "could not allocate ROCM stream for device " @@ -643,7 +644,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { } ScopedActivateContext activated{context}; - hipError_t res = hipStreamDestroy(*stream); + hipError_t res = tensorflow::wrap::hipStreamDestroy(*stream); if (res != hipSuccess) { LOG(ERROR) << "failed to destroy ROCM stream for device " << context->device_ordinal() << ": " << ToString(res); @@ -658,7 +659,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { uint64 bytes) { ScopedActivateContext activated{context}; hipDeviceptr_t result = 0; - hipError_t res = hipMalloc(&result, bytes); + hipError_t res = tensorflow::wrap::hipMallocVanilla(&result, bytes); if (res != hipSuccess) { LOG(ERROR) << "failed to allocate " << port::HumanReadableNumBytes::ToString(bytes) << " (" << bytes @@ -675,7 +676,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { void* location) { ScopedActivateContext activation{context}; hipDeviceptr_t pointer = absl::bit_cast(location); - hipError_t res = hipFree(pointer); + hipError_t res = tensorflow::wrap::hipFree(pointer); if (res != hipSuccess) { LOG(ERROR) << "failed to free device memory at " << location << "; result: " << ToString(res); @@ -704,7 +705,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { ScopedActivateContext activation{context}; void* host_mem = nullptr; // "Portable" memory is visible to all ROCM contexts. Safe for our use model. - hipError_t res = hipHostMalloc(&host_mem, bytes, hipHostMallocPortable); + hipError_t res = tensorflow::wrap::hipHostMallocVanilla(&host_mem, bytes, hipHostMallocPortable); if (res != hipSuccess) { LOG(ERROR) << "failed to alloc " << bytes << " bytes on host: " << ToString(res); @@ -715,7 +716,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { /* static */ void GpuDriver::HostDeallocate(GpuContext* context, void* location) { ScopedActivateContext activation{context}; - hipError_t res = hipHostFree(location); + hipError_t res = tensorflow::wrap::hipHostFree(location); if (res != hipSuccess) { LOG(ERROR) << "error deallocating host memory at " << location << ": " << ToString(res); @@ -726,7 +727,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { uint64 bytes) { ScopedActivateContext activation{context}; // "Portable" memory is visible to all ROCM contexts. Safe for our use model. - hipError_t res = hipHostRegister(location, bytes, hipHostRegisterPortable); + hipError_t res = tensorflow::wrap::hipHostRegister(location, bytes, hipHostRegisterPortable); if (res != hipSuccess) { LOG(ERROR) << "error registering host memory at " << location << ": " << ToString(res); @@ -738,7 +739,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { /* static */ bool GpuDriver::HostUnregister(GpuContext* context, void* location) { ScopedActivateContext activation{context}; - hipError_t res = hipHostUnregister(location); + hipError_t res = tensorflow::wrap::hipHostUnregister(location); if (res != hipSuccess) { LOG(ERROR) << "error unregistering host memory at " << location << ": " << ToString(res); @@ -755,7 +756,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { } ScopedActivateContext activated{context}; - hipError_t res = hipEventDestroy(*event); + hipError_t res = tensorflow::wrap::hipEventDestroy(*event); *event = nullptr; switch (res) { @@ -779,7 +780,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { GpuEventHandle event, GpuStreamHandle stream) { ScopedActivateContext activated{context}; - hipError_t res = hipEventRecord(event, stream); + hipError_t res = tensorflow::wrap::hipEventRecord(event, stream); switch (res) { case hipSuccess: return port::Status::OK(); @@ -800,7 +801,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { /* static */ port::StatusOr GpuDriver::QueryEvent( GpuContext* context, GpuEventHandle event) { ScopedActivateContext activated{context}; - hipError_t res = hipEventQuery(event); + hipError_t res = tensorflow::wrap::hipEventQuery(event); if (res != hipSuccess && res != hipErrorNotReady) { return port::Status{ port::error::INTERNAL, @@ -817,12 +818,12 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { ScopedActivateContext activated{context}; // The stop event must have completed in order for hipEventElapsedTime to // work. - hipError_t res = hipEventSynchronize(stop); + hipError_t res = tensorflow::wrap::hipEventSynchronize(stop); if (res != hipSuccess) { LOG(ERROR) << "failed to synchronize the stop event: " << ToString(res); return false; } - res = hipEventElapsedTime(elapsed_milliseconds, start, stop); + res = tensorflow::wrap::hipEventElapsedTime(elapsed_milliseconds, start, stop); if (res != hipSuccess) { LOG(ERROR) << "failed to get elapsed time between events: " << ToString(res); @@ -836,7 +837,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { GpuStreamHandle stream, GpuEventHandle event) { ScopedActivateContext activation{context}; - hipError_t res = hipStreamWaitEvent(stream, event, 0 /* = flags */); + hipError_t res = tensorflow::wrap::hipStreamWaitEvent(stream, event, 0 /* = flags */); if (res != hipSuccess) { LOG(ERROR) << "could not wait stream on event: " << ToString(res); return false; @@ -847,7 +848,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { /* static */ bool GpuDriver::SynchronizeContext(GpuContext* context) { ScopedActivateContext activation{context}; - hipError_t res = hipDeviceSynchronize(); + hipError_t res = tensorflow::wrap::hipDeviceSynchronize(); if (res != hipSuccess) { LOG(ERROR) << "could not synchronize on ROCM device: " << ToString(res) << " :: " << port::CurrentStackTrace(); @@ -861,7 +862,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { GpuStreamHandle stream) { ScopedActivateContext activated{context}; CHECK(stream != nullptr); - hipError_t res = hipStreamSynchronize(stream); + hipError_t res = tensorflow::wrap::hipStreamSynchronize(stream); if (res != hipSuccess) { port::Status status = port::InternalError( absl::StrCat("could not synchronize on ROCM stream: ", ToString(res))); @@ -877,7 +878,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { GpuStreamHandle stream) { ScopedActivateContext activated{context}; CHECK(stream != nullptr); - hipError_t res = hipStreamQuery(stream); + hipError_t res = tensorflow::wrap::hipStreamQuery(stream); if (res == hipSuccess) { return true; } @@ -891,7 +892,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { /* static */ port::Status GpuDriver::SynchronousMemcpyD2H( GpuContext* context, void* host_dst, hipDeviceptr_t gpu_src, uint64 size) { ScopedActivateContext activation{context}; - hipError_t res = hipMemcpyDtoH(host_dst, gpu_src, size); + hipError_t res = tensorflow::wrap::hipMemcpyDtoH(host_dst, gpu_src, size); if (res != hipSuccess) { return port::InternalError( absl::StrFormat("failed to synchronous memcpy from device to host: %s; " @@ -908,7 +909,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { GpuContext* context, hipDeviceptr_t gpu_dst, const void* host_src, uint64 size) { ScopedActivateContext activation{context}; - hipError_t res = hipMemcpyHtoD(gpu_dst, const_cast(host_src), size); + hipError_t res = tensorflow::wrap::hipMemcpyHtoD(gpu_dst, const_cast(host_src), size); if (res != hipSuccess) { return port::InternalError(absl::StrFormat( "failed to synchronous memcpy from host to device: %s; Gpu dst: %p;" @@ -924,7 +925,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { GpuContext* context, hipDeviceptr_t gpu_dst, hipDeviceptr_t gpu_src, uint64 size) { ScopedActivateContext activation{context}; - hipError_t res = hipMemcpyDtoD(gpu_dst, gpu_src, size); + hipError_t res = tensorflow::wrap::hipMemcpyDtoD(gpu_dst, gpu_src, size); if (res != hipSuccess) { return port::InternalError(absl::StrFormat( "failed to synchronous memcpy from host to device: %s; Gpu dst: %p; " @@ -942,7 +943,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { uint64 size, GpuStreamHandle stream) { ScopedActivateContext activation{context}; - hipError_t res = hipMemcpyDtoHAsync(host_dst, gpu_src, size, stream); + hipError_t res = tensorflow::wrap::hipMemcpyDtoHAsync(host_dst, gpu_src, size, stream); if (res != hipSuccess) { LOG(ERROR) << absl::StrFormat( "failed to enqueue async memcpy from device to host: %s; host dst: %p; " @@ -964,7 +965,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { GpuStreamHandle stream) { ScopedActivateContext activation{context}; hipError_t res = - hipMemcpyHtoDAsync(gpu_dst, const_cast(host_src), size, stream); + tensorflow::wrap::hipMemcpyHtoDAsync(gpu_dst, const_cast(host_src), size, stream); if (res != hipSuccess) { LOG(ERROR) << absl::StrFormat( "failed to enqueue async memcpy from host to device: %s; Gpu dst: %p; " @@ -984,7 +985,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { uint64 size, GpuStreamHandle stream) { ScopedActivateContext activation{context}; - hipError_t result = hipMemcpyDtoDAsync(gpu_dst, gpu_src, size, stream); + hipError_t result = tensorflow::wrap::hipMemcpyDtoDAsync(gpu_dst, gpu_src, size, stream); if (result != hipSuccess) { LOG(ERROR) << absl::StrFormat( "failed to enqueue async memcpy from device to device: %s" @@ -1021,7 +1022,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { } ScopedActivateContext activated{context}; - hipError_t res = hipEventCreateWithFlags(event, hipflags); + hipError_t res = tensorflow::wrap::hipEventCreateWithFlags(event, hipflags); if (res == hipSuccess) { return port::Status::OK(); @@ -1037,7 +1038,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { /* static */ int GpuDriver::GetDeviceCount() { int device_count = 0; - hipError_t res = hipGetDeviceCount(&device_count); + hipError_t res = tensorflow::wrap::hipGetDeviceCount(&device_count); if (res != hipSuccess) { LOG(ERROR) << "could not retrieve ROCM device count: " << ToString(res); return 0; @@ -1061,7 +1062,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { /* static */ port::Status GpuDriver::GetPointerAddressRange( hipDeviceptr_t dptr, hipDeviceptr_t* base, size_t* size) { - hipError_t result = hipMemGetAddressRange(base, size, dptr); + hipError_t result = tensorflow::wrap::hipMemGetAddressRange(base, size, dptr); if (result == hipSuccess) { return port::Status::OK(); } else if (result == hipErrorNotFound) { @@ -1106,7 +1107,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { /* static */ port::StatusOr GpuDriver::GetPointerDevice( hipDeviceptr_t pointer) { hipPointerAttribute_t pointerAttributes; - hipError_t result = hipPointerGetAttributes(&pointerAttributes, pointer); + hipError_t result = tensorflow::wrap::hipPointerGetAttributes(&pointerAttributes, pointer); if (result != hipSuccess) { return port::Status{ port::error::INTERNAL, @@ -1114,7 +1115,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { } hipDevice_t device; - result = hipDeviceGet(&device, pointerAttributes.device); + result = tensorflow::wrap::hipDeviceGet(&device, pointerAttributes.device); if (result != hipSuccess) { return port::Status{ port::error::INTERNAL, @@ -1127,7 +1128,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { /* static */ port::Status GpuDriver::GetGpuISAVersion(int* version, hipDevice_t device) { hipDeviceProp_t props; - hipError_t result = hipGetDeviceProperties(&props, device); + hipError_t result = tensorflow::wrap::hipGetDeviceProperties(&props, device); if (result == hipSuccess) { *version = props.gcnArch; return port::Status::OK(); @@ -1145,7 +1146,7 @@ template static port::StatusOr GetSimpleAttribute(hipDevice_t device, hipDeviceAttribute_t attribute) { int value = -1; - hipError_t result = hipDeviceGetAttribute(&value, attribute, device); + hipError_t result = tensorflow::wrap::hipDeviceGetAttribute(&value, attribute, device); if (result != hipSuccess) { return port::Status{ port::error::NOT_FOUND, @@ -1200,21 +1201,21 @@ static port::StatusOr GetSimpleAttribute(hipDevice_t device, hipDevice_t device) { int value; hipError_t res = - hipDeviceGetAttribute(&value, hipDeviceAttributeMaxGridDimX, device); + tensorflow::wrap::hipDeviceGetAttribute(&value, hipDeviceAttributeMaxGridDimX, device); if (res != hipSuccess) { LOG(ERROR) << "failed to query max grid dim x: " << ToString(res); return false; } *x = value; - res = hipDeviceGetAttribute(&value, hipDeviceAttributeMaxGridDimY, device); + res = tensorflow::wrap::hipDeviceGetAttribute(&value, hipDeviceAttributeMaxGridDimY, device); if (res != hipSuccess) { LOG(ERROR) << "failed to query max grid dim y: " << ToString(res); return false; } *y = value; - res = hipDeviceGetAttribute(&value, hipDeviceAttributeMaxGridDimZ, device); + res = tensorflow::wrap::hipDeviceGetAttribute(&value, hipDeviceAttributeMaxGridDimZ, device); if (res != hipSuccess) { LOG(ERROR) << "failed to query max grid dim z: " << ToString(res); return false; @@ -1224,7 +1225,7 @@ static port::StatusOr GetSimpleAttribute(hipDevice_t device, } /* static */ bool GpuDriver::GetDriverVersion(int* driver_version) { - hipError_t res = hipDriverGetVersion(driver_version); + hipError_t res = tensorflow::wrap::hipDriverGetVersion(driver_version); if (res != hipSuccess) { LOG(ERROR) << "failed to query driver version: " << ToString(res); return false; @@ -1235,7 +1236,7 @@ static port::StatusOr GetSimpleAttribute(hipDevice_t device, /* static */ bool GpuDriver::GetDeviceProperties( hipDeviceProp_t* device_properties, int device_ordinal) { - hipError_t res = hipGetDeviceProperties(device_properties, device_ordinal); + hipError_t res = tensorflow::wrap::hipGetDeviceProperties(device_properties, device_ordinal); if (res != hipSuccess) { LOG(ERROR) << "failed to query device properties: " << ToString(res); return false; @@ -1268,7 +1269,7 @@ static port::StatusOr GetSimpleAttribute(hipDevice_t device, ScopedActivateContext activation{context}; size_t free = 0; size_t total = 0; - hipError_t res = hipMemGetInfo(&free, &total); + hipError_t res = tensorflow::wrap::hipMemGetInfo(&free, &total); if (res != hipSuccess) { LOG(ERROR) << "failed to query device memory info: " << ToString(res); return false; @@ -1282,7 +1283,7 @@ static port::StatusOr GetSimpleAttribute(hipDevice_t device, /* static */ bool GpuDriver::GetDeviceTotalMemory(hipDevice_t device, uint64* result) { size_t value = -1; - hipError_t res = hipDeviceTotalMem(&value, device); + hipError_t res = tensorflow::wrap::hipDeviceTotalMem(&value, device); if (res != hipSuccess) { LOG(ERROR) << "failed to query total available memory: " << ToString(res); return false; @@ -1297,7 +1298,7 @@ static port::StatusOr GetSimpleAttribute(hipDevice_t device, static const int kBufferSize = 64; absl::InlinedVector chars(kBufferSize); chars[kBufferSize - 1] = '\0'; - hipError_t res = hipDeviceGetPCIBusId(chars.begin(), kBufferSize - 1, device); + hipError_t res = tensorflow::wrap::hipDeviceGetPCIBusId(chars.begin(), kBufferSize - 1, device); if (res != hipSuccess) { LOG(ERROR) << "failed to query PCI bus id for device: " << ToString(res); return pci_bus_id; @@ -1313,7 +1314,7 @@ static port::StatusOr GetSimpleAttribute(hipDevice_t device, } int can_access_peer = -1; - hipError_t res = hipDeviceCanAccessPeer( + hipError_t res = tensorflow::wrap::hipDeviceCanAccessPeer( &can_access_peer, from->device_ordinal(), to->device_ordinal()); if (res != hipSuccess) { LOG(ERROR) << "failed to detect peer access capability: " << ToString(res); @@ -1331,7 +1332,7 @@ static port::StatusOr GetSimpleAttribute(hipDevice_t device, ScopedActivateContext activated{from}; hipError_t result = - hipDeviceEnablePeerAccess(to->device_ordinal(), 0 /* = flags */); + tensorflow::wrap::hipDeviceEnablePeerAccess(to->device_ordinal(), 0 /* = flags */); if (result != hipSuccess && result != hipErrorPeerAccessAlreadyEnabled) { return port::Status{ port::error::INTERNAL, diff --git a/tensorflow/stream_executor/rocm/rocm_driver_wrapper.h b/tensorflow/stream_executor/rocm/rocm_driver_wrapper.h new file mode 100644 index 0000000000..0a0ab3ae74 --- /dev/null +++ b/tensorflow/stream_executor/rocm/rocm_driver_wrapper.h @@ -0,0 +1,147 @@ +/* Copyright 2019 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. +==============================================================================*/ + +// This file wraps rocm driver calls with dso loader so that we don't need to +// have explicit linking to librocm. All TF rocm driver usage should route +// through this wrapper. + +#ifndef TENSORFLOW_STREAM_EXECUTOR_ROCM_ROCM_DRIVER_WRAPPER_H_ +#define TENSORFLOW_STREAM_EXECUTOR_ROCM_ROCM_DRIVER_WRAPPER_H_ + +#include "tensorflow/stream_executor/lib/env.h" +#include "tensorflow/stream_executor/platform/dso_loader.h" +#include "tensorflow/stream_executor/platform/port.h" +#include "rocm/include/hip/hip_runtime.h" + + +#if defined(TENSORFLOW_USE_ROCM) + +#endif + +namespace tensorflow { +namespace wrap { +#ifdef PLATFORM_GOOGLE +// Use static linked library +#define STREAM_EXECUTOR_HIP_WRAP(hipSymbolName) \ + template \ + auto hipSymbolName(Args... args)->decltype(::hipSymbolName(args...)) { \ + return ::hipSymbolName(args...); \ + } + +// This macro wraps a global identifier, given by hipSymbolName, in a callable +// structure that loads the DLL symbol out of the DSO handle in a thread-safe +// manner on first use. This dynamic loading technique is used to avoid DSO +// dependencies on vendor libraries which may or may not be available in the +// deployed binary environment. +#else +#define TO_STR_(x) #x +#define TO_STR(x) TO_STR_(x) + +// hipMalloc and hipHostMalloc are defined as funtion templates in the +// HIP header files, and hence their names get mangled and the attempt +// to resolve their name when trying to dynamically load them will fail +// Updating the HIP header files to make them C functions is underway. +// Until that change flows through, we will workaround the issue by +// creating dummy wrappers for them here + +hipError_t hipMallocVanilla(void** ptr, size_t size) { + return hipErrorNotInitialized; +} + +hipError_t hipHostMallocVanilla(void** ptr, size_t size, unsigned int flags) { + return hipErrorNotInitialized; +} + +#define STREAM_EXECUTOR_HIP_WRAP(hipSymbolName) \ + template \ + auto hipSymbolName(Args... args)->decltype(::hipSymbolName(args...)) { \ + using FuncPtrT = std::add_pointer::type; \ + static FuncPtrT loaded = []() -> FuncPtrT { \ + static const char *kName = TO_STR(hipSymbolName); \ + void *f; \ + auto s = stream_executor::port::Env::Default()->GetSymbolFromLibrary( \ + stream_executor::internal::CachedDsoLoader::GetHipDsoHandle() \ + .ValueOrDie(), \ + kName, &f); \ + CHECK(s.ok()) << "could not find " << kName \ + << " in HIP DSO; dlerror: " << s.error_message(); \ + return reinterpret_cast(f); \ + }(); \ + return loaded(args...); \ + } +#endif + +// clang-format off +#define HIP_ROUTINE_EACH(__macro) \ + __macro(hipDeviceCanAccessPeer) \ + __macro(hipDeviceEnablePeerAccess) \ + __macro(hipDeviceGet) \ + __macro(hipDeviceGetAttribute) \ + __macro(hipDeviceGetName) \ + __macro(hipDeviceGetPCIBusId) \ + __macro(hipDeviceGetSharedMemConfig) \ + __macro(hipDeviceSetSharedMemConfig) \ + __macro(hipDeviceSynchronize) \ + __macro(hipDeviceTotalMem) \ + __macro(hipDriverGetVersion) \ + __macro(hipEventCreateWithFlags) \ + __macro(hipEventElapsedTime) \ + __macro(hipEventDestroy) \ + __macro(hipEventQuery) \ + __macro(hipEventRecord) \ + __macro(hipEventSynchronize) \ + __macro(hipFree) \ + __macro(hipFuncSetCacheConfig) \ + __macro(hipGetDevice) \ + __macro(hipGetDeviceCount) \ + __macro(hipGetDeviceProperties) \ + __macro(hipHostFree) \ + __macro(hipHostRegister) \ + __macro(hipHostUnregister) \ + __macro(hipInit) \ + __macro(hipMemGetAddressRange) \ + __macro(hipMemGetInfo) \ + __macro(hipMemcpyDtoD) \ + __macro(hipMemcpyDtoDAsync) \ + __macro(hipMemcpyDtoH) \ + __macro(hipMemcpyDtoHAsync) \ + __macro(hipMemcpyHtoD) \ + __macro(hipMemcpyHtoDAsync) \ + __macro(hipMemset) \ + __macro(hipMemsetAsync) \ + __macro(hipModuleGetFunction) \ + __macro(hipModuleGetGlobal) \ + __macro(hipModuleLaunchKernel) \ + __macro(hipModuleLoadData) \ + __macro(hipModuleUnload) \ + __macro(hipPointerGetAttributes) \ + __macro(hipSetDevice) \ + __macro(hipStreamAddCallback) \ + __macro(hipStreamCreateWithFlags) \ + __macro(hipStreamDestroy) \ + __macro(hipStreamQuery) \ + __macro(hipStreamSynchronize) \ + __macro(hipStreamWaitEvent) \ +// clang-format on + +HIP_ROUTINE_EACH(STREAM_EXECUTOR_HIP_WRAP) +#undef HIP_ROUTINE_EACH +#undef STREAM_EXECUTOR_HIP_WRAP +#undef TO_STR +#undef TO_STR_ +} // namespace wrap +} // namespace tensorflow + +#endif // TENSORFLOW_STREAM_EXECUTOR_ROCM_ROCM_DRIVER_WRAPPER_H_ -- GitLab From 7066b1c04dab63bd44c1450fc93032e58727e3e6 Mon Sep 17 00:00:00 2001 From: Deven Desai Date: Fri, 1 Feb 2019 18:32:40 +0000 Subject: [PATCH 158/766] adding a check in in the initialization routines for rocblas, rocfft, rocrand to avoid duplicate registrations --- tensorflow/stream_executor/rocm/rocm_blas.cc | 62 +++++++++++--------- tensorflow/stream_executor/rocm/rocm_fft.cc | 47 ++++++++------- tensorflow/stream_executor/rocm/rocm_rng.cc | 61 ++++++++++--------- 3 files changed, 94 insertions(+), 76 deletions(-) diff --git a/tensorflow/stream_executor/rocm/rocm_blas.cc b/tensorflow/stream_executor/rocm/rocm_blas.cc index a626d168c2..2137bc0027 100644 --- a/tensorflow/stream_executor/rocm/rocm_blas.cc +++ b/tensorflow/stream_executor/rocm/rocm_blas.cc @@ -2324,35 +2324,43 @@ bool ROCMBlas::DoBlasGemmStridedBatched( } // namespace gpu void initialize_rocblas() { - port::Status status = - PluginRegistry::Instance()->RegisterFactory( - rocm::kROCmPlatformId, gpu::kRocBlasPlugin, "rocBLAS", - [](internal::StreamExecutorInterface* parent) -> blas::BlasSupport* { - gpu::GpuExecutor* rocm_executor = - dynamic_cast(parent); - if (rocm_executor == nullptr) { - LOG(ERROR) - << "Attempting to initialize an instance of the rocBLAS " - << "support library with a non-ROCM StreamExecutor"; - return nullptr; - } - - gpu::ROCMBlas* blas = new gpu::ROCMBlas(rocm_executor); - if (!blas->Init()) { - // Note: Init() will log a more specific error. - delete blas; - return nullptr; - } - return blas; - }); + auto rocBlasAlreadyRegistered = PluginRegistry::Instance()->HasFactory( + rocm::kROCmPlatformId, PluginKind::kBlas, gpu::kRocBlasPlugin); - if (!status.ok()) { - LOG(ERROR) << "Unable to register rocBLAS factory: " - << status.error_message(); - } + if (!rocBlasAlreadyRegistered) { + port::Status status = + PluginRegistry::Instance() + ->RegisterFactory( + rocm::kROCmPlatformId, gpu::kRocBlasPlugin, "rocBLAS", + [](internal::StreamExecutorInterface* parent) + -> blas::BlasSupport* { + gpu::GpuExecutor* rocm_executor = + dynamic_cast(parent); + if (rocm_executor == nullptr) { + LOG(ERROR) + << "Attempting to initialize an instance of the " + "rocBLAS " + << "support library with a non-ROCM StreamExecutor"; + return nullptr; + } + + gpu::ROCMBlas* blas = new gpu::ROCMBlas(rocm_executor); + if (!blas->Init()) { + // Note: Init() will log a more specific error. + delete blas; + return nullptr; + } + return blas; + }); + + if (!status.ok()) { + LOG(ERROR) << "Unable to register rocBLAS factory: " + << status.error_message(); + } - PluginRegistry::Instance()->SetDefaultFactory( - rocm::kROCmPlatformId, PluginKind::kBlas, gpu::kRocBlasPlugin); + PluginRegistry::Instance()->SetDefaultFactory( + rocm::kROCmPlatformId, PluginKind::kBlas, gpu::kRocBlasPlugin); + } } } // namespace stream_executor diff --git a/tensorflow/stream_executor/rocm/rocm_fft.cc b/tensorflow/stream_executor/rocm/rocm_fft.cc index e8a72f61d3..b23e05d9dd 100644 --- a/tensorflow/stream_executor/rocm/rocm_fft.cc +++ b/tensorflow/stream_executor/rocm/rocm_fft.cc @@ -592,28 +592,33 @@ STREAM_EXECUTOR_ROCM_DEFINE_FFT(double, Z2Z, D2Z, Z2D) } // namespace gpu void initialize_rocfft() { - port::Status status = - PluginRegistry::Instance()->RegisterFactory( - rocm::kROCmPlatformId, gpu::kRocFftPlugin, "rocFFT", - [](internal::StreamExecutorInterface *parent) -> fft::FftSupport * { - gpu::GpuExecutor *rocm_executor = - dynamic_cast(parent); - if (rocm_executor == nullptr) { - LOG(ERROR) - << "Attempting to initialize an instance of the rocFFT " - << "support library with a non-ROCM StreamExecutor"; - return nullptr; - } - - return new gpu::ROCMFft(rocm_executor); - }); - if (!status.ok()) { - LOG(ERROR) << "Unable to register rocFFT factory: " - << status.error_message(); - } - - PluginRegistry::Instance()->SetDefaultFactory( + auto rocFftAlreadyRegistered = PluginRegistry::Instance()->HasFactory( rocm::kROCmPlatformId, PluginKind::kFft, gpu::kRocFftPlugin); + + if (!rocFftAlreadyRegistered) { + port::Status status = + PluginRegistry::Instance()->RegisterFactory( + rocm::kROCmPlatformId, gpu::kRocFftPlugin, "rocFFT", + [](internal::StreamExecutorInterface* parent) -> fft::FftSupport* { + gpu::GpuExecutor* rocm_executor = + dynamic_cast(parent); + if (rocm_executor == nullptr) { + LOG(ERROR) + << "Attempting to initialize an instance of the rocFFT " + << "support library with a non-ROCM StreamExecutor"; + return nullptr; + } + + return new gpu::ROCMFft(rocm_executor); + }); + if (!status.ok()) { + LOG(ERROR) << "Unable to register rocFFT factory: " + << status.error_message(); + } + + PluginRegistry::Instance()->SetDefaultFactory( + rocm::kROCmPlatformId, PluginKind::kFft, gpu::kRocFftPlugin); + } } } // namespace stream_executor diff --git a/tensorflow/stream_executor/rocm/rocm_rng.cc b/tensorflow/stream_executor/rocm/rocm_rng.cc index 2779784570..545bfc6d94 100644 --- a/tensorflow/stream_executor/rocm/rocm_rng.cc +++ b/tensorflow/stream_executor/rocm/rocm_rng.cc @@ -283,35 +283,40 @@ bool GpuRng::SetSeed(Stream* stream, const uint8* seed, uint64 seed_bytes) { } // namespace gpu void initialize_rocrand() { - port::Status status = - PluginRegistry::Instance()->RegisterFactory( - rocm::kROCmPlatformId, gpu::kGpuRandPlugin, "rocRAND", - [](internal::StreamExecutorInterface* parent) -> rng::RngSupport* { - gpu::GpuExecutor* rocm_executor = - dynamic_cast(parent); - if (rocm_executor == nullptr) { - LOG(ERROR) - << "Attempting to initialize an instance of the hipRAND " - << "support library with a non-ROCM StreamExecutor"; - return nullptr; - } - - gpu::GpuRng* rng = new gpu::GpuRng(rocm_executor); - if (!rng->Init()) { - // Note: Init() will log a more specific error. - delete rng; - return nullptr; - } - return rng; - }); - - if (!status.ok()) { - LOG(ERROR) << "Unable to register rocRAND factory: " - << status.error_message(); - } - - PluginRegistry::Instance()->SetDefaultFactory( + auto rocRandAlreadyRegistered = PluginRegistry::Instance()->HasFactory( rocm::kROCmPlatformId, PluginKind::kRng, gpu::kGpuRandPlugin); + + if (!rocRandAlreadyRegistered) { + port::Status status = + PluginRegistry::Instance()->RegisterFactory( + rocm::kROCmPlatformId, gpu::kGpuRandPlugin, "rocRAND", + [](internal::StreamExecutorInterface* parent) -> rng::RngSupport* { + gpu::GpuExecutor* rocm_executor = + dynamic_cast(parent); + if (rocm_executor == nullptr) { + LOG(ERROR) + << "Attempting to initialize an instance of the hipRAND " + << "support library with a non-ROCM StreamExecutor"; + return nullptr; + } + + gpu::GpuRng* rng = new gpu::GpuRng(rocm_executor); + if (!rng->Init()) { + // Note: Init() will log a more specific error. + delete rng; + return nullptr; + } + return rng; + }); + + if (!status.ok()) { + LOG(ERROR) << "Unable to register rocRAND factory: " + << status.error_message(); + } + + PluginRegistry::Instance()->SetDefaultFactory( + rocm::kROCmPlatformId, PluginKind::kRng, gpu::kGpuRandPlugin); + } } } // namespace stream_executor -- GitLab From e0c68de719ac303217ed0100f88c9835b16fb7fa Mon Sep 17 00:00:00 2001 From: Lukas Geiger Date: Fri, 15 Feb 2019 23:24:24 +0100 Subject: [PATCH 159/766] Add optimization to convert Square(Sub(x, y)) to SquaredDifference(x, y) --- .../optimizers/arithmetic_optimizer.cc | 31 ++++++++ .../optimizers/arithmetic_optimizer.h | 1 + .../optimizers/arithmetic_optimizer_test.cc | 77 +++++++++++++++++++ .../arithmetic_optimizer_test_utils.h | 5 ++ 4 files changed, 114 insertions(+) diff --git a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc index 902cb3fdd8..9775de8ba0 100644 --- a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc @@ -1774,6 +1774,35 @@ class SqrtDivToRsqrtMulStage : public ArithmeticOptimizerStage { } }; +// Performs the conversion: +// Square(Sub(x, y)) => Identity(SquaredDifference(x, y)) +class FuseSquaredDiffStage : public ArithmeticOptimizerStage { + public: + explicit FuseSquaredDiffStage(const GraphOptimizerContext& ctx, + const ArithmeticOptimizerContext& ctx_ext) + : ArithmeticOptimizerStage("FuseSquaredDiffStage", ctx, ctx_ext) {} + ~FuseSquaredDiffStage() override = default; + + bool IsSupported(const NodeDef* node) const override { + return IsSquare(*node); + } + + Status TrySimplify(NodeDef* node, string* simplified_node_name) override { + NodeDef* b; + TF_RETURN_IF_ERROR(GetInputNode(node->input(0), &b)); + // Optimize only if base is a Sub whose output is not being consumed + // elsewhere. + if (IsSub(*b) && !IsInPreserveSet(*b) && + (NumNonControlOutputs(*b, *ctx().node_map) == 1)) { + node->set_op("Identity"); + b->set_op("SquaredDifference"); + AddToOptimizationQueue(node); + AddToOptimizationQueue(b); + } + return Status::OK(); + } +}; + // Bypass redundant reshape nodes: // // Reshape Reshape <-+ @@ -3513,6 +3542,8 @@ Status ArithmeticOptimizer::SimplifyArithmeticOps(bool can_use_shapes) { pipeline.AddStage(ctx, ctx_ext); if (options_.remove_stack_strided_slice_same_axis) pipeline.AddStage(ctx, ctx_ext); + if (options_.fuse_squared_diff) + pipeline.AddStage(ctx, ctx_ext); VLOG(1) << "Run " << pipeline.NumStages() << " arithmetic optimizer stages: " << str_util::Join(pipeline.StageNames(), ", "); diff --git a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.h b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.h index e1395d7542..4535b27ae6 100644 --- a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.h +++ b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.h @@ -61,6 +61,7 @@ class ArithmeticOptimizer : public GraphOptimizer { bool fold_conjugate_into_transpose = true; bool fold_multiply_into_conv = true; bool fold_transpose_into_matmul = true; + bool fuse_squared_diff = true; bool hoist_common_factor_out_of_aggregation = true; bool hoist_cwise_unary_chains = true; bool minimize_broadcasts = true; diff --git a/tensorflow/core/grappler/optimizers/arithmetic_optimizer_test.cc b/tensorflow/core/grappler/optimizers/arithmetic_optimizer_test.cc index 2778334622..a0890868b8 100644 --- a/tensorflow/core/grappler/optimizers/arithmetic_optimizer_test.cc +++ b/tensorflow/core/grappler/optimizers/arithmetic_optimizer_test.cc @@ -2483,6 +2483,83 @@ TEST_F(ArithmeticOptimizerTest, DoNotConvertSqrtDivToRsqrtMulDivisorFetchNode) { } } +TEST_F(ArithmeticOptimizerTest, FuseSquaredDiff) { + tensorflow::Scope s = tensorflow::Scope::NewRootScope(); + auto x = ops::Const(s.WithOpName("x"), {1.0f, 2.0f}, {1, 2}); + auto y = ops::Const(s.WithOpName("y"), {3.0f, 4.0f}, {1, 2}); + Output sub_x_y = ops::Sub(s.WithOpName("sub_x_y"), x, y); + Output square_sub_x_y = ops::Square(s.WithOpName("output"), sub_x_y); + + GrapplerItem item; + item.fetch = {"output"}; + TF_CHECK_OK(s.ToGraphDef(&item.graph)); + const auto tensors_expected = EvaluateNodes(item.graph, item.fetch); + EXPECT_EQ(1, tensors_expected.size()); + + GraphDef output; + ArithmeticOptimizer optimizer; + EnableOnlyFuseSquaredDiff(&optimizer); + OptimizeAndPrune(&optimizer, &item, &output); + const auto tensors = EvaluateNodes(output, item.fetch); + EXPECT_EQ(1, tensors.size()); + + test::ExpectTensorNear(tensors_expected[0], tensors[0], 1e-6); + EXPECT_EQ(item.graph.node_size(), output.node_size()); + for (int i = 0; i < output.node_size(); ++i) { + const NodeDef& node = output.node(i); + if (node.name() == "output") { + EXPECT_EQ("Identity", node.op()); + EXPECT_EQ(1, node.input_size()); + EXPECT_EQ("sub_x_y", node.input(0)); + } else if (node.name() == "sub_x_y") { + EXPECT_EQ("SquaredDifference", node.op()); + EXPECT_EQ(2, node.input_size()); + EXPECT_EQ("x", node.input(0)); + EXPECT_EQ("y", node.input(1)); + } + } +} + +TEST_F(ArithmeticOptimizerTest, DoNotFuseSquaredDiffFetchNode) { + tensorflow::Scope s = tensorflow::Scope::NewRootScope(); + auto x = ops::Const(s.WithOpName("x"), {1.0f, 2.0f}, {1, 2}); + auto y = ops::Const(s.WithOpName("y"), {3.0f, 4.0f}, {1, 2}); + Output sub_x_y = ops::Sub(s.WithOpName("sub_x_y"), x, y); + Output square_sub_x_y = ops::Square(s.WithOpName("output"), sub_x_y); + + GrapplerItem item; + item.fetch = {"output", "sub_x_y"}; + TF_CHECK_OK(s.ToGraphDef(&item.graph)); + const auto tensors_expected = EvaluateNodes(item.graph, item.fetch); + ASSERT_EQ(2, tensors_expected.size()); + + GraphDef output; + ArithmeticOptimizer optimizer; + EnableOnlyFuseSquaredDiff(&optimizer); + OptimizeAndPrune(&optimizer, &item, &output); + const auto tensors = EvaluateNodes(output, item.fetch); + ASSERT_EQ(2, tensors.size()); + + for (int i = 0; i < tensors.size(); i++) { + EXPECT_EQ(tensors[i].NumElements(), tensors_expected[i].NumElements()); + test::ExpectTensorNear(tensors_expected[i], tensors[i], 1e-6); + } + EXPECT_EQ(item.graph.node_size(), output.node_size()); + for (int i = 0; i < output.node_size(); ++i) { + const NodeDef& node = output.node(i); + if (node.name() == "output") { + EXPECT_EQ("Square", node.op()); + EXPECT_EQ(1, node.input_size()); + EXPECT_EQ("sub_x_y", node.input(0)); + } else if (node.name() == "sub_x_y") { + EXPECT_EQ("Sub", node.op()); + EXPECT_EQ(2, node.input_size()); + EXPECT_EQ("x", node.input(0)); + EXPECT_EQ("y", node.input(1)); + } + } +} + TEST_F(ArithmeticOptimizerTest, ConvertPow) { tensorflow::Scope s = tensorflow::Scope::NewRootScope(); auto x = ops::Const(s.WithOpName("x"), {1.0f, 2.0f}, {1, 2}); diff --git a/tensorflow/core/grappler/optimizers/arithmetic_optimizer_test_utils.h b/tensorflow/core/grappler/optimizers/arithmetic_optimizer_test_utils.h index 94d0adc609..cd9950e17a 100644 --- a/tensorflow/core/grappler/optimizers/arithmetic_optimizer_test_utils.h +++ b/tensorflow/core/grappler/optimizers/arithmetic_optimizer_test_utils.h @@ -188,6 +188,11 @@ class ArithmeticOptimizerTest : public GrapplerTest { optimizer->options_.convert_pow = true; } + void EnableOnlyFuseSquaredDiff(ArithmeticOptimizer* optimizer) { + DisableAllStages(optimizer); + optimizer->options_.fuse_squared_diff = true; + } + void EnableOnlyRemoveIdempotent(ArithmeticOptimizer* optimizer) { DisableAllStages(optimizer); optimizer->options_.remove_idempotent = true; -- GitLab From b6f2215733cd160dd24be9a416b2c148d75fde35 Mon Sep 17 00:00:00 2001 From: Lukas Geiger Date: Wed, 30 Jan 2019 05:19:12 +0100 Subject: [PATCH 160/766] Remove unused function in arithmetic optimizer test --- .../core/grappler/optimizers/arithmetic_optimizer_test.cc | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tensorflow/core/grappler/optimizers/arithmetic_optimizer_test.cc b/tensorflow/core/grappler/optimizers/arithmetic_optimizer_test.cc index a0890868b8..4ed20d1741 100644 --- a/tensorflow/core/grappler/optimizers/arithmetic_optimizer_test.cc +++ b/tensorflow/core/grappler/optimizers/arithmetic_optimizer_test.cc @@ -71,10 +71,6 @@ string AggregationMulName(const string& name) { return AddPrefixToNodeName(name, kSimplifyAggregationMul, ""); } -string OptimizedName(const string& name) { - return AddPrefixToNodeName(name, kArithmeticOptimizer); -} - void VerifyGraphsMatch(const GraphDef& original_graph, const GraphDef& optimized_graph, int line) { EXPECT_EQ(original_graph.node_size(), optimized_graph.node_size()) << line; -- GitLab From 0a507dc55b51e4f5651b57473ff5e2b3ea50d95e Mon Sep 17 00:00:00 2001 From: Bhavani Subramanian Date: Fri, 15 Feb 2019 16:19:25 -0800 Subject: [PATCH 161/766] Addressed review comments. --- tensorflow/core/kernels/mkl_conv_ops.cc | 9 +++--- .../kernels/mkl_quantized_conv_ops_test.cc | 30 +++++++++++-------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/tensorflow/core/kernels/mkl_conv_ops.cc b/tensorflow/core/kernels/mkl_conv_ops.cc index aeb198c145..51b37480a7 100644 --- a/tensorflow/core/kernels/mkl_conv_ops.cc +++ b/tensorflow/core/kernels/mkl_conv_ops.cc @@ -466,7 +466,7 @@ class MklConvOp : public OpKernel { errors::InvalidArgument("filter must be 4-dimensional: ", filter.shape().DebugString())); - for (int i = 0; i < 3; i++) { + for (int i = 0; i < 3; ++i) { OP_REQUIRES(context, FastBoundsCheck(filter.dim_size(i), std::numeric_limits::max()), errors::InvalidArgument("filter too large")); @@ -939,9 +939,10 @@ class MklConvOp : public OpKernel { // `padding_list` attribute. Otherwise, we get it from one of the inputs. bool quantized_pad_enabled = false; for (auto const& padding_val : padding_list_) { - if (!padding_val) continue; - quantized_pad_enabled = true; - break; + if (padding_val) { + quantized_pad_enabled = true; + break; + } } if (fuse_pad_ || quantized_pad_enabled) { diff --git a/tensorflow/core/kernels/mkl_quantized_conv_ops_test.cc b/tensorflow/core/kernels/mkl_quantized_conv_ops_test.cc index 1d41b25193..2e599d3d9f 100644 --- a/tensorflow/core/kernels/mkl_quantized_conv_ops_test.cc +++ b/tensorflow/core/kernels/mkl_quantized_conv_ops_test.cc @@ -180,7 +180,7 @@ TEST_F(QuantizedConv2DTest, Small) { // Output -> float const int expected_width = image_width; - const int expected_height = image_height * filter_count; + const int expected_height = image_height; Tensor expected_float( DT_FLOAT, TensorShape({image_batch_count, expected_height, expected_width, filter_count})); @@ -203,11 +203,13 @@ TEST_F(QuantizedConv2DTest, Small) { test::ExpectTensorNear(expected_float, output_float, 1.0); } -// Output -> int32 +// Output -> qint32 TEST_F(QuantizedConv2DTest, Small32Bit) { const int stride = 1; ConfigureQuantizedConv2D(stride); + // The illustrations and details regarding inputs and outputs + // are in TEST_F(QuantizedConv2DTest, Small) const int depth = 1; const int image_width = 4; const int image_height = 3; @@ -239,9 +241,9 @@ TEST_F(QuantizedConv2DTest, Small32Bit) { TF_ASSERT_OK(RunOpKernel()); - // Output -> int32 + // Output -> qint32 const int expected_width = image_width; - const int expected_height = image_height * filter_count; + const int expected_height = image_height; Tensor expected(DT_QINT32, TensorShape({image_batch_count, expected_height, expected_width, filter_count})); test::FillValues( @@ -259,7 +261,7 @@ TEST_F(QuantizedConv2DTest, Small32Bit) { test::ExpectTensorEqual(expected, output_quantized); } -// Output -> int32 +// Output -> qint32 TEST_F(QuantizedConv2DTest, Small32BitWithPadding) { const int stride = 1; TF_ASSERT_OK(NodeDefBuilder("quantized_conv_op", "_MklQuantizedConv2D") @@ -288,6 +290,8 @@ TEST_F(QuantizedConv2DTest, Small32BitWithPadding) { .Finalize(node_def())); TF_ASSERT_OK(InitOp()); + // The illustrations and details regarding inputs and outputs + // are in TEST_F(QuantizedConv2DTest, Small) const int depth = 1; const int image_width = 4; const int image_height = 3; @@ -319,9 +323,9 @@ TEST_F(QuantizedConv2DTest, Small32BitWithPadding) { TF_ASSERT_OK(RunOpKernel()); - // Output -> int32 + // Output -> qint32 const int expected_width = image_width; - const int expected_height = image_height * filter_count; + const int expected_height = image_height; Tensor expected(DT_QINT32, TensorShape({image_batch_count, expected_height, expected_width, filter_count})); test::FillValues( @@ -339,7 +343,7 @@ TEST_F(QuantizedConv2DTest, Small32BitWithPadding) { test::ExpectTensorEqual(expected, output_quantized); } -// Output -> int32 +// Output -> qint32 TEST_F(QuantizedConv2DTest, OddPadding) { const int stride = 2; ConfigureQuantizedConv2D(stride); @@ -375,9 +379,9 @@ TEST_F(QuantizedConv2DTest, OddPadding) { TF_ASSERT_OK(RunOpKernel()); - // Output -> int32 + // Output -> qint32 const int expected_width = image_width / stride; - const int expected_height = (image_height * filter_count) / stride; + const int expected_height = image_height / stride; Tensor expected(DT_QINT32, TensorShape({image_batch_count, expected_height, expected_width, filter_count})); test::FillValues(&expected, {348, 252, 274, 175}); @@ -393,7 +397,7 @@ TEST_F(QuantizedConv2DTest, OddPadding) { test::ExpectTensorEqual(expected, output_quantized); } -// Output -> int32 +// Output -> qint32 TEST_F(QuantizedConv2DTest, OddPaddingBatch) { const int stride = 2; ConfigureQuantizedConv2D(stride); @@ -431,9 +435,9 @@ TEST_F(QuantizedConv2DTest, OddPaddingBatch) { TF_ASSERT_OK(RunOpKernel()); - // Output -> int32 + // Output -> qint32 const int expected_width = image_width / stride; - const int expected_height = (image_height * filter_count) / stride; + const int expected_height = image_height / stride; Tensor expected(DT_QINT32, TensorShape({image_batch_count, expected_height, expected_width, filter_count})); test::FillValues( -- GitLab From 33d0415f99d3f0d799be319c5a521921bdbc7c01 Mon Sep 17 00:00:00 2001 From: Duncan Riach Date: Wed, 6 Feb 2019 23:24:50 -0800 Subject: [PATCH 162/766] Add tests for TF_CUDNN_DETERMINISTIC --- tensorflow/python/kernel_tests/BUILD | 13 +++ .../kernel_tests/cudnn_determinism_test.py | 101 ++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 tensorflow/python/kernel_tests/cudnn_determinism_test.py diff --git a/tensorflow/python/kernel_tests/BUILD b/tensorflow/python/kernel_tests/BUILD index 79cf667a6f..03f9ff6b31 100644 --- a/tensorflow/python/kernel_tests/BUILD +++ b/tensorflow/python/kernel_tests/BUILD @@ -290,6 +290,19 @@ cuda_py_test( xla_enable_strict_auto_jit = True, ) +cuda_py_test( + name = "cudnn_determinism_test", + size = "small", + srcs = ["cudnn_determinism_test.py"], + additional_deps = [ + "//third_party/py/numpy", + "//tensorflow/python:constant_op", + "//tensorflow/python:dtypes", + "//tensorflow/python:client_testlib", + "//tensorflow/python:nn_ops", + ], +) + tf_py_test( name = "decode_csv_op_test", size = "small", diff --git a/tensorflow/python/kernel_tests/cudnn_determinism_test.py b/tensorflow/python/kernel_tests/cudnn_determinism_test.py new file mode 100644 index 0000000000..c05095902d --- /dev/null +++ b/tensorflow/python/kernel_tests/cudnn_determinism_test.py @@ -0,0 +1,101 @@ +# Copyright 2019 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 TF_CUDNN_DETERMINISTIC=true""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +from collections import namedtuple +import numpy as np + +from tensorflow.python.framework import constant_op +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import test_util +from tensorflow.python.platform import test +from tensorflow.python.ops import nn_ops + +# The TF_CUDNN_DETERMINISTIC flag disables autotuning of cuDNN algorithms and +# causes deterministic cuDNN algorithms to be selected when both deterministic +# and non-deterministic algorithms are available. These tests are intended to +# confirm that deterministic algorithms are chosen when +# TF_CUDNN_DETERMINISTIC=true. The configurations tested were confirmed to +# produce non-deterministic results without setting TF_CUDNN_DETERMINISTIC=true + +_PADDING = "SAME" +_STRIDES = [1, 1, 1, 1] + +LayerShape = namedtuple('LayerShape', 'batch, height, width, channels') +FilterShape = namedtuple( + 'FilterShape', 'height, width, in_channels, out_channels') + +class ConvolutionTest(test.TestCase): + + def _random_data_op(self, shape): + # np.random.random_sample can properly interpret either tf.TensorShape or + # namedtuple as a list. + return constant_op.constant(2 * np.random.random_sample(shape) - 1, + dtype=dtypes.float32) + + def _random_out_op(self, in_shape, filter_shape): + # Choosing not to use array_op.zeros() to prevent possible removal by + # optimization + in_op = self._random_data_op(in_shape) + filter_op = self._random_data_op(filter_shape) + # Use the forward op's shape-inference + conv_op = nn_ops.conv2d(in_op, filter_op, + strides=_STRIDES, padding=_PADDING) + out_shape = conv_op.get_shape() + out_op = self._random_data_op(out_shape) + return out_op + + def _assert_reproducible(self, operation): + with self.cached_session(force_gpu=True): + result_1 = self.evaluate(operation) + result_2 = self.evaluate(operation) + self.assertAllEqual(result_1, result_2) + + @test_util.run_cuda_only + def testBackwardFilterGradient(self): + np.random.seed(1) + in_shape = LayerShape(batch=8, height=128, width=128, channels=8) + filter_shape = FilterShape(height=3, width=3, in_channels=8, out_channels=8) + in_op = self._random_data_op(in_shape) + out_op = self._random_out_op(in_shape, filter_shape) + filter_gradient_op = nn_ops.conv2d_backprop_filter( + in_op, filter_shape, out_op, strides=_STRIDES, padding=_PADDING) + self._assert_reproducible(filter_gradient_op) + + @test_util.run_cuda_only + def testBackwardInputGradient(self): + np.random.seed(2) + in_shape = LayerShape(batch=8, height=32, width=32, channels=8) + filter_shape = FilterShape(height=7, width=7, + in_channels=8, out_channels=128) + filter_op = self._random_data_op(filter_shape) + out_op = self._random_out_op(in_shape, filter_shape) + input_gradient_op = nn_ops.conv2d_backprop_input( + in_shape, filter_op, out_op, strides=_STRIDES, padding=_PADDING) + self._assert_reproducible(input_gradient_op) + + # TODO(duncanriach-nvidia): (1) add test to confirm that forward autotuning + # is disabled for cuDNN convolution; (2) add test for deterministic cuDNN + # max-pooling + +if __name__ == "__main__": + os.environ['TF_CUDNN_DETERMINISTIC'] = 'true' + test.main() + -- GitLab From 3cd99fdb0d7f2b79451353721ebe4bd6348e50fb Mon Sep 17 00:00:00 2001 From: ryan jiang Date: Sat, 16 Feb 2019 23:02:25 -0500 Subject: [PATCH 163/766] error if output-shape is tuple --- tensorflow/python/ops/nn_ops.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tensorflow/python/ops/nn_ops.py b/tensorflow/python/ops/nn_ops.py index 031425a579..55d44feffd 100644 --- a/tensorflow/python/ops/nn_ops.py +++ b/tensorflow/python/ops/nn_ops.py @@ -2042,6 +2042,9 @@ def atrous_conv2d_transpose(value, if not output_shape_.get_shape().is_compatible_with(tensor_shape.vector(4)): raise ValueError("output_shape must have shape (4,), got {}".format( output_shape_.get_shape())) + + if isinstance(output_shape, tuple): + raise ValueError("output_shape cannot be of type tuple") if isinstance(output_shape, (list, np.ndarray)): # output_shape's shape should be == [4] if reached this point. -- GitLab From e27d9f7a8f497db30a63818c4bd983377d3ffe52 Mon Sep 17 00:00:00 2001 From: Yuxin Wu Date: Sat, 16 Feb 2019 23:47:43 -0800 Subject: [PATCH 164/766] Update clip_ops.py --- tensorflow/python/ops/clip_ops.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tensorflow/python/ops/clip_ops.py b/tensorflow/python/ops/clip_ops.py index 7e24c59269..eea8b5c282 100644 --- a/tensorflow/python/ops/clip_ops.py +++ b/tensorflow/python/ops/clip_ops.py @@ -306,7 +306,8 @@ def clip_by_global_norm(t_list, clip_norm, use_norm=None, name=None): date=None, instructions= "clip_by_average_norm is deprecated in TensorFlow 2.0. Please use " - "clip_by_norm(t, clip_norm * tf.cast(tf.size(t), tf.float32), name) instead.") + "clip_by_norm(t, clip_norm * tf.cast(tf.size(t), tf.float32), name) " + "instead.") @tf_export(v1=["clip_by_average_norm"]) def clip_by_average_norm(t, clip_norm, name=None): """Clips tensor values to a maximum average L2-norm. -- GitLab From b4f95454cf535b173291acab516cddf47b17a10d Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Mon, 18 Feb 2019 21:34:11 +0000 Subject: [PATCH 165/766] Create directory automatically in save_weights with h5 format This fix tries to address the issue raised in 25835 where save_weight with tf format creates directories automatically as needed, yet h5 format throws out excetion. This fix update the save_weight with h5 format to address the discrepancy between h5 and tf. This fix fixes 25835. Signed-off-by: Yong Tang --- tensorflow/python/keras/engine/network.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tensorflow/python/keras/engine/network.py b/tensorflow/python/keras/engine/network.py index 1e893bf412..6d1d5c5981 100644 --- a/tensorflow/python/keras/engine/network.py +++ b/tensorflow/python/keras/engine/network.py @@ -1393,6 +1393,8 @@ class Network(base_layer.Layer): if not proceed: return if save_format == 'h5': + if not os.path.exists(os.path.dirname(filepath)): + os.makedirs(os.path.dirname(filepath)) with h5py.File(filepath, 'w') as f: hdf5_format.save_weights_to_hdf5_group(f, self.layers) else: -- GitLab From 22f2222e5f97a66e75c5fb3fbd569e2b2cb14042 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Mon, 18 Feb 2019 21:36:09 +0000 Subject: [PATCH 166/766] Add test case for save_weights with tf and h5 format with directory not created before hand. Signed-off-by: Yong Tang --- .../python/keras/saving/hdf5_format_test.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tensorflow/python/keras/saving/hdf5_format_test.py b/tensorflow/python/keras/saving/hdf5_format_test.py index 534c78da17..5cedd92fb5 100644 --- a/tensorflow/python/keras/saving/hdf5_format_test.py +++ b/tensorflow/python/keras/saving/hdf5_format_test.py @@ -748,6 +748,30 @@ class TestWholeModelSaving(test.TestCase): os.close(fd) os.remove(fname) + def test_saving_with_dir_not_created(self): + if h5py is None: + self.skipTest('h5py required to run this test') + + temp_dir = self.get_temp_dir() + self.addCleanup(shutil.rmtree, temp_dir) + for f in ['tf', 'h5']: + with self.cached_session(): + data = np.random.random((1000, 32)).astype(np.float32) + labels = np.random.random((1000, 10)).astype(np.float32) + + model = keras.models.Sequential([ + keras.layers.Dense(10, activation='softmax'), + keras.layers.Dense(10, activation='softmax')]) + + model.compile(optimizer=training_module.RMSPropOptimizer(0.001), + loss='categorical_crossentropy', + metrics=['accuracy']) + + model.fit(data, labels) + prefix = os.path.join(temp_dir, f, 'ckpt') + model.save_weights(prefix, save_format=f) + model.load_weights(prefix) + class SubclassedModel(training.Model): -- GitLab From c306b633ded3875114adc7933ceb8a9ee50f4cfc Mon Sep 17 00:00:00 2001 From: csukuangfj <5284924+csukuangfj@users.noreply.github.com> Date: Tue, 19 Feb 2019 16:44:14 +0800 Subject: [PATCH 167/766] DOC: Fix typos in readme. --- tensorflow/lite/experimental/micro/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 53e55ae1e2..5deda60f4a 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -231,7 +231,7 @@ Successfully tagged riscv_build:latest Building micro_speech_test binary -- Lauch the Docker that we just created using: `docker run -it-v +- Launch the Docker that we just created using: `docker run -it-v /tmp/copybara_out:/workspace riscv_build:latest bash` - Enter the source root directory by running `cd /workspace` - Download the dependencies by running @@ -242,7 +242,7 @@ Building micro_speech_test binary - Build the binary: `make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=riscv32_mcu` -Lauching Renode to test the binary, currently this set up is not automated. +Launching Renode to test the binary, currently this set up is not automated. - Execute the binary on Renode: `renode -P 5000 --disable-xwt -e 's @/workspace/tensorflow/lite/experimental/micro/testing/sifive_fe310.resc'` -- GitLab From 40b41a9e188a9dabc3cbbf1938ad7993908bbefa Mon Sep 17 00:00:00 2001 From: zjjott Date: Tue, 19 Feb 2019 17:56:48 +0800 Subject: [PATCH 168/766] fix absl import issue #25282 --- 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 f0fa8b50db..38ff295ae4 100644 --- a/tensorflow/tools/pip_package/setup.py +++ b/tensorflow/tools/pip_package/setup.py @@ -48,7 +48,7 @@ DOCLINES = __doc__.split('\n') _VERSION = '1.12.0' REQUIRED_PACKAGES = [ - 'absl-py >= 0.1.6', + 'absl-py >= 0.7.0', 'astor >= 0.6.0', 'gast >= 0.2.0', 'google_pasta >= 0.1.1', -- GitLab From 9519cbdfbd778eaa0c018f7f414f231822d8b776 Mon Sep 17 00:00:00 2001 From: ryan jiang Date: Tue, 19 Feb 2019 17:24:19 -0500 Subject: [PATCH 169/766] convert to list instead of error --- tensorflow/python/ops/nn_ops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/ops/nn_ops.py b/tensorflow/python/ops/nn_ops.py index 55d44feffd..16d7975005 100644 --- a/tensorflow/python/ops/nn_ops.py +++ b/tensorflow/python/ops/nn_ops.py @@ -2044,7 +2044,7 @@ def atrous_conv2d_transpose(value, output_shape_.get_shape())) if isinstance(output_shape, tuple): - raise ValueError("output_shape cannot be of type tuple") + output_shape = list(output_shape) if isinstance(output_shape, (list, np.ndarray)): # output_shape's shape should be == [4] if reached this point. -- GitLab From b3c29b2638605a15bdb49ed6eab7ce36c16a51b2 Mon Sep 17 00:00:00 2001 From: Bhavani Subramanian Date: Tue, 19 Feb 2019 16:31:28 -0800 Subject: [PATCH 170/766] Changed type of 'padding_list_' to 'Tpadding'. Added additional registrations for QuantizedConv2D + bias + sum (and signed sum) + relu & requantize fusion for when Tbias is a float. --- tensorflow/core/kernels/mkl_conv_ops.cc | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/tensorflow/core/kernels/mkl_conv_ops.cc b/tensorflow/core/kernels/mkl_conv_ops.cc index 51b37480a7..5932237b94 100644 --- a/tensorflow/core/kernels/mkl_conv_ops.cc +++ b/tensorflow/core/kernels/mkl_conv_ops.cc @@ -1275,7 +1275,7 @@ class MklConvOp : public OpKernel { private: std::vector strides_; std::vector dilations_; - std::vector padding_list_; + std::vector padding_list_; bool is_filter_const_; mutex mu_; Padding padding_; @@ -2056,6 +2056,25 @@ REGISTER_KERNEL_BUILDER( .Label(mkl_op_registry::kMklQuantizedOpLabel), MklQuantizedConv2DSumReluOp); +REGISTER_KERNEL_BUILDER( + Name("_MklQuantizedConv2DWithBiasSumAndReluAndRequantize") + .Device(DEVICE_CPU) + .TypeConstraint("Tinput") + .TypeConstraint("Tfilter") + .TypeConstraint("Tbias") + .TypeConstraint("out_type") + .Label(mkl_op_registry::kMklQuantizedOpLabel), + MklQuantizedConv2DSumReluOp); + +REGISTER_KERNEL_BUILDER( + Name("_MklQuantizedConv2DWithBiasSignedSumAndReluAndRequantize") + .Device(DEVICE_CPU) + .TypeConstraint("Tinput") + .TypeConstraint("Tfilter") + .TypeConstraint("Tbias") + .TypeConstraint("out_type") + .Label(mkl_op_registry::kMklQuantizedOpLabel), + MklQuantizedConv2DSumReluOp); #endif // INTEL_MKL_ML // Register 2D operations @@ -2090,7 +2109,7 @@ REGISTER_KERNEL_BUILDER( .TypeConstraint("Tpaddings") \ .Label(mkl_op_registry::kMklOpLabel), \ MklConvOp); \ + float, int64, false, true, false>); \ REGISTER_KERNEL_BUILDER(Name("__MklDummyPadWithConv2D") \ .Device(DEVICE_CPU) \ .TypeConstraint("T") \ @@ -2132,7 +2151,7 @@ TF_CALL_float(REGISTER_MKL_CPU_2D_DEPTHWISE); .TypeConstraint("T") \ .TypeConstraint("Tpaddings") \ .Label(mkl_op_registry::kMklOpLabel), \ - MklFusedConvOp); \ + MklFusedConvOp); \ REGISTER_KERNEL_BUILDER(Name("__MklDummyPadWithFusedConv2D") \ .Device(DEVICE_CPU) \ .TypeConstraint("T") \ -- GitLab From 09df8391f954d27694b2737b80069279f82f9b92 Mon Sep 17 00:00:00 2001 From: Albin Joy Date: Wed, 20 Feb 2019 10:20:40 +0530 Subject: [PATCH 171/766] Removed redundant code from layout_optimizer.cc --- tensorflow/core/grappler/optimizers/layout_optimizer.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/tensorflow/core/grappler/optimizers/layout_optimizer.cc b/tensorflow/core/grappler/optimizers/layout_optimizer.cc index e9b706a583..e091e1a019 100644 --- a/tensorflow/core/grappler/optimizers/layout_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/layout_optimizer.cc @@ -2048,8 +2048,6 @@ class DataLayoutOptimizer : GraphProcessor { // only needs to be performed if at least one node in the previous pass is // expanded. if (graph_->node_size() > node_size_original) { - NodeDef* n = AddNodePermNHWCToNCHW(); - n = AddNodePermNCHWToNHWC(); std::set ops_format_agnostic = GetOpsFormatAgnostic(); for (int i = 0; i < graph_->node_size(); i++) { if (ops_format_agnostic.find(graph_->node(i).op()) != -- GitLab From 547137ee70a9acc3dc34642e63c5215bab7c8f0b Mon Sep 17 00:00:00 2001 From: Amit Srivastava Date: Wed, 20 Feb 2019 11:38:47 +0530 Subject: [PATCH 172/766] Fixed warning in the file Initialized the variable to fix the warning --- tensorflow/core/kernels/lookup_util.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/core/kernels/lookup_util.cc b/tensorflow/core/kernels/lookup_util.cc index 30fe4b077a..c3b80f04ed 100644 --- a/tensorflow/core/kernels/lookup_util.cc +++ b/tensorflow/core/kernels/lookup_util.cc @@ -163,7 +163,7 @@ class TextFileLineIterator int64 total_size() const override { if (vocab_size_ == -1) { - int64 new_size; + int64 new_size = -1; Status status = GetNumLinesInTextFile(env_, filename_, &new_size); if (!status.ok()) { LOG(WARNING) << "Unable to get line count: " << status; -- GitLab From cf87abf85dc6f45ec91a858e197b3615fede665b Mon Sep 17 00:00:00 2001 From: Bhavani Subramanian Date: Tue, 19 Feb 2019 22:26:41 -0800 Subject: [PATCH 173/766] Updated the API golden files since 'padding_list' attribute was added to quantized Conv2D ops. --- .../api/golden/v2/tensorflow.raw_ops.pbtxt | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tensorflow/tools/api/golden/v2/tensorflow.raw_ops.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.raw_ops.pbtxt index 144d564472..f340c9a43a 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.raw_ops.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.raw_ops.pbtxt @@ -2270,43 +2270,43 @@ tf_module { } member_method { name: "QuantizedConv2DAndRelu" - argspec: "args=[\'input\', \'filter\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'out_type\', \'strides\', \'padding\', \'dilations\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'input\', \'filter\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'out_type\', \'strides\', \'padding\', \'dilations\', \'padding_list\'], varargs=None, keywords=None, defaults=None" } member_method { name: "QuantizedConv2DAndReluAndRequantize" - argspec: "args=[\'input\', \'filter\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'min_freezed_output\', \'max_freezed_output\', \'out_type\', \'strides\', \'padding\', \'dilations\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'input\', \'filter\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'min_freezed_output\', \'max_freezed_output\', \'out_type\', \'strides\', \'padding\', \'dilations\', \'padding_list\'], varargs=None, keywords=None, defaults=None" } member_method { name: "QuantizedConv2DAndRequantize" - argspec: "args=[\'input\', \'filter\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'min_freezed_output\', \'max_freezed_output\', \'out_type\', \'strides\', \'padding\', \'dilations\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'input\', \'filter\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'min_freezed_output\', \'max_freezed_output\', \'out_type\', \'strides\', \'padding\', \'dilations\', \'padding_list\'], varargs=None, keywords=None, defaults=None" } member_method { name: "QuantizedConv2DWithBias" - argspec: "args=[\'input\', \'filter\', \'bias\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'out_type\', \'strides\', \'padding\', \'dilations\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'input\', \'filter\', \'bias\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'out_type\', \'strides\', \'padding\', \'dilations\', \'padding_list\'], varargs=None, keywords=None, defaults=None" } member_method { name: "QuantizedConv2DWithBiasAndRelu" - argspec: "args=[\'input\', \'filter\', \'bias\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'out_type\', \'strides\', \'padding\', \'dilations\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'input\', \'filter\', \'bias\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'out_type\', \'strides\', \'padding\', \'dilations\', \'padding_list\'], varargs=None, keywords=None, defaults=None" } member_method { name: "QuantizedConv2DWithBiasAndReluAndRequantize" - argspec: "args=[\'input\', \'filter\', \'bias\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'min_freezed_output\', \'max_freezed_output\', \'out_type\', \'strides\', \'padding\', \'dilations\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'input\', \'filter\', \'bias\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'min_freezed_output\', \'max_freezed_output\', \'out_type\', \'strides\', \'padding\', \'dilations\', \'padding_list\'], varargs=None, keywords=None, defaults=None" } member_method { name: "QuantizedConv2DWithBiasAndRequantize" - argspec: "args=[\'input\', \'filter\', \'bias\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'min_freezed_output\', \'max_freezed_output\', \'out_type\', \'strides\', \'padding\', \'dilations\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'input\', \'filter\', \'bias\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'min_freezed_output\', \'max_freezed_output\', \'out_type\', \'strides\', \'padding\', \'dilations\', \'padding_list\'], varargs=None, keywords=None, defaults=None" } member_method { name: "QuantizedConv2DWithBiasSignedSumAndReluAndRequantize" - argspec: "args=[\'input\', \'filter\', \'bias\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'min_freezed_output\', \'max_freezed_output\', \'summand\', \'min_summand\', \'max_summand\', \'out_type\', \'strides\', \'padding\', \'dilations\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'input\', \'filter\', \'bias\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'min_freezed_output\', \'max_freezed_output\', \'summand\', \'min_summand\', \'max_summand\', \'out_type\', \'strides\', \'padding\', \'dilations\', \'padding_list\'], varargs=None, keywords=None, defaults=None" } member_method { name: "QuantizedConv2DWithBiasSumAndRelu" - argspec: "args=[\'input\', \'filter\', \'bias\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'summand\', \'out_type\', \'strides\', \'padding\', \'dilations\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'input\', \'filter\', \'bias\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'summand\', \'out_type\', \'strides\', \'padding\', \'dilations\', \'padding_list\'], varargs=None, keywords=None, defaults=None" } member_method { name: "QuantizedConv2DWithBiasSumAndReluAndRequantize" - argspec: "args=[\'input\', \'filter\', \'bias\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'min_freezed_output\', \'max_freezed_output\', \'summand\', \'min_summand\', \'max_summand\', \'out_type\', \'strides\', \'padding\', \'dilations\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'input\', \'filter\', \'bias\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'min_freezed_output\', \'max_freezed_output\', \'summand\', \'min_summand\', \'max_summand\', \'out_type\', \'strides\', \'padding\', \'dilations\', \'padding_list\'], varargs=None, keywords=None, defaults=None" } member_method { name: "QuantizedInstanceNorm" @@ -2655,7 +2655,7 @@ tf_module { member_method { name: "Requantize" argspec: "args=[\'input\', \'input_min\', \'input_max\', \'requested_output_min\', \'requested_output_max\', \'out_type\'], varargs=None, keywords=None, defaults=None" - } + } member_method { name: "RequantizePerChannel" argspec: "args=[\'input\', \'input_min\', \'input_max\', \'requested_output_min\', \'requested_output_max\', \'out_type\'], varargs=None, keywords=None, defaults=None" -- GitLab From dcbbefa8c703c4091951adc6fec7bba18bae29cb Mon Sep 17 00:00:00 2001 From: Bhavani Subramanian Date: Tue, 19 Feb 2019 23:36:06 -0800 Subject: [PATCH 174/766] Added v1 golden file for 'api_compatibility_test' --- .../api/golden/v1/tensorflow.raw_ops.pbtxt | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tensorflow/tools/api/golden/v1/tensorflow.raw_ops.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.raw_ops.pbtxt index 58d3482e5f..e0760e5bbf 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.raw_ops.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.raw_ops.pbtxt @@ -2278,43 +2278,43 @@ tf_module { } member_method { name: "QuantizedConv2DAndRelu" - argspec: "args=[\'input\', \'filter\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'out_type\', \'strides\', \'padding\', \'dilations\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'input\', \'filter\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'out_type\', \'strides\', \'padding\', \'dilations\', \'padding_list\'], varargs=None, keywords=None, defaults=None" } member_method { name: "QuantizedConv2DAndReluAndRequantize" - argspec: "args=[\'input\', \'filter\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'min_freezed_output\', \'max_freezed_output\', \'out_type\', \'strides\', \'padding\', \'dilations\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'input\', \'filter\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'min_freezed_output\', \'max_freezed_output\', \'out_type\', \'strides\', \'padding\', \'dilations\', \'padding_list\'], varargs=None, keywords=None, defaults=None" } member_method { name: "QuantizedConv2DAndRequantize" - argspec: "args=[\'input\', \'filter\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'min_freezed_output\', \'max_freezed_output\', \'out_type\', \'strides\', \'padding\', \'dilations\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'input\', \'filter\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'min_freezed_output\', \'max_freezed_output\', \'out_type\', \'strides\', \'padding\', \'dilations\', \'padding_list\'], varargs=None, keywords=None, defaults=None" } member_method { name: "QuantizedConv2DWithBias" - argspec: "args=[\'input\', \'filter\', \'bias\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'out_type\', \'strides\', \'padding\', \'dilations\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'input\', \'filter\', \'bias\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'out_type\', \'strides\', \'padding\', \'dilations\', \'padding_list\'], varargs=None, keywords=None, defaults=None" } member_method { name: "QuantizedConv2DWithBiasAndRelu" - argspec: "args=[\'input\', \'filter\', \'bias\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'out_type\', \'strides\', \'padding\', \'dilations\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'input\', \'filter\', \'bias\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'out_type\', \'strides\', \'padding\', \'dilations\', \'padding_list\'], varargs=None, keywords=None, defaults=None" } member_method { name: "QuantizedConv2DWithBiasAndReluAndRequantize" - argspec: "args=[\'input\', \'filter\', \'bias\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'min_freezed_output\', \'max_freezed_output\', \'out_type\', \'strides\', \'padding\', \'dilations\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'input\', \'filter\', \'bias\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'min_freezed_output\', \'max_freezed_output\', \'out_type\', \'strides\', \'padding\', \'dilations\', \'padding_list\'], varargs=None, keywords=None, defaults=None" } member_method { name: "QuantizedConv2DWithBiasAndRequantize" - argspec: "args=[\'input\', \'filter\', \'bias\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'min_freezed_output\', \'max_freezed_output\', \'out_type\', \'strides\', \'padding\', \'dilations\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'input\', \'filter\', \'bias\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'min_freezed_output\', \'max_freezed_output\', \'out_type\', \'strides\', \'padding\', \'dilations\', \'padding_list\'], varargs=None, keywords=None, defaults=None" } member_method { name: "QuantizedConv2DWithBiasSignedSumAndReluAndRequantize" - argspec: "args=[\'input\', \'filter\', \'bias\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'min_freezed_output\', \'max_freezed_output\', \'summand\', \'min_summand\', \'max_summand\', \'out_type\', \'strides\', \'padding\', \'dilations\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'input\', \'filter\', \'bias\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'min_freezed_output\', \'max_freezed_output\', \'summand\', \'min_summand\', \'max_summand\', \'out_type\', \'strides\', \'padding\', \'dilations\', \'padding_list\'], varargs=None, keywords=None, defaults=None" } member_method { name: "QuantizedConv2DWithBiasSumAndRelu" - argspec: "args=[\'input\', \'filter\', \'bias\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'summand\', \'out_type\', \'strides\', \'padding\', \'dilations\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'input\', \'filter\', \'bias\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'summand\', \'out_type\', \'strides\', \'padding\', \'dilations\', \'padding_list\'], varargs=None, keywords=None, defaults=None" } member_method { name: "QuantizedConv2DWithBiasSumAndReluAndRequantize" - argspec: "args=[\'input\', \'filter\', \'bias\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'min_freezed_output\', \'max_freezed_output\', \'summand\', \'min_summand\', \'max_summand\', \'out_type\', \'strides\', \'padding\', \'dilations\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'input\', \'filter\', \'bias\', \'min_input\', \'max_input\', \'min_filter\', \'max_filter\', \'min_freezed_output\', \'max_freezed_output\', \'summand\', \'min_summand\', \'max_summand\', \'out_type\', \'strides\', \'padding\', \'dilations\', \'padding_list\'], varargs=None, keywords=None, defaults=None" } member_method { name: "QuantizedInstanceNorm" -- GitLab From a6cfcd144d67fee98f72f98fb508f4e4591038f3 Mon Sep 17 00:00:00 2001 From: Siju Date: Wed, 20 Feb 2019 13:47:08 +0530 Subject: [PATCH 175/766] Update split_handler_ops.cc Removed unused variables best_dimension_idx & dimension_id from split_handler_ops.cc --- tensorflow/contrib/boosted_trees/kernels/split_handler_ops.cc | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tensorflow/contrib/boosted_trees/kernels/split_handler_ops.cc b/tensorflow/contrib/boosted_trees/kernels/split_handler_ops.cc index 6d78e27e8f..65276242ab 100644 --- a/tensorflow/contrib/boosted_trees/kernels/split_handler_ops.cc +++ b/tensorflow/contrib/boosted_trees/kernels/split_handler_ops.cc @@ -538,7 +538,6 @@ class BuildSparseInequalitySplitsOp : public OpKernel { partition_boundaries[non_empty_partitions[root_idx]]; float best_gain = std::numeric_limits::lowest(); - int32 best_dimension_idx = 0; bool default_right = false; int32 best_element_idx = 0; @@ -571,7 +570,6 @@ class BuildSparseInequalitySplitsOp : public OpKernel { // Iterate through dimensions. for (int j = 0; j < dimension_boundaries.size() - 1; ++j) { const DimensionBoundary& dimension_and_start = dimension_boundaries[j]; - const int32 dimension_id = dimension_and_start.dimension_id; int start_index = dimension_and_start.start_index; // Even for the last dimension, we always have additional dummy @@ -630,7 +628,6 @@ class BuildSparseInequalitySplitsOp : public OpKernel { best_right_node_stats = right_stats_default_left; best_element_idx = element_idx; default_right = false; - best_dimension_idx = dimension_id; } } // Consider calculating the default direction only when there were @@ -648,7 +645,6 @@ class BuildSparseInequalitySplitsOp : public OpKernel { best_right_node_stats = right_stats_default_right; best_element_idx = element_idx; default_right = true; - best_dimension_idx = dimension_id; } } } -- GitLab From 3e04ea541d8842d47b3c8f38e06b18ce96256462 Mon Sep 17 00:00:00 2001 From: Siju Date: Wed, 20 Feb 2019 13:51:30 +0530 Subject: [PATCH 176/766] Update bigtable_lib.cc grpc_code was assigned and not used, probably a small bug. --- tensorflow/contrib/bigtable/kernels/bigtable_lib.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/contrib/bigtable/kernels/bigtable_lib.cc b/tensorflow/contrib/bigtable/kernels/bigtable_lib.cc index 98f906408c..a81c3ec5c2 100644 --- a/tensorflow/contrib/bigtable/kernels/bigtable_lib.cc +++ b/tensorflow/contrib/bigtable/kernels/bigtable_lib.cc @@ -27,7 +27,7 @@ Status GrpcStatusToTfStatus(const ::grpc::Status& status) { status.error_code() == ::grpc::StatusCode::OUT_OF_RANGE) { grpc_code = ::grpc::StatusCode::INTERNAL; } - return Status(static_cast<::tensorflow::error::Code>(status.error_code()), + return Status(static_cast<::tensorflow::error::Code>(grpc_code), strings::StrCat("Error reading from Cloud Bigtable: ", status.error_message())); } -- GitLab From 0dc6013d37f34337bda379a6160b477f450ad862 Mon Sep 17 00:00:00 2001 From: Siju Date: Wed, 20 Feb 2019 15:26:28 +0530 Subject: [PATCH 177/766] Update xla_data.proto --- tensorflow/compiler/xla/xla_data.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/compiler/xla/xla_data.proto b/tensorflow/compiler/xla/xla_data.proto index 226299a718..687eaa735a 100644 --- a/tensorflow/compiler/xla/xla_data.proto +++ b/tensorflow/compiler/xla/xla_data.proto @@ -199,7 +199,7 @@ message ShapeProto { // in this field represents an upper bound on the size of the dimension. repeated int64 dimensions = 3; - // For tuples only, the shapes of constitutent shapes in the tuple sequence. + // For tuples only, the shapes of constituent shapes in the tuple sequence. repeated ShapeProto tuple_shapes = 4; // The layout used to back this shape. -- GitLab From c965ec6f2a6eb0024b8dabc0934db183a103bc12 Mon Sep 17 00:00:00 2001 From: Siju Date: Wed, 20 Feb 2019 15:28:04 +0530 Subject: [PATCH 178/766] Update local_computation_builder.h --- tensorflow/compiler/xla/python/local_computation_builder.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/compiler/xla/python/local_computation_builder.h b/tensorflow/compiler/xla/python/local_computation_builder.h index 74996d2e6b..ccfe1ac1f8 100644 --- a/tensorflow/compiler/xla/python/local_computation_builder.h +++ b/tensorflow/compiler/xla/python/local_computation_builder.h @@ -87,7 +87,7 @@ class LocalShapedBuffer { // analogous to std::unique_ptr::release(). ShapedBuffer Release(); - // Destructures a tuple-valued LocalShapedBuffer into its constitutent + // Destructures a tuple-valued LocalShapedBuffer into its constituent // elements in LocalShapedBufferTuple form. StatusOr DestructureTuple(); -- GitLab From 3db2261857a2f7710332c4b86c152222e57a227c Mon Sep 17 00:00:00 2001 From: Siju Date: Wed, 20 Feb 2019 15:28:39 +0530 Subject: [PATCH 179/766] Update xrt.h --- tensorflow/compiler/xla/python/xrt.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/compiler/xla/python/xrt.h b/tensorflow/compiler/xla/python/xrt.h index dd5bba6d5c..710c3af3fa 100644 --- a/tensorflow/compiler/xla/python/xrt.h +++ b/tensorflow/compiler/xla/python/xrt.h @@ -69,7 +69,7 @@ class XrtAllocationTuple { std::vector elements_; }; -// Destructures a tuple-valued XrtAllocation into its constitutent elements +// Destructures a tuple-valued XrtAllocation into its constituent elements // in XrtAllocationTuple form. // // Accepts a `session_target` argument, used in constructing the -- GitLab From fa9c446359dc50ece65f7a77e19f5e0a41bbf0dd Mon Sep 17 00:00:00 2001 From: Siju Date: Wed, 20 Feb 2019 15:38:09 +0530 Subject: [PATCH 180/766] Update utils_impl.py --- tensorflow/python/saved_model/utils_impl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/saved_model/utils_impl.py b/tensorflow/python/saved_model/utils_impl.py index a82007fd54..2e7b208057 100644 --- a/tensorflow/python/saved_model/utils_impl.py +++ b/tensorflow/python/saved_model/utils_impl.py @@ -50,7 +50,7 @@ def build_tensor_info(tensor): Args: tensor: Tensor or SparseTensor whose name, dtype and shape are used to build the TensorInfo. For SparseTensors, the names of the three - constitutent Tensors are used. + constituent Tensors are used. Returns: A TensorInfo protocol buffer constructed based on the supplied argument. -- GitLab From 68e45519a931f83cedfe6cc0688335dcaefad2ce Mon Sep 17 00:00:00 2001 From: Siju Date: Wed, 20 Feb 2019 16:17:09 +0530 Subject: [PATCH 181/766] Update types.cc accesing->accessing --- tensorflow/lite/toco/tflite/types.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/toco/tflite/types.cc b/tensorflow/lite/toco/tflite/types.cc index f878dafc1e..96cad557ba 100644 --- a/tensorflow/lite/toco/tflite/types.cc +++ b/tensorflow/lite/toco/tflite/types.cc @@ -37,7 +37,7 @@ DataBuffer::FlatBufferOffset CopyStringToBuffer( } // vector may be implemented using a bit-set, so we can't just -// reinterpret_cast, accesing it data as vector and let flatbuffer +// reinterpret_cast, accessing its data as vector and let flatbuffer // CreateVector handle it. // Background: https://isocpp.org/blog/2012/11/on-vectorbool DataBuffer::FlatBufferOffset CopyBoolToBuffer( -- GitLab From 20444ef677cd528d4ce9560527d0164dfc39a747 Mon Sep 17 00:00:00 2001 From: Siju Date: Wed, 20 Feb 2019 16:20:13 +0530 Subject: [PATCH 182/766] Update profiler.cc --- tensorflow/core/profiler/profiler.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/core/profiler/profiler.cc b/tensorflow/core/profiler/profiler.cc index 808e3c853b..cdcb8dddf6 100644 --- a/tensorflow/core/profiler/profiler.cc +++ b/tensorflow/core/profiler/profiler.cc @@ -114,7 +114,7 @@ int Run(int argc, char** argv) { Flag("min_output_bytes", &FLAGS_min_output_bytes, "min_output_bytes"), Flag("min_micros", &FLAGS_min_micros, "min micros"), Flag("min_accelerator_micros", &FLAGS_min_accelerator_micros, - "min acclerator_micros"), + "min accelerator_micros"), Flag("min_cpu_micros", &FLAGS_min_cpu_micros, "min_cpu_micros"), Flag("min_params", &FLAGS_min_params, "min params"), Flag("min_float_ops", &FLAGS_min_float_ops, "min float ops"), -- GitLab From 786e9b1a0d3e1de322de64942adcce6c8baab132 Mon Sep 17 00:00:00 2001 From: Siju Date: Wed, 20 Feb 2019 16:23:28 +0530 Subject: [PATCH 183/766] Update mkl_concat_op.cc accross -> across --- tensorflow/core/kernels/mkl_concat_op.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/core/kernels/mkl_concat_op.cc b/tensorflow/core/kernels/mkl_concat_op.cc index b95bbca6b5..d8fbb83940 100644 --- a/tensorflow/core/kernels/mkl_concat_op.cc +++ b/tensorflow/core/kernels/mkl_concat_op.cc @@ -484,7 +484,7 @@ class MklConcatOp : public OpKernel { output_tensor->flat().size() * sizeof(uint8)); } - // This method finds the most commom format accross all MKL inputs + // This method finds the most commom format across all MKL inputs // Inputs: // 1. input_shapes: shapes of input (MKL) tensors. // 2. concat_dim: concat dimension. -- GitLab From c331193b811a439bbbbcc0119014bf6e5e847724 Mon Sep 17 00:00:00 2001 From: Armen Poghosov Date: Wed, 20 Feb 2019 16:29:07 +0100 Subject: [PATCH 184/766] removing extra bool array as it is not needed --- tensorflow/cc/framework/gradients.cc | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/tensorflow/cc/framework/gradients.cc b/tensorflow/cc/framework/gradients.cc index d5ba56ea4a..84a1ad585f 100644 --- a/tensorflow/cc/framework/gradients.cc +++ b/tensorflow/cc/framework/gradients.cc @@ -167,11 +167,12 @@ Status SymbolicGradientBuilder::BackpropAlongEdge(const Output& dst_grad, std::vector SymbolicGradientBuilder::GetReachableNodes() { std::vector reachable_nodes(scope_.graph()->num_node_ids(), false); std::deque queue; - std::vector visited(scope_.graph()->num_node_ids(), false); for (const Output& out : outputs_) { - if (!reachable_nodes[out.node()->id()]) { - queue.push_back(out.node()); - reachable_nodes[out.node()->id()] = true; + const Node* const out_node = out.node(); + const int out_node_id = out_node->id(); + if (!reachable_nodes[out_node_id]) { + queue.push_back(out_node); + reachable_nodes[out_node_id] = true; } } @@ -180,10 +181,12 @@ std::vector SymbolicGradientBuilder::GetReachableNodes() { queue.pop_front(); for (const Edge* e : n->in_edges()) { if (e->IsControlEdge()) continue; - if (visited[e->src()->id()]) continue; - queue.push_back(e->src()); - reachable_nodes[e->src()->id()] = true; - visited[e->src()->id()] = true; + const Node* const src_node = e->src(); + const int src_node_id = src_node->id(); + if (!reachable_nodes[src_node_id]) { + queue.push_back(src_node); + reachable_nodes[src_node_id] = true; + } } } return reachable_nodes; -- GitLab From 9f769c72fa6c8a33742b1a5a09537bed2160c5b2 Mon Sep 17 00:00:00 2001 From: Nupur Garg Date: Wed, 20 Feb 2019 11:34:55 -0800 Subject: [PATCH 185/766] Add support for TFLiteConverter in 2.0. PiperOrigin-RevId: 234836290 --- tensorflow/lite/python/BUILD | 15 ++ tensorflow/lite/python/convert.py | 2 +- tensorflow/lite/python/lite.py | 167 ++++++++++++++-- tensorflow/lite/python/lite_v2_test.py | 182 ++++++++++++++++++ .../v2/tensorflow.lite.-toco-converter.pbtxt | 24 --- .../tools/api/golden/v2/tensorflow.lite.pbtxt | 8 - tensorflow/tools/compatibility/renames_v2.py | 2 + 7 files changed, 348 insertions(+), 52 deletions(-) create mode 100644 tensorflow/lite/python/lite_v2_test.py delete mode 100644 tensorflow/tools/api/golden/v2/tensorflow.lite.-toco-converter.pbtxt diff --git a/tensorflow/lite/python/BUILD b/tensorflow/lite/python/BUILD index ceda5effed..dd5a76049d 100644 --- a/tensorflow/lite/python/BUILD +++ b/tensorflow/lite/python/BUILD @@ -96,6 +96,21 @@ py_test( ], ) +py_test( + name = "lite_v2_test", + srcs = ["lite_v2_test.py"], + srcs_version = "PY2AND3", + tags = [ + "no_oss", + "no_windows", + ], + deps = [ + ":lite", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework_test_lib", + ], +) + py_test( name = "lite_flex_test", srcs = ["lite_flex_test.py"], diff --git a/tensorflow/lite/python/convert.py b/tensorflow/lite/python/convert.py index 9c60399871..4d38ee9be4 100644 --- a/tensorflow/lite/python/convert.py +++ b/tensorflow/lite/python/convert.py @@ -443,7 +443,7 @@ def toco_convert_impl(input_data, input_tensors, output_tensors, *args, return data -@_tf_export("lite.toco_convert") +@_tf_export(v1=["lite.toco_convert"]) @deprecation.deprecated(None, "Use `lite.TFLiteConverter` instead.") def toco_convert(input_data, input_tensors, output_tensors, *args, **kwargs): """Convert a model using TOCO. diff --git a/tensorflow/lite/python/lite.py b/tensorflow/lite/python/lite.py index b037c3cce0..e609a2cb8d 100644 --- a/tensorflow/lite/python/lite.py +++ b/tensorflow/lite/python/lite.py @@ -12,26 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""TensorFlow Lite tooling helper functionality. +"""TensorFlow Lite tooling helper functionality.""" -EXPERIMENTAL: APIs here are unstable and likely to change without notice. - -@@TocoConverter -@@TFLiteConverter -@@toco_convert -@@toco_convert_protos -@@Interpreter -@@OpHint -@@convert_op_hints_to_stubs -@@build_toco_convert_protos - -@@TFLITE -@@GRAPHVIZ_DOT - -""" from __future__ import absolute_import from __future__ import division from __future__ import print_function + import warnings import enum from six import PY3 @@ -60,6 +46,10 @@ from tensorflow.core.protobuf import config_pb2 as _config_pb2 from tensorflow.core.protobuf import meta_graph_pb2 as _meta_graph_pb2 from tensorflow.python import keras as _keras from tensorflow.python.client import session as _session +from tensorflow.python.eager import def_function as _def_function +from tensorflow.python.eager import function as _function +from tensorflow.python.framework import convert_to_constants as _convert_to_constants +from tensorflow.python.framework import dtypes as _dtypes from tensorflow.python.framework import graph_util as _tf_graph_util from tensorflow.python.framework import ops as _ops from tensorflow.python.framework.errors_impl import NotFoundError as _NotFoundError @@ -73,18 +63,20 @@ from tensorflow.python.util import deprecation as _deprecation from tensorflow.python.util.tf_export import tf_export as _tf_export -def _run_graph_optimizations(graph_def, input_arrays, output_arrays): +def _run_graph_optimizations(graph_def, input_arrays, output_arrays, + graph=None): """Apply standard TensorFlow optimizations to the graph_def. Args: graph_def: Frozen GraphDef to be optimized. input_arrays: List of arrays that are considered inputs of the graph. output_arrays: List of arrays that are considered outputs of the graph. + graph: TensorFlow Graph. Required when Eager mode is enabled. (default None) Returns: A new, optimized GraphDef. """ - meta_graph = _export_meta_graph(graph_def=graph_def) + meta_graph = _export_meta_graph(graph_def=graph_def, graph=graph) # We need to add a collection called 'train_op' so that grappler # knows what the outputs are. @@ -155,6 +147,143 @@ class RepresentativeDataset(object): self.output_gen = output_gen +class TFLiteConverterV2(object): + """Converts a TensorFlow model into TensorFlow Lite model. + + Attributes: + allow_custom_ops: Boolean indicating whether to allow custom operations. + When false any unknown operation is an error. When true, custom ops are + created for any op that is unknown. The developer will need to provide + these to the TensorFlow Lite runtime with a custom resolver. (default + False) + target_ops: Experimental flag, subject to change. Set of OpsSet options + indicating which converter to use. (default set([OpsSet.TFLITE_BUILTINS])) + optimizations: Experimental flag, subject to change, A list of optimizations + to apply when converting the model. The converter applies the + optimizations by giving priority to the optimizations specified earlier in + the list. E.g. `[optimize.OPTIMIZE_FOR_SIZE, + optimize.OPTIMIZE_FOR_LATENCY]` requires the converter to do both size and + latency optimizations giving priority to size optimizations over latency + optimizations. + representative_dataset: a representative dataset that can be used to + generate input and output samples for the model. The converter can use the + dataset to evaluate different optimizations. + + Example usage: + + ```python + # Converting a GraphDef from a ConcreteFunction. + converter = lite.TFLiteConverter.from_concrete_function(func) + tflite_model = converter.convert() + open("converted_model.tflite", "wb").write(tflite_model) + ``` + """ + + def __init__(self, func): + """Constructor for TFLiteConverter. + + Args: + func: TensorFlow ConcreteFunction. + """ + self._func = func + self.allow_custom_ops = False + self.target_ops = set([OpsSet.TFLITE_BUILTINS]) + self.representative_dataset = None + self.optimizations = [] + + @classmethod + def from_concrete_function(cls, func): + """Creates a TFLiteConverter class from a ConcreteFunction. + + Args: + func: TensorFlow ConcreteFunction. + + Returns: + TFLiteConverter class. + """ + if not isinstance(func, _function.ConcreteFunction): + message = "This function takes in a ConcreteFunction." + if isinstance(func, _def_function.Function): + message += (" To get the ConcreteFunction from a Function," + " call from_concrete_function.") + raise ValueError(message) + return cls(func) + + def convert(self): + """Converts a TensorFlow GraphDef based on instance variables. + + Returns: + The converted data in serialized format. + + Raises: + ValueError: + Input shape is not specified. + None value for dimension in input_tensor. + """ + graph_def = _convert_to_constants.convert_variables_to_constants_v2( + self._func) + input_tensors = [ + tensor for tensor in self._func.inputs + if tensor.dtype != _dtypes.resource + ] + output_tensors = self._func.outputs + + # Run a Grappler pass. + graph_def = _run_graph_optimizations(graph_def, input_tensors, + output_tensors, self._func.graph) + + # Checks dimensions in input tensor. + for tensor in input_tensors: + # Note that shape_list might be empty for scalar shapes. + shape_list = tensor.get_shape().as_list() + if None in shape_list[1:]: + raise ValueError( + "None is only supported in the 1st dimension. Tensor '{0}' has " + "invalid shape '{1}'.".format(_tensor_name(tensor), shape_list)) + elif shape_list and shape_list[0] is None: + self._set_batch_size(batch_size=1) + + if self.representative_dataset: + if not isinstance(self.representative_dataset, RepresentativeDataset): + raise TypeError("representative_dataset must be an instance of " + "RepresentativeDataset") + if self.representative_dataset.input_gen is None: + raise ValueError( + "Provide an input generator for representative_dataset") + + # TODO(shashishekhar): For now use optimizations order is ignored. + # Both size and latency optimizations decide whether to apply post + # training optimizations. + post_training_optimize = bool( + len( + set(self.optimizations) + & set([Optimize.OPTIMIZE_FOR_LATENCY, Optimize.OPTIMIZE_FOR_SIZE]))) + # Do weights only quantization if there is no dataset for calibration. + weights_only_quantize_flag = ( + post_training_optimize and (self.representative_dataset is None)) + + converter_kwargs = { + "input_format": constants.TENSORFLOW_GRAPHDEF, + "allow_custom_ops": self.allow_custom_ops, + "post_training_quantize": weights_only_quantize_flag, + "target_ops": self.target_ops, + } + + # Converts model. + result = _toco_convert_impl( + input_data=graph_def, + input_tensors=input_tensors, + output_tensors=output_tensors, + **converter_kwargs) + + if self.representative_dataset and post_training_optimize: + calibrate_quantize = _calibrator.Calibrator(result) + result = calibrate_quantize.calibrate_and_quantize( + self.representative_dataset.input_gen) + + return result + + @_tf_export("lite.TFLiteConverter") class TFLiteConverter(object): """Convert a TensorFlow model into `output_format` using TOCO. @@ -658,7 +787,7 @@ class TFLiteConverter(object): tensor.set_shape(shape) -@_tf_export("lite.TocoConverter") +@_tf_export(v1=["lite.TocoConverter"]) class TocoConverter(object): """Convert a TensorFlow model into `output_format` using TOCO. diff --git a/tensorflow/lite/python/lite_v2_test.py b/tensorflow/lite/python/lite_v2_test.py new file mode 100644 index 0000000000..126a778348 --- /dev/null +++ b/tensorflow/lite/python/lite_v2_test.py @@ -0,0 +1,182 @@ +# Copyright 2019 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 lite.py functionality related to TensorFlow 2.0.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os + +from tensorflow.lite.python import lite +from tensorflow.lite.python.interpreter import Interpreter +from tensorflow.python.eager import def_function +from tensorflow.python.framework import constant_op +from tensorflow.python.framework import test_util +from tensorflow.python.ops import variables +from tensorflow.python.platform import test +from tensorflow.python.saved_model.load import load +from tensorflow.python.saved_model.save import save +from tensorflow.python.training.tracking import tracking + + +class FromConcreteFunctionTest(test_util.TensorFlowTestCase): + + def _evaluateTFLiteModel(self, tflite_model, input_data): + """Evaluates the model on the `input_data`.""" + interpreter = Interpreter(model_content=tflite_model) + interpreter.allocate_tensors() + + input_details = interpreter.get_input_details() + output_details = interpreter.get_output_details() + + for input_tensor, tensor_data in zip(input_details, input_data): + interpreter.set_tensor(input_tensor['index'], tensor_data.numpy()) + interpreter.invoke() + return interpreter.get_tensor(output_details[0]['index']) + + @test_util.run_v2_only + def testTypeInvalid(self): + root = tracking.AutoTrackable() + root.v1 = variables.Variable(3.) + root.v2 = variables.Variable(2.) + root.f = def_function.function(lambda x: root.v1 * root.v2 * x) + + with self.assertRaises(ValueError) as error: + _ = lite.TFLiteConverterV2.from_concrete_function(root.f) + self.assertIn('call from_concrete_function', str(error.exception)) + + @test_util.run_v2_only + def testFloat(self): + input_data = constant_op.constant(1., shape=[1]) + root = tracking.AutoTrackable() + root.v1 = variables.Variable(3.) + root.v2 = variables.Variable(2.) + root.f = def_function.function(lambda x: root.v1 * root.v2 * x) + concrete_func = root.f.get_concrete_function(input_data) + + # Convert model. + converter = lite.TFLiteConverterV2.from_concrete_function(concrete_func) + tflite_model = converter.convert() + + # Check values from converted model. + expected_value = root.f(input_data) + actual_value = self._evaluateTFLiteModel(tflite_model, [input_data]) + self.assertEqual(expected_value.numpy(), actual_value) + + @test_util.run_v2_only + def testSizeNone(self): + # Test with a shape of None + input_data = constant_op.constant(1., shape=None) + root = tracking.AutoTrackable() + root.v1 = variables.Variable(3.) + root.f = def_function.function(lambda x: root.v1 * x) + concrete_func = root.f.get_concrete_function(input_data) + + # Convert model. + converter = lite.TFLiteConverterV2.from_concrete_function(concrete_func) + tflite_model = converter.convert() + + # Check values from converted model. + expected_value = root.f(input_data) + actual_value = self._evaluateTFLiteModel(tflite_model, [input_data]) + self.assertEqual(expected_value.numpy(), actual_value) + + @test_util.run_v2_only + def testConstSavedModel(self): + """Test a basic model with functions to make sure functions are inlined.""" + self.skipTest('b/124205572') + input_data = constant_op.constant(1., shape=[1]) + root = tracking.AutoTrackable() + root.f = def_function.function(lambda x: 2. * x) + to_save = root.f.get_concrete_function(input_data) + + save_dir = os.path.join(self.get_temp_dir(), 'saved_model') + save(root, save_dir, to_save) + saved_model = load(save_dir) + concrete_func = saved_model.signatures['serving_default'] + + # Convert model and ensure model is not None. + converter = lite.TFLiteConverterV2.from_concrete_function(concrete_func) + tflite_model = converter.convert() + + # Check values from converted model. + expected_value = root.f(input_data) + actual_value = self._evaluateTFLiteModel(tflite_model, [input_data]) + self.assertEqual(expected_value.numpy(), actual_value) + + @test_util.run_v2_only + def testVariableSavedModel(self): + """Test a basic model with Variables with saving/loading the SavedModel.""" + self.skipTest('b/124205572') + input_data = constant_op.constant(1., shape=[1]) + root = tracking.AutoTrackable() + root.v1 = variables.Variable(3.) + root.v2 = variables.Variable(2.) + root.f = def_function.function(lambda x: root.v1 * root.v2 * x) + to_save = root.f.get_concrete_function(input_data) + + save_dir = os.path.join(self.get_temp_dir(), 'saved_model') + save(root, save_dir, to_save) + saved_model = load(save_dir) + concrete_func = saved_model.signatures['serving_default'] + + # Convert model and ensure model is not None. + converter = lite.TFLiteConverterV2.from_concrete_function(concrete_func) + tflite_model = converter.convert() + + # Check values from converted model. + expected_value = root.f(input_data) + actual_value = self._evaluateTFLiteModel(tflite_model, [input_data]) + self.assertEqual(expected_value.numpy(), actual_value) + + @test_util.run_v2_only + def testMultiFunctionModel(self): + """Test a basic model with Variables.""" + + class BasicModel(tracking.AutoTrackable): + + def __init__(self): + self.y = None + self.z = None + + @def_function.function + def add(self, x): + if self.y is None: + self.y = variables.Variable(2.) + return x + self.y + + @def_function.function + def sub(self, x): + if self.z is None: + self.z = variables.Variable(3.) + return x - self.z + + input_data = constant_op.constant(1., shape=[1]) + root = BasicModel() + concrete_func = root.add.get_concrete_function(input_data) + + # Convert model and ensure model is not None. + converter = lite.TFLiteConverterV2.from_concrete_function(concrete_func) + tflite_model = converter.convert() + + # Check values from converted model. + expected_value = root.add(input_data) + actual_value = self._evaluateTFLiteModel(tflite_model, [input_data]) + self.assertEqual(expected_value.numpy(), actual_value) + + +if __name__ == '__main__': + test.main() diff --git a/tensorflow/tools/api/golden/v2/tensorflow.lite.-toco-converter.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.lite.-toco-converter.pbtxt deleted file mode 100644 index 3ef90b8bc4..0000000000 --- a/tensorflow/tools/api/golden/v2/tensorflow.lite.-toco-converter.pbtxt +++ /dev/null @@ -1,24 +0,0 @@ -path: "tensorflow.lite.TocoConverter" -tf_class { - is_instance: "" - is_instance: "" - member_method { - name: "__init__" - } - member_method { - name: "from_frozen_graph" - argspec: "args=[\'cls\', \'graph_def_file\', \'input_arrays\', \'output_arrays\', \'input_shapes\'], varargs=None, keywords=None, defaults=[\'None\'], " - } - member_method { - name: "from_keras_model_file" - argspec: "args=[\'cls\', \'model_file\', \'input_arrays\', \'input_shapes\', \'output_arrays\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'None\'], " - } - member_method { - name: "from_saved_model" - argspec: "args=[\'cls\', \'saved_model_dir\', \'input_arrays\', \'input_shapes\', \'output_arrays\', \'tag_set\', \'signature_key\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'None\', \'None\', \'None\'], " - } - member_method { - name: "from_session" - argspec: "args=[\'cls\', \'sess\', \'input_tensors\', \'output_tensors\'], varargs=None, keywords=None, defaults=None" - } -} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.lite.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.lite.pbtxt index 18664f7acb..6fea044973 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.lite.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.lite.pbtxt @@ -24,16 +24,8 @@ tf_module { name: "TFLiteConverter" mtype: "" } - member { - name: "TocoConverter" - mtype: "" - } member { name: "constants" mtype: "" } - member_method { - name: "toco_convert" - argspec: "args=[\'input_data\', \'input_tensors\', \'output_tensors\'], varargs=args, keywords=kwargs, defaults=None" - } } diff --git a/tensorflow/tools/compatibility/renames_v2.py b/tensorflow/tools/compatibility/renames_v2.py index 05f08ec209..9d883d55fd 100644 --- a/tensorflow/tools/compatibility/renames_v2.py +++ b/tensorflow/tools/compatibility/renames_v2.py @@ -321,6 +321,8 @@ renames = { 'tf.lbeta': 'tf.math.lbeta', 'tf.lgamma': 'tf.math.lgamma', 'tf.lin_space': 'tf.linspace', + 'tf.lite.toco_convert': 'tf.compat.v1.lite.toco_convert', + 'tf.lite.TocoConverter': 'tf.compat.v1.lite.TocoConverter', 'tf.local_variables': 'tf.compat.v1.local_variables', 'tf.local_variables_initializer': 'tf.compat.v1.local_variables_initializer', 'tf.log': 'tf.math.log', -- GitLab From c757e3180db74a2dc7b0cffc77cf4abfd7fbde81 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 20 Feb 2019 11:43:11 -0800 Subject: [PATCH 186/766] Fixing gbdt quantile loss with CORE head PiperOrigin-RevId: 234838122 --- .../estimator_batch/estimator.py | 127 +++++++++--------- 1 file changed, 60 insertions(+), 67 deletions(-) diff --git a/tensorflow/contrib/boosted_trees/estimator_batch/estimator.py b/tensorflow/contrib/boosted_trees/estimator_batch/estimator.py index a178820841..5ffbb90670 100644 --- a/tensorflow/contrib/boosted_trees/estimator_batch/estimator.py +++ b/tensorflow/contrib/boosted_trees/estimator_batch/estimator.py @@ -84,12 +84,10 @@ class GradientBoostedDecisionTreeClassifier(estimator.Estimator): output_leaf_index: whether to output leaf indices along with predictions during inference. The leaf node indexes are available in predictions dict by the key 'leaf_index'. It is a Tensor of rank 2 and its shape is - [batch_size, num_trees]. - For example, - result_iter = classifier.predict(...) - for result_dict in result_iter: - # access leaf index list by result_dict["leaf_index"] - # which contains one leaf index per tree + [batch_size, num_trees]. For example, result_iter = + classifier.predict(...) + for result_dict in result_iter: # access leaf index list by + result_dict["leaf_index"] # which contains one leaf index per tree override_global_step_value: If after the training is done, global step value must be reset to this value. This should be used to reset global step to a number > number of steps used to train the current ensemble. @@ -179,8 +177,8 @@ class GradientBoostedDecisionTreeRegressor(estimator.Estimator): `[batch_size, label_dimension]`). num_trees: An int, number of trees to build. feature_columns: A list of feature columns. - label_name: String, name of the key in label dict. Can be null if label - is a tensor (single headed models). + label_name: String, name of the key in label dict. Can be null if label is + a tensor (single headed models). weight_column_name: Name of the column for weights, or None if not weighted. model_dir: Directory for model exports, etc. @@ -195,11 +193,11 @@ class GradientBoostedDecisionTreeRegressor(estimator.Estimator): opposed to contrib) version of tensorflow. output_leaf_index: whether to output leaf indices along with predictions during inference. The leaf node indexes are available in predictions - dict by the key 'leaf_index'. For example, - result_dict = classifier.predict(...) - for example_prediction_result in result_dict: - # access leaf index list by example_prediction_result["leaf_index"] - # which contains one leaf index per tree + dict by the key 'leaf_index'. For example, result_dict = + classifier.predict(...) + for example_prediction_result in result_dict: # access leaf index list + by example_prediction_result["leaf_index"] # which contains one leaf + index per tree override_global_step_value: If after the training is done, global step value must be reset to this value. This should be used to reset global step to a number > number of steps used to train the current ensemble. @@ -286,11 +284,11 @@ class GradientBoostedDecisionTreeEstimator(estimator.Estimator): opposed to contrib) version of tensorflow. output_leaf_index: whether to output leaf indices along with predictions during inference. The leaf node indexes are available in predictions - dict by the key 'leaf_index'. For example, - result_dict = classifier.predict(...) - for example_prediction_result in result_dict: - # access leaf index list by example_prediction_result["leaf_index"] - # which contains one leaf index per tree + dict by the key 'leaf_index'. For example, result_dict = + classifier.predict(...) + for example_prediction_result in result_dict: # access leaf index list + by example_prediction_result["leaf_index"] # which contains one leaf + index per tree override_global_step_value: If after the training is done, global step value must be reset to this value. This should be used to reset global step to a number > number of steps used to train the current ensemble. @@ -353,10 +351,9 @@ class GradientBoostedDecisionTreeRanker(estimator.Estimator): layer. It can also be a function that computes the number of examples based on the depth of the layer that's being built. head: `Head` instance. - ranking_model_pair_keys: Keys to distinguish between features - for left and right part of the training pairs for ranking. For example, - for an Example with features "a.f1" and "b.f1", the keys would be - ("a", "b"). + ranking_model_pair_keys: Keys to distinguish between features for left and + right part of the training pairs for ranking. For example, for an + Example with features "a.f1" and "b.f1", the keys would be ("a", "b"). num_trees: An int, number of trees to build. feature_columns: A list of feature columns. weight_column_name: Name of the column for weights, or None if not @@ -376,12 +373,10 @@ class GradientBoostedDecisionTreeRanker(estimator.Estimator): output_leaf_index: whether to output leaf indices along with predictions during inference. The leaf node indexes are available in predictions dict by the key 'leaf_index'. It is a Tensor of rank 2 and its shape is - [batch_size, num_trees]. - For example, - result_iter = classifier.predict(...) - for result_dict in result_iter: - # access leaf index list by result_dict["leaf_index"] - # which contains one leaf index per tree + [batch_size, num_trees]. For example, result_iter = + classifier.predict(...) + for result_dict in result_iter: # access leaf index list by + result_dict["leaf_index"] # which contains one leaf index per tree override_global_step_value: If after the training is done, global step value must be reset to this value. This should be used to reset global step to a number > number of steps used to train the current ensemble. @@ -417,12 +412,12 @@ class GradientBoostedDecisionTreeRanker(estimator.Estimator): config=config, feature_engineering_fn=feature_engineering_fn) + # When using this estimator, make sure to regularize the hessian (at least l2, # min_node_weight)! # TODO(nponomareva): extend to take multiple quantiles in one go. class GradientBoostedDecisionTreeQuantileRegressor(estimator.Estimator): - """An estimator that does quantile regression and returns quantile estimates. - """ + """An estimator that does quantile regression and returns quantile estimates.""" def __init__(self, learner_config, @@ -449,8 +444,8 @@ class GradientBoostedDecisionTreeQuantileRegressor(estimator.Estimator): layer. It can also be a function that computes the number of examples based on the depth of the layer that's being built. quantiles: a list of quantiles for the loss, each between 0 and 1. - label_dimension: Dimension of regression label. This is the size - of the last dimension of the labels `Tensor` (typically, this has shape + label_dimension: Dimension of regression label. This is the size of the + last dimension of the labels `Tensor` (typically, this has shape `[batch_size, label_dimension]`). When label_dimension>1, it is recommended to use multiclass strategy diagonal hessian or full hessian. num_trees: An int, number of trees to build. @@ -469,11 +464,11 @@ class GradientBoostedDecisionTreeQuantileRegressor(estimator.Estimator): opposed to contrib) version of tensorflow. output_leaf_index: whether to output leaf indices along with predictions during inference. The leaf node indexes are available in predictions - dict by the key 'leaf_index'. For example, - result_dict = classifier.predict(...) - for example_prediction_result in result_dict: - # access leaf index list by example_prediction_result["leaf_index"] - # which contains one leaf index per tree + dict by the key 'leaf_index'. For example, result_dict = + classifier.predict(...) + for example_prediction_result in result_dict: # access leaf index list + by example_prediction_result["leaf_index"] # which contains one leaf + index per tree override_global_step_value: If after the training is done, global step value must be reset to this value. This should be used to reset global step to a number > number of steps used to train the current ensemble. @@ -519,6 +514,7 @@ class GradientBoostedDecisionTreeQuantileRegressor(estimator.Estimator): config=config, feature_engineering_fn=feature_engineering_fn) + # ================== New Estimator interface=================================== # The estimators below use new core Estimator interface and must be used with # new feature columns and heads. @@ -534,10 +530,8 @@ def core_multiclass_head( def loss_fn(labels, logits): result = losses.per_example_maxent_loss( - labels=labels, - logits=logits, - weights=weight_column, - num_classes=n_classes) + # Don't pass the weights: head already multiplies by them. + labels=labels, logits=logits, weights=None, num_classes=n_classes) return result[0] # pylint:disable=protected-access @@ -564,7 +558,8 @@ def core_quantile_regression_head( result = losses.per_example_quantile_regression_loss( labels=labels, predictions=logits, - weights=weight_column, + # Don't pass the weights: head already multiplies by them. + weights=None, quantile=quantiles) return result[0] @@ -623,11 +618,11 @@ class CoreGradientBoostedDecisionTreeEstimator(core_estimator.Estimator): the bias. output_leaf_index: whether to output leaf indices along with predictions during inference. The leaf node indexes are available in predictions - dict by the key 'leaf_index'. For example, - result_dict = classifier.predict(...) - for example_prediction_result in result_dict: - # access leaf index list by example_prediction_result["leaf_index"] - # which contains one leaf index per tree + dict by the key 'leaf_index'. For example, result_dict = + classifier.predict(...) + for example_prediction_result in result_dict: # access leaf index list + by example_prediction_result["leaf_index"] # which contains one leaf + index per tree num_quantiles: Number of quantiles to build for numeric feature values. """ @@ -685,10 +680,9 @@ class CoreGradientBoostedDecisionTreeRanker(core_estimator.Estimator): layer. It can also be a function that computes the number of examples based on the depth of the layer that's being built. head: `Head` instance. - ranking_model_pair_keys: Keys to distinguish between features - for left and right part of the training pairs for ranking. For example, - for an Example with features "a.f1" and "b.f1", the keys would be - ("a", "b"). + ranking_model_pair_keys: Keys to distinguish between features for left and + right part of the training pairs for ranking. For example, for an + Example with features "a.f1" and "b.f1", the keys would be ("a", "b"). num_trees: An int, number of trees to build. feature_columns: A list of feature columns. weight_column_name: Name of the column for weights, or None if not @@ -703,12 +697,10 @@ class CoreGradientBoostedDecisionTreeRanker(core_estimator.Estimator): output_leaf_index: whether to output leaf indices along with predictions during inference. The leaf node indexes are available in predictions dict by the key 'leaf_index'. It is a Tensor of rank 2 and its shape is - [batch_size, num_trees]. - For example, - result_iter = classifier.predict(...) - for result_dict in result_iter: - # access leaf index list by result_dict["leaf_index"] - # which contains one leaf index per tree + [batch_size, num_trees]. For example, result_iter = + classifier.predict(...) + for result_dict in result_iter: # access leaf index list by + result_dict["leaf_index"] # which contains one leaf index per tree num_quantiles: Number of quantiles to build for numeric feature values. Raises: @@ -748,8 +740,7 @@ class CoreGradientBoostedDecisionTreeRanker(core_estimator.Estimator): # TODO(nponomareva): extend to take multiple quantiles in one go. class CoreGradientBoostedDecisionTreeQuantileRegressor( core_estimator.Estimator): - """An estimator that does quantile regression and returns quantile estimates. - """ + """An estimator that does quantile regression and returns quantile estimates.""" def __init__(self, learner_config, @@ -775,8 +766,8 @@ class CoreGradientBoostedDecisionTreeQuantileRegressor( layer. It can also be a function that computes the number of examples based on the depth of the layer that's being built. quantiles: a list of quantiles for the loss, each between 0 and 1. - label_dimension: Dimension of regression label. This is the size - of the last dimension of the labels `Tensor` (typically, this has shape + label_dimension: Dimension of regression label. This is the size of the + last dimension of the labels `Tensor` (typically, this has shape `[batch_size, label_dimension]`). When label_dimension>1, it is recommended to use multiclass strategy diagonal hessian or full hessian. num_trees: An int, number of trees to build. @@ -795,11 +786,11 @@ class CoreGradientBoostedDecisionTreeQuantileRegressor( the bias. output_leaf_index: whether to output leaf indices along with predictions during inference. The leaf node indexes are available in predictions - dict by the key 'leaf_index'. For example, - result_dict = classifier.predict(...) - for example_prediction_result in result_dict: - # access leaf index list by example_prediction_result["leaf_index"] - # which contains one leaf index per tree + dict by the key 'leaf_index'. For example, result_dict = + classifier.predict(...) + for example_prediction_result in result_dict: # access leaf index list + by example_prediction_result["leaf_index"] # which contains one leaf + index per tree num_quantiles: Number of quantiles to build for numeric feature values. """ if len(quantiles) > 1: @@ -814,7 +805,9 @@ class CoreGradientBoostedDecisionTreeQuantileRegressor( params={ 'head': core_quantile_regression_head( - quantiles[0], label_dimension=label_dimension), + quantiles[0], + label_dimension=label_dimension, + weight_column=weight_column_name), 'feature_columns': feature_columns, 'learner_config': -- GitLab From 8cb0d7f4b9fdb12b3d74bcc4cf50684a718ec46c Mon Sep 17 00:00:00 2001 From: armenpoghosov <39712046+armenpoghosov@users.noreply.github.com> Date: Wed, 20 Feb 2019 21:11:21 +0100 Subject: [PATCH 187/766] const removed --- tensorflow/cc/framework/gradients.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/cc/framework/gradients.cc b/tensorflow/cc/framework/gradients.cc index 84a1ad585f..e312ba7a35 100644 --- a/tensorflow/cc/framework/gradients.cc +++ b/tensorflow/cc/framework/gradients.cc @@ -168,7 +168,7 @@ std::vector SymbolicGradientBuilder::GetReachableNodes() { std::vector reachable_nodes(scope_.graph()->num_node_ids(), false); std::deque queue; for (const Output& out : outputs_) { - const Node* const out_node = out.node(); + Node* const out_node = out.node(); const int out_node_id = out_node->id(); if (!reachable_nodes[out_node_id]) { queue.push_back(out_node); @@ -181,7 +181,7 @@ std::vector SymbolicGradientBuilder::GetReachableNodes() { queue.pop_front(); for (const Edge* e : n->in_edges()) { if (e->IsControlEdge()) continue; - const Node* const src_node = e->src(); + Node* const src_node = e->src(); const int src_node_id = src_node->id(); if (!reachable_nodes[src_node_id]) { queue.push_back(src_node); -- GitLab From ccaf1842be47b2216f1b55acd1ab129ba9639f2e Mon Sep 17 00:00:00 2001 From: Eugene Zhulenev Date: Wed, 20 Feb 2019 12:10:10 -0800 Subject: [PATCH 188/766] Print function control returns. Use `@return` for control returns, to be somewhat consistent with printing control dependencies for nodes. PiperOrigin-RevId: 234843536 --- tensorflow/core/framework/function.cc | 3 ++ tensorflow/core/framework/function_test.cc | 42 ++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/tensorflow/core/framework/function.cc b/tensorflow/core/framework/function.cc index 542f82b99c..35d04eb727 100644 --- a/tensorflow/core/framework/function.cc +++ b/tensorflow/core/framework/function.cc @@ -569,6 +569,9 @@ string Print(const FunctionDef& fdef) { for (const auto& n : fdef.node_def()) { strings::StrAppend(&out, " ", Print(n), "\n"); } + for (const auto& cr : fdef.control_ret()) { + strings::StrAppend(&out, " @return ", cr.first, " = ", cr.second, "\n"); + } for (const auto& r : fdef.ret()) { strings::StrAppend(&out, " return ", r.first, " = ", r.second, "\n"); } diff --git a/tensorflow/core/framework/function_test.cc b/tensorflow/core/framework/function_test.cc index 779e9f7f41..6fbbabfc95 100644 --- a/tensorflow/core/framework/function_test.cc +++ b/tensorflow/core/framework/function_test.cc @@ -156,6 +156,48 @@ ControlDep(x:int32) -> (y:int32) { EXPECT_EQ(DebugString(result.nodes), e2); } +TEST(TFunc, ControlRet) { + auto fdef = FDH::Create( + // Name + "ControlRet", + // Inputs + {"x: int32"}, + // Outputs + {"y: int32"}, + // Attrs + {}, + // Nodes + { + {{"a"}, "Identity", {"x"}, {{"T", DT_INT32}}}, + }, + // Returns + {{"y", "a:output:0"}}, + // Control returns + {{"must_execute", "a"}}); + + const char* e = R"P( +ControlRet(x:int32) -> (y:int32) { + a = Identity[T=int32](x) + @return must_execute = a + return y = a:output:0 +} +)P"; + EXPECT_EQ(DebugString(fdef), e); + + // Instantiate one with T=float + InstantiationResult result; + TF_ASSERT_OK( + InstantiateFunction(fdef, Attrs({{"T", DT_FLOAT}}), GetOpSig, &result)); + const char* e2 = R"P( +(x:int32) -> (a:int32) { + a = Identity[T=int32](x) +} +)P"; + EXPECT_EQ(result.arg_types, DataTypeVector({DT_INT32})); + EXPECT_EQ(result.ret_types, DataTypeVector({DT_INT32})); + EXPECT_EQ(DebugString(result.nodes), e2); +} + REGISTER_OP("HasDefaultType") .Output("out: T") .Attr("T: {float, double, int32, int64} = DT_FLOAT"); -- GitLab From 3f4d63835f0d58f7c48a27bd78293e2b4d62c99c Mon Sep 17 00:00:00 2001 From: armenpoghosov <39712046+armenpoghosov@users.noreply.github.com> Date: Wed, 20 Feb 2019 21:40:56 +0100 Subject: [PATCH 189/766] reverted variables as per request --- tensorflow/cc/framework/gradients.cc | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/tensorflow/cc/framework/gradients.cc b/tensorflow/cc/framework/gradients.cc index e312ba7a35..a7e645e8b5 100644 --- a/tensorflow/cc/framework/gradients.cc +++ b/tensorflow/cc/framework/gradients.cc @@ -168,11 +168,9 @@ std::vector SymbolicGradientBuilder::GetReachableNodes() { std::vector reachable_nodes(scope_.graph()->num_node_ids(), false); std::deque queue; for (const Output& out : outputs_) { - Node* const out_node = out.node(); - const int out_node_id = out_node->id(); - if (!reachable_nodes[out_node_id]) { - queue.push_back(out_node); - reachable_nodes[out_node_id] = true; + if (!reachable_nodes[out.node()->id()]) { + queue.push_back(out.node()); + reachable_nodes[out.node()->id()] = true; } } @@ -181,11 +179,9 @@ std::vector SymbolicGradientBuilder::GetReachableNodes() { queue.pop_front(); for (const Edge* e : n->in_edges()) { if (e->IsControlEdge()) continue; - Node* const src_node = e->src(); - const int src_node_id = src_node->id(); - if (!reachable_nodes[src_node_id]) { - queue.push_back(src_node); - reachable_nodes[src_node_id] = true; + if (!reachable_nodes[e->src()->id()]) { + queue.push_back(e->src()); + reachable_nodes[e->src()->id()] = true; } } } -- GitLab From 26a9094f711d6a1cd0961aa3acff053b18c7573f Mon Sep 17 00:00:00 2001 From: Reed Wanderman-Milne Date: Wed, 20 Feb 2019 12:30:03 -0800 Subject: [PATCH 190/766] Add AutoCastVariable class, a variable that casts itself when used. Currently AutoCastVariable is unused, but it will later be used for the mixed precision API. Co-authored-by: James Qin PiperOrigin-RevId: 234846973 --- tensorflow/python/framework/ops.py | 42 +++ tensorflow/python/keras/BUILD | 1 + .../keras/mixed_precision/experimental/BUILD | 54 ++++ .../experimental/autocast_variable.py | 178 +++++++++++++ .../experimental/autocast_variable_test.py | 245 ++++++++++++++++++ 5 files changed, 520 insertions(+) create mode 100644 tensorflow/python/keras/mixed_precision/experimental/BUILD create mode 100644 tensorflow/python/keras/mixed_precision/experimental/autocast_variable.py create mode 100644 tensorflow/python/keras/mixed_precision/experimental/autocast_variable_test.py diff --git a/tensorflow/python/framework/ops.py b/tensorflow/python/framework/ops.py index 6e64622131..41d6bdeec1 100644 --- a/tensorflow/python/framework/ops.py +++ b/tensorflow/python/framework/ops.py @@ -5048,6 +5048,48 @@ class Graph(object): self._thread_local._distribution_strategy_stack = ( # pylint: disable=protected-access _distribution_strategy_stack) + @property + def _auto_cast_variable_read_dtype(self): + """The dtype that instances of `AutoCastVariable` will be casted to. + + This is None if `AutoCastVariables` should not be casted. + + See `AutoCastVariable` for more information. + + Returns: + The dtype that instances of `AutoCastVariable` will be casted to. + """ + if not hasattr(self._thread_local, "_auto_cast_variable_read_dtype"): + self._thread_local._auto_cast_variable_read_dtype = None # pylint: disable=protected-access + return self._thread_local._auto_cast_variable_read_dtype # pylint: disable=protected-access + + @_auto_cast_variable_read_dtype.setter + def _auto_cast_variable_read_dtype(self, _auto_cast_variable_read_dtype): + self._thread_local._auto_cast_variable_read_dtype = ( # pylint: disable=protected-access + _auto_cast_variable_read_dtype) + + @tf_contextlib.contextmanager + def _enable_auto_casting_variables(self, dtype): + """Context manager to automatically cast AutoCastVariables. + + If an AutoCastVariable `var` is used under this context manager, it will be + casted to `dtype` before being used. + + See `AutoCastVariable` for more information. + + Args: + dtype: The dtype that AutoCastVariables should be casted to. + + Yields: + Nothing. + """ + prev_read_dtype = self._auto_cast_variable_read_dtype + try: + self._auto_cast_variable_read_dtype = dtype + yield + finally: + self._auto_cast_variable_read_dtype = prev_read_dtype + def _mutation_lock(self): """Returns a lock to guard code that creates & mutates ops. diff --git a/tensorflow/python/keras/BUILD b/tensorflow/python/keras/BUILD index eda6755d6a..931a5d6785 100755 --- a/tensorflow/python/keras/BUILD +++ b/tensorflow/python/keras/BUILD @@ -63,6 +63,7 @@ py_library( ":pil_for_keras", ":saving", "//tensorflow/python:training", + "//tensorflow/python/keras/mixed_precision/experimental:autocast_variable", "//tensorflow/python/keras/optimizer_v2", "//tensorflow/python/saved_model", "@keras_applications_archive//:keras_applications", diff --git a/tensorflow/python/keras/mixed_precision/experimental/BUILD b/tensorflow/python/keras/mixed_precision/experimental/BUILD new file mode 100644 index 0000000000..f994ab9c70 --- /dev/null +++ b/tensorflow/python/keras/mixed_precision/experimental/BUILD @@ -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. +# ============================================================================== + +# Description: +# Contains the Keras Mixed Precision API (TensorFlow version). + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) # Apache 2.0 + +exports_files(["LICENSE"]) + +load("//tensorflow:tensorflow.bzl", "py_test") + +py_library( + name = "autocast_variable", + srcs = [ + "autocast_variable.py", + ], + srcs_version = "PY2AND3", + deps = [ + "//tensorflow/python:framework", + "//tensorflow/python:math_ops", + "//tensorflow/python:resource_variable_ops", + "//tensorflow/python/distribute:values", + ], +) + +py_test( + name = "autocast_variable_test", + size = "medium", + srcs = ["autocast_variable_test.py"], + deps = [ + ":autocast_variable", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework", + "//tensorflow/python:platform_test", + "//tensorflow/python/distribute:mirrored_strategy", + "//tensorflow/python/eager:context", + "@absl_py//absl/testing:parameterized", + ], +) diff --git a/tensorflow/python/keras/mixed_precision/experimental/autocast_variable.py b/tensorflow/python/keras/mixed_precision/experimental/autocast_variable.py new file mode 100644 index 0000000000..a64b517831 --- /dev/null +++ b/tensorflow/python/keras/mixed_precision/experimental/autocast_variable.py @@ -0,0 +1,178 @@ +# Copyright 2019 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. +# ============================================================================== +"""Contains AutoCastVariable, a variable which automatically casts itself.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.python.distribute import values as distribute_values +from tensorflow.python.framework import ops +from tensorflow.python.ops import math_ops +from tensorflow.python.ops import resource_variable_ops + + +# TODO(reedwm): Make checkpointable? +class AutoCastVariable(object): + """Variable that will cast itself to a different dtype in applicable contexts. + + This class wraps a floating-point tf.Variable. It emulates the variable + interface and delegates to the wrapped variable, but it additionally will cast + the wrapped variable under a `Graph._enable_variable_auto_cast(dtype)` context + manager. + + For example: + + ``` + v = tf.Variable(1.0, dtype=tf.float32) + v = AutoCastVariable(v) + print(tf.identity(v).dtype) # tf.float32 + with ops.get_default_graph()._enable_variable_auto_cast(tf.float16): + print(tf.identity(v).dtype) # tf.float16, as v will cast itself to float16 + print(v.dtype) # tf.float16, as v.dtype also changes under the ctx manager. + ``` + + The purpose of this class is to allow Keras layers to create variables in + float32, and automatically cast them to float16 or bfloat16 when the layer is + called. + """ + + def __init__(self, variable): + """Creates an AutoCastVariable instance. + + Args: + variable: A floating-point resource variable to wrap. + + Raises: + ValueError: If `variable` is not a floating-point resource variable + """ + if not resource_variable_ops.is_resource_variable(variable): + raise ValueError('variable must be of type tf.ResourceVariable, but got: ' + '%s' % variable) + if not variable.dtype.is_floating: + raise ValueError('variable must be a floating point variable but has ' + 'type: %s' % variable.dtype.name) + self._variable = variable + + @property + def name(self): + return self._variable.name + + def _should_cast(self): + """Returns True if this variable should be casted when accessed.""" + g = ops.get_default_graph() + # pylint:disable=protected-access + return (g._auto_cast_variable_read_dtype is not None and + self.true_dtype != g._auto_cast_variable_read_dtype) + # pylint:enable=protected-access + + @property + def dtype(self): + """The dtype this variable will be casted to when read.""" + if self._should_cast(): + return ops.get_default_graph()._auto_cast_variable_read_dtype # pylint:disable=protected-access + else: + return self._variable.dtype + + @property + def true_dtype(self): + """The dtype of the underlying variable, before any casts are done.""" + return self._variable.dtype + + def value(self): + val = self._variable.value() + if not self._should_cast(): + return val + # We colocate_with(None) to ignore the existing device constraints, so that + # the cast is always done on the variable's device + with ops.colocate_with(None, ignore_existing=True): + with ops.device(val.device): + return math_ops.cast(val, self.dtype) + + def read_value(self): + val = self._variable.read_value() + if not self._should_cast(): + return val + return math_ops.cast(val, self.dtype) + + def sparse_read(self, indices, name=None): + """Reads the value of this variable sparsely, using `gather`.""" + val = self._variable.sparse_read(indices, name=name) + if not self._should_cast(): + return val + return math_ops.cast(val, self.dtype) + + def assign(self, value, use_locking=None, name=None, read_value=True): + return self._variable.assign( + value, use_locking=use_locking, name=name, read_value=read_value) + + def assign_add(self, delta, use_locking=None, name=None, read_value=True): + return self._variable.assign_add( + delta, use_locking=use_locking, name=name, read_value=read_value) + + def assign_sub(self, delta, use_locking=None, name=None, read_value=True): + return self._variable.assign_sub( + delta, use_locking=use_locking, name=name, read_value=read_value) + + # TODO(reedwm): Support assigning variables with tf.assign(), var.scatter_add, + # etc. + + def __getattr__(self, name): + return getattr(self._variable, name) + + def _dense_var_to_tensor(self, dtype=None, name=None, as_ref=False): + """Converts this variable to a tensor.""" + if not self._should_cast(): + return ops.internal_convert_to_tensor(self._variable, dtype, name, + as_ref) + # TODO(reedwm): Support as_ref? + assert not as_ref + if dtype is not None and not dtype.is_compatible_with(self.dtype): + raise ValueError( + 'Incompatible type conversion requested to type {!r} for variable ' + 'of type {!r}'.format(dtype.name, self.dtype.name)) + val = ops.internal_convert_to_tensor(self._variable, + self._variable.dtype, name, + as_ref=False) + with ops.colocate_with(None, ignore_existing=True): + with ops.device(val.device): + return math_ops.cast(val, self.dtype) + + def _should_act_as_resource_variable(self): + """Pass resource_variable_ops.is_resource_variable check.""" + pass + + # TODO(reedwm): Define operator overloads. + + +ops.register_tensor_conversion_function( + AutoCastVariable, AutoCastVariable._dense_var_to_tensor) # pylint:disable=protected-access +ops.register_dense_tensor_like_type(AutoCastVariable) + + +# We have DistributedVariable subclass to pass +# isinstance(..., DistributedVariable) checks when wrapping a +# DistributedVariable. +# TODO(reedwm): We should not wrap DistributedVariable, but instead have +# DistributedVariable wrap AutoCastVariable. Subclassing DistributedVariable is +# messy, because we do not fully implement the interface of DistributedVariable. +class AutoCastDistributedVariable(AutoCastVariable, + distribute_values.DistributedVariable): + """Version of AutoCastVariable that subclasses DistributedVariable.""" + + def __init__(self, variable): + if not isinstance(variable, distribute_values.DistributedValues): + raise ValueError('variable must be of type DistributedValues, ' + 'but got: %s' % variable) + super(AutoCastDistributedVariable, self).__init__(variable) diff --git a/tensorflow/python/keras/mixed_precision/experimental/autocast_variable_test.py b/tensorflow/python/keras/mixed_precision/experimental/autocast_variable_test.py new file mode 100644 index 0000000000..1caec6a738 --- /dev/null +++ b/tensorflow/python/keras/mixed_precision/experimental/autocast_variable_test.py @@ -0,0 +1,245 @@ +# Copyright 2019 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 AutoCastVariable.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from absl.testing import parameterized +import numpy as np + +from tensorflow.python.distribute import mirrored_strategy +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.mixed_precision.experimental import autocast_variable + +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import variables +from tensorflow.python.platform import test + +TESTCASES = ({ + 'testcase_name': 'base', + 'distribute': False +}, { + 'testcase_name': 'distribute', + 'distribute': True +}) + + +def get_distribute_scope(distribute): + + class DummyContextManager(object): + + def __enter__(self): + pass + + def __exit__(self, *args): + pass + + if distribute: + return mirrored_strategy.MirroredStrategy(['cpu:0']).scope() + else: + return DummyContextManager() + + +def get_autocast_var(var, distribute): + if distribute: + return autocast_variable.AutoCastDistributedVariable(var) + else: + return autocast_variable.AutoCastVariable(var) + + +def get_var(val, dtype): + return variables.VariableV1(val, use_resource=True, dtype=dtype) + + +@test_util.run_all_in_graph_and_eager_modes +class AutoCastVariableTest(test.TestCase, parameterized.TestCase): + + @parameterized.named_parameters(*TESTCASES) + def test_read(self, distribute): + with get_distribute_scope(distribute): + x = get_var(1., dtypes.float32) + x = get_autocast_var(x, distribute) + self.evaluate(x.initializer) + + # outside of auto cast scope. + self.assertEqual(x.dtype, dtypes.float32) + self.assertEqual(x.value().dtype, dtypes.float32) + self.assertEqual(x.read_value().dtype, dtypes.float32) + self.assertEqual(array_ops.identity(x).dtype, dtypes.float32) + + # within auto cast scope of different dtype + with ops.get_default_graph()._enable_auto_casting_variables( + dtypes.float16): + self.assertEqual(x.dtype, dtypes.float16) + self.assertEqual(x.value().dtype, dtypes.float16) + self.assertEqual(x.read_value().dtype, dtypes.float16) + self.assertEqual(array_ops.identity(x).dtype, dtypes.float16) + + # within auto cast scope of same dtype + with ops.get_default_graph()._enable_auto_casting_variables( + dtypes.float32): + self.assertEqual(x.dtype, dtypes.float32) + self.assertEqual(x.value().dtype, dtypes.float32) + self.assertEqual(x.read_value().dtype, dtypes.float32) + self.assertEqual(array_ops.identity(x).dtype, dtypes.float32) + + @parameterized.named_parameters(*TESTCASES) + def test_read_nested_scopes(self, distribute): + with get_distribute_scope(distribute): + x = get_var(1., dtypes.float32) + x = get_autocast_var(x, distribute) + self.evaluate(x.initializer) + + with ops.get_default_graph()._enable_auto_casting_variables( + dtypes.float16): + self.assertEqual(x.dtype, dtypes.float16) + self.assertEqual(x.read_value().dtype, dtypes.float16) + + with ops.get_default_graph()._enable_auto_casting_variables( + dtypes.float32): + self.assertEqual(x.dtype, dtypes.float32) + self.assertEqual(x.read_value().dtype, dtypes.float32) + + self.assertEqual(x.dtype, dtypes.float16) + self.assertEqual(x.read_value().dtype, dtypes.float16) + + @parameterized.named_parameters(*TESTCASES) + def test_operator_overloads(self, distribute): + with get_distribute_scope(distribute): + x = get_var(1., dtypes.float32) + x = get_autocast_var(x, distribute) + self.evaluate(x.initializer) + + v1 = constant_op.constant(2., dtype=dtypes.float32) + v2 = constant_op.constant(2., dtype=dtypes.float16) + + # Because autocast variables do not yet define operator overloads, the + # operator is defined by the non-variable tensor + + # Test variable as the LHS. Currently, this is not supported with + # distributed autocast variables + if not distribute: + self.assertEqual(self.evaluate(x + v1), 3.) + + with ops.get_default_graph()._enable_auto_casting_variables( + dtypes.float16): + self.assertEqual(self.evaluate(x + v2), 3.) + + # Test variable as the RHS + self.assertEqual(self.evaluate(v1 + x), 3.) + + with ops.get_default_graph()._enable_auto_casting_variables( + dtypes.float16): + self.assertEqual(self.evaluate(v2 + x), 3.) + + @parameterized.named_parameters(*TESTCASES) + def test_assign(self, distribute): + with get_distribute_scope(distribute): + x = get_var(0., dtypes.float32) + x = get_autocast_var(x, distribute) + self.evaluate(x.initializer) + + # outside of auto cast scope. + v1 = constant_op.constant(3.14, dtype=dtypes.float32) + v2 = constant_op.constant(3.14, dtype=dtypes.float16) + + def run_and_check(): + # Assign float32 values + self.assertAllClose(3.14, self.evaluate(x.assign(v1))) + self.assertAllClose(3.14 * 2, self.evaluate(x.assign_add(v1))) + self.assertAllClose(3.14, self.evaluate(x.assign_sub(v1))) + + # Attempt to assign float16 values + with self.assertRaisesRegexp( + ValueError, + 'conversion requested dtype float32 for Tensor with dtype float16'): + self.evaluate(x.assign(v2)) + with self.assertRaisesRegexp( + ValueError, + 'conversion requested dtype float32 for Tensor with dtype float16'): + self.evaluate(x.assign_add(v2)) + with self.assertRaisesRegexp( + ValueError, + 'conversion requested dtype float32 for Tensor with dtype float16'): + self.evaluate(x.assign_sub(v2)) + + # Assign Python floats + self.assertAllClose(3.14, self.evaluate(x.assign(3.14))) + self.assertAllClose(3.14 * 2, self.evaluate(x.assign_add(3.14))) + self.assertAllClose(3.14, self.evaluate(x.assign_sub(3.14))) + + run_and_check() + # reset x + self.evaluate(x.assign(0.)) + # within auto cast scope. + with ops.get_default_graph()._enable_auto_casting_variables( + dtypes.float16): + # assign still expect float32 value even if in float16 scope + run_and_check() + + @parameterized.named_parameters(*TESTCASES) + def test_assign_stays_in_true_dtype(self, distribute): + with get_distribute_scope(distribute): + x = get_var(1., dtypes.float32) + x = get_autocast_var(x, distribute) + self.evaluate(x.initializer) + # small_val is a value such that 1.0 + small_val == 1.0 in fp16, but not + # in fp32 + small_val = np.finfo('float16').eps / 2 + small_tensor = constant_op.constant(small_val, dtype=dtypes.float32) + with ops.get_default_graph()._enable_auto_casting_variables( + dtypes.float16): + # Variable should be increased, despite it appearing to be the same + # float16 value. + self.assertEqual(1. + small_val, + self.evaluate(x.assign(1. + small_tensor))) + self.assertEqual(1., self.evaluate(x.value())) + self.assertEqual(1. + small_val, self.evaluate(x.value())) + + self.evaluate(x.assign(1.)) + with ops.get_default_graph()._enable_auto_casting_variables( + dtypes.float16): + self.assertEqual(1. + small_val, + self.evaluate(x.assign_add(small_tensor))) + self.assertEqual(1., self.evaluate(x.value())) + self.assertEqual(1. + small_val, self.evaluate(x.value())) + + @parameterized.named_parameters(*TESTCASES) + def test_invalid_wrapped_variable(self, distribute): + with get_distribute_scope(distribute): + # Wrap a non-variable + with self.assertRaisesRegexp(ValueError, 'variable must be of type'): + x = constant_op.constant([1.], dtype=dtypes.float32) + get_autocast_var(x, distribute) + + # Wrap a non-floating point variable + with self.assertRaisesRegexp(ValueError, + 'variable must be a floating point'): + x = get_var(1, dtypes.int32) + get_autocast_var(x, distribute) + + if distribute: + # Wrap a non-distributed variable with AutoCastDistributedVariable + with self.assertRaisesRegexp(ValueError, 'variable must be of type'): + x = get_var(1., dtypes.float32) + get_autocast_var(x, distribute) + + +if __name__ == '__main__': + test.main() -- GitLab From 7b159c498a71a039474050c1d026fd9ac1fa92bc Mon Sep 17 00:00:00 2001 From: Gunhan Gulsoy Date: Wed, 20 Feb 2019 12:45:59 -0800 Subject: [PATCH 191/766] Add an override to the ABI compatibility checks for shared objects. This is meant to be a handle to be used for development only. PiperOrigin-RevId: 234849588 --- tensorflow/core/framework/op_kernel.cc | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tensorflow/core/framework/op_kernel.cc b/tensorflow/core/framework/op_kernel.cc index c0b81e8538..16ca40c31c 100644 --- a/tensorflow/core/framework/op_kernel.cc +++ b/tensorflow/core/framework/op_kernel.cc @@ -20,6 +20,9 @@ limitations under the License. #include #include +#include +#include + #include "tensorflow/core/framework/attr_value_util.h" #include "tensorflow/core/framework/device_attributes.pb.h" #include "tensorflow/core/framework/graph.pb_text.h" @@ -998,6 +1001,12 @@ static Status IsProbablySafeToLoad(const string& path) { void LoadDynamicKernelsInternal() { Env* env = Env::Default(); + + // Override to allow loading unsafe packages for development. + // DO NOT USE UNLESS YOU KNOW WHAT ABI ISSUES YOU CAN ENCOUNTER. + bool override_abi_check = + strcmp(getenv("TF_REALLY_LOAD_UNSAFE_PACKAGES"), "1") == 0; + string bazel_kernel_dir = io::JoinPath(env->GetRunfilesDir(), "tensorflow", "core", @@ -1010,7 +1019,12 @@ void LoadDynamicKernelsInternal() { string fullpath = io::JoinPath(bazel_kernel_dir, file); if (env->MatchPath(fullpath, dll_spec)) { Status s = IsProbablySafeToLoad(fullpath); - if (s.ok()) { + if (!s.ok() && override_abi_check) { + LOG(WARNING) << "Loading UNSAFE library " << fullpath + << " because ABI check override is set: " + << s.error_message(); + } + if (s.ok() || override_abi_check) { // TODO(gunan): Store the handles to the opened files. void* unused_filehandle; TF_CHECK_OK(env->LoadLibrary(fullpath.c_str(), &unused_filehandle)); -- GitLab From 125785ee0a6d01ab5432f2b28f2ef44cc2f92b45 Mon Sep 17 00:00:00 2001 From: Davide Libenzi Date: Wed, 20 Feb 2019 12:46:50 -0800 Subject: [PATCH 192/766] Allows the first error complete with stack trace to be returned by the XLA builder. PiperOrigin-RevId: 234849729 --- tensorflow/compiler/xla/client/xla_builder.cc | 13 +++++++------ tensorflow/compiler/xla/client/xla_builder.h | 4 ++++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/tensorflow/compiler/xla/client/xla_builder.cc b/tensorflow/compiler/xla/client/xla_builder.cc index b371b5af37..9b7c01a727 100644 --- a/tensorflow/compiler/xla/client/xla_builder.cc +++ b/tensorflow/compiler/xla/client/xla_builder.cc @@ -299,12 +299,17 @@ XlaComputation XlaBuilder::BuildAndNoteError() { return build_status.ConsumeValueOrDie(); } -StatusOr XlaBuilder::Build(bool remove_dynamic_dimensions) { +Status XlaBuilder::GetCurrentStatus() const { if (!first_error_.ok()) { string backtrace; first_error_backtrace_.Dump(tensorflow::DebugWriteToString, &backtrace); return AppendStatus(first_error_, backtrace); } + return Status::OK(); +} + +StatusOr XlaBuilder::Build(bool remove_dynamic_dimensions) { + TF_RETURN_IF_ERROR(GetCurrentStatus()); return Build(instructions_.back().id(), remove_dynamic_dimensions); } @@ -318,11 +323,7 @@ StatusOr XlaBuilder::Build(XlaOp root, StatusOr XlaBuilder::Build(int64 root_id, bool remove_dynamic_dimensions) { - if (!first_error_.ok()) { - string backtrace; - first_error_backtrace_.Dump(tensorflow::DebugWriteToString, &backtrace); - return AppendStatus(first_error_, backtrace); - } + TF_RETURN_IF_ERROR(GetCurrentStatus()); // TODO(b/121223198): XLA backend cannot handle dynamic dimensions yet, remove // all dynamic dimensions before building xla program until we have support in diff --git a/tensorflow/compiler/xla/client/xla_builder.h b/tensorflow/compiler/xla/client/xla_builder.h index fd2e9816e8..379f0ff2a7 100644 --- a/tensorflow/compiler/xla/client/xla_builder.h +++ b/tensorflow/compiler/xla/client/xla_builder.h @@ -238,6 +238,10 @@ class XlaBuilder { // See also set_die_immediately_on_error(). Status first_error() const { return first_error_; } + // Returns the current status of the builder, complete with the stack trace + // information. + Status GetCurrentStatus() const; + // Returns the shape of the given op. StatusOr GetShape(const XlaOp& op) const; -- GitLab From 2825b62b27032e83e159df33dbd5c3c14e833c6a Mon Sep 17 00:00:00 2001 From: Haoliang Zhang Date: Wed, 20 Feb 2019 12:54:59 -0800 Subject: [PATCH 193/766] Fix doc hyperlink. PiperOrigin-RevId: 234851086 --- tensorflow/lite/g3doc/using_select_tf_ops.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/g3doc/using_select_tf_ops.md b/tensorflow/lite/g3doc/using_select_tf_ops.md index cb428e3862..7c1ad20e1b 100644 --- a/tensorflow/lite/g3doc/using_select_tf_ops.md +++ b/tensorflow/lite/g3doc/using_select_tf_ops.md @@ -49,7 +49,7 @@ partially supported by TensorFlow Lite, and one would like to avoid those limitations. The following example shows how to use `target_ops` in the -[`TFLiteConverter`](./convert/python_api) Python API. +[`TFLiteConverter`](./convert/python_api.md) Python API. ``` import tensorflow as tf -- GitLab From d23fc2b7fff49ea8f8b7eb32729fb045d04697fb Mon Sep 17 00:00:00 2001 From: Jonathan Hseu Date: Wed, 20 Feb 2019 12:59:38 -0800 Subject: [PATCH 194/766] Move TPU python files to TF core. PiperOrigin-RevId: 234851893 --- tensorflow/contrib/tpu/BUILD | 205 +- tensorflow/contrib/tpu/profiler/BUILD | 8 - tensorflow/contrib/tpu/python/ops/tpu_ops.py | 409 +- .../tpu/python/ops/tpu_ordinal_selector_op.py | 26 +- .../contrib/tpu/python/profiler/__init__.py | 18 +- tensorflow/contrib/tpu/python/tpu/__init__.py | 13 +- .../python/tpu/_tpu_estimator_embedding.py | 325 +- .../tpu/python/tpu/async_checkpoint.py | 207 +- tensorflow/contrib/tpu/python/tpu/bfloat16.py | 68 +- tensorflow/contrib/tpu/python/tpu/datasets.py | 182 +- .../tpu/python/tpu/device_assignment.py | 304 +- .../contrib/tpu/python/tpu/error_handling.py | 123 +- .../contrib/tpu/python/tpu/feature_column.py | 435 +- .../contrib/tpu/python/tpu/functional.py | 14 +- .../contrib/tpu/python/tpu/session_support.py | 433 +- .../contrib/tpu/python/tpu/tensor_tracer.py | 1629 +------ tensorflow/contrib/tpu/python/tpu/topology.py | 211 +- tensorflow/contrib/tpu/python/tpu/tpu.py | 1572 +------ .../contrib/tpu/python/tpu/tpu_config.py | 267 +- .../contrib/tpu/python/tpu/tpu_context.py | 754 +--- .../contrib/tpu/python/tpu/tpu_embedding.py | 1097 +---- .../tpu/python/tpu/tpu_embedding_gradient.py | 144 +- .../contrib/tpu/python/tpu/tpu_estimator.py | 3759 +--------------- tensorflow/contrib/tpu/python/tpu/tpu_feed.py | 912 +--- .../contrib/tpu/python/tpu/tpu_function.py | 57 +- .../contrib/tpu/python/tpu/tpu_optimizer.py | 194 +- .../contrib/tpu/python/tpu/tpu_sharding.py | 244 +- .../tpu/python/tpu/tpu_system_metadata.py | 149 +- .../contrib/tpu/python/tpu/training_loop.py | 214 +- tensorflow/contrib/tpu/python/tpu/util.py | 42 +- tensorflow/python/BUILD | 1 + tensorflow/python/tpu/BUILD | 334 ++ tensorflow/python/tpu/__init__.py | 20 + .../python/tpu/_tpu_estimator_embedding.py | 334 ++ tensorflow/python/tpu/async_checkpoint.py | 212 + tensorflow/python/tpu/bfloat16.py | 77 + .../tpu => }/python/tpu/bfloat16_test.py | 3 +- tensorflow/python/tpu/datasets.py | 191 + .../tpu => }/python/tpu/datasets_test.py | 2 +- tensorflow/python/tpu/device_assignment.py | 313 ++ tensorflow/python/tpu/error_handling.py | 132 + tensorflow/python/tpu/feature_column.py | 435 ++ .../python/tpu/feature_column_test.py | 4 +- tensorflow/python/tpu/functional.py | 23 + tensorflow/python/tpu/ops/tpu_ops.py | 418 ++ .../python/tpu/ops/tpu_ordinal_selector_op.py | 20 + tensorflow/python/tpu/profiler/BUILD | 27 + tensorflow/python/tpu/profiler/__init__.py | 31 + .../tpu_profiler_analysis_pb2_grpc.py | 0 tensorflow/python/tpu/session_support.py | 438 ++ tensorflow/python/tpu/tensor_tracer.py | 1638 +++++++ tensorflow/python/tpu/topology.py | 220 + .../tpu => }/python/tpu/topology_test.py | 3 +- tensorflow/python/tpu/tpu.py | 1576 +++++++ tensorflow/python/tpu/tpu_config.py | 276 ++ .../tpu => }/python/tpu/tpu_config_test.py | 2 +- tensorflow/python/tpu/tpu_context.py | 763 ++++ tensorflow/python/tpu/tpu_embedding.py | 1105 +++++ .../python/tpu/tpu_embedding_gradient.py | 153 + tensorflow/python/tpu/tpu_estimator.py | 3760 +++++++++++++++++ .../python/tpu/tpu_estimator_signals_test.py | 2 +- tensorflow/python/tpu/tpu_feed.py | 919 ++++ tensorflow/python/tpu/tpu_function.py | 66 + .../tpu => }/python/tpu/tpu_infeed_test.py | 2 +- tensorflow/python/tpu/tpu_optimizer.py | 203 + tensorflow/python/tpu/tpu_sharding.py | 253 ++ .../tpu => }/python/tpu/tpu_sharding_test.py | 2 +- tensorflow/python/tpu/tpu_system_metadata.py | 156 + .../{contrib/tpu => }/python/tpu/tpu_test.py | 8 +- tensorflow/python/tpu/training_loop.py | 222 + tensorflow/python/tpu/util.py | 51 + tensorflow/python/tpu/xla.py | 106 + 72 files changed, 14714 insertions(+), 13802 deletions(-) create mode 100644 tensorflow/python/tpu/BUILD create mode 100644 tensorflow/python/tpu/__init__.py create mode 100644 tensorflow/python/tpu/_tpu_estimator_embedding.py create mode 100644 tensorflow/python/tpu/async_checkpoint.py create mode 100644 tensorflow/python/tpu/bfloat16.py rename tensorflow/{contrib/tpu => }/python/tpu/bfloat16_test.py (96%) create mode 100644 tensorflow/python/tpu/datasets.py rename tensorflow/{contrib/tpu => }/python/tpu/datasets_test.py (99%) create mode 100644 tensorflow/python/tpu/device_assignment.py create mode 100644 tensorflow/python/tpu/error_handling.py create mode 100644 tensorflow/python/tpu/feature_column.py rename tensorflow/{contrib/tpu => }/python/tpu/feature_column_test.py (98%) create mode 100644 tensorflow/python/tpu/functional.py create mode 100644 tensorflow/python/tpu/ops/tpu_ops.py create mode 100644 tensorflow/python/tpu/ops/tpu_ordinal_selector_op.py create mode 100644 tensorflow/python/tpu/profiler/BUILD create mode 100644 tensorflow/python/tpu/profiler/__init__.py rename tensorflow/{contrib => python}/tpu/profiler/tpu_profiler_analysis_pb2_grpc.py (100%) create mode 100644 tensorflow/python/tpu/session_support.py create mode 100644 tensorflow/python/tpu/tensor_tracer.py create mode 100644 tensorflow/python/tpu/topology.py rename tensorflow/{contrib/tpu => }/python/tpu/topology_test.py (96%) create mode 100644 tensorflow/python/tpu/tpu.py create mode 100644 tensorflow/python/tpu/tpu_config.py rename tensorflow/{contrib/tpu => }/python/tpu/tpu_config_test.py (98%) create mode 100644 tensorflow/python/tpu/tpu_context.py create mode 100644 tensorflow/python/tpu/tpu_embedding.py create mode 100644 tensorflow/python/tpu/tpu_embedding_gradient.py create mode 100644 tensorflow/python/tpu/tpu_estimator.py rename tensorflow/{contrib/tpu => }/python/tpu/tpu_estimator_signals_test.py (99%) create mode 100644 tensorflow/python/tpu/tpu_feed.py create mode 100644 tensorflow/python/tpu/tpu_function.py rename tensorflow/{contrib/tpu => }/python/tpu/tpu_infeed_test.py (99%) create mode 100644 tensorflow/python/tpu/tpu_optimizer.py create mode 100644 tensorflow/python/tpu/tpu_sharding.py rename tensorflow/{contrib/tpu => }/python/tpu/tpu_sharding_test.py (98%) create mode 100644 tensorflow/python/tpu/tpu_system_metadata.py rename tensorflow/{contrib/tpu => }/python/tpu/tpu_test.py (94%) create mode 100644 tensorflow/python/tpu/training_loop.py create mode 100644 tensorflow/python/tpu/util.py create mode 100644 tensorflow/python/tpu/xla.py diff --git a/tensorflow/contrib/tpu/BUILD b/tensorflow/contrib/tpu/BUILD index e274ff081a..7c1661d20f 100644 --- a/tensorflow/contrib/tpu/BUILD +++ b/tensorflow/contrib/tpu/BUILD @@ -28,8 +28,7 @@ py_library( srcs = ["python/ops/tpu_ops.py"], srcs_version = "PY2AND3", deps = [ - "//tensorflow/python:framework_for_generated_wrappers", - "//tensorflow/python:tpu_ops_gen", + "//tensorflow/python/tpu:tpu_py", ], ) @@ -38,19 +37,7 @@ py_library( srcs = ["python/tpu/async_checkpoint.py"], srcs_version = "PY2AND3", deps = [ - "//tensorflow/python:array_ops", - "//tensorflow/python:control_flow_ops", - "//tensorflow/python:framework_for_generated_wrappers", - "//tensorflow/python:init_ops", - "//tensorflow/python:math_ops", - "//tensorflow/python:platform", - "//tensorflow/python:state_ops", - "//tensorflow/python:summary", - "//tensorflow/python:summary_ops_v2", - "//tensorflow/python:training", - "//tensorflow/python:variable_scope", - "//tensorflow/python:variables", - "//tensorflow/python/estimator:estimator_py", + "//tensorflow/python/tpu:async_checkpoint", ], ) @@ -72,24 +59,7 @@ py_library( ":tpu_embedding", ":tpu_lib", "//tensorflow/contrib/training:training_py", - "//tensorflow/core:protos_all_py", - "//tensorflow/python:array_ops", - "//tensorflow/python:control_flow_ops", - "//tensorflow/python:framework_for_generated_wrappers", - "//tensorflow/python:function", - "//tensorflow/python:init_ops", - "//tensorflow/python:math_ops", - "//tensorflow/python:platform", - "//tensorflow/python:session", - "//tensorflow/python:state_ops", - "//tensorflow/python:summary", - "//tensorflow/python:summary_ops_v2", - "//tensorflow/python:training", - "//tensorflow/python:variable_scope", - "//tensorflow/python:variables", - "//tensorflow/python/estimator:estimator_py", - "//tensorflow/python/estimator:util", - "@six_archive//:six", + "//tensorflow/python/tpu:tpu_estimator", ], ) @@ -101,7 +71,7 @@ py_library( "//visibility:public", ], deps = [ - "//tensorflow/python:tpu_ops_gen", + "//tensorflow/python/tpu:functional", ], ) @@ -110,10 +80,7 @@ py_library( srcs = ["python/profiler/__init__.py"], srcs_version = "PY2AND3", deps = [ - "//tensorflow/contrib/tpu/profiler:tpu_profiler_analysis_pb2_grpc", - "//tensorflow/core/profiler:profiler_analysis_proto_py", - "//tensorflow/core/profiler:protos_all_py", - "//tensorflow/python:util", + "//tensorflow/python/tpu/profiler", ], ) @@ -130,6 +97,7 @@ py_library( ":tpu_embedding", ":tpu_estimator", ":tpu_lib", + "//tensorflow/python/tpu", ], ) @@ -196,29 +164,9 @@ py_library( ":functional", ":profiler", ":tpu_py", - "//tensorflow/compiler/xla/experimental/xla_sharding", - "//tensorflow/compiler/xla/python_api:xla_shape", "//tensorflow/contrib/cluster_resolver:cluster_resolver_py", "//tensorflow/contrib/compiler:xla", - "//tensorflow/core:protos_all_py", - "//tensorflow/core/protobuf/tpu:compilation_result_proto_py", - "//tensorflow/core/protobuf/tpu:dynamic_padding_proto_py", - "//tensorflow/core/protobuf/tpu:optimization_parameters_proto_py", - "//tensorflow/core/protobuf/tpu:topology_proto_py", - "//tensorflow/core/protobuf/tpu:tpu_embedding_configuration_proto_py", - "//tensorflow/core/protobuf/tpu:tpu_embedding_output_layout_proto_py", - "//tensorflow/python:array_ops", - "//tensorflow/python:control_flow_ops", - "//tensorflow/python:control_flow_util", - "//tensorflow/python:dtypes", - "//tensorflow/python:framework", - "//tensorflow/python:framework_ops", - "//tensorflow/python:tensor_shape", - "//tensorflow/python:tpu_ops_gen", - "//tensorflow/python:training", - "//tensorflow/python:util", - "//tensorflow/python:variable_scope", - "//tensorflow/python/ops/losses", + "//tensorflow/python/tpu:tpu_lib", ], ) @@ -229,106 +177,7 @@ py_library( ], srcs_version = "PY2AND3", deps = [ - "//tensorflow/contrib/data/python/ops:batching", - "//tensorflow/contrib/data/python/ops:interleave_ops", - "//tensorflow/python:dtypes", - "//tensorflow/python:function", - "//tensorflow/python:functional_ops", - "//tensorflow/python/data/ops:dataset_ops", - "//tensorflow/python/data/ops:iterator_ops", - "//tensorflow/python/data/ops:readers", - ], -) - -tf_py_test( - name = "datasets_test", - size = "medium", - srcs = ["python/tpu/datasets_test.py"], - additional_deps = [ - "//tensorflow/python:client_testlib", - ":datasets", - ], - grpc_enabled = True, - shard_count = 4, - tags = ["no_oss"], -) - -tf_py_test( - name = "tpu_test", - size = "small", - srcs = ["python/tpu/tpu_test.py"], - additional_deps = [ - ":tpu", - "//tensorflow/python:client_testlib", - "//tensorflow/python:dtypes", - "//tensorflow/python:framework", - "//tensorflow/python:layers", - ], - tags = ["no_windows"], # TODO: needs investigation on Windows -) - -tf_py_test( - name = "tpu_sharding_test", - size = "small", - srcs = ["python/tpu/tpu_sharding_test.py"], - additional_deps = [ - ":tpu", - "//tensorflow/python:client_testlib", - "//tensorflow/python:framework", - ], -) - -tf_py_test( - name = "bfloat16_test", - size = "small", - srcs = ["python/tpu/bfloat16_test.py"], - additional_deps = [ - ":tpu", - "//tensorflow/python:client_testlib", - "//tensorflow/python:framework", - ], -) - -tf_py_test( - name = "tpu_infeed_test", - size = "small", - srcs = ["python/tpu/tpu_infeed_test.py"], - additional_deps = [ - ":tpu", - "//tensorflow/python:framework", - "//tensorflow/python:framework_test_lib", - ], -) - -tf_py_test( - name = "tpu_config_test", - size = "small", - srcs = ["python/tpu/tpu_config_test.py"], - additional_deps = [ - ":tpu_estimator", - "//tensorflow/python:framework", - "//tensorflow/python:framework_test_lib", - ], -) - -tf_py_test( - name = "tpu_estimator_signals_test", - size = "small", - srcs = ["python/tpu/tpu_estimator_signals_test.py"], - additional_deps = [ - ":tpu_estimator", - "//tensorflow/python:framework", - "//tensorflow/python:framework_test_lib", - ], -) - -tf_py_test( - name = "topology_test", - size = "medium", - srcs = ["python/tpu/topology_test.py"], - additional_deps = [ - ":tpu", - "//tensorflow/python:framework_test_lib", + "//tensorflow/python/tpu:datasets", ], ) @@ -341,16 +190,7 @@ py_library( srcs_version = "PY2AND3", deps = [ ":tpu_lib", - "//tensorflow/core/protobuf/tpu:tpu_embedding_configuration_proto_py", - "//tensorflow/python:array_ops", - "//tensorflow/python:framework_for_generated_wrappers", - "//tensorflow/python:init_ops", - "//tensorflow/python:math_ops", - "//tensorflow/python:partitioned_variables", - "//tensorflow/python:tpu_ops_gen", - "//tensorflow/python:variable_scope", - "//tensorflow/python:variables", - "@six_archive//:six", + "//tensorflow/python/tpu:tpu_embedding", ], ) @@ -359,31 +199,6 @@ py_library( srcs = ["python/tpu/feature_column.py"], deps = [ ":tpu_lib", - "//tensorflow/python:framework_ops", - "//tensorflow/python:init_ops", - "//tensorflow/python:variable_scope", - "//tensorflow/python/feature_column", - "//tensorflow/python/feature_column:feature_column_py", - ], -) - -tf_py_test( - name = "feature_column_test", - srcs = [ - "python/tpu/feature_column_test.py", - ], - additional_deps = [ - ":feature_column", - "//third_party/py/numpy", - "//tensorflow/python:dtypes", - "//tensorflow/python:framework_ops", - "//tensorflow/python:lookup_ops", - "//tensorflow/python:parsing_ops", - "//tensorflow/python:session", - "//tensorflow/python:sparse_tensor", - "//tensorflow/python:variables", - "//tensorflow/python/feature_column", - "//tensorflow/python/feature_column:feature_column_py", + "//tensorflow/python/tpu:feature_column", ], - main = "python/tpu/feature_column_test.py", ) diff --git a/tensorflow/contrib/tpu/profiler/BUILD b/tensorflow/contrib/tpu/profiler/BUILD index 0c3743cd11..2eff4f2468 100644 --- a/tensorflow/contrib/tpu/profiler/BUILD +++ b/tensorflow/contrib/tpu/profiler/BUILD @@ -63,11 +63,3 @@ tf_cc_test( "@jsoncpp_git//:jsoncpp", ], ) - -py_library( - name = "tpu_profiler_analysis_pb2_grpc", - srcs = ["tpu_profiler_analysis_pb2_grpc.py"], - srcs_version = "PY2AND3", - visibility = ["//visibility:public"], - deps = ["//tensorflow/core/profiler:profiler_analysis_proto_py"], -) diff --git a/tensorflow/contrib/tpu/python/ops/tpu_ops.py b/tensorflow/contrib/tpu/python/ops/tpu_ops.py index ec0d5fec44..8605bae5c1 100644 --- a/tensorflow/contrib/tpu/python/ops/tpu_ops.py +++ b/tensorflow/contrib/tpu/python/ops/tpu_ops.py @@ -1,418 +1,23 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 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 +# 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. -# ============================================================================= - -"""Operations for TPUs.""" +# ============================================================================== +"""Stub file to maintain backwards compatibility.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function -import platform - -from tensorflow.contrib.tpu.python.tpu import tpu_function -from tensorflow.python.framework import dtypes -from tensorflow.python.framework import ops -from tensorflow.python.ops import array_ops -from tensorflow.python.platform import tf_logging as logging - -if platform.system() != "Windows": - # pylint: disable=wildcard-import,unused-import,g-import-not-at-top - from tensorflow.python.ops import gen_tpu_ops - from tensorflow.python.ops.gen_tpu_ops import * - # pylint: enable=wildcard-import,unused-import,g-import-not-at-top - - def _create_default_group_assignment(): - num_shards = tpu_function.get_tpu_context().number_of_shards - if num_shards is None: - logging.warning( - "cross_replica_sum should be used within a tpu_shard_context, but " - "got unset number_of_shards. Assuming 1.") - num_shards = 1 - group_assignment = [list(range(num_shards))] - return group_assignment - - def all_to_all(x, - concat_dimension, - split_dimension, - split_count, - group_assignment=None, - name=None): - """Exchange data across TPU replicas. - - Args: - x: The local tensor. - concat_dimension: The dimension number to concatenate. - split_dimension: The dimension number to split. - split_count: The number of splits, this number must equal to the sub-group - size(group_assignment.get_shape()[1]) - group_assignment: Optional 2d int32 lists with shape [num_groups, - num_replicas_per_group]. `group_assignment[i]` represents the replica - ids in the ith subgroup. - name: Optional op name. - - Returns: - A `Tensor` which is concatenated by data from different replicas. - """ - if group_assignment is None: - group_assignment = _create_default_group_assignment() - return gen_tpu_ops.all_to_all( - x, - group_assignment, - concat_dimension=concat_dimension, - split_dimension=split_dimension, - split_count=split_count, - name=name) - - @ops.RegisterGradient("AllToAll") - def _all_to_all_grad(op, grad): - # The gradient of a all-to-all is also a all-to-all but the - # split_dimension and concat_dimension is swapped. - # The graident with respect to group_assignment is None. - return [ - gen_tpu_ops.all_to_all( - grad, - op.inputs[1], - concat_dimension=op.get_attr("split_dimension"), - split_dimension=op.get_attr("concat_dimension"), - split_count=op.get_attr("split_count")), None - ] - - def cross_replica_sum(x, group_assignment=None, name=None): - """Sum the input tensor across replicas according to group_assignment. - - Args: - x: The local tensor to the sum. - group_assignment: Optional 2d int32 lists with shape [num_groups, - num_replicas_per_group]. `group_assignment[i]` represents the replica - ids in the ith subgroup. - name: Optional op name. - - Returns: - A `Tensor` which is summed across replicas. - """ - if group_assignment is None: - group_assignment = _create_default_group_assignment() - - return gen_tpu_ops.cross_replica_sum(x, group_assignment, name=name) - - def collective_permute(x, source_target_pairs, name=None): - """Permute the input tensor across replicas given source_target_pairs. - - For each source_target_pair , we send replica a's input to replica b. - Each replica id must only appear once in the source column. Also it must - only appear once in the target column. - For the replica id not in the target column, this op returns a zero tensor - with the same shape and dtype of the input x. - - For example, suppose there are 4 TPU instances: `[A, B, C, D]`. Passing - source_target_pairs=`[[0,1],[1,2],[2,3]]` gets the outputs: - `[0, A, B, C]`. - - Args: - x: The local tensor to be permuted. - source_target_pairs: 2d int lists with shape [num_pairs, 2]. - source_target_pairs[i][0] represents the source replica id and - source_target_pairs[i][1] represents the target replica id. - name: Optional op name. - - Returns: - A `Tensor` which is permuted. - """ - return gen_tpu_ops.collective_permute(x, source_target_pairs, name=name) - - @ops.RegisterGradient("CollectivePermute") - def _collective_permute_grad(op, grad): - # The gradient of a collective permute operation is also a collective - # permute, but with source/target pairs reversed. The gradient with respect - # to input argument `source_target_pairs` is `None`. - source_target_pairs = op.inputs[1][:, ::-1] - return [gen_tpu_ops.collective_permute(grad, source_target_pairs), None] - - @ops.RegisterGradient("CrossReplicaSum") - def _cross_replica_sum_grad(op, grad): - # The gradient of a cross replica sum is also a cross-replica sum. - # The gradient with respect to group_assignment is None. - return [gen_tpu_ops.cross_replica_sum(grad, op.inputs[1]), None] - - # This extra type checking exists to give a more helpful error message in - # the common case that uint8 and int64 values are infed. Remove when both - # types are supported. - - _SUPPORTED_INFEED_DTYPES = set([ - dtypes.bool, dtypes.int32, dtypes.int64, dtypes.bfloat16, dtypes.float32, - dtypes.complex64, dtypes.uint32 - ]) - - @ops.RegisterGradient("TPUEmbeddingActivations") - def _embedding_activations_grad(activations_op, grad_wrt_activations): - """Saves the gradient of embedding activations ops in a graph collection.""" - g = ops.get_default_graph() - table_id = activations_op.get_attr("table_id") - lookup_id = activations_op.get_attr("lookup_id") - table_gradients = g.get_collection_ref( - "tpu_embedding_gradients_table_%d" % table_id) - - if not table_gradients: - raise RuntimeError( - "Gradients for TPUEmbedding have been generated in non-training mode." - "This is not expected. Consider putting your Optimizer.minimize code " - "behind the training mode condition check. For Estimator, you can " - "do \n\n" - " if mode == tf.estimator.ModeKeys.TRAIN:\n" - " train_op = opt.minimize(loss)\n" - "\n") - - table_gradients[lookup_id] = array_ops.identity(grad_wrt_activations) - return [ - # RegisterGradient requires that value be returned for all inputs. Since - # the first argument (tpu_gradient_variable_{table_name}) has shape [1], - # we will return zeros(shape=[1]). The actual gradient w.r.t. the - # embedding activations (grad_wrt_activations) has the same shape as the - # activations returned by embedding_activations. - array_ops.zeros(arg.shape, dtype=dtypes.float32) - for arg in activations_op.inputs - ] - - def infeed_dequeue(dtype, shape, name=None): - """A placeholder op for a value that will be fed into the computation. - - Args: - dtype: A `tf.DType`. The type of elements in the tensor. - shape: A `tf.TensorShape` or list of `ints`. The shape of the tensor. - name: A name for the operation (optional). - - Returns: - A `Tensor` of type `dtype`. - A tensor that will be provided using the infeed mechanism. - - Raises: - TypeError: If 'dtype` is not a supported infeed type. - """ - if dtype not in _SUPPORTED_INFEED_DTYPES: - raise TypeError( - "{} is not a supported TPU infeed type. Supported types are: " - "{}".format(dtype, list(_SUPPORTED_INFEED_DTYPES))) - - return gen_tpu_ops.infeed_dequeue(dtype, shape, name=name) - - # pylint: disable=redefined-outer-name - def infeed_dequeue_tuple(dtypes, shapes, name=None): - """A placeholder op for values fed into the TPU simultaneously as a tuple. - - Args: - dtypes: A list of `tf.DType`s that has length `>= 1`. - The element types of each element in `outputs`. - shapes: A list of shapes (each a `tf.TensorShape` or list of `ints`). - The shapes of each tensor in `outputs`. - name: A name for the operation (optional). - - Returns: - A list of `Tensor` objects of type `dtypes`. - A list of tensors that will be provided using the infeed mechanism. - - Raises: - TypeError: If a type in 'dtypes` is not a supported infeed type. - """ - for dtype in dtypes: - if dtype not in _SUPPORTED_INFEED_DTYPES: - raise TypeError( - "{} is not a supported TPU infeed type. Supported types are: " - "{}".format(dtype, list(_SUPPORTED_INFEED_DTYPES))) - return gen_tpu_ops.infeed_dequeue_tuple(dtypes, shapes, name=name) - # pylint: enable=redefined-outer-name - - # pylint: disable=protected-access - def send_tpu_embedding_gradients(inputs, - config, - learning_rates=None, - name=None): - """A placeholder op for feeding per-sample gradients to the embedding layer. - - Args: - inputs: A TensorList of gradients with which to update embedding tables. - This argument has the same length and shapes as the return value of - RecvTPUEmbeddingActivations, but contains gradients of the model's - loss with respect to the embedding activations. The embedding tables - are updated from these gradients via the optimizers specified in the - TPU embedding configuration given to tpu.initialize_system. - config: Serialized TPUEmbeddingConfiguration proto. - learning_rates: A TensorList of float32 scalars, one for each dynamic - learning rate tag: see the comments in - //third_party/tensorflow/core/protobuf/tpu/ - optimization_parameters.proto. - Multiple tables can share the same dynamic learning rate tag as - specified in the configuration. If the learning rates for all tables - are constant, this list should be empty. - name: A name for the operation (optional). - - Returns: - A SendTPUEmbeddingGradients operation. - """ - if learning_rates is None: - learning_rates = [] - return gen_tpu_ops.send_tpu_embedding_gradients( - inputs=inputs, learning_rates=learning_rates, config=config, name=name) - - send_tpu_embedding_gradients.__doc__ = ( - gen_tpu_ops.send_tpu_embedding_gradients.__doc__) - - # pylint: disable=protected-access - def enqueue_tpu_embedding_integer_batch(batch, - device_ordinal, - mode_override=None, - name=None): - """A placeholder op for enqueueing embedding IDs to the TPU. - - Args: - batch: A list of 1D tensors, one for each embedding table, containing the - indices into the tables. - device_ordinal: The TPU device to use. Should be >= 0 and less than the - number of TPU cores in the task on which the node is placed. - mode_override: A string input that overrides the mode specified in the - TPUEmbeddingConfiguration. Supported values are {'unspecified', - 'inference', 'training', 'backward_pass_only'}. When set to - 'unspecified', the mode set in TPUEmbeddingConfiguration is used, - otherwise mode_override is used (optional). - name: A name for the operation (optional). - - Returns: - An EnqueueTPUEmbeddingIntegerBatch operation. - """ - if mode_override is None: - mode_override = "unspecified" - return gen_tpu_ops.enqueue_tpu_embedding_integer_batch( - batch=batch, - device_ordinal=device_ordinal, - mode_override=mode_override, - name=name) - - enqueue_tpu_embedding_integer_batch.__doc__ = ( - gen_tpu_ops.enqueue_tpu_embedding_integer_batch.__doc__) - - # pylint: disable=protected-access - def enqueue_tpu_embedding_sparse_batch(sample_indices, - embedding_indices, - aggregation_weights, - device_ordinal, - combiners=None, - mode_override=None, - name=None): - """A placeholder op for enqueueing embedding IDs to the TPU. - - Args: - sample_indices: A list of rank 1 Tensors specifying the training example - and feature to which the corresponding embedding_indices and - aggregation_weights values belong. sample_indices[i] must equal b * nf + - f, where nf is the number of features from the corresponding table, f is - in [0, nf), and b is in [0, batch size). - embedding_indices: A list of rank 1 Tensors, indices into the embedding - tables. - aggregation_weights: A list of rank 1 Tensors containing per sample -- - i.e. per (training example, feature) -- aggregation weights. - device_ordinal: The TPU device to use. Should be >= 0 and less than the - number of TPU cores in the task on which the node is placed. - combiners: A list of string scalars, one for each embedding table that - specify how to normalize the embedding activations after weighted - summation. Supported combiners are 'mean', 'sum', or 'sqrtn'. It is - invalid to have the sum of the weights be 0 for 'mean' or the sum of the - squared weights be 0 for 'sqrtn'. If combiners isn't passed, the default - is to use 'sum' for all tables (optional). - mode_override: A string input that overrides the mode specified in the - TPUEmbeddingConfiguration. Supported values are {'unspecified', - 'inference', 'training', 'backward_pass_only'}. When set to - 'unspecified', the mode set in TPUEmbeddingConfiguration is used, - otherwise mode_override is used (optional). - name: A name for the operation (optional). - - Returns: - An EnqueueTPUEmbeddingSparseBatch operation. - """ - if mode_override is None: - mode_override = "unspecified" - return gen_tpu_ops.enqueue_tpu_embedding_sparse_batch( - sample_indices=sample_indices, - embedding_indices=embedding_indices, - aggregation_weights=aggregation_weights, - device_ordinal=device_ordinal, - combiners=combiners, - mode_override=mode_override, - name=name) - - enqueue_tpu_embedding_sparse_batch.__doc__ = ( - gen_tpu_ops.enqueue_tpu_embedding_sparse_batch.__doc__) - - # pylint: disable=protected-access - def enqueue_tpu_embedding_sparse_tensor_batch(sample_indices, - embedding_indices, - aggregation_weights, - table_ids, - device_ordinal, - combiners=None, - mode_override=None, - name=None): - """A placeholder op for enqueueing embedding IDs to the TPU. - - Args: - sample_indices: A list of rank 1 Tensors specifying the training example - to which the corresponding embedding_indices and aggregation_weights - values belong. It corresponds to sp_ids.indices[:,0] in - embedding_lookup_sparse(). - embedding_indices: A list of rank 1 Tensors, indices into the embedding - tables. It corresponds to sp_ids.values in embedding_lookup_sparse(). - aggregation_weights: A list of rank 1 Tensors containing per training - example aggregation weights. It corresponds to sp_weights.values in - embedding_lookup_sparse(). - table_ids: A list of integers specifying the identifier of the embedding - table (offset of TableDescriptor in the TPUEmbeddingConfiguration) to - lookup the corresponding input. The ith input is looked up using - table_ids[i]. The size of the table_ids list must be equal to that of - sample_indices, embedding_indices and aggregation_weights. - device_ordinal: The TPU device to use. Should be >= 0 and less than the - number of TPU cores in the task on which the node is placed. - combiners: A list of string scalars, one for each embedding table that - specify how to normalize the embedding activations after weighted - summation. Supported combiners are 'mean', 'sum', or 'sqrtn'. It is - invalid to have the sum of the weights be 0 for 'mean' or the sum of the - squared weights be 0 for 'sqrtn'. If combiners isn't passed, the default - is to use 'sum' for all tables (optional). - mode_override: A string input that overrides the mode specified in the - TPUEmbeddingConfiguration. Supported values are {'unspecified', - 'inference', 'training', 'backward_pass_only'}. When set to - 'unspecified', the mode set in TPUEmbeddingConfiguration is used, - otherwise mode_override is used (optional). - name: A name for the operation (optional). - - Returns: - An EnqueueTPUEmbeddingSparseTensorBatch operation. - """ - if mode_override is None: - mode_override = "unspecified" - return gen_tpu_ops.enqueue_tpu_embedding_sparse_tensor_batch( - sample_indices=sample_indices, - embedding_indices=embedding_indices, - aggregation_weights=aggregation_weights, - table_ids=table_ids, - device_ordinal=device_ordinal, - combiners=combiners, - mode_override=mode_override, - name=name) - - enqueue_tpu_embedding_sparse_tensor_batch.__doc__ = ( - gen_tpu_ops.enqueue_tpu_embedding_sparse_tensor_batch.__doc__) - -else: - # We have already built the appropriate libraries into the binary via CMake - # if we have built contrib, so we don't need this - pass +# pylint: disable=wildcard-import,unused-import +from tensorflow.python.tpu.ops.tpu_ops import * +# pylint: enable=wildcard-import,unused-import diff --git a/tensorflow/contrib/tpu/python/ops/tpu_ordinal_selector_op.py b/tensorflow/contrib/tpu/python/ops/tpu_ordinal_selector_op.py index 6917ac2e1a..788e1fe056 100644 --- a/tensorflow/contrib/tpu/python/ops/tpu_ordinal_selector_op.py +++ b/tensorflow/contrib/tpu/python/ops/tpu_ordinal_selector_op.py @@ -1,35 +1,23 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 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 +# 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. -# ============================================================================= - -"""Operations to select TPU core to run.""" +# ============================================================================== +"""Stub file to maintain backwards compatibility.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function -import platform - -if platform.system() != "Windows": - # pylint: disable=wildcard-import,unused-import,g-import-not-at-top - from tensorflow.python.ops.gen_tpu_ops import tpu_ordinal_selector - - from tensorflow.contrib.util import loader - from tensorflow.python.platform import resource_loader - # pylint: enable=wildcard-import,unused-import,g-import-not-at-top - -else: - # We have already built the appropriate libraries into the binary via CMake - # if we have built contrib, so we don't need this - pass +# pylint: disable=wildcard-import,unused-import +from tensorflow.python.tpu.ops.tpu_ordinal_selector_op import * +# pylint: enable=wildcard-import,unused-import diff --git a/tensorflow/contrib/tpu/python/profiler/__init__.py b/tensorflow/contrib/tpu/python/profiler/__init__.py index 0c183aaf53..aeb061dbe1 100644 --- a/tensorflow/contrib/tpu/python/profiler/__init__.py +++ b/tensorflow/contrib/tpu/python/profiler/__init__.py @@ -1,31 +1,23 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 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 +# 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 TPU trace events.""" +# ============================================================================== +"""Stub file to maintain backwards compatibility.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function # pylint: disable=wildcard-import,unused-import -from tensorflow.core.profiler.trace_events_pb2 import * -from tensorflow.core.profiler.profiler_analysis_pb2 import * +from tensorflow.python.tpu.profiler import * # pylint: enable=wildcard-import,unused-import - -from tensorflow.python.util.all_util import remove_undocumented - -_allowed_symbols = ['Trace', 'Resource', 'Device', 'TraceEvent'] - -remove_undocumented(__name__, _allowed_symbols) diff --git a/tensorflow/contrib/tpu/python/tpu/__init__.py b/tensorflow/contrib/tpu/python/tpu/__init__.py index 0dffd7064b..82d4f68c02 100644 --- a/tensorflow/contrib/tpu/python/tpu/__init__.py +++ b/tensorflow/contrib/tpu/python/tpu/__init__.py @@ -1,20 +1,23 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 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 +# 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. -# ============================================================================= - -"""Ops related to Tensor Processing Units.""" +# ============================================================================== +"""Stub file to maintain backwards compatibility.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function + +# pylint: disable=wildcard-import,unused-import +from tensorflow.python.tpu import * +# pylint: enable=wildcard-import,unused-import diff --git a/tensorflow/contrib/tpu/python/tpu/_tpu_estimator_embedding.py b/tensorflow/contrib/tpu/python/tpu/_tpu_estimator_embedding.py index 98aa7827fc..41aa4d2678 100644 --- a/tensorflow/contrib/tpu/python/tpu/_tpu_estimator_embedding.py +++ b/tensorflow/contrib/tpu/python/tpu/_tpu_estimator_embedding.py @@ -1,334 +1,23 @@ -# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 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 +# 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. -# =================================================================== -"""Tooling for support TPU embedding in TPUEstimator.""" +# ============================================================================== +"""Stub file to maintain backwards compatibility.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function -import collections - -from tensorflow.contrib.tpu.python.tpu import feature_column as tpu_fc -from tensorflow.contrib.tpu.python.tpu import tpu_embedding -from tensorflow.python.estimator import model_fn as model_fn_lib -from tensorflow.python.feature_column import feature_column as core_fc -from tensorflow.python.feature_column import feature_column_lib as core_fc_lib - -# pylint: disable=protected-access -_TPU_EMBEDDING_COLUMN_CLASSES = (tpu_fc._TPUEmbeddingColumn, - tpu_fc._TPUSharedEmbeddingColumn) -_EMBEDDING_COLUMN_CLASSES = (core_fc._EmbeddingColumn, - core_fc_lib.EmbeddingColumn, - core_fc._SharedEmbeddingColumn) -_SUPPORTED_FEATURE_COLUMNS = (core_fc._NumericColumn, core_fc_lib.NumericColumn) - -# pylint: enable=protected-access - -_TABLE_NAME_PREFIX = 'tbl_' -_LEN_TABLE_NAME_PREFIX = len(_TABLE_NAME_PREFIX) - - -def _get_table_name_from_embedding_var_name(embedding_var_name): - return '{}{}'.format(_TABLE_NAME_PREFIX, embedding_var_name) - - -def _get_embedding_var_name_from_table_name(table_name): - return table_name[_LEN_TABLE_NAME_PREFIX:] - - -def _get_embedding_variable_name(scope_name, var_name): - return '{}/{}'.format(scope_name, var_name) - - -def _get_slot_variable_names(scope_name, var_name, optimization_parameters): - """Return embedding variable names which are consistent with CPU runs.""" - if isinstance(optimization_parameters, tpu_embedding.AdagradParameters): - return tpu_embedding.AdagradSlotVariableName( - '{}/{}/Adagrad'.format(scope_name, var_name) - ) - elif isinstance(optimization_parameters, tpu_embedding.AdamParameters): - return tpu_embedding.AdamSlotVariableNames( - '{}/{}/Adam/m'.format(scope_name, var_name), - '{}/{}/Adam/v'.format(scope_name, var_name) - ) - elif isinstance(optimization_parameters, - tpu_embedding.StochasticGradientDescentParameters): - return None - else: - raise ValueError('Support to infer full variable name ' - 'for optimization_parameter {} has not been added.' - .format(optimization_parameters)) - - -def get_full_variable_names( - graph, table_to_config_dict, optimization_parameters): - """Return embedding variable names and slot variables which are consistent with CPU runs.""" - collection = graph.get_collection_ref(tpu_fc._TPU_FC_TO_SCOPE) # pylint: disable=protected-access - if not collection: - raise RuntimeError( - 'Embedding feature column did not capture any thing. Make sure the ' - 'feature columns passed to TPUEstimator constructor is properly ' - 'used in model_fn.') - - embedding_variable_name_by_table = {} - slot_variable_names_by_table = {} - for table_name in table_to_config_dict: - embedding_var_name = _get_embedding_var_name_from_table_name(table_name) - (scope_name, var_name) = collection[0][embedding_var_name] - embedding_variable_name_by_table[table_name] = ( - _get_embedding_variable_name(scope_name, var_name)) - slot_variable_names_by_table[table_name] = _get_slot_variable_names( - scope_name, var_name, optimization_parameters) - - graph.clear_collection(tpu_fc._TPU_FC_TO_SCOPE) # pylint: disable=protected-access - return embedding_variable_name_by_table, slot_variable_names_by_table - - -def get_tpu_embedding_config_from_feature_columns(feature_columns): - """Create configs for TPUEmbedding from a list of feature columns. - - This function will place one embedding tensor per table and the return is - intended to be used as input to TPUEmbedding. - - Args: - feature_columns: a list of supported feature columns. - - Returns: - A pair of dicts, the first maps tables to their config, the second maps - features to tables. - """ - - allowed = (tpu_fc._TPUEmbeddingColumn, tpu_fc._TPUSharedEmbeddingColumn) # pylint: disable=protected-access - - for column in feature_columns: - if not isinstance(column, allowed): - raise TypeError( - 'Unsupported feature column {}. Supported types are {}.'.format( - type(column), allowed)) - - table_to_config = {} - feature_to_table = {} - for column in feature_columns: - feature_name = column.get_feature_key_name() - table_name = _get_table_name_from_embedding_var_name( - column.get_embedding_var_name()) - if feature_name in feature_to_table: - raise ValueError( - 'Feature column {} is used with multiple embeddings and this is ' - 'not supported.'.format(feature_name)) - feature_to_table[feature_name] = table_name - vocabulary_size, dimension = column.get_embedding_table_size() - table_to_config[table_name] = tpu_embedding.TableConfig( - vocabulary_size=vocabulary_size, - dimension=dimension, - initializer=column.get_initializer(), - combiner=column.get_combiner()) - - return table_to_config, feature_to_table - - -def _get_tpu_embedding_optimization_parameters(embedding_config_spec): - """Get tpu_embedding._OptimizationParameters from EmbeddingConfigSpec.""" - if embedding_config_spec.optimizer_type == 'adagrad': - return tpu_embedding.AdagradParameters( - embedding_config_spec.learning_rate, - embedding_config_spec.adagrad_initial_accumulator, - embedding_config_spec.use_gradient_accumulation) - elif embedding_config_spec.optimizer_type == 'sgd': - return tpu_embedding.StochasticGradientDescentParameters( - embedding_config_spec.learning_rate, - embedding_config_spec.use_gradient_accumulation) - elif embedding_config_spec.optimizer_type == 'adam': - return tpu_embedding.AdamParameters( - embedding_config_spec.learning_rate, - embedding_config_spec.adam_parameters.beta1, - embedding_config_spec.adam_parameters.beta2, - embedding_config_spec.adam_parameters.epsilon, - use_gradient_accumulation=embedding_config_spec - .use_gradient_accumulation) - else: - raise ValueError('optimizer_type must be adagrad or sgd or adam for now.') - - -AdamParameters = collections.namedtuple('AdamParameters', - ['beta1', 'beta2', 'epsilon']) - - -# TODO(shizhiw): Improve the API to support more optimizer parameters in API. -class EmbeddingConfigSpec( - collections.namedtuple('EmbeddingConfigSpec', [ - 'feature_columns', 'learning_rate', 'optimizer_type', - 'adagrad_initial_accumulator', 'clipping_limit', - 'use_gradient_accumulation', 'adam_parameters' - ])): - """Class to keep track of embedding config specification.""" - - def __new__(cls, - feature_columns, - learning_rate, - optimizer_type='adagrad', - adagrad_initial_accumulator=None, - clipping_limit=None, - use_gradient_accumulation=False, - adam_parameters=None): - """Creates an EmbeddingConfigSpec instance. - - Args: - feature_columns: All `FeatureColumn`s used by model. - learning_rate: embedding optimizer learning rate. - optimizer_type: (String) Name of the optimizer for embedding gradients - updates. Must be either 'adagrad' ( `tf.train.AdagradOptimizer`, default - value), 'sgd' (`tf.train.GradientDescentOptimizer`), or 'adam' - (`tf.contrib.opt.LazyAdamOptimizer`) for lazy Adam. This optimizer will - be applied to all embedding variables specified by `feature_columns`. - adagrad_initial_accumulator: Initial accumulator for Adagrad. Used when - optimizer_type is 'adagrad'. Default is `0.1`. - clipping_limit: (Optional) Clipping limit (absolute value). - use_gradient_accumulation: (Experimental) Whether to accumulate the - gradients across TPU embedding mini-batches. Gradient accumulation does - not affect SGD and therefore this is applicable only for Adagrad. - adam_parameters: AdamParameters. Used when optimizer_type is 'adam'. - Default is 0.9 for beta1, 0.999 for beta2 and 1e-8 for epsilon. - - Returns: - An EmbeddingConfigSpec instance. - - Raises: - ValueError: If the feature_columns are not specified. - TypeError: If the feature columns are not of ths correct type (one of - _SUPPORTED_FEATURE_COLUMNS, _TPU_EMBEDDING_COLUMN_CLASSES OR - _EMBEDDING_COLUMN_CLASSES). - ValueError: If use_gradient_accumulation is True for SGD. - ValueError: If `optimizer_type` is not one of "adagrad" or "sgd" or - "adam". - """ - if not feature_columns: - raise ValueError('`feature_columns` cannot be `None` or empty.') - - # It is unknown at this moment, whether the TPUEstimator is running in CPU - # or TPU mode. So allow non-TPU embedding columns also. - supported_classes = tuple( - list(_SUPPORTED_FEATURE_COLUMNS) + list(_TPU_EMBEDDING_COLUMN_CLASSES) + - list(_EMBEDDING_COLUMN_CLASSES)) - - for column in feature_columns: - if not isinstance(column, supported_classes): - raise TypeError( - 'All feature columns must be supported types in {}. Got {}'.format( - supported_classes, type(column))) - - if optimizer_type == 'adagrad': - if adagrad_initial_accumulator is None: - adagrad_initial_accumulator = 0.1 - if adagrad_initial_accumulator <= 0: - raise ValueError('Adagrad initial_accumulator must be positive') - elif optimizer_type == 'sgd': - if use_gradient_accumulation: - raise ValueError('Gradient accumulation makes sense for Adagrad only.') - elif optimizer_type == 'adam': - if adam_parameters is None: - adam_parameters = AdamParameters(0.9, 0.999, 1e-8) - if adam_parameters.beta1 < 0. or adam_parameters.beta1 >= 1.: - raise ValueError('beta1 must be between 0. and 1; got {}.'.format( - adam_parameters.beta1)) - if adam_parameters.beta2 < 0. or adam_parameters.beta2 >= 1.: - raise ValueError('beta2 must be between 0. and 1; got {}.'.format( - adam_parameters.beta2)) - if adam_parameters.epsilon <= 0.: - raise ValueError('epsilon must be positive; got {}.'.format( - adam_parameters.epsilon)) - else: - raise ValueError('optimizer_type must be adagrad or sgd or adam for now.') - - return super(EmbeddingConfigSpec, cls).__new__( - cls, - feature_columns=feature_columns, - learning_rate=learning_rate, - optimizer_type=optimizer_type, - adagrad_initial_accumulator=adagrad_initial_accumulator, - clipping_limit=clipping_limit, - use_gradient_accumulation=use_gradient_accumulation, - adam_parameters=adam_parameters) - - -class EmbeddingConfig(object): - """This is the internal immutable object for embedding config. - - `_EmbeddingConfig` is responsible to _translate_ user provided - `EmbeddingConfigSpec` to internal data structures, mostly constructor - arguments of `TPUEmbedding`. - """ - - def __init__(self, embedding_config_spec, train_batch_size, eval_batch_size, - num_hosts, num_cores, master): - self._embedding_config_spec = embedding_config_spec - self._train_batch_size = train_batch_size - self._eval_batch_size = eval_batch_size - self._num_hosts = num_hosts - self._num_cores = num_cores - self._master = master - - self._table_to_config_dict, self._feature_to_table_dict = ( - get_tpu_embedding_config_from_feature_columns( - embedding_config_spec.feature_columns)) - self._optimization_parameters = _get_tpu_embedding_optimization_parameters( - self._embedding_config_spec) - self._mode_to_tpu_embedding_dict = {} - self.dummy_table_variables = None - - def has_embedding_tables(self): - return bool(self._table_to_config_dict) - - def _create_tpu_embedding(self, mode): - """Create tpu_embedding.TPUEmbedding based on mode.""" - if mode == model_fn_lib.ModeKeys.TRAIN: - batch_size = self._train_batch_size - else: - batch_size = self._eval_batch_size - - if mode == model_fn_lib.ModeKeys.TRAIN: - tpu_embedding_mode = tpu_embedding.TRAINING - elif (mode == model_fn_lib.ModeKeys.EVAL or - mode == model_fn_lib.ModeKeys.PREDICT): - tpu_embedding_mode = tpu_embedding.INFERENCE - else: - raise ValueError('Mode {} is not supported.'.format(mode)) - - tpu_embedding_ = tpu_embedding.TPUEmbedding( - self._table_to_config_dict, - self._feature_to_table_dict, - batch_size, - tpu_embedding_mode, - self._master, - self._optimization_parameters, - ) - return tpu_embedding_ - - def get_tpu_embedding(self, mode): - if mode not in self._mode_to_tpu_embedding_dict: - self._mode_to_tpu_embedding_dict[mode] = ( - self._create_tpu_embedding(mode)) - return self._mode_to_tpu_embedding_dict[mode] - - -def split_inputs(ctx, features, labels): - """Splits the dense and sparse tensors inside the features and labels.""" - sparse_features = collections.OrderedDict() - if ctx.embedding_config: - tpu_embedding_ = ctx.embedding_config.tpu_embedding - for feature_key in tpu_embedding_.feature_to_table_dict: - sparse_features[feature_key] = features.pop(feature_key) - - return features, labels, sparse_features +# pylint: disable=wildcard-import,unused-import +from tensorflow.python.tpu._tpu_estimator_embedding import * +# pylint: enable=wildcard-import,unused-import diff --git a/tensorflow/contrib/tpu/python/tpu/async_checkpoint.py b/tensorflow/contrib/tpu/python/tpu/async_checkpoint.py index 1b09ce173a..5eb8034e47 100644 --- a/tensorflow/contrib/tpu/python/tpu/async_checkpoint.py +++ b/tensorflow/contrib/tpu/python/tpu/async_checkpoint.py @@ -1,212 +1,23 @@ -# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 The TensorFlow Authors. All Rights Reserved. # -# Licensed under the Apache License, Version 2.0 (the 'License'); +# 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 +# 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, +# 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. -# ====================================== -"""Hook for asynchronous checkpointing. - -This hook dispatches checkpoint writing operations in a separate thread to -allow execution to continue on the main thread. -""" +# ============================================================================== +"""Stub file to maintain backwards compatibility.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function -import os -import threading -import time - -from tensorflow.core.util.event_pb2 import SessionLog -from tensorflow.python.framework import meta_graph -from tensorflow.python.framework import ops -from tensorflow.python.platform import tf_logging as logging -from tensorflow.python.training import basic_session_run_hooks -from tensorflow.python.training import training_util -from tensorflow.python.training.session_run_hook import SessionRunArgs -from tensorflow.python.training.summary_io import SummaryWriterCache - - -class AsyncCheckpointSaverHook(basic_session_run_hooks.CheckpointSaverHook): - """Saves checkpoints every N steps or seconds.""" - - def __init__(self, - checkpoint_dir, - save_secs=None, - save_steps=None, - saver=None, - checkpoint_basename="model.ckpt", - scaffold=None, - listeners=None): - """Initializes a `CheckpointSaverHook`. - - Args: - checkpoint_dir: `str`, base directory for the checkpoint files. - save_secs: `int`, save every N secs. - save_steps: `int`, save every N steps. - saver: `Saver` object, used for saving. - checkpoint_basename: `str`, base name for the checkpoint files. - scaffold: `Scaffold`, use to get saver object. - listeners: List of `CheckpointSaverListener` subclass instances. Used for - callbacks that run immediately before or after this hook saves the - checkpoint. - - Raises: - ValueError: One of `save_steps` or `save_secs` should be set. - ValueError: At most one of `saver` or `scaffold` should be set. - """ - logging.info("Create AsyncCheckpointSaverHook.") - if saver is not None and scaffold is not None: - raise ValueError("You cannot provide both saver and scaffold.") - self._saver = saver - self._save_thread = None - self._write_graph_thread = None - self._checkpoint_dir = checkpoint_dir - self._save_path = os.path.join(checkpoint_dir, checkpoint_basename) - self._scaffold = scaffold - self._timer = basic_session_run_hooks.SecondOrStepTimer( - every_secs=save_secs, every_steps=save_steps) - self._listeners = listeners or [] - self._steps_per_run = 1 - self._summary_writer = None - self._global_step_tensor = None - - self._last_checkpoint_step = None - - def _set_steps_per_run(self, steps_per_run): - self._steps_per_run = steps_per_run - - def begin(self): - self._summary_writer = SummaryWriterCache.get(self._checkpoint_dir) - self._global_step_tensor = training_util._get_or_create_global_step_read() # pylint: disable=protected-access - if self._global_step_tensor is None: - raise RuntimeError( - "Global step should be created to use CheckpointSaverHook.") - for l in self._listeners: - l.begin() - - def after_create_session(self, session, coord): - global_step = session.run(self._global_step_tensor) - - # We do write graph and saver_def at the first call of before_run. - # We cannot do this in begin, since we let other hooks to change graph and - # add variables in begin. Graph is finalized after all begin calls. - def _write_graph_fn(self): - training_util.write_graph( - ops.get_default_graph().as_graph_def(add_shapes=True), - self._checkpoint_dir, "graph.pbtxt") - self._write_graph_thread = threading.Thread(target=_write_graph_fn, - args=[self]) - self._write_graph_thread.start() - - saver_def = self._get_saver().saver_def if self._get_saver() else None - graph = ops.get_default_graph() - meta_graph_def = meta_graph.create_meta_graph_def( - graph_def=graph.as_graph_def(add_shapes=True), saver_def=saver_def) - self._summary_writer.add_graph(graph) - self._summary_writer.add_meta_graph(meta_graph_def) - # The checkpoint saved here is the state at step "global_step". - self._save(session, global_step) - self._timer.update_last_triggered_step(global_step) - - def before_run(self, run_context): # pylint: disable=unused-argument - return SessionRunArgs(self._global_step_tensor) - - def after_run(self, run_context, run_values): - global_step = run_context.session.run(self._global_step_tensor) - if self._timer.should_trigger_for_step(global_step): - self._timer.update_last_triggered_step(global_step) - logging.info("Triggering checkpoint. %s", global_step) - if self._save(run_context.session, global_step): - run_context.request_stop() - - def end(self, session): - if self._save_thread: - logging.info("Waiting for any pending checkpoints to finish.") - self._save_thread.join() - if self._write_graph_thread: - logging.info("Waiting for any pending write_graph to finish.") - self._write_graph_thread.join() - - last_step = session.run(self._global_step_tensor) - - if self._last_checkpoint_step != last_step: - self._save(session, last_step, asynchronous=False) - - for l in self._listeners: - l.end(session, last_step) - - def _save(self, session, step, asynchronous=True): - """Saves the latest checkpoint, returns should_stop.""" - - # Skip saving on step 0 - if step == 0: - return - - def _save_fn(): - """Run the saver process.""" - logging.info("Saving checkpoints for %d into %s.", step, self._save_path) - - start_time = time.time() - for l in self._listeners: - l.before_save(session, step) - - self._get_saver().save(session, self._save_path, global_step=step) - self._summary_writer.add_session_log( - SessionLog( - status=SessionLog.CHECKPOINT, checkpoint_path=self._save_path), - step) - - for l in self._listeners: - l.after_save(session, step) - - end_time = time.time() - logging.info("Checkpoint actual writing time: (%.3f sec)", - end_time - start_time) - logging.info("Checkpoint finished for %d into %s.", step, self._save_path) - - if not asynchronous: - self._last_checkpoint_step = step - _save_fn() - return - - if self._save_thread is not None: - self._save_thread.join(timeout=0.1) - if self._save_thread.is_alive(): - logging.info("Saver thread still in progress, skipping checkpoint.") - return - - self._last_checkpoint_step = step - self._save_thread = threading.Thread(target=_save_fn) - self._save_thread.start() - - def _get_saver(self): - if self._saver is not None: - return self._saver - elif self._scaffold is not None: - return self._scaffold.saver - - # Get saver from the SAVERS collection if present. - collection_key = ops.GraphKeys.SAVERS - savers = ops.get_collection(collection_key) - if not savers: - raise RuntimeError( - "No items in collection {}. Please add a saver to the collection " - "or provide a saver or scaffold.".format(collection_key)) - elif len(savers) > 1: - raise RuntimeError( - "More than one item in collection {}. " - "Please indicate which one to use by passing it to the constructor." - .format(collection_key)) - - self._saver = savers[0] - return savers[0] +# pylint: disable=wildcard-import,unused-import +from tensorflow.python.tpu.async_checkpoint import * +# pylint: enable=wildcard-import,unused-import diff --git a/tensorflow/contrib/tpu/python/tpu/bfloat16.py b/tensorflow/contrib/tpu/python/tpu/bfloat16.py index fa74f651aa..f3d392a8da 100644 --- a/tensorflow/contrib/tpu/python/tpu/bfloat16.py +++ b/tensorflow/contrib/tpu/python/tpu/bfloat16.py @@ -1,77 +1,23 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 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 +# 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. -# ============================================================================= - -"""Helper context for running models with bfloat16.""" +# ============================================================================== +"""Stub file to maintain backwards compatibility.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.python.framework import dtypes -from tensorflow.python.ops import math_ops -from tensorflow.python.ops import variable_scope -from tensorflow.python.util import tf_contextlib - - -def _get_custom_getter(): - """Returns a custom getter that this class's methods must be called under. - - All methods of this class must be called under a variable scope that was - passed this custom getter. Example: - - ```python - network = ConvNetBuilder(...) - with tf.variable_scope('cg', custom_getter=network.get_custom_getter()): - network.conv(...) - # Call more methods of network here - ``` - - Currently, this custom getter only does anything if self.use_tf_layers is - True. In that case, it causes variables to be stored as dtype - self.variable_type, then casted to the requested dtype, instead of directly - storing the variable as the requested dtype. - """ - - def inner_custom_getter(getter, *args, **kwargs): - """Custom getter that forces variables to have type self.variable_type.""" - cast_to_bfloat16 = False - requested_dtype = kwargs['dtype'] - if requested_dtype == dtypes.bfloat16: - # Only change the variable dtype if doing so does not decrease variable - # precision. - kwargs['dtype'] = dtypes.float32 - cast_to_bfloat16 = True - var = getter(*args, **kwargs) - # This if statement is needed to guard the cast, because batch norm - # assigns directly to the return value of this custom getter. The cast - # makes the return value not a variable so it cannot be assigned. Batch - # norm variables are always in fp32 so this if statement is never - # triggered for them. - if cast_to_bfloat16: - var = math_ops.cast(var, dtypes.bfloat16) - return var - - return inner_custom_getter - - -@tf_contextlib.contextmanager -def bfloat16_scope(): - """Scope class for bfloat16 variables so that the model uses custom getter. - - This enables variables to be read as bfloat16 type when using get_variable. - """ - with variable_scope.variable_scope( - '', custom_getter=_get_custom_getter()) as varscope: - yield varscope +# pylint: disable=wildcard-import,unused-import +from tensorflow.python.tpu.bfloat16 import * +# pylint: enable=wildcard-import,unused-import diff --git a/tensorflow/contrib/tpu/python/tpu/datasets.py b/tensorflow/contrib/tpu/python/tpu/datasets.py index bc0cd41d21..c20aac7e36 100644 --- a/tensorflow/contrib/tpu/python/tpu/datasets.py +++ b/tensorflow/contrib/tpu/python/tpu/datasets.py @@ -1,191 +1,23 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 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 +# 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 of Cloud TPU helper functions for data loading.""" +# ============================================================================== +"""Stub file to maintain backwards compatibility.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.python.data.experimental.ops import batching -from tensorflow.python.data.experimental.ops import interleave_ops -from tensorflow.python.data.ops import dataset_ops -from tensorflow.python.data.ops import iterator_ops -from tensorflow.python.data.ops import readers -from tensorflow.python.framework import dtypes -from tensorflow.python.framework import function -from tensorflow.python.framework import ops -from tensorflow.python.ops import functional_ops - - -def _TextLineDataset(filename): - buffer_size = 8 * 1024 * 1024 # 8 MiB per file - dataset = readers.TextLineDataset(filename, buffer_size=buffer_size) - return dataset - - -def _TFRecordDataset(filename): - buffer_size = 8 * 1024 * 1024 # 8 MiB per file - dataset = readers.TFRecordDataset(filename, buffer_size=buffer_size) - return dataset - - -_FILETYPE_MAP = { - 'tfrecord': _TFRecordDataset, - 'textline': _TextLineDataset, - 'text': _TextLineDataset, -} - - -def StreamingFilesDataset(files, - filetype=None, - file_reader_job=None, - worker_job=None, - num_epochs=None, - filename_shuffle_buffer_size=None, - num_parallel_reads=None, - batch_transfer_size=None, - sloppy=None): - """StreamingFilesDataset constructs a dataset to stream from workers (GCE VM). - - Because Cloud TPUs are allocated over the network, a Cloud TPU cannot read - files local to your GCE VM. In order to train using files stored on your local - VM (e.g. on local SSD for extreme performance), use the StreamingFilesDataset - helper to generate a dataset to feed your Cloud TPU with files from your GCE - VM. - - The resulting dataset may return an OutOfRangeError if there are no files - found as a result of the fileglob expansion. - - Note: StreamingFilesDataset assumes that the session is using a - TPUClusterResolver and has therefore a worker and a coordinator job. File - loading will be done on the coordinator job. - - Args: - files: A string glob to match files, or a `tf.data.Dataset` generating file - names. - filetype: A string (one of 'tfrecord', or 'textline') or a single-argument - TensorFlow function that when given a filename returns a dataset. - file_reader_job: An optional string that corresponds to the job that should - perform the file reads. - worker_job: An optional string that corresponds to the job that should - process the tensors (i.e. your GPU or TPU worker). - num_epochs: The number of epochs through the training set that should be - generated. By default, it will repeat infinitely. - filename_shuffle_buffer_size: An optional integer whose value controls the - shuffling of the file names. If you would like to read from the files in - the same order, set to 0 or False. - num_parallel_reads: An optional integer controlling the number of files to - read from concurrently. (Set to 1 for no parallelism.) - batch_transfer_size: An optional integer controlling the batching used to - amortize the remote function invocation overhead. Set to a very large - number to increase throughput. Set to a very small number to reduce memory - consumption. Set to False to skip batching. - sloppy: (Optional.) If `False`, read input data while maintaining a - deterministic order. (This may have significant performance impacts.) - sloppy defaults to: True. - Returns: - A `tf.data.Dataset` with an infinite stream of elements generated by a - parallel interleaving of the set of files matched (or generated) by `files` - with a type is the output of the dataset specified by `filetype`. - - Raises: - ValueError: if any argument is not of the expected type. - """ - if filetype is None: - filetype = 'tfrecord' - - if isinstance(filetype, str): - if filetype not in _FILETYPE_MAP: - raise ValueError('Unexpected filetype: %s' % filetype) - reader_fn = _FILETYPE_MAP[filetype] - elif callable(filetype): - reader_fn = filetype - else: - raise ValueError('filetype should be a string or a callable') - - file_reader_job = file_reader_job or 'coordinator' - - worker_job = worker_job or 'worker' - - if filename_shuffle_buffer_size is None: - filename_shuffle_buffer_size = 4096 - - num_parallel_reads = num_parallel_reads or 8 - - if batch_transfer_size is None: - batch_transfer_size = 256 - - if sloppy is None: - sloppy = True - - with ops.device('/job:%s' % file_reader_job): - if isinstance(files, str): - source_dataset = dataset_ops.Dataset.list_files(files) - elif isinstance(files, dataset_ops.DatasetV2): - source_dataset = files - else: - raise ValueError('files was not a string or a dataset: %s' % files) - - if filename_shuffle_buffer_size: - source_dataset = source_dataset.shuffle( - buffer_size=filename_shuffle_buffer_size) - - source_dataset = source_dataset.apply( - interleave_ops.parallel_interleave( - reader_fn, cycle_length=num_parallel_reads, sloppy=sloppy)) - - source_dataset = source_dataset.repeat(num_epochs) - - if batch_transfer_size: - source_dataset = source_dataset.batch(batch_transfer_size) - - source_dataset = source_dataset.prefetch(1) - - source_iterator = dataset_ops.make_one_shot_iterator(source_dataset) - source_handle = source_iterator.string_handle() - - @function.Defun(dtypes.string) - def LoadingFunc(h): - remote_iterator = iterator_ops.Iterator.from_string_handle( - h, source_dataset.output_types, source_dataset.output_shapes) - return remote_iterator.get_next() - - def MapFn(unused_input): - if isinstance(source_dataset.output_types, dtypes.DType): - output_types = [source_dataset.output_types] - elif isinstance(source_dataset.output_types, (list, tuple)): - output_types = source_dataset.output_types - else: - raise ValueError('source dataset has invalid output types') - remote_calls = functional_ops.remote_call( - args=[source_handle], - Tout=output_types, - f=LoadingFunc, - target='/job:%s/replica:0/task:0/cpu:0' % file_reader_job) - if len(remote_calls) == 1: - return remote_calls[0] - else: - return remote_calls - - with ops.device('/job:%s' % worker_job): - output_dataset = dataset_ops.Dataset.range(2).repeat().map( - MapFn, num_parallel_calls=4 if sloppy else None) - output_dataset = output_dataset.prefetch(1) - - if batch_transfer_size: - # Undo the batching used during the transfer. - output_dataset = output_dataset.apply(batching.unbatch()).prefetch(1) - - return output_dataset +# pylint: disable=wildcard-import,unused-import +from tensorflow.python.tpu.datasets import * +# pylint: enable=wildcard-import,unused-import diff --git a/tensorflow/contrib/tpu/python/tpu/device_assignment.py b/tensorflow/contrib/tpu/python/tpu/device_assignment.py index 112c6bb42d..05dffef3a1 100644 --- a/tensorflow/contrib/tpu/python/tpu/device_assignment.py +++ b/tensorflow/contrib/tpu/python/tpu/device_assignment.py @@ -1,313 +1,23 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 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 +# 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 of TPU helper functions.""" +# ============================================================================== +"""Stub file to maintain backwards compatibility.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function -import math -import numpy as np -from six.moves import xrange # pylint: disable=redefined-builtin - -from tensorflow.contrib.tpu.python.tpu.topology import Topology - - -SINGLE_CORE_ASSIGNMENT = [[[0, 0, 0]]] - - -def _compute_task_and_cores_to_replicas(core_assignment, topology): - """Computes a nested dict which maps task and logical core to replicas.""" - task_and_cores_to_replicas = {} - for replica in xrange(core_assignment.shape[0]): - for logical_core in xrange(core_assignment.shape[1]): - coordinates = core_assignment[replica, logical_core, :] - task_id = topology.task_ordinal_at_coordinates(coordinates) - if task_id not in task_and_cores_to_replicas: - task_and_cores_to_replicas[task_id] = {} - if logical_core not in task_and_cores_to_replicas[task_id]: - task_and_cores_to_replicas[task_id][logical_core] = set() - - task_and_cores_to_replicas[task_id][logical_core].add(replica) - - task_to_sorted_replica_id = {} - - for task, core_to_replicas in task_and_cores_to_replicas.items(): - core_to_sorted_replicas = {} - for core, replicas in core_to_replicas.items(): - core_to_sorted_replicas[core] = sorted(replicas) - - task_to_sorted_replica_id[task] = core_to_sorted_replicas - return task_to_sorted_replica_id - - -class DeviceAssignment(object): - """Mapping from logical cores in a computation to the physical TPU topology. - - Prefer to use the `device_assignment()` helper to construct a - `DeviceAssignment`; it is easier if less flexible than constructing a - `DeviceAssignment` directly. - """ - - def __init__(self, topology, core_assignment): - """Constructs a `DeviceAssignment` object. - - Args: - topology: A `Topology` object that describes the physical TPU topology. - core_assignment: A logical to physical core mapping, represented as a - rank 3 numpy array. See the description of the `core_assignment` - property for more details. - - Raises: - ValueError: If `topology` is not `Topology` object. - ValueError: If `core_assignment` is not a rank 3 numpy array. - """ - if not isinstance(topology, Topology): - raise ValueError("topology must be a Topology object, got {}".format( - type(topology))) - core_assignment = np.asarray(core_assignment, dtype=np.int32) - - self._topology = topology - - if core_assignment.ndim != 3: - raise ValueError("core_assignment must be a rank 3 numpy array, " - "got shape {}".format(core_assignment.shape)) - - self._num_replicas = core_assignment.shape[0] - self._num_cores_per_replica = core_assignment.shape[1] - - if core_assignment.shape[-1] != topology.mesh_rank: - raise ValueError( - "minor dimension of core_assignment must have size equal to topology " - "rank ({}), got shape {}".format(topology.mesh_rank, - core_assignment.shape)) - - self._core_assignment = core_assignment - self._task_and_cores_to_replicas = _compute_task_and_cores_to_replicas( - self._core_assignment, topology) - - @property - def topology(self): - """A `Topology` that describes the TPU topology.""" - return self._topology - - @property - def num_cores_per_replica(self): - """The number of cores per replica.""" - return self._num_cores_per_replica - - @property - def num_replicas(self): - """The number of replicas of the computation.""" - return self._num_replicas - - @property - def core_assignment(self): - """The logical to physical core mapping. - - Returns: - An integer numpy array of rank 3, with shape - `[num_replicas, num_cores_per_replica, topology_rank]`. Maps - (replica, logical core) pairs to physical topology coordinates. - """ - return self._core_assignment - - def coordinates(self, replica, logical_core): - """Returns the physical topology coordinates of a logical core.""" - return tuple(self.core_assignment[replica, logical_core, :]) - - def lookup_replicas(self, task_id, logical_core): - """Lookup replica ids by task number and logical core. - - Args: - task_id: TensorFlow task number. - logical_core: An integer, identifying a logical core. - Returns: - A sorted list of the replicas that are attached to that task and - logical_core. - Raises: - ValueError: If no replica exists in the task which contains the logical - core. - """ - try: - return self._task_and_cores_to_replicas[task_id][logical_core] - except KeyError: - raise ValueError( - "Can not find any replica in task: {} contains logical_core: {} ". - format(task_id, logical_core)) - - def tpu_ordinal(self, replica=0, logical_core=0): - """Returns the ordinal of the TPU device assigned to a logical core.""" - coordinates = self.coordinates(replica, logical_core) - return self._topology.tpu_device_ordinal_at_coordinates(coordinates) - - def host_device(self, replica=0, logical_core=0, job=None): - """Returns the CPU device attached to a logical core.""" - coordinates = self.coordinates(replica, logical_core) - return self._topology.cpu_device_name_at_coordinates(coordinates, job=job) - - def tpu_device(self, replica=0, logical_core=0, job=None): - """Returns the name of the TPU device assigned to a logical core.""" - coordinates = self.coordinates(replica, logical_core) - return self._topology.tpu_device_name_at_coordinates(coordinates, job=job) - - -def device_assignment(topology, - computation_shape=None, - computation_stride=None, - num_replicas=1): - """Computes a device_assignment of a computation across a TPU topology. - - Attempts to choose a compact grid of cores for locality. - - Returns a `DeviceAssignment` that describes the cores in the topology assigned - to each core of each replica. - - `computation_shape` and `computation_stride` values should be powers of 2 for - optimal packing. - - Args: - topology: A `Topology` object that describes the TPU cluster topology. - To obtain a TPU topology, evaluate the `Tensor` returned by - `initialize_system` using `Session.run`. Either a serialized - `TopologyProto` or a `Topology` object may be passed. Note: you must - evaluate the `Tensor` first; you cannot pass an unevaluated `Tensor` here. - computation_shape: A rank 1 int32 numpy array with size equal to the - topology rank, describing the shape of the computation's block of cores. - If None, the `computation_shape` is `[1] * topology_rank`. - computation_stride: A rank 1 int32 numpy array of size `topology_rank`, - describing the inter-core spacing of the `computation_shape` cores in the - TPU topology. If None, the `computation_stride` is `[1] * topology_rank`. - num_replicas: The number of computation replicas to run. The replicas will - be packed into the free spaces of the topology. - - Returns: - A DeviceAssignment object, which describes the mapping between the logical - cores in each computation replica and the physical cores in the TPU - topology. - - Raises: - ValueError: If `topology` is not a valid `Topology` object. - ValueError: If `computation_shape` or `computation_stride` are not 1D int32 - numpy arrays with shape [3] where all values are positive. - ValueError: If computation's replicas cannot fit into the TPU topology. - """ - # Deserialize the Topology proto, if it is a string. - if isinstance(topology, bytes): - topology = Topology(serialized=topology) - - if not isinstance(topology, Topology): - raise ValueError("`topology` is not a Topology object; got {}".format( - type(topology))) - - topology_rank = len(topology.mesh_shape) - mesh_shape = topology.mesh_shape - if computation_shape is None: - computation_shape = np.array([1] * topology_rank, dtype=np.int32) - else: - computation_shape = np.asarray(computation_shape, dtype=np.int32) - - if computation_stride is None: - computation_stride = np.array([1] * topology_rank, dtype=np.int32) - else: - computation_stride = np.asarray(computation_stride, dtype=np.int32) - - if computation_shape.shape != (topology_rank,): - raise ValueError("computation_shape must have shape [{}]; got {}".format( - topology_rank, computation_shape.shape)) - if computation_stride.shape != (topology_rank,): - raise ValueError("computation_stride must have shape [{}]; got {}".format( - topology_rank, computation_stride.shape)) - - if any(computation_shape < 1): - raise ValueError( - "computation_shape must be positive; got computation_shape={}".format( - computation_shape)) - if any(computation_stride < 1): - raise ValueError( - "computation_stride must be positive; got computation_stride={}".format( - computation_stride)) - - # Computes the physical size of one computation instance. - computation_footprint = computation_shape * computation_stride - if any(computation_footprint > mesh_shape): - raise ValueError( - "computation footprint {} does not fit in TPU topology shape {}".format( - computation_footprint, mesh_shape)) - - # Computes how many copies of the computation footprint fit in the mesh. - block_counts = mesh_shape // computation_footprint - - replica_counts = block_counts * computation_stride - max_replicas = np.prod(replica_counts) - if num_replicas > max_replicas: - raise ValueError( - "requested {} replicas but only {} replicas with shape {} and " - "computation_stride {} fit in a TPU mesh of shape {}".format( - num_replicas, max_replicas, computation_shape, computation_stride, - mesh_shape)) - - def ceil_of_ratio(n, m): - return (n + m - 1) // m - - replica_shape = [0] * topology_rank - if num_replicas > 0: - remaining_replicas = num_replicas - remaining_dims = topology_rank - - # Choose dimensions as close to an equal cube as possible, in order of - # increasing dimension size. By visiting dimensions in increasing size, we - # assign the most constrained dimension first, so we won't make infeasible - # choices. - # - # As a secondary sort order, visit the dimensions in reverse order. This - # means we try to use both cores on the same chip in preference to two cores - # on different chips. - for x, ni in sorted(((x, -i) for (i, x) in enumerate(replica_counts))): - i = -ni - target_size = int(math.ceil(remaining_replicas**(1.0 / remaining_dims))) - replica_shape[i] = min(target_size, x) - remaining_replicas = ceil_of_ratio(remaining_replicas, replica_shape[i]) - remaining_dims -= 1 - - assert remaining_replicas == 1 and remaining_dims == 0 - - # Assigns an offset to each replica such that no two replicas overlap. - replica_offsets = np.full([num_replicas, topology_rank], -1, dtype=np.int32) - for replica in xrange(num_replicas): - # Chooses a replica number in each axis. - t = replica - pos = [] - for dim in replica_shape[::-1]: - pos.append(t % dim) - t //= dim - replica_pos = np.array(pos[::-1], dtype=np.int32) - - # Determines where that replica starts in each axis. - outer = replica_pos // computation_stride - inner = replica_pos % computation_stride - replica_offsets[replica, :] = outer * computation_footprint + inner - - # Computes a complete logical core -> physical core mapping for each replica. - indices = [ - np.arange(0, computation_shape[i] * computation_stride[i], - computation_stride[i]) for i in xrange(topology_rank) - ] - indices = np.concatenate( - [i[..., np.newaxis] for i in np.meshgrid(*indices, indexing="ij")], - axis=-1) - indices = indices.reshape((-1, topology_rank)) - assignment = indices + replica_offsets[:, np.newaxis, :] - return DeviceAssignment(topology, core_assignment=assignment) +# pylint: disable=wildcard-import,unused-import,redefined-builtin +from tensorflow.python.tpu.device_assignment import * +# pylint: enable=wildcard-import,unused-import,redefined-builtin diff --git a/tensorflow/contrib/tpu/python/tpu/error_handling.py b/tensorflow/contrib/tpu/python/tpu/error_handling.py index 52e1ea4237..1b1328b407 100644 --- a/tensorflow/contrib/tpu/python/tpu/error_handling.py +++ b/tensorflow/contrib/tpu/python/tpu/error_handling.py @@ -1,132 +1,23 @@ -# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 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 +# 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. -# =================================================================== -"""ErrorRendezvous handler for collecting errors from multiple threads.""" +# ============================================================================== +"""Stub file to maintain backwards compatibility.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function -import contextlib -import sys -import threading -import time - -import six - -from tensorflow.python.framework import errors -from tensorflow.python.platform import tf_logging as logging - -_UNINTERESTING_ERRORS = (errors.CancelledError,) - - -class ErrorRendezvous(object): - """Resolve errors from multiple threads during TPU execution. - - TPU errors can occur on the infeed or outfeed threads as well as the main - training thread. - - Depending on which thread "wins" and receives the session error first, we may - end up showing users a confusing and non-actionable error message (session - cancelled) instead of a root cause (e.g. a bad filename). - - The rendezvous object provides a location to capture these errors until all - threads terminate. At that point we can choose the most informative error - to report. - """ - - def __init__(self, num_sources): - # string -> (message, traceback) - self._errors = {} - self._num_sources = num_sources - self._session_cancel_timer = None - - def record_error(self, source, exc_info, session=None): - """Report an exception from the given source. - - If a session is passed, a timer will be registered to close it after a few - seconds. This is necessary to ensure the main training loop does not hang - if an infeed/oufeed error occurs. We sleep a few seconds to allow a more - interesting error from another thread to propagate. - - Args: - source: string, source of the error - exc_info: Output from `sys.exc_info` (type, value, traceback) - session: Session to close after delay. - """ - _, value, _ = exc_info - self._errors[source] = exc_info - logging.info('Error recorded from %s: %s', source, value) - - if session is not None and self._session_cancel_timer is None: - - def _cancel_session(): - time.sleep(5) - try: - session.close() - except: # pylint: disable=bare-except - pass - - self._session_cancel_timer = threading.Thread(target=_cancel_session,) - self._session_cancel_timer.daemon = True - self._session_cancel_timer.start() - - def record_done(self, source): - """Mark execution source `source` as done. - - If an error was originally reported from `source` it is left intact. - - Args: - source: `str`, source being recorded - """ - logging.info('%s marked as finished', source) - if source not in self._errors: - self._errors[source] = None - - @contextlib.contextmanager - def catch_errors(self, source, session=None): - """Context manager to report any errors within a block.""" - try: - yield - except Exception: # pylint: disable=broad-except - self.record_error(source, sys.exc_info(), session) - - def raise_errors(self, timeout_sec=0): - """Wait for up to `timeout` seconds for all error sources to finish. - - Preferentially raise "interesting" errors (errors not in the - _UNINTERESTING_ERRORS) set. - - Args: - timeout_sec: Seconds to wait for other error sources. - """ - for _ in range(timeout_sec): - if len(self._errors) == self._num_sources: - break - time.sleep(1) - - kept_errors = [(k, v) for (k, v) in self._errors.items() if v is not None] - - # First check for any interesting errors, then fall back on the session - # cancelled errors etc. - for k, (typ, value, traceback) in kept_errors: - if isinstance(value, _UNINTERESTING_ERRORS): - continue - else: - logging.warn('Reraising captured error') - six.reraise(typ, value, traceback) - - for k, (typ, value, traceback) in kept_errors: - logging.warn('Reraising captured error') - six.reraise(typ, value, traceback) +# pylint: disable=wildcard-import,unused-import +from tensorflow.python.tpu.error_handling import * +# pylint: enable=wildcard-import,unused-import diff --git a/tensorflow/contrib/tpu/python/tpu/feature_column.py b/tensorflow/contrib/tpu/python/tpu/feature_column.py index e80d0de705..ded75e975b 100644 --- a/tensorflow/contrib/tpu/python/tpu/feature_column.py +++ b/tensorflow/contrib/tpu/python/tpu/feature_column.py @@ -1,435 +1,30 @@ -# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 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 +# 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. -# =================================================================== -"""TPU Feature Column Library.""" +# ============================================================================== +"""Stub file to maintain backwards compatibility.""" + from __future__ import absolute_import from __future__ import division from __future__ import print_function -import math - -from tensorflow.contrib.tpu.python.tpu import tpu -from tensorflow.contrib.tpu.python.tpu import tpu_function -from tensorflow.python.feature_column import feature_column as fc -from tensorflow.python.feature_column import feature_column_lib as fc_lib -from tensorflow.python.framework import ops -from tensorflow.python.ops import init_ops -from tensorflow.python.ops import variable_scope -# pylint: disable=protected-access - - -_TPU_FC_TO_SCOPE = '_tpu_feature_column_scope' -_SUPPORTED_CATEGORICAL_COLUMNS = (fc._IdentityCategoricalColumn, - fc._VocabularyFileCategoricalColumn, - fc._VocabularyListCategoricalColumn, - fc._WeightedCategoricalColumn, - fc_lib.IdentityCategoricalColumn, - fc_lib.VocabularyFileCategoricalColumn, - fc_lib.VocabularyListCategoricalColumn, - fc_lib.WeightedCategoricalColumn) - - -def embedding_column(categorical_column, - dimension, - combiner='mean', - initializer=None): - """TPU embedding_column for `tf.feature_column.embedding_column`. - - Note that the interface for TPU embedding_column is different from the non-TPU - version. The following args available for the non-TPU version are NOT - supported: ckpt_to_load_from, tensor_name_in_ckp, max_norm and trainable. - - Args: - categorical_column: A categorical_column returned from - categorical_column_with_identity, weighted_categorical_column, - categorical_column_with_vocabulary_list or - categorical_column_with_vocabulary_file. - dimension: An integer specifying dimension of the embedding, must be > 0. - combiner: A string specifying how to reduce if there are multiple entries - in a single row. For more information, see - `tf.feature_column.embedding_column`. - initializer: A variable initializer function to be used in embedding - variable initialization. If not specified, defaults to - `tf.truncated_normal_initializer` with mean `0.0` and standard deviation - `1/sqrt(dimension)`. - - Returns: - A _TPUEmbeddingColumn. - - Raises: - ValueError: if `dimension` not > 0. - ValueError: if `initializer` is specified but not callable. - """ - if not isinstance(categorical_column, _SUPPORTED_CATEGORICAL_COLUMNS): - raise TypeError( - 'categorical_column for tpu ' - ' embedding_column must be type %s, got %s.' % (' or '.join([ - cc.__name__ for cc in _SUPPORTED_CATEGORICAL_COLUMNS - ]), type(categorical_column))) - if (dimension is None) or (dimension < 1): - raise ValueError('Invalid dimension {}.'.format(dimension)) - - if (initializer is not None) and (not callable(initializer)): - raise ValueError('initializer must be callable if specified. ' - 'Embedding of column_name: {}'.format( - categorical_column.name)) - if initializer is None: - initializer = init_ops.truncated_normal_initializer( - mean=0.0, stddev=1 / math.sqrt(dimension)) - - embedding_shape = categorical_column._num_buckets, dimension # pylint: disable=protected-access - - def _creator(weight_collections, scope): - embedding_column_layer = fc._EmbeddingColumnLayer( - embedding_shape=embedding_shape, - initializer=initializer, - weight_collections=weight_collections, - trainable=True, - name='embedding_column_layer') - return embedding_column_layer(None, scope=scope) # pylint: disable=not-callable - - column = _TPUEmbeddingColumn( - categorical_column=categorical_column, - dimension=dimension, - combiner=combiner, - layer_creator=_creator, - ckpt_to_load_from=None, - tensor_name_in_ckpt=None, - max_norm=None, - trainable=True) - # For Embedding column, the initializer is hidden inside the creator Fn, which - # is not accessiable later. So, we attach it to a speicial field. Also note - # that non-TPU Embedding column and non-TPU shared Embedding column handle the - # initializer differently. See shared_embedding_columns for details. - column._tpu_initializer = initializer - return column - - -def shared_embedding_columns(categorical_columns, - dimension, - combiner='mean', - initializer=None, - shared_embedding_collection_name=None): - """List of dense columns that convert from sparse, categorical input.""" - for categorical_column in categorical_columns: - if not isinstance(categorical_column, _SUPPORTED_CATEGORICAL_COLUMNS): - raise TypeError( - 'categorical_column for tpu ' - ' shared_embedding_columns must be type %s, got %s.' % (' or '.join([ - cc.__name__ for cc in _SUPPORTED_CATEGORICAL_COLUMNS - ]), type(categorical_column))) - columns = fc_lib.shared_embedding_columns( - categorical_columns, - dimension, - combiner=combiner, - initializer=initializer, - shared_embedding_collection_name=shared_embedding_collection_name, - ckpt_to_load_from=None, - tensor_name_in_ckpt=None, - max_norm=None, - trainable=True) - - # Use the initializer and shared_embedding_collection_name to create TPU - # version - initializer = columns[0].initializer - shared_embedding_collection_name = columns[0].shared_embedding_collection_name - tpu_columns = [] - - # Create the state (_SharedEmbeddingColumnLayer) here. - for categorical_column in categorical_columns: - column = _TPUSharedEmbeddingColumn( - categorical_column=categorical_column, - dimension=dimension, - combiner=combiner, - initializer=initializer, - shared_embedding_collection_name=shared_embedding_collection_name, - ckpt_to_load_from=None, - tensor_name_in_ckpt=None, - max_norm=None, - trainable=True) - tpu_columns.append(column) - - return tpu_columns - - -class _TPUBaseEmbeddingColumn(object): - """Base class for TPU Embedding Column.""" - - def __init__(self, categorical_column): - self._tpu_categorical_column = categorical_column - - def get_combiner(self): - """Returns the embedding combiner.""" - raise NotImplementedError('not implemented') - - def get_embedding_table_size(self): - """Returns the embedding table size, tuple of vocab size and dimension.""" - raise NotImplementedError('not implemented') - - def get_feature_key_name(self): - """Returns the feature key name in the features dict.""" - raise NotImplementedError('not impl') - - def get_weight_key_name(self): - """Return the key name for weights.""" - raise NotImplementedError('not impl') - - def get_embedding_var_name(self): - """Returns the embedding variable name. - - Feature key name and embedding variable name are usually one-to-one mapping. - But for shared embedding columns, it is many-to-one mapping. - """ - raise NotImplementedError('not impl') - - def get_initializer(self): - """Returns the initializer.""" - raise NotImplementedError('not impl') - - def is_categorical_column_weighted(self): - """Check if the categorical column of the embedding column is weighted.""" - raise NotImplementedError('not impl') - - -class _TPUEmbeddingColumn(_TPUBaseEmbeddingColumn, fc._EmbeddingColumn): - """Core Embedding Column.""" - - def __new__(cls, - categorical_column, - dimension, - combiner='mean', - layer_creator=None, - ckpt_to_load_from=None, - tensor_name_in_ckpt=None, - max_norm=None, - trainable=True): - # Note, args ckpt_to_load_from, tensor_name_in_ckpt, max_norm and trainable - # are not supported on TPU. They are solely for matching the signature of - # __new__ of parent class fc._EmbeddingColumn. - return fc._EmbeddingColumn.__new__( - cls, - categorical_column, - dimension, - combiner=combiner, - layer_creator=layer_creator, - ckpt_to_load_from=ckpt_to_load_from, - tensor_name_in_ckpt=tensor_name_in_ckpt, - max_norm=max_norm, - trainable=trainable) - - def __init__(self, - categorical_column, - dimension, - combiner='mean', - layer_creator=None, - ckpt_to_load_from=None, - tensor_name_in_ckpt=None, - max_norm=None, - trainable=True): - _TPUBaseEmbeddingColumn.__init__(self, categorical_column) - self._key = None - - def get_combiner(self): - return self.combiner - - def get_embedding_table_size(self): - """Returns num_ids and width.""" - return (self.categorical_column._num_buckets, self.dimension) - - def get_feature_key_name(self): - """get_feature_key_name.""" - if self.is_categorical_column_weighted(): - return self.categorical_column.categorical_column.name - return self.categorical_column.name - - def get_weight_key_name(self): - """get_weight_key_name.""" - if self.is_categorical_column_weighted(): - return self.categorical_column.weight_feature_key - return None - - def get_embedding_var_name(self): - """get_embedding_var_name.""" - return self.categorical_column.name - - def get_initializer(self): - return self._tpu_initializer - - def is_categorical_column_weighted(self): - """Check if the categorical column of the embedding column is weighted.""" - if isinstance( - self.categorical_column, - ( - fc._WeightedCategoricalColumn, # pylint: disable=protected-access - fc_lib.WeightedCategoricalColumn)): - return True - return False - - def _get_dense_tensor(self, inputs, weight_collections=None, trainable=None): - if tpu.under_tpu_inference_context(): - def host_computation(): - return fc._EmbeddingColumn._get_dense_tensor( - self, inputs, weight_collections, trainable) - return tpu.outside_compilation(host_computation) - - if _is_running_on_cpu(): - return fc._EmbeddingColumn._get_dense_tensor( - self, inputs, weight_collections, trainable) - - # TPU mode - # Get the embeddings from the LazyBuilder. - tensor = inputs.get(self.get_feature_key_name()) - - # Add to collection for _create_tpu_embedding_variables_and_ops - _record_variable_scope_and_name(self.get_embedding_var_name(), - 'embedding_weights') - - return tensor - - -class _TPUSharedEmbeddingColumn(_TPUBaseEmbeddingColumn, - fc._SharedEmbeddingColumn): - """Core Shared Embedding Column.""" - - def __new__(cls, - categorical_column, - dimension, - combiner='mean', - initializer=None, - shared_embedding_collection_name=None, - ckpt_to_load_from=None, - tensor_name_in_ckpt=None, - max_norm=None, - trainable=True): - return fc._SharedEmbeddingColumn.__new__( - cls, - categorical_column, - dimension, - combiner=combiner, - initializer=initializer, - shared_embedding_collection_name=shared_embedding_collection_name, - ckpt_to_load_from=ckpt_to_load_from, - tensor_name_in_ckpt=tensor_name_in_ckpt, - max_norm=max_norm, - trainable=trainable) - - def __init__(self, - categorical_column, - dimension, - combiner='mean', - initializer=None, - shared_embedding_collection_name=None, - ckpt_to_load_from=None, - tensor_name_in_ckpt=None, - max_norm=None, - trainable=True): - - _TPUBaseEmbeddingColumn.__init__(self, categorical_column) - self._key = None - - def get_combiner(self): - return self.combiner - - def get_embedding_table_size(self): - """Returns num_ids and width.""" - return (self.categorical_column._num_buckets, self.dimension) - - def get_feature_key_name(self): - """get_feature_key_name.""" - if self.is_categorical_column_weighted(): - return self.categorical_column.categorical_column.name - return self.categorical_column.name - - def get_weight_key_name(self): - """get_weight_key_name.""" - if self.is_categorical_column_weighted(): - return self.categorical_column.weight_feature_key - return None - - def get_embedding_var_name(self): - """get_embedding_var_name.""" - return self.shared_embedding_collection_name - - def get_initializer(self): - return self.initializer - - def is_categorical_column_weighted(self): - """Check if the categorical column of the embedding column is weighted.""" - if isinstance( - self.categorical_column, - ( - fc._WeightedCategoricalColumn, # pylint: disable=protected-access - fc_lib.WeightedCategoricalColumn)): - return True - return False - - def _get_dense_tensor(self, inputs, weight_collections=None, trainable=None): - if tpu.under_tpu_inference_context(): - def host_computation(): - return fc._SharedEmbeddingColumn._get_dense_tensor( - self, inputs, weight_collections, trainable) - return tpu.outside_compilation(host_computation) - - if _is_running_on_cpu(): - return fc._SharedEmbeddingColumn._get_dense_tensor( - self, inputs, weight_collections, trainable) - - # TPU mode - # Get the embeddings from the LazyBuilder. - tensor = inputs.get(self.get_feature_key_name()) - - # Add to collection for _create_tpu_embedding_variables_and_ops - _record_variable_scope_and_name( - self.get_embedding_var_name(), - 'embedding_weights', - is_shared_embedding=True) - return tensor - - -def _record_variable_scope_and_name(embedding_var_name, - embedding_var_name_in_fc, - is_shared_embedding=False): - """Add embedding variable name and scope to collection.""" - g = ops.get_default_graph() - collection = g.get_collection_ref(_TPU_FC_TO_SCOPE) - if not collection: - collection.append({}) - - var_def_dict = collection[0] - - captured_scope = variable_scope.get_variable_scope() - captured_scope_name = captured_scope.name - - if embedding_var_name in var_def_dict: - if (var_def_dict[embedding_var_name][0] != captured_scope_name - and not is_shared_embedding): - raise ValueError( - 'For embedding var name {}, the variable scope name is different, ' - 'got {}; expected {}'.format(embedding_var_name, - captured_scope_name, - var_def_dict[embedding_var_name][0])) - if var_def_dict[embedding_var_name][1] != embedding_var_name_in_fc: - raise ValueError( - 'For embedding var name {}, the embedding name is different, ' - 'got {}; expected {}'.format(embedding_var_name, - embedding_var_name_in_fc, - var_def_dict[embedding_var_name][1])) - else: - var_def_dict[embedding_var_name] = (captured_scope_name, - embedding_var_name_in_fc) - - -def _is_running_on_cpu(): - """Returns True if the current context is CPU model.""" - return tpu_function.get_tpu_context().number_of_shards is None +# pylint: disable=wildcard-import,unused-import +from tensorflow.python.tpu.feature_column import * +# used by tests +from tensorflow.python.tpu.feature_column import _is_running_on_cpu +from tensorflow.python.tpu.feature_column import _record_variable_scope_and_name +from tensorflow.python.tpu.feature_column import _TPU_FC_TO_SCOPE +from tensorflow.python.tpu.feature_column import _TPUBaseEmbeddingColumn +from tensorflow.python.tpu.feature_column import _TPUEmbeddingColumn +from tensorflow.python.tpu.feature_column import _TPUSharedEmbeddingColumn +# pylint: enable=wildcard-import,unused-import diff --git a/tensorflow/contrib/tpu/python/tpu/functional.py b/tensorflow/contrib/tpu/python/tpu/functional.py index 3d04c64033..9a5759221e 100644 --- a/tensorflow/contrib/tpu/python/tpu/functional.py +++ b/tensorflow/contrib/tpu/python/tpu/functional.py @@ -1,23 +1,23 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 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 +# 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 operations.""" +# ============================================================================== +"""Stub file to maintain backwards compatibility.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib.tpu.python.ops import tpu_ops - -TPUPartitionedCall = tpu_ops.tpu_partitioned_call # pylint: disable=invalid-name +# pylint: disable=wildcard-import,unused-import +from tensorflow.python.tpu.functional import * +# pylint: enable=wildcard-import,unused-import diff --git a/tensorflow/contrib/tpu/python/tpu/session_support.py b/tensorflow/contrib/tpu/python/tpu/session_support.py index 5cb2ca6478..ed8f9525c9 100644 --- a/tensorflow/contrib/tpu/python/tpu/session_support.py +++ b/tensorflow/contrib/tpu/python/tpu/session_support.py @@ -1,438 +1,23 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 The TensorFlow Authors. All Rights Reserved. # -# Licensed under the Apache License, Version 2.0 (the 'License'); +# 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 +# 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, +# 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. -# ====================================== -"""Operations for handling session logging and shutdown notifications.""" +# ============================================================================== +"""Stub file to maintain backwards compatibility.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function -import threading - -import time -from google.protobuf import text_format - -from tensorflow.contrib.tpu.python.ops import tpu_ops -from tensorflow.core.protobuf import config_pb2 -from tensorflow.core.util import event_pb2 -from tensorflow.python.client import session as session_lib -from tensorflow.python.framework import dtypes -from tensorflow.python.framework import errors -from tensorflow.python.framework import ops -from tensorflow.python.ops import array_ops -from tensorflow.python.platform import tf_logging as logging -from tensorflow.python.training import session_run_hook -from tensorflow.python.training import training_util - -_WATCHDOG = None - - -class CoordinatorShutdownException(Exception): - """Raised when the coordinator needs to shutdown.""" - pass - - -def _clone_session(session, graph=None): - return session_lib.Session( - target=session.sess_str, - config=session._config, # pylint: disable=protected-access - graph=graph if graph else session.graph) - - -def _make_heartbeat_op(session, device, request_ph): - """Return a heartbeat op or None if heartbeats are not supported by device.""" - try: - # Test if we can connect in a isolated graph + session - with ops.Graph().as_default(): - with _clone_session(session) as temp_session: - with ops.device(device): - heartbeat_op = tpu_ops.worker_heartbeat('') - options = config_pb2.RunOptions(timeout_in_ms=5000) - temp_session.run(heartbeat_op, options=options) - except errors.InvalidArgumentError as _: - logging.warning('Error running heartbeat on %s', device) - return None - except errors.DeadlineExceededError as _: - logging.warning('Timeout connecting to %s when testing heartbeat', device) - return None - - # If we successfully connected and pinged the worker, go ahead and construct - # the operation. - with ops.device(device): - return tpu_ops.worker_heartbeat(request_ph) - - -class WorkerHeartbeatManager(object): - """Manages the status/heartbeat monitor for a set of workers.""" - - def __init__(self, session, devices, heartbeat_ops, request_placeholder): - """Construct a new WorkerHeartbeatManager. - - (Prefer using `WorkerHeartbeatManager.from_devices` when possible.) - - Args: - session: `tf.Session`, session to use for heartbeat operations. - devices: `list[string]` Set of devices to connect to. - heartbeat_ops: `list[tf.Operation]` Heartbeat operations. - request_placeholder: `tf.Placeholder[String]` Placeholder used to specify - the WorkerHeartbeatRequest protocol buffer. - """ - self._session = session - self._devices = devices - self._ops = heartbeat_ops - self._request_placeholder = request_placeholder - - @staticmethod - def from_devices(session, devices): - """Construct a heartbeat manager for the given devices.""" - if not devices: - logging.error('Trying to create heartbeat manager with no devices?') - - logging.info('Creating heartbeat manager for %s', devices) - request_placeholder = array_ops.placeholder( - name='worker_heartbeat_request', dtype=dtypes.string) - - heartbeat_ops = [] - kept_devices = [] - for device in devices: - heartbeat_op = _make_heartbeat_op(session, device, request_placeholder) - if heartbeat_op is not None: - kept_devices.append(device) - heartbeat_ops.append(heartbeat_op) - else: - logging.warning('Heartbeat support not available for %s', device) - - return WorkerHeartbeatManager(session, kept_devices, heartbeat_ops, - request_placeholder) - - def num_workers(self): - return len(self._devices) - - def configure(self, message): - """Configure heartbeat manager for all devices. - - Args: - message: `event_pb2.WorkerHeartbeatRequest` - Returns: `None` - """ - logging.info('Configuring worker heartbeat: %s', - text_format.MessageToString(message)) - self._session.run(self._ops, - {self._request_placeholder: message.SerializeToString()}) - - def ping(self, request=None, timeout_in_ms=5000): - """Ping all workers, returning the parsed status results.""" - if request is None: - request = event_pb2.WorkerHeartbeatRequest() - - options = config_pb2.RunOptions(timeout_in_ms=timeout_in_ms) - results = self._session.run( - self._ops, - feed_dict={self._request_placeholder: request.SerializeToString()}, - options=options) - parsed_results = [ - event_pb2.WorkerHeartbeatResponse.FromString(res_pb) - for res_pb in results - ] - logging.debug('Ping results: %s', parsed_results) - return parsed_results - - def lame_workers(self): - """Ping all workers, returning manager containing lame workers (or None).""" - ping_results = self.ping() - lame_workers = [] - - for ping_response, device, op in zip(ping_results, self._devices, - self._ops): - if ping_response.health_status != event_pb2.OK: - lame_workers.append((device, op)) - - if not lame_workers: - return None - - bad_devices, bad_ops = zip(*lame_workers) - return WorkerHeartbeatManager(self._session, bad_devices, bad_ops, - self._request_placeholder) - - def __repr__(self): - return 'HeartbeatManager(%s)' % ','.join(self._devices) - - def shutdown(self, timeout_ms=10000): - """Shutdown all workers after `shutdown_timeout_secs`.""" - logging.info('Shutting down %s.', self) - req = event_pb2.WorkerHeartbeatRequest( - watchdog_config=event_pb2.WatchdogConfig(timeout_ms=timeout_ms), - shutdown_mode=event_pb2.WAIT_FOR_COORDINATOR) - self.configure(req) - - # Wait for workers to shutdown. This isn't strictly required - # but it avoids triggering multiple checkpoints with the same lame worker. - logging.info('Waiting %dms for worker shutdown.', timeout_ms) - time.sleep(timeout_ms / 1000) - - -def all_worker_devices(session): - """Return a list of devices for each worker in the system.""" - devices = session.list_devices() - return [ - device.name - for device in devices - if ':CPU:' in device.name and 'coordinator' not in device.name - ] - - -class WatchdogManager(threading.Thread): - """Configures worker watchdog timer and handles periodic pings. - - Usage: - # Ping workers every minute, shutting down workers if they haven't received - # a ping after 1 hour. - watchdog_manager = WatchdogManager( - ping_interval=60, shutdown_timeout=3600 - ) - - # Use as a context manager, resetting watchdog on context exit: - with watchdog_manager: - session.run(...) - - # Or setup globally; watchdog will remain active until program exit. - watchdog_manager.configure_and_run() - """ - - def __init__(self, - session, - devices=None, - ping_interval=60, - shutdown_timeout=3600): - """Initialize a watchdog manager. - - Args: - session: Session connected to worker devices. A cloned session and graph - will be created for managing worker pings. - devices: Set of devices to monitor. If none, all workers will be - monitored. - ping_interval: Time, in seconds, between watchdog pings. - shutdown_timeout: Time, in seconds, before watchdog timeout. - """ - threading.Thread.__init__(self) - self.ping_interval = ping_interval - self.shutdown_timeout = shutdown_timeout - self.daemon = True - self._config = session._config # pylint: disable=protected-access - self._target = session.sess_str - self._running = False - self._devices = devices - - self._graph = None - self._session = None - self._worker_manager = None - - def _reset_manager(self): - """Reset the graph, session and worker manager.""" - self._graph = ops.Graph() - self._session = session_lib.Session( - target=self._target, - graph=self._graph, - config=self._config, - ) - - if self._devices is None: - self._devices = all_worker_devices(self._session) - - with self._graph.as_default(): - self._worker_manager = WorkerHeartbeatManager.from_devices( - self._session, self._devices) - - self._worker_manager.configure( - event_pb2.WorkerHeartbeatRequest( - watchdog_config=event_pb2.WatchdogConfig( - timeout_ms=self.shutdown_timeout * 1000,), - shutdown_mode=event_pb2.WAIT_FOR_COORDINATOR)) - - def configure_and_run(self): - logging.info( - 'Enabling watchdog timer with %d second timeout ' - 'and %d second ping interval.', self.shutdown_timeout, - self.ping_interval) - self._reset_manager() - self._running = True - self.start() - - def stop(self): - logging.info('Stopping worker watchdog.') - self._worker_manager.configure( - event_pb2.WorkerHeartbeatRequest( - watchdog_config=event_pb2.WatchdogConfig(timeout_ms=-1,), - shutdown_mode=event_pb2.NOT_CONFIGURED)) - self._running = False - self.join() - - def __enter__(self): - self.configure_and_run() - - def __exit__(self, exc_type, exc_val, exc_tb): - self.stop() - - def run(self): - # Don't fetch logs or adjust timing: just ping the watchdog. - # - # If we hit an exception, reset our session as it is likely broken. - while self._running: - try: - self._worker_manager.ping(request=None) - time.sleep(self.ping_interval) - except errors.OpError as e: - # Catch any TF errors that occur so we don't stop sending heartbeats - logging.debug('Caught error while sending heartbeat: %s', e) - self._reset_manager() - - -def start_worker_watchdog(session, - devices=None, - ping_interval=60, - shutdown_timeout=3600): - """Start global worker watchdog to shutdown workers on coordinator exit.""" - global _WATCHDOG - if _WATCHDOG is None: - # Ensure we can send a few pings before we timeout! - ping_interval = min(shutdown_timeout / 10., ping_interval) - _WATCHDOG = WatchdogManager(session, devices, ping_interval, - shutdown_timeout) - _WATCHDOG.configure_and_run() - - -class GracefulShutdownHook(session_run_hook.SessionRunHook): - """Session hook that watches for shutdown events. - - If a shutdown is indicated, `saver.save(checkpoint_prefix)` is executed, and a - SystemShutdown exception is raised to terminate the main session. If `saver` - is None the `SAVERS` collection will be read to find a saver. - - `on_shutdown_hooks` is an optional list of functions that should be called - after checkpointing. The function is called with (`run_context`, - `all_workers`, `lame_workers`). - - If `heartbeat_group` is not specified, it will default to all CPU workers - in the system. - """ - - def __init__(self, checkpoint_prefix, saver=None, on_shutdown_hooks=None): - self._saver = saver - self._checkpoint_prefix = checkpoint_prefix - self._on_shutdown_hooks = on_shutdown_hooks if on_shutdown_hooks else [] - - # Worker heartbeats are managed independently of the main training graph. - self._graph = ops.Graph() - self._workers = None - self._session = None - self._heartbeat_supported = False - - def after_create_session(self, training_session, coord): # pylint: disable=unused-argument - # N.B. We have to pull the global step here to avoid it being unavailable - # at checkpoint time; the graph has been frozen at that point. - if training_util.get_global_step() is None and self.saver() is not None: - raise ValueError( - 'Saver defined but no global step. Run `get_or_create_global_step()`' - ' in your model definition to allow checkpointing.') - - with self._graph.as_default(): - logging.info('Installing graceful shutdown hook.') - self._session = _clone_session(training_session, self._graph) - self._workers = WorkerHeartbeatManager.from_devices( - self._session, all_worker_devices(self._session)) - self._heartbeat_supported = self._workers.num_workers() > 0 - if self._heartbeat_supported: - self._workers.configure( - event_pb2.WorkerHeartbeatRequest( - shutdown_mode=event_pb2.WAIT_FOR_COORDINATOR)) - else: - logging.warn( - 'No workers support hearbeats. Failure handling will be disabled.') - - def saver(self): - if self._saver: - return self._saver - - savers = ops.get_collection(ops.GraphKeys.SAVERS) - if not savers: - return None - - if not isinstance(savers, list): - return savers - - if len(savers) > 1: - logging.error( - 'Multiple savers in the SAVERS collection. On-demand checkpointing ' - 'will be disabled. Pass an explicit `saver` to the constructor to ' - 'override this behavior.') - return None - - return savers[0] - - def after_run(self, run_context, run_values): - del run_values - - if not self._heartbeat_supported: - return - - lame_workers = self._workers.lame_workers() - if lame_workers: - logging.info('ShutdownHook: lame workers found: %s', lame_workers) - - if self.saver(): - logging.info('ShutdownHook: saving checkpoint to %s', - self._checkpoint_prefix) - self.saver().save( - run_context.session, - self._checkpoint_prefix, - global_step=training_util.get_global_step(), - write_state=True, - ) - else: - logging.info('ShutdownHook: no Saver defined.') - - for fn in self._on_shutdown_hooks: - fn(run_context, self._workers, lame_workers) - - -class RestartComputation(object): - """Restart the entire computation. - - This hook shuts down all workers and returns control to the top-level by - throwing a CoordinatorShutdownException. - """ - - def __init__(self, timeout_ms=10000): - self.timeout_ms = timeout_ms - - def __call__(self, run_context, all_workers, lame_workers): - del run_context, lame_workers - all_workers.shutdown(timeout_ms=self.timeout_ms) - - logging.info('Terminating coordinator.') - raise CoordinatorShutdownException() - - -class ShutdownLameWorkers(object): - """Shutdown lamed workers. - - Processing will continue normally (typically by waiting for the down - workers to be restarted). - """ - - def __init__(self, timeout_ms=10000): - self.timeout_in_ms = timeout_ms - - def __call__(self, run_context, all_workers, lame_workers): - lame_workers.shutdown(timeout_ms=self.timeout_in_ms) +# pylint: disable=wildcard-import,unused-import +from tensorflow.python.tpu.session_support import * +# pylint: enable=wildcard-import,unused-import diff --git a/tensorflow/contrib/tpu/python/tpu/tensor_tracer.py b/tensorflow/contrib/tpu/python/tpu/tensor_tracer.py index ae05822084..73db253fd7 100644 --- a/tensorflow/contrib/tpu/python/tpu/tensor_tracer.py +++ b/tensorflow/contrib/tpu/python/tpu/tensor_tracer.py @@ -1,1638 +1,23 @@ -# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 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 +# 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 utility to trace tensor values on TPU.""" +# ============================================================================== +"""Stub file to maintain backwards compatibility.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function -import os -import os.path -import re -import sys - -from tensorflow.contrib.tpu.python.ops import tpu_ops -from tensorflow.contrib.tpu.python.tpu import tpu -from tensorflow.python.framework import constant_op -from tensorflow.python.framework import dtypes -from tensorflow.python.framework import graph_io -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 control_flow_ops -from tensorflow.python.ops import control_flow_util -from tensorflow.python.ops import gen_math_ops -from tensorflow.python.ops import init_ops -from tensorflow.python.ops import linalg_ops -from tensorflow.python.ops import logging_ops -from tensorflow.python.ops import math_ops -from tensorflow.python.ops import state_ops -from tensorflow.python.ops import variable_scope -from tensorflow.python.platform import gfile -from tensorflow.python.platform import tf_logging as logging - -_TRACER_LOG_PREFIX = ' [>>>TT>>>]' -_DEVICE_TYPE_TPU = 'tpu' -_DEVICE_TYPE_CPU = 'cpu' -_TRACE_MODE_NAN_INF = 'nan-inf' -_TRACE_MODE_PART_TENSOR = 'part-tensor' -_TRACE_MODE_PART_TENSOR_SIZE = 3 -_TRACE_MODE_FULL_TENSOR = 'full-tensor' -_TRACE_MODE_NORM = 'norm' -_TRACE_MODE_MAX_ABS = 'max-abs' -_SUBMODE_BRIEF = 'brief' -_SUBMODE_DETAILED = 'detailed' -_REASON_OUTSIDE_OP_RANGE = 'not-traced-outside-op-range' -_REASON_UNSAFE_OP = 'not-traced-unsafe-op' -_REASON_WHILELOOP_OP = 'not-traced-special-whileloop-op' -_REASON_UNSAFE_SCALAR = 'not-traced-unsafe-scalar' -_REASON_LESS_INTERESTING_OP = 'not-traced-less-interesting-op' -_REASON_DEVICE_MISMATCH = 'not-traced-device-mismatch' -_REASON_DYNAMIC_SHAPE = 'not-traced-dynamic-shape' -_REASON_SCALAR_GET_TRACED = 'traced-scalar' -_REASON_TENSOR_GET_TRACED = 'traced-tensor' -_REASON_USER_INCLUDED = 'traced-user-included' -_REASON_USER_EXCLUDED = 'not-traced-user-excluded' -_REASON_NOT_EXECUTED = 'not-traced-not-in-exec-path' -_REASON_NON_NUMERIC_TENSOR = 'not-traced-non-numeric-tensor' -_REASON_FEEDS_WHILELOOP_OP = 'not-traced-feeds-special-whileloop-op' -_MARKER_SECTION_BEGIN = '!!!!!!! section-begin:' -_MARKER_SECTION_END = '!!!!!!! section-end:' -_SECTION_NAME_CONFIG = 'configuration' -_SECTION_NAME_REASON = 'reason' -_SECTION_NAME_OP_LIST = 'op-list' -_SECTION_NAME_TENSOR_LIST = 'tensor-list' -_SECTION_NAME_CACHE_INDEX_MAP = 'cache-index-map' -_SECTION_NAME_GRAPH = 'graph' -_FIELD_NAME_VERSION = 'version:' -_FIELD_NAME_DEVICE = 'device:' -_FIELD_NAME_TRACE_MODE = 'trace-mode:' -_FIELD_NAME_SUBMODE = 'submode:' -_FIELD_NAME_NUM_REPLICAS = 'num-replicas:' -_FIELD_NAME_NUM_REPLICAS_PER_HOST = 'num-replicas-per-host:' -_FIELD_NAME_NUM_HOSTS = 'num-hosts:' -_FIELD_NAME_NUM_OPS = 'number-of-ops:' -_FIELD_NAME_NUM_TENSORS = 'number-of-tensors:' -_FIELD_NAME_NUM_CACHE_INDICES = 'number-of-indices:' -_FIELD_NAME_TOPOLOGICAL_SORT_SUCCEED = 'topological-sort-succeed:' -_FLAGS_ENV_VAR = 'TENSOR_TRACER_FLAGS' -_FLAG_SINGLE_QUOTE_PAT = re.compile(r"\s*--([^=]+)='([^']*)'") -_FLAG_DOUBLE_QUOTE_PAT = re.compile(r'\s*--([^=]+)="([^"]*)"') -_FLAG_NO_QUOTE_PAT = re.compile(r'\s*--([^=]+)=(\S*)') -_FLAG_NO_EQUAL_PAT = re.compile(r'\s*--([^=]+)\s*') -_FLAG_NAME_ENABLE = 'enable' -_FLAG_NAME_TRACE_MODE = 'trace_mode' -_FLAG_NAME_USE_COMPACT_TRACE = 'compact_trace' -_FLAG_NAME_SUBMODE = 'submode' -_FLAG_NAME_INCLUDE_LESS_INTERESTING_OPS = 'include_less_interesting_ops' -_FLAG_NAME_EXCLUDED_OPNAMES = 'excluded_opnames' -_FLAG_NAME_EXCLUDED_OPTYPES = 'excluded_optypes' -_FLAG_NAME_INCLUDED_OPNAMES = 'included_opnames' -_FLAG_NAME_INCLUDED_OPTYPES = 'included_optypes' -_FLAG_NAME_TRACE_DIR = 'trace_dir' -_FLAG_NAME_REPORT_FILE = 'report_file' -_FLAG_NAME_USE_TEST_UNDECLARED_OUTPUTS_DIR = 'use_test_undeclared_outputs_dir' -_FLAG_NAME_OP_RANGE = 'op_range' -# Folder to dump the pre (before tensor tracer updates) and post graphs (after -# tensor tracer updates). -_FLAG_DUMP_BEFORE_AFTER_GRAPHS = 'dump_graphs' -_OP_RANGE_PAT = re.compile(r'(\d+):(\d+)') -_OUTPUT_STREAM_ESCAPE = 'file://' -_TEST_UNDECLARED_OUTPUTS_DIR_ENV_VAR = 'TEST_UNDECLARED_OUTPUTS_DIR' -_TENSOR_TRACER_COLLECTION = 'tensor_tracer_variables' -_TENSOR_TRACER_CHECKPOINT = 'tensor_tracer_checkpoint' -_TRACE_FILE_NAME = 'trace.all' -_COMPACT_TRACE_FILE_PREFIX = 'compact_trace.' -_COMPACT_TRACE_ENTRY_INIT_VALUE = -1.0 -_TENSOR_TRACER_STORAGE = 'tensor_tracer_storage' -_TENSOR_VALUES_CACHE = 'tensor_values_cache' -_REPLICA_ID_TAG = '#replica-id: ' - - -def tensor_tracepoint(tensor, checkpoint_name): - """Adds a checkpoint with the given checkpoint name for the given tensor. - - The tensor will be added to the list of tensors that will be traced by the - tensor tracer. - - Args: - tensor: the tensor object for which the tracing is requested. - checkpoint_name: a string name for the checkpoint. This name has to be a - unique name if used within model comparison. The tensors that have the same - checkpoint identifier is compared in model comparison. - Returns: - The provided tensor. - """ - - tensor.graph.get_collection(_TENSOR_TRACER_COLLECTION) - tensor.graph.add_to_collection(_TENSOR_TRACER_COLLECTION, - (tensor, checkpoint_name)) - return tensor - - -def keras_layer_tracepoint(layer, checkpoint_name): - """An interface for adding the tensor outputs of a keras layer. - - Encapsulates tensor_tracepoint. - - Args: - layer: A keras layer. - checkpoint_name: a string name for the checkpoint. This name has to be a - unique name if used within model comparison. The tensors that have the same - checkpoint identifier is compared in model comparison. - - Returns: - The provided layer. - """ - try: - outputs = layer.output - if tensor_util.is_tensor(outputs): - tensor_tracepoint(outputs, '%s' % (checkpoint_name)) - else: - idx = 0 - for output_tensor in outputs: - if tensor_util.is_tensor(outputs): - tensor_tracepoint(output_tensor, '%s_%d' % (checkpoint_name, idx)) - idx += 1 - except AttributeError: - pass - except RuntimeError: - pass - return layer - - -def _trace_files_need_precreated(output_dir): - """Return True if trace files must be pre-created by users.""" - - if not output_dir.startswith('/'): - return False - if len(output_dir) < 5: - return False - if output_dir[2] != 'n': - return False - if output_dir[3] != 's': - return False - if output_dir[1] != 'c': - return False - if output_dir[4] != '/': - return False - return True - - -def _get_tensor_values_cache(graph=None): - """Returns the variable that implements tensor-value caching.""" - - graph = graph or ops.get_default_graph() - collection = graph.get_collection(_TENSOR_TRACER_STORAGE) - if len(collection) == 1: - return collection[0] - elif not collection: - raise RuntimeError('%s has not been created'%_TENSOR_VALUES_CACHE) - else: - raise RuntimeError('Multiple %s created'%_TENSOR_VALUES_CACHE) - return None - - -def _create_tensor_values_cache(graph, num_tensors): - """Creates a variable as the cache to store intermediate tensor values.""" - graph = graph or ops.get_default_graph() - # Create in proper graph and base name_scope. - with graph.as_default() as g, g.name_scope(None): - return variable_scope.get_variable( - _TENSOR_VALUES_CACHE, - shape=[num_tensors], - dtype=dtypes.float32, - initializer=init_ops.constant_initializer( - _COMPACT_TRACE_ENTRY_INIT_VALUE), - trainable=False, - use_resource=True, - collections=[_TENSOR_TRACER_STORAGE, ops.GraphKeys.GLOBAL_VARIABLES]) - - -class TensorTracer(object): - """A software construct for tracing tensor values in a TF graph on TPU. - - This utility is disabled by default. It can be enabled by setting - the TENSOR_TRACER_FLAGS env variable as: - export TENSOR_TRACER_FLAGS="--enable=1" - If it is enabled, it will trace the output tensor values of - selected Ops in the graph. It has two outputs: (1) the traces and (2) - a report. The traces are dumped to a specified local file on the TPU - host. The report is printed to the log.info of the TPU job. - By passing options via the env variable, users can change: - (1) the trace mode (e.g., detecting NaN/Inf, printing partial or - full tensor values) - (2) which Ops to be traced (via op.name or op.type) - (3) output trace file path. - """ - # The set of graphs that are rewritten by tensor tracer. - _traced_graphs = set() - @staticmethod - def _match_next_flag(flags, pos): - """Returns the match for the next TensorTracer flag. - - Args: - flags: a string that contains the flags. - pos: where in flags to start the search. - - Returns: - A pair where the first element is the regular-expression - match found and the second element indicates if the match - has a value. - """ - - match = _FLAG_DOUBLE_QUOTE_PAT.match(flags, pos) - if match: - return match, True - match = _FLAG_SINGLE_QUOTE_PAT.match(flags, pos) - if match: - return match, True - match = _FLAG_NO_QUOTE_PAT.match(flags, pos) - if match: - return match, True - match = _FLAG_NO_EQUAL_PAT.match(flags, pos) - if match: - # The flag is found but is not given a value. - return match, False - # The flag is not found. - return None, False - - @staticmethod - def validate_flag_names(): - """Validates if the TensorTrace flags passed are valid.""" - valid_flag_names = [_FLAG_NAME_ENABLE, _FLAG_NAME_TRACE_MODE, - _FLAG_NAME_USE_COMPACT_TRACE, - _FLAG_NAME_SUBMODE, - _FLAG_NAME_EXCLUDED_OPNAMES, - _FLAG_NAME_EXCLUDED_OPTYPES, - _FLAG_NAME_INCLUDED_OPNAMES, - _FLAG_NAME_INCLUDED_OPTYPES, - _FLAG_NAME_TRACE_DIR, - _FLAG_NAME_REPORT_FILE, - _FLAG_NAME_USE_TEST_UNDECLARED_OUTPUTS_DIR, - _FLAG_NAME_INCLUDE_LESS_INTERESTING_OPS, - _FLAG_NAME_OP_RANGE, - _FLAG_DUMP_BEFORE_AFTER_GRAPHS] - tensor_tracer_flags = os.environ.get(_FLAGS_ENV_VAR) - if not tensor_tracer_flags: - return - pos = 0 - while True: - match, _ = TensorTracer._match_next_flag(tensor_tracer_flags, pos) - if not match: - break - flag_name = match.group(1) - if flag_name not in valid_flag_names: - raise ValueError( - 'The flag name "%s" passed via the environment variable "%s" ' - 'is invalid. Valid flag names are:' - '\n%s'%(flag_name, _FLAGS_ENV_VAR, valid_flag_names)) - pos = match.end() - - @staticmethod - def print_flag_values(): - """Prints all TensorTracer flags passed via environment variables.""" - - tensor_tracer_flags = os.environ.get(_FLAGS_ENV_VAR) - if not tensor_tracer_flags: - return 'Env variable "%s" is not set'%_FLAGS_ENV_VAR - result = 'Env variable "%s" is set to "%s"\n'%(_FLAGS_ENV_VAR, - tensor_tracer_flags) - result += 'Individual flag value:\n' - pos = 0 - while True: - match, has_value = TensorTracer._match_next_flag( - tensor_tracer_flags, pos) - if not match: - break - flag_name = match.group(1) - if has_value: - flag_value = match.group(2) - else: - flag_value = None - result += ' %s: %s\n'%(flag_name, flag_value) - pos = match.end() - result += '\n' - return result - - @staticmethod - def get_flag_value(wanted_flag_name): - """Returns the value of a TensorTracer flags. - - Args: - wanted_flag_name: the name the the flag we are looking for. - - Returns: - A pair where the first element indicates if the flag is - found and the second element is the value of the flag. - - Raises: - RuntimeError: If supposedly deadcode is reached. - """ - - tensor_tracer_flags = os.getenv(_FLAGS_ENV_VAR) - if not tensor_tracer_flags: - return False, None - pos = 0 - while True: - match, has_value = TensorTracer._match_next_flag( - tensor_tracer_flags, pos) - if not match: - return False, None - flag_name = match.group(1) - if has_value: - flag_value = match.group(2) - else: - flag_value = None - if flag_name == wanted_flag_name: - return True, flag_value - pos = match.end() - raise RuntimeError('Should not reach here.') - - @staticmethod - def flag_value_to_re_list(flag_name): - """Converts list of strings to compiled RE.""" - - re_list = [] - found, flag_value = TensorTracer.get_flag_value(flag_name) - if not found or not flag_value: - return re_list - list_of_values = flag_value.split() - for v in list_of_values: - r = re.compile(v) - re_list.append(r) - return re_list - - @staticmethod - def _is_flag_on(flag_name): - """Returns True if the given flag is on.""" - - found, flag_value = TensorTracer.get_flag_value(flag_name) - if not found: - return False - if flag_value is None: - return True - # Depends on the flag value. - flag_value = flag_value.lower() - enabled = flag_value in ['1', 't', 'true', 'y', 'yes'] - return enabled - - @staticmethod - def is_enabled(): - """Returns True if TensorTracer is enabled.""" - - return TensorTracer._is_flag_on(_FLAG_NAME_ENABLE) - - @staticmethod - def use_test_undeclared_outputs_dir(): - """Decides the output directory of the report and trace files. - - Args: - None. - - Returns: - True if the output files should be written to the - test-undeclared-outputs-directory defined via an - env variable. - """ - - return TensorTracer._is_flag_on( - _FLAG_NAME_USE_TEST_UNDECLARED_OUTPUTS_DIR) - - @staticmethod - def use_compact_trace(): - return TensorTracer._is_flag_on( - _FLAG_NAME_USE_COMPACT_TRACE) - - @staticmethod - def check_device_type(device_type): - """Checks if the given device type is valid.""" - - if device_type not in [_DEVICE_TYPE_TPU, _DEVICE_TYPE_CPU]: - raise ValueError('Invalid device_type "%s"'%device_type) - - @staticmethod - def check_trace_mode(trace_mode): - """Checks if the given trace mode is valid.""" - - valid_trace_modes = [_TRACE_MODE_NAN_INF, _TRACE_MODE_PART_TENSOR, - _TRACE_MODE_FULL_TENSOR, _TRACE_MODE_NORM, - _TRACE_MODE_MAX_ABS] - if trace_mode not in valid_trace_modes: - raise ValueError('Invalid trace mode "%s" given to the Tensor_Tracer.' - 'Valid trace modes are: %s'%(trace_mode, - valid_trace_modes)) - - @staticmethod - def check_submode(submode): - """Checks if the given submode is valid.""" - - if not submode: - return - valid_submodes = [_SUBMODE_DETAILED, _SUBMODE_BRIEF] - if submode not in valid_submodes: - raise ValueError('Invalid submode "%s" given to the Tensor_Tracer.' - 'Valid submodes are: %s'%(submode, - valid_submodes)) - - @staticmethod - def loop_cond_op(op): - return op.type in ('LoopCond', 'RefLoopCond') - - @staticmethod - def while_loop_op(op): - """Returns true if op is one of the special ops of in a while loop. - - Args: - op: A tf.Operation. - - Returns: - True if the given op is one of [Switch, Merge, Enter, Exit, - NextIteration, LoopCond], which are all building blocks for TF while - loops. - """ - return (control_flow_util.IsLoopSwitch(op) or - control_flow_util.IsLoopMerge(op) or - control_flow_util.IsLoopEnter(op) or - control_flow_util.IsLoopExit(op) or - TensorTracer.loop_cond_op(op) or - op.type in ('RefNextIteration', 'NextIteration')) - - @staticmethod - def unsafe_op(op): - """Returns True if this op is not safe to be traced.""" - - if control_flow_util.IsInCond(op): - return True - # Reasons for not including following op types: - # Assign: cause incorrect result with CPU tracing. - if op.type in ['Assign']: - return True - return False - - @staticmethod - def device_mismatch(device_type, op): - if device_type == _DEVICE_TYPE_TPU: - # pylint: disable=protected-access - return tpu._TPU_REPLICATE_ATTR not in op.node_def.attr - # pylint: enable=protected-access - return False - - @staticmethod - def unsafe_scalar_trace(op): - """Return true if scalar output tensor from Op is not safe to be traced.""" - - # Tracing the following causes cycle in the graph on TPU. - if op.type in ['LoopCond', 'Enter', 'Merge', 'Const', - 'Switch', 'Less', 'ReadVariableOp']: - return True - # Tracing the following will cause casting-issue - # with the norm tracing mode or other compilation issues on CPU. - if op.type in ['VarHandleOp', 'IteratorToStringHandle', - 'IteratorGetNext', 'OneShotIterator', - 'IteratorV2', 'MakeIterator', - 'BatchDatasetV2', 'MapDataset', - 'FixedLengthRecordDataset', 'TakeDataset', 'ZipDataset', - 'Placeholder', 'PlaceholderWithDefault', 'StridedSlice']: - return True - return False - - @staticmethod - def less_interesting_op(op): - """Returns True if the given Op is not an interesting one to be traced.""" - - found, _ = TensorTracer.get_flag_value( - _FLAG_NAME_INCLUDE_LESS_INTERESTING_OPS) - if found: - # users force to include all ops. - return False - # Following ops are highly unlikey to cause bugs. - return op.type in ['Const', 'Identity', 'Cast', 'Shape'] - - @staticmethod - def reason(op_idx, details): - """Returns reason why the Op at op_idx is traced or not.""" - - return '%d %s'%(op_idx, details) - - @staticmethod - def topological_sort(g): - """Performs topological sort on the given graph. - - Args: - g: the graph. - - Returns: - A pair where the first element indicates if the topological - sort succeeded (True if there is no cycle found; False if a - cycle is found) and the second element is either the sorted - list of nodes or the cycle of nodes found. - """ - - def visit(op, cycle, permanently_marked_ops, - temporarily_marked_ops, sorted_ops): - """Recursively visits all Ops in a graph. - - Args: - op: the current Op being visited. - cycle: a cycle of Ops found. - permanently_marked_ops: the set of Ops that were already visited. - temporarily_marked_ops: the set of Ops that we have visited during - the current descent. - sorted_ops: the list of Ops sorted in topological order. - """ - - if cycle: - return - if op in permanently_marked_ops: - return - if op in temporarily_marked_ops: - cycle = temporarily_marked_ops - return - temporarily_marked_ops.add(op) - for i in range(len(op.outputs)): - out_tensor = op.outputs[i] - for consumer_op in out_tensor.consumers(): - visit(consumer_op, cycle, permanently_marked_ops, - temporarily_marked_ops, sorted_ops) - # pylint: disable=protected-access - for ctrl_output_op in op._control_outputs: - # pylint: enable=protected-access - visit(ctrl_output_op, cycle, permanently_marked_ops, - temporarily_marked_ops, sorted_ops) - temporarily_marked_ops.remove(op) - permanently_marked_ops.add(op) - sorted_ops.insert(0, op) - - graph_cycle = set([]) - sorted_ops = [] - permanently_marked_ops = set([]) - temporarily_marked_ops = set([]) - unsorted_ops = g.get_operations() - for op in unsorted_ops: - visit(op, graph_cycle, permanently_marked_ops, - temporarily_marked_ops, sorted_ops) - if graph_cycle: - return (False, graph_cycle) - else: - assert len(unsorted_ops) == len(sorted_ops) - return (True, sorted_ops) - - @staticmethod - def _make_op_and_tensor_maps(op_list): - """Creates various maps and lists from op_list. - - Args: - op_list: a list of Ops - - Returns: - opname_idx_map: a map from Op's name to its index in op_list. - tensor_list: a list of output tensors of the Ops in op_list. - tensorname_idx_map: a map from output tensor name to its index - in tensor_list. - """ - - opname_idx_map = {} - tensor_list = [] - tensorname_idx_map = {} - for op_id, op in enumerate(op_list): - if op.name in opname_idx_map: - raise ValueError('Duplicated Op name: %s'%op.name) - opname_idx_map[op.name] = op_id - for output_tensor in op.outputs: - if output_tensor.name not in tensorname_idx_map: - tensor_list.append(output_tensor) - tensorname_idx_map[output_tensor.name] = len(tensor_list)-1 - return (opname_idx_map, tensor_list, tensorname_idx_map) - - def __init__(self): - """Initializes a TensorTracer. - - Sets the various member fields from the flags (if given) or the defaults. - """ - self._version = 'use-outside-compilation' - self._device_type = None - TensorTracer.validate_flag_names() - found, self._trace_mode = TensorTracer.get_flag_value(_FLAG_NAME_TRACE_MODE) - if not found or not self._trace_mode: - self._trace_mode = _TRACE_MODE_NAN_INF - TensorTracer.check_trace_mode(self._trace_mode) - found, self._submode = TensorTracer.get_flag_value(_FLAG_NAME_SUBMODE) - if not found or not self._submode: - self._submode = _SUBMODE_DETAILED - TensorTracer.check_submode(self._submode) - self._part_tensor_size = _TRACE_MODE_PART_TENSOR_SIZE - self._instrument_records = {} - self._set_trace_dir() - self._set_report_file() - self._set_op_range() - self._set_excluded_opnames() - self._set_excluded_optypes() - self._set_included_opnames() - self._set_included_optypes() - self._num_replicas = None - self._num_replicas_per_host = None - self._num_hosts = None - self._replica_id = None - _, self._graph_dump_path = TensorTracer.get_flag_value( - _FLAG_DUMP_BEFORE_AFTER_GRAPHS) - - def _add_replica_id_to_graph(self): - """Adds nodes for computing the replica ID to the graph.""" - - if self._num_replicas: - with ops.control_dependencies(None): - # Uses None as dependency to run outside of TPU graph rewrites. - self._replica_id = tpu_ops.tpu_replicated_input( - list(range(self._num_replicas)), - name='tt_replica_id') - else: - self._replica_id = 'unknown' - - def _set_trace_dir(self): - found, self._trace_dir = TensorTracer.get_flag_value(_FLAG_NAME_TRACE_DIR) - if found and self._trace_dir \ - and TensorTracer.use_test_undeclared_outputs_dir(): - raise ValueError('Cannot not use --%s and --%s at the same time' - %(_FLAG_NAME_TRACE_DIR, - _FLAG_NAME_USE_TEST_UNDECLARED_OUTPUTS_DIR)) - if TensorTracer.use_test_undeclared_outputs_dir(): - self._trace_dir = os.environ.get(_TEST_UNDECLARED_OUTPUTS_DIR_ENV_VAR) - - def _set_report_file(self): - """Sets the path of the output report file.""" - - found, self._report_file_path = TensorTracer.get_flag_value( - _FLAG_NAME_REPORT_FILE) - if found and self._report_file_path \ - and TensorTracer.use_test_undeclared_outputs_dir(): - if os.path.isabs(self._report_file_path): - raise ValueError('If use_test_undeclared_outputs_dir is set,' - 'report_file_path cannot be an absolute path (%s)' - %self._report_file_path) - outputs_dir = os.environ.get(_TEST_UNDECLARED_OUTPUTS_DIR_ENV_VAR) - self._report_file_path = os.path.join(outputs_dir, - self._report_file_path) - if not self._report_file_path: - self._report_file = None - return - try: - self._report_file = gfile.Open(self._report_file_path, 'w') - except IOError as e: - raise e - - def _close_report_file(self): - if self._report_file: - self._report_file.close() - - def _set_op_range(self): - """Sets the index range of the Ops that we will consider tracing.""" - - found, op_range = TensorTracer.get_flag_value(_FLAG_NAME_OP_RANGE) - if not found or not op_range: - self._op_range = (-1, -1) # this means including all ops. - return - match = _OP_RANGE_PAT.match(op_range) - if not match: - self._op_range = (-1, -1) # this means including all ops. - return - self._op_range = (int(match.group(1)), int(match.group(2))) - - def _inside_op_range(self, idx): - """Return True if the given index is inside the selected range.""" - - if idx < self._op_range[0]: - return False - return self._op_range[1] < 0 or idx <= self._op_range[1] - - def _set_excluded_opnames(self): - self._excluded_opname_re_list = TensorTracer.flag_value_to_re_list( - _FLAG_NAME_EXCLUDED_OPNAMES) - - def _set_excluded_optypes(self): - self._excluded_optype_re_list = TensorTracer.flag_value_to_re_list( - _FLAG_NAME_EXCLUDED_OPTYPES) - - def _set_included_opnames(self): - self._included_opname_re_list = TensorTracer.flag_value_to_re_list( - _FLAG_NAME_INCLUDED_OPNAMES) - - def _set_included_optypes(self): - self._included_optype_re_list = TensorTracer.flag_value_to_re_list( - _FLAG_NAME_INCLUDED_OPTYPES) - - def _is_user_included_op(self, op): - for opname_re in self._included_opname_re_list: - if opname_re.match(op.name): - return True - for optype_re in self._included_optype_re_list: - if optype_re.match(op.type): - return True - return False - - def _is_user_excluded_op(self, op): - for opname_re in self._excluded_opname_re_list: - if opname_re.match(op.name): - return True - for optype_re in self._excluded_optype_re_list: - if optype_re.match(op.type): - return True - return False - - def _use_tensor_values_cache(self): - """Returns True if immediate tensors should be first saved to a cache.""" - - if self._trace_mode not in set([_TRACE_MODE_NAN_INF, - _TRACE_MODE_NORM, _TRACE_MODE_MAX_ABS]): - return False - if self._trace_dir and _trace_files_need_precreated(self._trace_dir): - return True - if TensorTracer.use_compact_trace(): - return True - return False - - def _save_tensor_value_to_cache_op(self, graph, cache_idx, updates): - """Returns an Op that will save the given updates to an entry in the cache.""" - - cache = _get_tensor_values_cache(graph) - indices = constant_op.constant([cache_idx]) - return state_ops.scatter_update(cache, indices, updates).op - - def _write_report(self, content): - """Writes the given content to the report.""" - - line = '%s %s'%(_TRACER_LOG_PREFIX, content) - if self._report_file: - self._report_file.write(line) - else: - logging.info(line) - - def _write_config_section(self): - """Writes the config section of the report.""" - - self._write_report('%s %s\n'%(_MARKER_SECTION_BEGIN, _SECTION_NAME_CONFIG)) - self._write_report('%s %s\n'%(_FIELD_NAME_VERSION, self._version)) - self._write_report('%s %s\n'%(_FIELD_NAME_DEVICE, self._device_type)) - self._write_report('%s %s\n'%(_FIELD_NAME_TRACE_MODE, self._trace_mode)) - self._write_report('%s %s\n'%(_FIELD_NAME_SUBMODE, self._submode)) - self._write_report('%s %s\n'%(_FIELD_NAME_NUM_REPLICAS, self._num_replicas)) - self._write_report('%s %s\n'%(_FIELD_NAME_NUM_REPLICAS_PER_HOST, - self._num_replicas_per_host)) - self._write_report('%s %s\n'%(_FIELD_NAME_NUM_HOSTS, self._num_hosts)) - self._write_report('%s %s\n'%(_MARKER_SECTION_END, _SECTION_NAME_CONFIG)) - - def _write_reason_section(self): - """Writes the reason section of the report.""" - - self._write_report('%s %s\n'%(_MARKER_SECTION_BEGIN, _SECTION_NAME_REASON)) - for key in sorted(self._instrument_records): - self._write_report('"%s" %s\n'%(key, self._instrument_records[key])) - self._write_report('%s %s\n'%(_MARKER_SECTION_END, _SECTION_NAME_REASON)) - - def _write_op_list_section(self, op_list): - """Writes the Op-list section of the report.""" - - self._write_report('%s %s\n'%(_MARKER_SECTION_BEGIN, _SECTION_NAME_OP_LIST)) - self._write_report('%s %d\n'%(_FIELD_NAME_NUM_OPS, len(op_list))) - for i in range(0, len(op_list)): - op = op_list[i] - line = '%d "%s" %s'%(i, op.name, op.type) - for out_tensor in op.outputs: - if out_tensor.name not in self._tensorname_idx_map: - raise ValueError( - 'out_tensor %s is not in tensorname_idx_map'%out_tensor.name) - line += ' %d'%self._tensorname_idx_map[out_tensor.name] - line += '\n' - self._write_report(line) - self._write_report('%s %s\n'%(_MARKER_SECTION_END, _SECTION_NAME_OP_LIST)) - - def _write_tensor_list_section(self, tensor_list, opname_idx_map): - """Writes the tensor-list section of the report.""" - - self._write_report('%s %s\n'%(_MARKER_SECTION_BEGIN, - _SECTION_NAME_TENSOR_LIST)) - self._write_report('%s %d\n'%(_FIELD_NAME_NUM_TENSORS, len(tensor_list))) - for i in range(0, len(tensor_list)): - tensor = tensor_list[i] - line = '%d "%s"'%(i, tensor.name) - for consumer_op in tensor.consumers(): - if consumer_op.name not in opname_idx_map: - raise ValueError( - 'consumer_op %s is not in opname_idx_map'%consumer_op.name) - line += ' %d'%opname_idx_map[consumer_op.name] - line += '\n' - self._write_report(line) - self._write_report('%s %s\n'%(_MARKER_SECTION_END, - _SECTION_NAME_TENSOR_LIST)) - - def _write_cache_index_map_section(self): - """Writes the mapping from cache index to tensor index to the report.""" - - self._write_report('%s %s\n'%(_MARKER_SECTION_BEGIN, - _SECTION_NAME_CACHE_INDEX_MAP)) - self._write_report('%s %d\n'%(_FIELD_NAME_NUM_CACHE_INDICES, - len(self._cache_idx_to_tensor_idx))) - for cache_idx in range(0, len(self._cache_idx_to_tensor_idx)): - tensor_idx = self._cache_idx_to_tensor_idx[cache_idx] - line = '%d %d\n'%(cache_idx, tensor_idx) - self._write_report(line) - self._write_report('%s %s\n'%(_MARKER_SECTION_END, - _SECTION_NAME_CACHE_INDEX_MAP)) - - def _write_graph_section(self, succeed, sorted_or_cycle): - """Writes the graph section of the report.""" - - self._write_report('%s %s\n'%(_MARKER_SECTION_BEGIN, _SECTION_NAME_GRAPH)) - self._write_report('%s %s\n'%(_FIELD_NAME_TOPOLOGICAL_SORT_SUCCEED, - succeed)) - l = list(sorted_or_cycle) - for i in range(0, len(l)): - self._write_report('%d "%s"\n'%(i, l[i].name)) - self._write_report('%s %s\n'%(_MARKER_SECTION_END, _SECTION_NAME_GRAPH)) - - def _preprocess_traced_tensor(self, tensor): - """Computes NAN/Norm/Max on TPUs before sending to CPU. - - Args: - tensor: The tensor to be traced. - Returns: - A tensor that should be input to the trace_function. - Raises: - RuntimeError: If the trace mode is invalid. - """ - - def _detect_nan_inf(tensor): - """Trace function for detecting any NaN/Inf in the tensor.""" - - if tensor.dtype.is_floating: - mask = math_ops.reduce_any( - gen_math_ops.logical_or( - gen_math_ops.is_nan(tensor), gen_math_ops.is_inf(tensor))) - output_tensor = control_flow_ops.cond(mask, - lambda: constant_op.constant(1.0), - lambda: constant_op.constant(0.0)) - else: - output_tensor = constant_op.constant(0.0) - # The shape has to be 1. Set it if it does not have the information. - output_tensor = array_ops.reshape(output_tensor, [1]) - return output_tensor - - def _show_norm(tensor): - tensor = math_ops.cast(tensor, dtypes.float32) - output_tensor = linalg_ops.norm(tensor) - # The shape has to be 1. Set it if it does not have the information. - output_tensor = array_ops.reshape(output_tensor, [1]) - return output_tensor - - def _show_max_abs(tensor): - tensor = math_ops.cast(tensor, dtypes.float32) - output_tensor = math_ops.reduce_max(math_ops.abs(tensor)) - zero = constant_op.constant(0, dtypes.float32) - output_tensor = gen_math_ops.maximum(zero, output_tensor) - # The shape has to be 1. Set it if it does not have the information. - output_tensor = array_ops.reshape(output_tensor, [1]) - return output_tensor - - if self._trace_mode == _TRACE_MODE_NAN_INF: - return _detect_nan_inf(tensor) - if self._trace_mode == _TRACE_MODE_PART_TENSOR: - return tensor - if self._trace_mode == _TRACE_MODE_FULL_TENSOR: - return tensor - if self._trace_mode == _TRACE_MODE_NORM: - return _show_norm(tensor) - if self._trace_mode == _TRACE_MODE_MAX_ABS: - return _show_max_abs(tensor) - raise RuntimeError( - 'Tensor trace fun for %s is not yet implemented' % self._trace_mode) - - def _make_tensor_trace_fun(self, tensor_name): - """Makes the tensor tracing function called by outside compilation. - - Args: - tensor_name: name of the tensor being traced. - - Returns: - A function to be passed as the first argument to outside compilation. - - Raises: - RuntimeError: If the trace mode is invalid. - """ - - def _print_tensor(tensor_name, num_elements, tensor, output_tensor): - """Prints a tensor value to a file. - - Args: - tensor_name: name of the tensor being traced. - num_elements: number of elements to print (-1 means print all). - tensor: the tensor needs to be returned. - output_tensor: the tensor needs to be printed. - - Returns: - The same tensor passed via the "tensor" argument. - - Raises: - ValueError: If tensor_name is not already in - self._tensorname_idx_map. - """ - - if self._submode == _SUBMODE_BRIEF: - if tensor_name not in self._tensorname_idx_map: - raise ValueError( - 'Tensor name %s is not in the tensorname_idx_map'%tensor_name) - msg = '%d'%self._tensorname_idx_map[tensor_name] - else: - msg = '"%s"'%tensor_name - - if self._trace_dir: - output_path = os.path.join(self._trace_dir, _TRACE_FILE_NAME) - output_stream = _OUTPUT_STREAM_ESCAPE + output_path - else: - output_stream = sys.stderr - return logging_ops.print_v2(msg, array_ops.shape(output_tensor), - '@', self._replica_id, - '\n', output_tensor, '\n', - summarize=num_elements, - output_stream=output_stream) - - def _show_part_tensor(tensor): - """Trace function for printing part of the tensor.""" - - return _print_tensor(tensor_name, self._part_tensor_size, - tensor, tensor) - - def _show_full_tensor(tensor): - """Trace function for printing the entire tensor.""" - - return _print_tensor(tensor_name, -1, tensor, tensor) - - if self._trace_mode == _TRACE_MODE_PART_TENSOR: - return _show_part_tensor - # The input tensor has a shape of "[1]" for _TRACE_MODE_NAN_INF, - # _TRACE_MODE_NORM, and _TRACE_MODE_MAX_ABS, as related computations are - # performed within TPUs and only their results are transferred to CPU. - # Simply, print the full tensor for these trace modes. - if self._trace_mode in [ - _TRACE_MODE_NAN_INF, _TRACE_MODE_NORM, _TRACE_MODE_FULL_TENSOR, - _TRACE_MODE_MAX_ABS - ]: - return _show_full_tensor - - raise RuntimeError('Tensor trace fun for %s is not yet implemented' - %self._trace_mode) - - def _skip_op(self, op_id, op, user_included, user_excluded, - in_exec_path=True): - """Returns True if we should not trace Op.""" - - if TensorTracer.while_loop_op(op): - self._instrument_records[op.name] = TensorTracer.reason( - op_id, _REASON_WHILELOOP_OP) - return True - if TensorTracer.unsafe_op(op): - self._instrument_records[op.name] = TensorTracer.reason( - op_id, _REASON_UNSAFE_OP) - return True - if TensorTracer.device_mismatch(self._device_type, op): - self._instrument_records[op.name] = TensorTracer.reason( - op_id, _REASON_DEVICE_MISMATCH) - return True - if not in_exec_path: - self._instrument_records[op.name] = TensorTracer.reason( - op_id, _REASON_NOT_EXECUTED) - return True - - if not self._inside_op_range(op_id): - self._instrument_records[op.name] = TensorTracer.reason( - op_id, _REASON_OUTSIDE_OP_RANGE) - return True - if TensorTracer.less_interesting_op(op): - self._instrument_records[op.name] = TensorTracer.reason( - op_id, _REASON_LESS_INTERESTING_OP) - return True - if user_included: - self._instrument_records[op.name] = TensorTracer.reason( - op_id, _REASON_USER_INCLUDED) - return False - if user_excluded: - self._instrument_records[op.name] = TensorTracer.reason( - op_id, _REASON_USER_EXCLUDED) - return True - return False - - def _skip_tensor(self, op_id, out_tensor, user_included, - user_excluded): - """Returns True if we should not trace out_tensor.""" - - # Skips a tensor if the tensor has a non-numeric type. - # Note: we cannot use check_ops.is_numeric_tensor(out_tensor) - # because it also excludes tensors with dtypes, bool, and - # float32_ref, which we actually want to trace. - non_numeric_tensor_types = set([dtypes.variant, dtypes.resource, - dtypes.string]) - if out_tensor.dtype in non_numeric_tensor_types: - self._instrument_records[out_tensor.name] = TensorTracer.reason( - op_id, _REASON_NON_NUMERIC_TENSOR) - return True - # Skip a tensor if it feeds a special while loop op. - if [consumer for consumer in out_tensor.consumers() if - TensorTracer.while_loop_op(consumer)]: - self._instrument_records[out_tensor.name] = TensorTracer.reason( - op_id, _REASON_FEEDS_WHILELOOP_OP) - return True - if user_included: - self._instrument_records[out_tensor.name] = TensorTracer.reason( - op_id, _REASON_USER_INCLUDED) - return False - if user_excluded: - self._instrument_records[out_tensor.name] = TensorTracer.reason( - op_id, _REASON_USER_EXCLUDED) - return True - if not out_tensor.get_shape().is_fully_defined(): - # If trace mode is nan-inf, norm or max, then the tensor will be reduced - # to a scalar before the outside compilation call. - if self._trace_mode in [ - _TRACE_MODE_NAN_INF, _TRACE_MODE_NORM, _TRACE_MODE_MAX_ABS - ]: - self._instrument_records[out_tensor.name] = TensorTracer.reason( - op_id, _REASON_TENSOR_GET_TRACED) - return False - else: - self._instrument_records[out_tensor.name] = TensorTracer.reason( - op_id, _REASON_DYNAMIC_SHAPE) - return True - rank = len(out_tensor.shape) - if rank < 1: - # scalar - if TensorTracer.unsafe_scalar_trace(out_tensor.op): - self._instrument_records[out_tensor.name] = TensorTracer.reason( - op_id, _REASON_UNSAFE_SCALAR) - return True - else: - self._instrument_records[out_tensor.name] = TensorTracer.reason( - op_id, _REASON_SCALAR_GET_TRACED) - return False - else: - # tensor - self._instrument_records[out_tensor.name] = TensorTracer.reason( - op_id, _REASON_TENSOR_GET_TRACED) - return False - - def _filter_execution_path_operations(self, operations, fetches): - """Returns the set of ops in the execution path to compute given fetches.""" - - # If no fetch provided, then return all operations. - if fetches is None: - return set(operations) - # Convert to list, if a single element is provided. - if not isinstance(fetches, (list, tuple)): - fetches = [fetches] - # If a tensor is given as fetch, convert it to op. - op_fetches = [] - for fetch in fetches: - if isinstance(fetch, ops.Operation): - op_fetches.append(fetch) - elif isinstance(fetch, ops.Tensor): - op_fetches.append(fetch.op) - else: - raise RuntimeError('Given fetch:%s is neither a tensor nor an op.' - %fetch) - - execution_path_operations = set(op_fetches) - traverse_stack = list(op_fetches) - while True: - if not traverse_stack: - break - head_op = traverse_stack.pop() - input_ops = [tensor_input.op for tensor_input in head_op.inputs] - input_ops.extend(head_op.control_inputs) - - for input_op in input_ops: - if input_op not in execution_path_operations: - # Filter out loop condition operations, tracing them causes a cycle. - # Trace only the loop-body. - if TensorTracer.loop_cond_op(input_op): - continue - execution_path_operations.add(input_op) - traverse_stack.append(input_op) - return execution_path_operations - - def _determine_traced_tensors(self, graph, ops_in_exec_path): - """Determines the tensors that will be traced.""" - - self._traced_tensorname_to_cache_idx_map = {} - self._cache_idx_to_tensor_idx = [] - operations = graph.get_operations() - checkpoint_operations = self._get_checkpoints(graph) - for op_id, op in enumerate(operations): - if checkpoint_operations and op.name not in checkpoint_operations: - continue - user_included = self._is_user_included_op(op) - user_excluded = self._is_user_excluded_op(op) - in_exec_path = op in ops_in_exec_path - if self._skip_op(op_id, op, user_included, user_excluded, in_exec_path): - continue - for i in range(len(op.outputs)): - out_tensor = op.outputs[i] - if self._skip_tensor(op_id, out_tensor, user_included, - user_excluded): - continue - tensor_name = out_tensor.name - if tensor_name in self._traced_tensorname_to_cache_idx_map: - raise ValueError( - 'Tensor name %s should not be already in ' - 'traced_tensorname_to_cache_idx_map'%tensor_name) - if tensor_name not in self._tensorname_idx_map: - raise ValueError( - 'Tensor name %s is not in the tensorname_idx_map'%tensor_name) - tensor_idx = self._tensorname_idx_map[tensor_name] - cache_idx = len(self._traced_tensorname_to_cache_idx_map) - self._traced_tensorname_to_cache_idx_map[tensor_name] = cache_idx - self._cache_idx_to_tensor_idx.append(tensor_idx) - if len(self._traced_tensorname_to_cache_idx_map) != len( - self._cache_idx_to_tensor_idx): - raise RuntimeError('len(self._traced_tensorname_to_cache_idx_map) != ' - 'len(self._cache_idx_to_tensor_idx') - - def _check_trace_files(self): - """Checks if any requirements for trace files are satisfied.""" - - if not self._trace_dir: - # traces will be written to stderr. No need to check trace files. - return - if _trace_files_need_precreated(self._trace_dir): - for replica_id in range(0, self._num_replicas): - trace_file_path = os.path.join( - self._trace_dir, - _COMPACT_TRACE_FILE_PREFIX) + '%d'%replica_id - if not gfile.Exists(trace_file_path): - raise RuntimeError( - '%s must be pre-created with the ' - 'appropriate properties.'%trace_file_path) - else: - if not gfile.Exists(self._trace_dir): - gfile.MkDir(self._trace_dir) - if not gfile.Exists(self._trace_dir): - raise RuntimeError('Failed to create %s'%self._trace_dir) - - def _pre_tracing(self, graph, fetches): - """Work needs to be done prior to TPU or CPU tracing.""" - - self._check_trace_files() - operations = graph.get_operations() - (opname_idx_map, tensor_list, self._tensorname_idx_map) = ( - TensorTracer._make_op_and_tensor_maps(operations)) - self._write_config_section() - self._write_op_list_section(operations) - self._write_tensor_list_section(tensor_list, opname_idx_map) - # Filter out the operations that won't be executed. - # if fetches=None, then ops_in_exec_path = set(operations) - ops_in_exec_path = self._filter_execution_path_operations(operations, - fetches) - self._determine_traced_tensors(graph, ops_in_exec_path) - self._write_cache_index_map_section() - # Does the topological sort before adding any nodes to the graph. - (succeed, sorted_or_cycle) = TensorTracer.topological_sort(graph) - if self._use_tensor_values_cache(): - _create_tensor_values_cache(graph, - len(self._cache_idx_to_tensor_idx)) - return (ops_in_exec_path, succeed, sorted_or_cycle) - - def _post_tracing(self, succeed, sorted_or_cycle): - """Work needs to be done after TPU or CPU tracing.""" - - self._write_reason_section() - self._write_graph_section(succeed, sorted_or_cycle) - self._close_report_file() - - def _get_checkpoints(self, graph): - """Returns the list of Ops that produce the tensors traced with API. - - Args: - graph: the graph of Ops. - - Returns: - A set of operation names which should be traced. - """ - - self._write_report('%s %s\n'%(_MARKER_SECTION_BEGIN, - _TENSOR_TRACER_CHECKPOINT)) - checkpoint_operations = set() - tensor_tracer_variables = graph.get_collection(_TENSOR_TRACER_COLLECTION) - for (tensor, checkpoint_name) in tensor_tracer_variables: - self._write_report('%s %s\n'%(tensor.name, checkpoint_name)) - checkpoint_operations.add(tensor.op.name) - self._write_report('%s %s\n'%(_MARKER_SECTION_END, - _TENSOR_TRACER_CHECKPOINT)) - return checkpoint_operations - - def _generate_flush_cache_op(self, graph, start_replica, on_tpu): - """Generates an Op that will flush the cache to file. - - Args: - graph: the graph of Ops - start_replica: the ID of the first replica being flushed by this Op. - on_tpu: if the graph is executed on TPU. - - Returns: - The Op to flush the cache to file. - """ - def _make_flush_fun(replica_id): - """Makes a function for flushing the cache for the given replica.""" - - def _fun(): - """A function that flushes the cache to a file.""" - - def _flush_fun(cache): - """Flushes the cache to a file.""" - - if isinstance(replica_id, str): - replica_id_str = replica_id - else: - replica_id_str = '%d'%replica_id - if self._trace_dir: - output_path = os.path.join(self._trace_dir, - _COMPACT_TRACE_FILE_PREFIX) \ - + replica_id_str - output_stream = _OUTPUT_STREAM_ESCAPE + output_path - else: - output_stream = sys.stderr - new_step_line = _REPLICA_ID_TAG + replica_id_str - print_op = logging_ops.print_v2( - new_step_line, '\n', - cache, '\n', - summarize=-1, - output_stream=output_stream) - with ops.control_dependencies([print_op]): - return constant_op.constant(0).op - - cache = _get_tensor_values_cache(graph) - if on_tpu: - flush_op = tpu.outside_compilation(_flush_fun, cache.value()) - else: - flush_op = _flush_fun(cache.value()) - with ops.control_dependencies([flush_op]): - reset_value = constant_op.constant(_COMPACT_TRACE_ENTRY_INIT_VALUE, - dtype=cache.dtype, - shape=cache.shape) - assign_op = state_ops.assign(cache, reset_value).op - with ops.control_dependencies([assign_op]): - return flush_op.outputs[0] - - return _fun - - def _f(replica_id): - return _make_flush_fun(replica_id) - def _eq(x): - return math_ops.equal(x, self._replica_id) - def _do_nothing(): - return constant_op.constant(0) - - return control_flow_ops.case({\ - _eq(start_replica): _f(start_replica), \ - _eq(start_replica+1): _f(start_replica+1), \ - _eq(start_replica+2): _f(start_replica+2), \ - _eq(start_replica+3): _f(start_replica+3), \ - _eq(start_replica+4): _f(start_replica+4), \ - _eq(start_replica+5): _f(start_replica+5), \ - _eq(start_replica+6): _f(start_replica+6), \ - _eq(start_replica+7): _f(start_replica+7), \ - }, - default=_do_nothing, - exclusive=True).op - - def _flush_tensor_values_cache(self, graph, tensor_fetches, op_fetches, - on_tpu): - """Flushes the intermediate tensor values in the graph to the cache. - - Args: - graph: the graph of Ops - tensor_fetches: list of tensor results returned by the model_fn. - op_fetches: list of ops that are returned by the model_fn, e.g., train_op. - on_tpu: if the graph is executed on TPU. - - Returns: - An identical copy of tensor_fetches. - """ - # Add a dependency to op and tensor fetches to make sure that all tracing - # ops are executed before flushing trace results. - with ops.control_dependencies(op_fetches + - [tensor.op for tensor in tensor_fetches]): - flush_cache_op_list = [] - for host in range(self._num_hosts): - start_replica = host * 8 - flush_op = self._generate_flush_cache_op(graph, start_replica, on_tpu) - flush_cache_op_list.append(flush_op) - return control_flow_ops.tuple(tensor_fetches, - control_inputs=flush_cache_op_list) - - def _process_tensor_fetches(self, tensor_fetches): - """Check that tensor_fetches is not empty and have valid tensors.""" - # If none or empty list. - if tensor_fetches is None: - raise RuntimeError('tensor_fetches provided to tensor_tracer cannot be ' - 'None.') - if not isinstance(tensor_fetches, (list, tuple)): - tensor_fetches = [tensor_fetches] - elif not tensor_fetches: - raise RuntimeError('tensor_fetches provided to tensor_tracer cannot be ' - 'empty list.') - fetches = [] - for fetch in tensor_fetches: - if isinstance(fetch, ops.Tensor): - fetches.append(fetch) - else: - raise RuntimeError('Given tensor_fetch:%s is not a tensor.' % fetch) - return fetches - - def _process_op_fetches(self, op_fetches): - """Check that op_fetches have valid ops.""" - if op_fetches is None: - return [] - - if not isinstance(op_fetches, (list, tuple)): - op_fetches = [op_fetches] - - fetches = [] - for fetch in op_fetches: - if isinstance(fetch, ops.Operation): - fetches.append(fetch) - else: - logging.warning('Ignoring the given op_fetch:%s, which is not an op.' % - fetch) - return fetches - - def _convert_fetches_to_input_format(self, input_fetches, current_fetches): - """Changes current_fetches' format, so that it matches input_fetches.""" - if isinstance(input_fetches, ops.Tensor): - if len(current_fetches) != 1: - raise RuntimeError('Tensor tracer input/output fetches do not match.') - return current_fetches[0] - else: - if len(current_fetches) != len(current_fetches): - raise RuntimeError('Tensor tracer input/output fetches do not match.') - elif isinstance(input_fetches, tuple): - return tuple(current_fetches) - else: - return current_fetches - - def _get_op_control_flow_context(self, op): - """Returns the control flow of the given op. - - Args: - op: tf.Operation for which the control flow context is requested. - Returns: - op_control_flow_context: which the is control flow context of the given - op. If the operation type is LoopExit, returns the outer control flow - context. - """ - # pylint: disable=protected-access - op_control_flow_context = op._control_flow_context - # pylint: enable=protected-access - if control_flow_util.IsLoopExit(op): - op_control_flow_context = op_control_flow_context.outer_context - return op_control_flow_context - - def _trace_execution(self, graph, - tensor_fetches, - op_fetches=None, - on_tpu=True): - """Commong tracing function for both CPU and TPUs. - - The caller function should set _device_type, _num_replicas, - _num_replicas_per_host, _num_hosts and _replica_id before calling - _trace_execution. - - - Args: - graph: the graph of Ops executed on the TPU. - tensor_fetches: a (list,tuple,or a single object) of tensor fetches - returned by model_fn given to session.run. Function must be provided - with as least one tensor to fetch. - op_fetches: A list of op fetches returned by model_fn given to - session.run. op_fetches and tensor_fetches are used to determine the - nodes that will be executed. Can be None. - on_tpu: True if executing on TPU. - - Returns: - tensor_fetches: an exact copy of tensor_fetches that has additional - dependencies. - Raises: - RuntimeError: If tensor_fetches is None or empty. - """ - def _cast_unsupported_dtypes(tensor): - """Casts tensor to a supported type.""" - - if tensor.dtype.__eq__(dtypes.int64): - # outside-compilation doesn't support int64 input yet. - return math_ops.cast(tensor, dtypes.int32) - if tensor.dtype.__eq__(dtypes.bfloat16) or tensor.dtype.__eq__( - dtypes.float16): - # Since host can't handle bf16, convert tensor to f32. - return math_ops.cast(tensor, dtypes.float32) - return tensor - - TensorTracer.check_device_type(self._device_type) - # Check in_tensor_fetches, and op_fetches and convert them to lists. - processed_t_fetches = self._process_tensor_fetches(tensor_fetches) - op_fetches = self._process_op_fetches(op_fetches) - all_fetches = op_fetches + [tensor.op for tensor in processed_t_fetches] - - # Filter the set of ops that will be executed, and topological sort. - (exec_op_set, succeed, sorted_or_cycle) = self._pre_tracing(graph, - all_fetches) - - tensor_fetch_set = set(processed_t_fetches) - tracing_ops = [] - - # pylint: disable=protected-access - current_control_flow_context = graph._get_control_flow_context() - # pylint: enable=protected-access - - # Trace ops only if they are in the execution path. - for op in exec_op_set: - for i in range(len(op.outputs)): - out_tensor = op.outputs[i] - tensor_name = out_tensor.name - if tensor_name not in self._traced_tensorname_to_cache_idx_map: - continue - # Create the list of consumers before calling _preprocess_traced_tensor. - # Otherwise, adding control input below, will introduce a cycle in the - # graph. - consumers = out_tensor.consumers() - # Not all consumers may be in the exec path. Filter out the consumers - # to keep the graph simpler. - consumers = [cop for cop in consumers if cop in exec_op_set] - - # If there is no consumer of the tensor, there is no need to trace it; - # unless the tensor itself is one of the fetches. - is_a_fetched_tensor = out_tensor in tensor_fetch_set - if (not consumers) and (not is_a_fetched_tensor): - continue - - op_control_flow_context = self._get_op_control_flow_context(op) - # pylint: disable=protected-access - graph._set_control_flow_context(op_control_flow_context) - # pylint: enable=protected-access - processed_out_tensor = self._preprocess_traced_tensor(out_tensor) - - if on_tpu: - processed_out_tensor = _cast_unsupported_dtypes(processed_out_tensor) - - if self._use_tensor_values_cache(): - cache_idx = self._traced_tensorname_to_cache_idx_map[tensor_name] - trace_op = self._save_tensor_value_to_cache_op(graph, - cache_idx, - processed_out_tensor) - elif on_tpu: - trace_op = tpu.outside_compilation( - self._make_tensor_trace_fun(tensor_name), processed_out_tensor) - else: - trace_fun = self._make_tensor_trace_fun(tensor_name) - trace_op = trace_fun(processed_out_tensor) - - if is_a_fetched_tensor: - tracing_ops.append(trace_op) - continue - # Add it to all consumers, as some consumers may not be executed if they - # are in a control flow. - for consumer_op in consumers: - # pylint: disable=protected-access - consumer_op._add_control_input(trace_op) - # pylint: enable=protected-access - - # pylint: disable=protected-access - graph._set_control_flow_context(current_control_flow_context) - # pylint: enable=protected-access - if tracing_ops: - # If we are tracing a fetched tensor, their dependency is stored in - # tracing_ops. - processed_t_fetches = control_flow_ops.tuple(processed_t_fetches, - control_inputs=tracing_ops) - if self._use_tensor_values_cache(): - processed_t_fetches = self._flush_tensor_values_cache(graph, - processed_t_fetches, - op_fetches, - on_tpu=on_tpu) - self._post_tracing(succeed, sorted_or_cycle) - # processed_t_fetches is a list at this point. Convert it to the same - # format as given in tensor_fetches. - return self._convert_fetches_to_input_format(tensor_fetches, - processed_t_fetches) - - def trace_tpu(self, graph, - tensor_fetches, - op_fetches=None, - num_replicas=None, - num_replicas_per_host=None, - num_hosts=None): - """Traces the tensors generated by TPU Ops in a TF graph. - - Args: - graph: the graph of Ops executed on the TPU. - tensor_fetches: a (list,tuple,or a single object) of tensor fetches - returned by model_fn given to session.run. Function must be provided - with as least one tensor to fetch. - op_fetches: A list of op fetches returned by model_fn given to - session.run. op_fetches and tensor_fetches are used to determine the - nodes that will be executed. Can be None. - num_replicas: number of replicas used on the TPU. - num_replicas_per_host: number of replicas per TPU host. - num_hosts: total number of TPU hosts. - - Returns: - tensor_fetches: an exact copy of tensor_fetches that has additional - dependencies. - Raises: - RuntimeError: If num_replicas_per_host > 8. - RuntimeError: If tensor_fetches is None or empty. - """ - - if graph in TensorTracer._traced_graphs: - logging.warning('Graph is already rewritten with tensor tracer, ignoring ' - 'multiple calls.') - return tensor_fetches - else: - TensorTracer._traced_graphs.add(graph) - self._device_type = _DEVICE_TYPE_TPU - self._num_replicas = num_replicas - self._num_replicas_per_host = num_replicas_per_host - self._num_hosts = num_hosts - if self._num_replicas is not None: - if self._num_replicas_per_host is None: - self._num_replicas_per_host = 8 - if self._num_hosts is None: - self._num_hosts = num_replicas // self._num_replicas_per_host + \ - (num_replicas % self._num_replicas_per_host > 0) - - if self._num_replicas_per_host > 8: - # Checks for the assumption in _generate_flush_cache_op(). - raise RuntimeError('num_replicas_per_host (%d) is ' - 'greater than 8'%self._num_replicas_per_host) - if self._graph_dump_path: - graph_io.write_graph(graph, self._graph_dump_path, - 'graph_before_tt.pbtxt') - with graph.as_default(): - self._add_replica_id_to_graph() - tensor_fetches = self._trace_execution(graph, tensor_fetches, op_fetches, - on_tpu=True) - if self._graph_dump_path: - graph_io.write_graph(graph, self._graph_dump_path, - 'graph_after_tt.pbtxt') - return tensor_fetches - - def trace_cpu(self, graph, tensor_fetches, op_fetches=None): - """Traces the tensors generated by CPU Ops in a TF graph. - - Args: - graph: the graph of Ops executed on the CPU. - tensor_fetches: a (list,tuple,or a single object) of tensor fetches - returned by model_fn given to session.run. Function must be provided - with as least one tensor to fetch. - op_fetches: A list of op fetches returned by model_fn given to - session.run. op_fetches and tensor_fetches are used to determine the - nodes that will be executed. Can be None. - - Returns: - tensor_fetches: an exact copy of tensor_fetches that has additional - dependencies. - Raises: - RuntimeError: If tensor_fetches is None or empty. - """ - - if graph in TensorTracer._traced_graphs: - logging.warning('Graph is already rewritten with tensor tracer, ignoring ' - 'multiple calls.') - return tensor_fetches - else: - TensorTracer._traced_graphs.add(graph) - - self._device_type = _DEVICE_TYPE_CPU - self._num_replicas = 1 - self._num_replicas_per_host = 1 - self._num_hosts = 1 - self._replica_id = 0 - if self._graph_dump_path: - graph_io.write_graph(graph, self._graph_dump_path, - 'graph_before_tt.pbtxt') - with graph.as_default(): - tensor_fetches = self._trace_execution(graph, tensor_fetches, op_fetches, - on_tpu=False) - if self._graph_dump_path: - graph_io.write_graph(graph, self._graph_dump_path, - 'graph_after_tt.pbtxt') - return tensor_fetches - - +# pylint: disable=wildcard-import,unused-import +from tensorflow.python.tpu.tensor_tracer import * +# pylint: enable=wildcard-import,unused-import diff --git a/tensorflow/contrib/tpu/python/tpu/topology.py b/tensorflow/contrib/tpu/python/tpu/topology.py index 00ee21e694..5bf805752c 100644 --- a/tensorflow/contrib/tpu/python/tpu/topology.py +++ b/tensorflow/contrib/tpu/python/tpu/topology.py @@ -1,220 +1,23 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 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 +# 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. -# ====================================== -"""Defines the `Topology` class, that describes a TPU fabric topology.""" +# ============================================================================== +"""Stub file to maintain backwards compatibility.""" 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.core.protobuf.tpu import topology_pb2 - - -def _tpu_device_name(job, task, device): - """Returns the device name for the TPU `device` on `task` of `job`.""" - if job is None: - return "/task:%d/device:TPU:%d" % (task, device) - else: - return "/job:%s/task:%d/device:TPU:%d" % (job, task, device) - - -def _tpu_host_device_name(job, task): - """Returns the device name for the CPU device on `task` of `job`.""" - if job is None: - return "/task:%d/device:CPU:0" % task - else: - return "/job:%s/task:%d/device:CPU:0" % (job, task) - - -class Topology(object): - """Describes a set of TPU devices. - - Represents both the shape of the physical mesh, and the mapping between - TensorFlow TPU devices to physical mesh coordinates. - """ - - def __init__(self, serialized=None, mesh_shape=None, device_coordinates=None): - """Builds a Topology object. - - If `serialized` is not `None`, the topology is parsed from `serialized` and - the other arguments are ignored. Otherwise, the topology is computed from - `mesh_shape` and `device_coordinates`. - - Args: - serialized: A serialized `TopologyProto`, or `None`. If not `None`, the - serialized proto is parsed to discover the topology. - mesh_shape: A sequence of 3 positive integers, or `None`. If not `None`, - the shape of the TPU topology, in number of cores. Ignored if - `serialized` is not `None`. - device_coordinates: A rank 3 numpy array that describes the mapping from - TensorFlow TPU devices to TPU fabric coordinates, or `None`. Ignored - if `serialized is not `None`. - - Raises: - ValueError: If `serialized` does not describe a well-formed topology. - ValueError: If `serialized` is `None` and `mesh_shape` is not a sequence - of 3 positive integers. - ValueError: If `serialized` is `None` and `device_coordinates` is not a - rank 3 numpy int32 array that describes a valid coordinate mapping. - """ - - self._serialized = serialized - - if serialized: - self._parse_topology(serialized) - else: - self._mesh_shape = np.asarray(mesh_shape, dtype=np.int32) - self._device_coordinates = np.asarray(device_coordinates, np.int32) - if len(self._mesh_shape) != 3 or any(self._mesh_shape < 1): - raise ValueError("`mesh_shape` must be a sequence of 3 positive " - "entries; got {}".format(self._mesh_shape)) - - if (len(self._device_coordinates.shape) != 3 or - self._device_coordinates.shape[2] != len(self._mesh_shape)): - raise ValueError("`device_coordinates` must be a rank 3 int32 array " - "with minor dimension equal to the mesh shape rank") - - self._topology_tasks, self._topology_devices = self._invert_topology() - - def _parse_topology(self, serialized): - """Parses a serialized `TopologyProto` into `self`.""" - proto = topology_pb2.TopologyProto() - proto.ParseFromString(serialized) - - self._mesh_shape = np.array(proto.mesh_shape, dtype=np.int32) - if len(self._mesh_shape) != 3 or any(self._mesh_shape < 1): - raise ValueError("`mesh_shape` must be a vector of size 3 with positive " - "entries; got {}".format(self._mesh_shape)) - - if proto.num_tasks < 0: - raise ValueError("`num_tasks` must be >= 0; got {}".format( - proto.num_tasks)) - if proto.num_tpu_devices_per_task < 0: - raise ValueError("`num_tpu_devices_per_task` must be >= 0; got {}".format( - proto.num_tpu_devices_per_task)) - - expected_coordinates_size = ( - proto.num_tasks * proto.num_tpu_devices_per_task * len( - proto.mesh_shape)) - if len(proto.device_coordinates) != expected_coordinates_size: - raise ValueError("`device_coordinates` must have shape num_tasks ({}) * " - "num_tpu_devices_per_task ({}) * len(mesh_shape) ({}); " - "got shape {}".format(proto.num_tasks, - proto.num_tpu_devices_per_task, - proto.mesh_shape, - len(proto.device_coordinates))) - - coords = np.array(proto.device_coordinates, dtype=np.int32) - if any(coords < 0): - raise ValueError("`device_coordinates` must be >= 0") - coords = coords.reshape((proto.num_tasks, proto.num_tpu_devices_per_task, - len(proto.mesh_shape))) - self._device_coordinates = coords - - def _invert_topology(self): - """Inverts a [task,device,axis] topology to [x,y,z] -> task/device maps.""" - tasks = np.full(list(self.mesh_shape), -1, dtype=np.int32) - devices = np.full(list(self.mesh_shape), -1, dtype=np.int32) - for task in xrange(self.device_coordinates.shape[0]): - for device in xrange(self.device_coordinates.shape[1]): - x, y, z = self.device_coordinates[task, device, :] - tasks[x, y, z] = task - devices[x, y, z] = device - return tasks, devices - - @property - def mesh_shape(self): - """A rank 1 int32 array describing the shape of the TPU topology.""" - return self._mesh_shape - - @property - def mesh_rank(self): - """Returns the number of dimensions in the mesh.""" - return len(self._mesh_shape) - - @property - def device_coordinates(self): - """Describes the mapping from TPU devices to topology coordinates. - - Returns: - A rank 3 int32 array with shape `[tasks, devices, axis]`. - `tasks` is the number of tasks in the TPU cluster, `devices` is the number - of TPU devices per task, and `axis` is the number of axes in the TPU - cluster topology. Each entry gives the `axis`-th coordinate in the - topology of a task/device pair. TPU topologies are 3-dimensional, with - dimensions `(x, y, core number)`. - """ - return self._device_coordinates - - def task_ordinal_at_coordinates(self, device_coordinates): - """Returns the TensorFlow task number attached to `device_coordinates`. - - Args: - device_coordinates: An integer sequence describing a device's physical - coordinates in the TPU fabric. - - Returns: - Returns the TensorFlow task number that contains the TPU device with those - physical coordinates. - """ - return self._topology_tasks[tuple(device_coordinates)] - - def tpu_device_ordinal_at_coordinates(self, device_coordinates): - """Returns the TensorFlow device number at `device_coordinates`. - - Args: - device_coordinates: An integer sequence describing a device's physical - coordinates in the TPU fabric. - - Returns: - Returns the TensorFlow device number within the task corresponding to - attached to the device with those physical coordinates. - """ - return self._topology_devices[tuple(device_coordinates)] - - def cpu_device_name_at_coordinates(self, device_coordinates, job=None): - """Returns the CPU device attached to a logical core.""" - return _tpu_host_device_name( - job, self._topology_tasks[tuple(device_coordinates)]) - - def tpu_device_name_at_coordinates(self, device_coordinates, job=None): - """Returns the name of the TPU device assigned to a logical core.""" - return _tpu_device_name(job, - self._topology_tasks[tuple(device_coordinates)], - self._topology_devices[tuple(device_coordinates)]) - - @property - def num_tasks(self): - """Returns the number of TensorFlow tasks in the TPU slice.""" - return self._device_coordinates.shape[0] - - @property - def num_tpus_per_task(self): - """Returns the number of TPU devices per task in the TPU slice.""" - return self._device_coordinates.shape[1] - - def serialized(self): - """Returns the serialized form of the topology.""" - if self._serialized is None: - proto = topology_pb2.TopologyProto() - proto.mesh_shape[:] = list(self._mesh_shape) - proto.num_tasks = self._device_coordinates.shape[0] - proto.num_tpu_devices_per_task = self._device_coordinates.shape[1] - proto.device_coordinates.extend(list(self._device_coordinates.flatten())) - self._serialized = proto.SerializeToString() - - return self._serialized +# pylint: disable=wildcard-import,unused-import,redefined-builtin +from tensorflow.python.tpu.topology import * +# pylint: enable=wildcard-import,unused-import,redefined-builtin diff --git a/tensorflow/contrib/tpu/python/tpu/tpu.py b/tensorflow/contrib/tpu/python/tpu/tpu.py index 3b2d053477..5364b20f23 100644 --- a/tensorflow/contrib/tpu/python/tpu/tpu.py +++ b/tensorflow/contrib/tpu/python/tpu/tpu.py @@ -1,1579 +1,25 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 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 +# 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 of TPU helper functions.""" +# ============================================================================== +"""Stub file to maintain backwards compatibility.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function -import collections -from six.moves import xrange # pylint: disable=redefined-builtin - -from tensorflow.contrib.compiler import xla -from tensorflow.contrib.framework.python.framework import experimental -from tensorflow.contrib.tpu.python.ops import tpu_ops -from tensorflow.contrib.tpu.python.tpu import tpu_function -from tensorflow.core.framework import attr_value_pb2 -from tensorflow.core.protobuf.tpu import dynamic_padding_pb2 as dynamic_padding -from tensorflow.python.compat import compat as api_compat -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 ops -from tensorflow.python.framework import tensor_shape -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 variable_scope -from tensorflow.python.platform import tf_logging as logging -from tensorflow.python.util import compat -from tensorflow.python.util import nest - - -# Operations that indicate some error in the users graph, e.g. a placeholder -# that's introduced outside of the infeed. -_BLACKLISTED_OPS = set([ - "Placeholder", -]) - -# XLA doesn't currently support reading of intermediate tensors, thus some ops -# are not supported. -_UNSUPPORTED_OPS = set([ - "AudioSummary", - "AudioSummaryV2", - "HistogramSummary", - "ImageSummary", - "MergeSummary", - "Print", - "ScalarSummary", - "TensorSummary", - "TensorSummaryV2", - ]) - -_MAX_WARNING_LINES = 5 - -_TPU_REPLICATE_ATTR = "_tpu_replicate" -_TPU_COMPILATION_STATUS_ATTR = "_tpu_compilation_status" -_OUTSIDE_COMPILATION_ATTR = "_xla_outside_compilation" - - -def _tpu_system_device_name(job): - """Returns the device name for the TPU_SYSTEM device of `job`.""" - if job is None: - return "/device:TPU_SYSTEM:0" - else: - return "/job:%s/device:TPU_SYSTEM:0" % job - - -def initialize_system(embedding_config=None, job=None): - """Initializes a distributed TPU system for use with TensorFlow. - - Args: - embedding_config: If not None, a `TPUEmbeddingConfiguration` proto - describing the desired configuration of the hardware embedding lookup - tables. If embedding_config is None, no hardware embeddings can be used. - job: The job (the XXX in TensorFlow device specification /job:XXX) that - contains the TPU devices that will be initialized. If job=None it is - assumed there is only one job in the TensorFlow flock, and an error will - be returned if this assumption does not hold. - Returns: - A serialized `TopologyProto` that describes the TPU system. Note: - the topology must be evaluated using `Session.run` before it can be used. - """ - config_string = ("" if embedding_config is None else - embedding_config.SerializeToString()) - with ops.device(_tpu_system_device_name(job)): - return tpu_ops.configure_distributed_tpu(embedding_config=config_string) - - -def shutdown_system(job=None): - """Shuts down a running a distributed TPU system.""" - with ops.device(_tpu_system_device_name(job)): - shutdown_distributed_tpu = tpu_ops.shutdown_distributed_tpu() - return shutdown_distributed_tpu - - -def core(num): - """Returns the device name for a core in a replicated TPU computation. - - Args: - num: the virtual core number within each replica to which operators should - be assigned. - Returns: - A device name, suitable for passing to `tf.device()`. - """ - return "device:TPU_REPLICATED_CORE:{}".format(num) - - -class TPUReplicateContext(control_flow_ops.XLAControlFlowContext): - """A `ControlFlowContext` for nodes inside a TPU computation. - - The primary role of `TPUReplicateContext` is to mark operators inside a - tpu.replicate() computation with the attribute "_tpu_replicate=XYZ", where XYZ - is a unique name. - - We use a `ControlFlowContext` to perform the annotation since it integrates - with Tensorflow constructs like ResourceVariables. For example, if a - `ResourceVariable` is constructed inside a tpu.replicate() block, the - `ResourceVariable` implementation can use - `with ops.control_dependencies(None)` to build the variable's definition - outside the replicated computation. - """ - - def __init__(self, name, num_replicas, pivot): - """Builds a new TPUReplicateContext. - - Args: - name: a unique name for the context, used to populate the `_tpu_replicate` - attribute. - num_replicas: an integer that gives the number of replicas for the - computation. - pivot: a pivot node. Nodes in the TPUReplicateContext that do not have any - inputs will have a control dependency on the pivot node. This ensures - that nodes are correctly included in any enclosing control flow - contexts. - """ - super(TPUReplicateContext, self).__init__() - self._num_replicas = num_replicas - self._outer_device_function_stack = None - self._oc_dev_fn_stack = None - self._outside_compilation_cluster = None - self._outside_compilation_counter = 0 - self._in_gradient_colocation = None - self._gradient_colocation_stack = [] - self._host_compute_core = [] - self._name = name - self._name_as_bytes = compat.as_bytes(name) - self._unsupported_ops = [] - self._pivot = pivot - self._replicated_vars = {} - - def get_replicated_var_handle(self, name, vars_): - """Returns a variable handle for replicated TPU variable 'var'. - - This is a method used by an experimental replicated variable implementation - and is not intended as a public API. - - Args: - name: The common name of the variable. - vars_: The replicated TPU variables. - - Returns: - The handle of the TPU replicated input node. - """ - handle = self._replicated_vars.get(name) - if handle is not None: - return handle - - # Builds a TPUReplicatedInput node for the variable, if one does not already - # exist. The TPUReplicatedInput node must belong to the enclosing - # control-flow scope of the TPUReplicateContext. - # TODO(phawkins): consider changing the contract of the TPU encapsulation - # so the TPUReplicatedInput nodes go inside the TPUReplicateContext scope - # instead. - - # pylint: disable=protected-access - graph = ops.get_default_graph() - saved_context = graph._get_control_flow_context() - graph._set_control_flow_context(self.outer_context) - handle = tpu_ops.tpu_replicated_input( - [v.handle for v in vars_], name=name + "/handle") - graph._set_control_flow_context(saved_context) - # pylint: enable=protected-access - self._replicated_vars[name] = handle - return handle - - def report_unsupported_operations(self): - if self._unsupported_ops: - op_str = "\n".join([" %s (%s)" % (op.type, op.name) - for op in self._unsupported_ops[:_MAX_WARNING_LINES]]) - logging.warning("%d unsupported operations found: \n%s", - len(self._unsupported_ops), op_str) - if len(self._unsupported_ops) > _MAX_WARNING_LINES: - logging.warning("... and %d more" % - (len(self._unsupported_ops) - _MAX_WARNING_LINES)) - - def EnterGradientColocation(self, op, gradient_uid): - if op is not None: - self._gradient_colocation_stack.append(op) - if not self._outside_compilation_cluster: - try: - outside_attr = op.get_attr(_OUTSIDE_COMPILATION_ATTR) - if self._in_gradient_colocation: - raise NotImplementedError( - "Cannot nest gradient colocation operations outside compilation" - ) - if gradient_uid == "__unsupported__": - raise NotImplementedError( - "No gradient_uid calling gradient within outside_compilation") - # When we take the gradient of an op X in an outside_compilation - # cluster C in a forward computation we would like to put the ops - # corresponding to the gradient of X into a new outside_compilation - # cluster C'. However, if we take the gradient of X twice, the second - # one should get yet another new outside_compilation cluster C''. - # - # The mechanism we adopt is to use a 'root_cluster' which is the - # cluster that X was in before we took gradients, and a 'gradient_uid' - # which is different for every invocation of gradients, and put the - # gradient of X in cluster 'root_cluster.gradient_uid'. - # - # When taking a gradient of a gradient, some ops will be colocated - # with Op in the forward pass (e.g., cluster root_cluster) and some in - # the backward pass (e.g., cluster root_cluster.initial_gradient_uid). - # We need all of the grad-of-grad ops to be in the same cluster to - # avoid cyclic dependencies between clusters. We adopt a heuristic - # that puts any op clustered with root_cluster. in - # root_cluster.gradient_uid, even if xxx was initial_gradient_uid. - self._in_gradient_colocation = op - parts = outside_attr.split(".") - cluster = parts[0] + "." + gradient_uid - self._EnterOutsideCompilationScope(cluster=cluster) - except ValueError: - # The attr was not present: do nothing. - pass - - def ExitGradientColocation(self, op, gradient_uid): - if op is not None: - if not self._gradient_colocation_stack: - raise errors.InternalError( - op.node_def, op, - "Badly nested gradient colocation: empty stack when popping Op " + - op.name) - last_op = self._gradient_colocation_stack.pop() - if op is last_op: - if op is self._in_gradient_colocation: - self._in_gradient_colocation = None - self._ExitOutsideCompilationScope() - else: - raise errors.InternalError( - op.node_def, op, "Badly nested gradient colocation, expected " + - last_op + ", got " + op.name) - - def _EnterOutsideCompilationScope(self, cluster=None): - - class FakeOp(object): - """A helper class to determine the current device. - - Supports only the type and device set/get methods needed to run the - graph's _apply_device_function method. - """ - - def __init__(self): - self._device = "" - - @property - def type(self): - return "FakeOp" - - @property - def device(self): - return self._device - - def _set_device(self, device): - if isinstance(device, pydev.DeviceSpec): - self._device = device.to_string() - else: - self._device = device - - if self._outside_compilation_cluster: - raise NotImplementedError("Cannot nest outside_compilation clusters") - if cluster: - self._outside_compilation_cluster = cluster - else: - self._outside_compilation_cluster = str(self._outside_compilation_counter) - self._outside_compilation_counter += 1 - graph = ops.get_default_graph() - fake_op = FakeOp() - graph._apply_device_functions(fake_op) # pylint: disable=protected-access - device = pydev.DeviceSpec.from_string(fake_op.device) - if (device.device_type == "TPU_REPLICATED_CORE" and - device.device_index is not None): - self._host_compute_core.append(self._outside_compilation_cluster + ":" + - str(device.device_index)) - self._oc_dev_fn_stack = graph._device_function_stack # pylint: disable=protected-access - graph._device_function_stack = self._outer_device_function_stack # pylint: disable=protected-access - - def _ExitOutsideCompilationScope(self): - if not self._outside_compilation_cluster: - raise NotImplementedError( - "Attempted to exit outside_compilation scope when not in scope") - self._outside_compilation_cluster = None - graph = ops.get_default_graph() - graph._device_function_stack = self._oc_dev_fn_stack # pylint: disable=protected-access - - def Enter(self): - if not self._outer_device_function_stack: - # Capture the device function stack at the time of first entry - # since that is the stack that will be used outside_compilation. - graph = ops.get_default_graph() - # pylint: disable=protected-access - self._outer_device_function_stack = graph._device_function_stack.copy() - # pylint: enable=protected-access - super(TPUReplicateContext, self).Enter() - - def HostComputeCore(self): - return self._host_compute_core - - def _RemoveExternalControlEdges(self, op): - """Remove any external control dependency on this op.""" - internal_control_inputs = [] - external_control_inputs = [] - for x in op.control_inputs: - # pylint: disable=protected-access - is_internal_op = False - ctxt = x._get_control_flow_context() - while ctxt is not None: - if ctxt == self: - is_internal_op = True - break - ctxt = ctxt._outer_context - if is_internal_op: - internal_control_inputs.append(x) - else: - external_control_inputs.append(x) - # pylint: enable=protected-access - # pylint: disable=protected-access - op._remove_all_control_inputs() - op._add_control_inputs(internal_control_inputs) - # pylint: enable=protected-access - return internal_control_inputs, external_control_inputs - - def AddOp(self, op): - # pylint: disable=protected-access - if op.type in _BLACKLISTED_OPS: - logging.error("Operation of type %s (%s) is not supported on the TPU. " - "Execution will fail if this op is used in the graph. " % - (op.type, op.name)) - - if op.type in _UNSUPPORTED_OPS: - self._unsupported_ops.append(op) - - if any(x.dtype._is_ref_dtype for x in op.inputs): - raise NotImplementedError( - "Non-resource Variables are not supported inside TPU computations " - "(operator name: %s)" % op.name) - if _TPU_REPLICATE_ATTR in op.node_def.attr: - raise ValueError("TPU computations cannot be nested") - op._set_attr(_TPU_REPLICATE_ATTR, - attr_value_pb2.AttrValue(s=self._name_as_bytes)) - if self._outside_compilation_cluster: - op._set_attr( - _OUTSIDE_COMPILATION_ATTR, - attr_value_pb2.AttrValue( - s=compat.as_bytes(self._outside_compilation_cluster))) - if self._num_replicas > 1 or not self._outside_compilation_cluster: - # Prevent feeding or fetching anything that is being compiled, - # and any replicated outside_compilation Op. - op.graph.prevent_feeding(op) - op.graph.prevent_fetching(op) - - # Remove any control edges from outer control flow contexts. These may cause - # mismatched frame errors. - (internal_control_inputs, - external_control_inputs) = self._RemoveExternalControlEdges(op) - - if not op.inputs: - # Add a control edge from the control pivot to this op. - if not internal_control_inputs: - # pylint: disable=protected-access - op._add_control_input(self.GetControlPivot()) - # pylint: enable=protected-access - else: - for index in xrange(len(op.inputs)): - x = op.inputs[index] - real_x = self.AddValue(x) - if real_x != x: - op._update_input(index, real_x) # pylint: disable=protected-access - - if external_control_inputs: - # Use an identity to pull control inputs as data inputs. Note that we - # ignore ops which don't have outputs. TODO(phawkins): fix that. - with ops.control_dependencies(None): - self.Enter() - external_control_inputs = [ - array_ops.identity(x.outputs[0]).op - for x in external_control_inputs - if x.outputs - ] - self.Exit() - # pylint: disable=protected-access - op._add_control_inputs(external_control_inputs) - # pylint: enable=protected-access - - # Mark op's outputs as seen by this context and any outer contexts. - output_names = [x.name for x in op.outputs] - context = self - while context is not None: - # pylint: disable=protected-access - context._values.update(output_names) - context = context._outer_context - # pylint: enable=protected-access - - if self._outer_context: - self._outer_context.AddInnerOp(op) - - def AddValue(self, val): - """Add `val` to the current context and its outer context recursively.""" - if val.name in self._values: - # Use the real value if it comes from outer context. - result = self._external_values.get(val.name) - return val if result is None else result - - result = val - self._values.add(val.name) - if self._outer_context: - result = self._outer_context.AddValue(val) - self._values.add(result.name) - - self._external_values[val.name] = result - - return result - - def AddInnerOp(self, op): - self.AddOp(op) - if self._outer_context: - self._outer_context.AddInnerOp(op) - - @property - def grad_state(self): - # Define the gradient loop state associated with the TPUReplicateContext to - # be None as the TPUReplicateContext does not get nested nor does the - # grad_state outside the TPUReplicateContext affect the graph inside so the - # grad_state should be as if this is the top-level gradient state. - return None - - @property - def back_prop(self): - """Forwards to the enclosing while context, if any.""" - if self.GetWhileContext(): - return self.GetWhileContext().back_prop - return False - - def GetControlPivot(self): - return self._pivot - - -def outside_compilation(computation, *args, **kwargs): - """Builds part of a computation outside any current TPU replicate scope. - - Args: - computation: A Python function that builds the computation to - place on the host. - *args: the positional arguments for the computation. - **kwargs: the keyword arguments for the computation. - - Returns: - The Tensors returned by computation. - """ - args = [] if args is None else args - graph = ops.get_default_graph() - - # If we are in a TPUReplicateContext, signal that we are now - # outside_compilation - initial_context = graph._get_control_flow_context() # pylint: disable=protected-access - context = initial_context - while context: - if isinstance(context, TPUReplicateContext): - context._EnterOutsideCompilationScope() # pylint: disable=protected-access - context = context.outer_context - - retval = computation(*args, **kwargs) - - # If we are in a TPUReplicateContext, signal that we are no longer - # outside_compilation - final_context = graph._get_control_flow_context() # pylint: disable=protected-access - if initial_context is not final_context: - raise NotImplementedError( - "Control-flow context cannot be different at start and end of an " - "outside_compilation scope") - context = initial_context - while context: - if isinstance(context, TPUReplicateContext): - context._ExitOutsideCompilationScope() # pylint: disable=protected-access - context = context.outer_context - - return retval - - -def replicate(computation, - inputs=None, - infeed_queue=None, - device_assignment=None, - name=None, - maximum_shapes=None): - """Builds a graph operator that runs a replicated TPU computation. - - Args: - computation: A Python function that builds the computation to replicate. - inputs: A list of lists of input tensors or `None` (equivalent to - `[[]]`), indexed by `[replica_num][input_num]`. All replicas must - have the same number of inputs. Each input can be a nested structure - containing values that are convertible to tensors. Note that passing an - N-dimension list of compatible values will result in a N-dimention list of - scalar tensors rather than a single Rank-N tensors. If you need different - behavior, convert part of inputs to tensors with `tf.convert_to_tensor`. - infeed_queue: If not `None`, the `InfeedQueue` from which to append a tuple - of arguments as inputs to computation. - device_assignment: If not `None`, a `DeviceAssignment` describing the - mapping between logical cores in the computation with physical cores in - the TPU topology. Uses a default device assignment if `None`. The - `DeviceAssignment` may be omitted if each replica of the computation uses - only one core, and there is either only one replica, or the number of - replicas is equal to the number of cores in the TPU system. - name: (Deprecated) Does nothing. - maximum_shapes: A nested structure of tf.TensorShape representing the shape - to which the respective component of each input element in each replica - should be padded. Any unknown dimensions (e.g. tf.Dimension(None) in a - tf.TensorShape or -1 in a tensor-like object) will be padded to the - maximum size of that dimension over all replicas. Note that if the input - dimension is already static, we won't do padding on it and we require the - maximum_shapes to have the same value or None on that dimension. The - structure of `maximum_shapes` needs to be the same as `inputs[0]`. - Returns: - A list of outputs, indexed by `[replica_num]` each output can be a nested - structure same as what computation() returns with a few exceptions. - - Exceptions include: - 1) None output: a NoOp would be returned which control-depends on - computation. - 2) Single value output: A tuple containing the value would be returned. - 3) Operation-only outputs: a NoOp would be returned which - control-depends on computation. - TODO(b/121383831): Investigate into removing these special cases. - - Raises: - ValueError: If all replicas do not have equal numbers of input tensors. - ValueError: If the number of inputs per replica does not match - the number of formal parameters to `computation`. - ValueError: If the static `inputs` dimensions don't match with the values - given in `maximum_shapes`. - ValueError: If the structure of inputs per replica does not match - the structure of `maximum_shapes`. - """ - return split_compile_and_replicate( - computation, - inputs, - infeed_queue, - device_assignment, - name, - maximum_shapes=maximum_shapes)[1] - - -def _pad_all_input(inputs, padded_shapes): - """Pad all input tensors given padded_shapes. - - The real shape tensors will be concatenated with the padded original inputs. - - Args: - inputs: The original inputs. - padded_shapes: A list of padded shapes for each input. - - Returns: - The padded inputs and a PaddingMap list which maps the padded input - dimension to the real shape argument index. - """ - input_shape_tensors = [] - for core_idx, inputs_per_core in enumerate(inputs): - for idx, input_tensor in enumerate(inputs_per_core): - if core_idx == 0: - input_shape_tensors.append([]) - input_shape_tensors[idx].append(array_ops.shape(input_tensor)) - - maximum_shapes = [] - for shapes_per_input in input_shape_tensors: - maximum_shapes.append( - math_ops.reduce_max(array_ops.stack(shapes_per_input), axis=0)) - - padded_inputs = [] - real_shapes = [] - padding_maps = [] - for core_idx, inputs_per_core in enumerate(inputs): - padded_inputs.append([]) - real_shapes.append([]) - real_shape_idx = len(inputs_per_core) - 1 - for idx, input_tensor in enumerate(inputs_per_core): - input_shape_tensor = input_shape_tensors[idx][core_idx] - input_shape = input_tensor.get_shape() - padded_shape = padded_shapes[idx] - - # The static shape of inputs should be compatible with the given padded - # shapes. - input_shape.assert_is_compatible_with(padded_shape) - - if input_shape.is_fully_defined(): - # Do nothing if the shape of the whole tensor is already static. - padded_inputs[core_idx].append(input_tensor) - else: - # Only pad the non static shape dimension. - for i, s in enumerate(input_shape): - if s.value is None: - if core_idx == 0: - real_shape_idx += 1 - padding_map = dynamic_padding.PaddingMap() - padding_map.arg_index = idx - padding_map.shape_index = i - padding_map.padding_arg_index = real_shape_idx - padding_maps.append(padding_map) - real_shapes[core_idx].append( - math_ops.cast(input_shape_tensor[i], dtypes.uint32)) - - paddings = [] - for i, s in enumerate(padded_shape): - if input_shape[i].value: - # Don't pad if input shape is already static. - padding = [0, 0] - else: - if s.value: - # Pad to the given maximum value. - padding = [0, s.value - input_shape_tensor[i]] - else: - # If maximum value is not given, then pad to the maximum dimension - # among all the cores. - padding = [0, maximum_shapes[idx][i] - input_shape_tensor[i]] - paddings.append(padding) - - padded_input = array_ops.pad(input_tensor, paddings) - padded_inputs[core_idx].append(padded_input) - - num_replicas = len(padded_inputs) - for i in range(num_replicas): - padded_inputs[i].extend(real_shapes[i]) - - return padded_inputs, padding_maps - - -def split_compile_and_replicate(computation, - inputs=None, - infeed_queue=None, - device_assignment=None, - name=None, - use_tpu=True, - maximum_shapes=None): - """Builds graph operators that runs compilation and replicated computation. - - This is a lower level interface than replicate that returns a separate compile - and execute output tensor. In the generated graph the compile op feeds into - the execute op and no additional compilation is incurred when running the - compile op before the execute op. The compile op returns additional - information about the compilation but does not return the compiled program. - - Args: - computation: A Python function that builds the computation to replicate. - inputs: A list of lists of input tensors or `None` (equivalent to - `[[]]`), indexed by `[replica_num][input_num]`. All replicas must - have the same number of inputs. Each input can be a nested structure - containing values that are convertible to tensors. Note that passing an - N-dimension list of compatible values will result in a N-dimention list of - scalar tensors rather than a single Rank-N tensors. If you need different - behavior, convert part of inputs to tensors with `tf.convert_to_tensor`. - infeed_queue: If not `None`, the `InfeedQueue` from which to append a tuple - of arguments as inputs to computation. - device_assignment: If not `None`, a `DeviceAssignment` describing the - mapping between logical cores in the computation with physical cores in - the TPU topology. Uses a default device assignment if `None`. The - `DeviceAssignment` may be omitted if each replica of the computation uses - only one core, and there is either only one replica, or the number of - replicas is equal to the number of cores in the TPU system. - name: (Deprecated) Does nothing. - use_tpu: When false, the input `computation` is executed on the XLA CPU/GPU - backends. Currently, only supports a default placement (computation is - placed on GPU if one is available, and on CPU if not). - maximum_shapes: A nested structure of tf.TensorShape representing the shape - to which the respective component of each input element in each replica - should be padded. Any unknown dimensions (e.g. tf.Dimension(None) in a - tf.TensorShape or -1 in a tensor-like object) will be padded to the - maximum size of that dimension over all replicas. Note that if the input - dimension is already static, we won't do padding on it and we require the - maximum_shapes to have the same value or None on that dimension. The - structure of `maximum_shapes` needs to be the same as `inputs[0]`. - - Returns: - A list of lists with the first list corresponding to the compile op and the - second a list of output tensors, indexed by `[replica_num][output_num]`. - Raises: - ValueError: If all replicas do not have equal numbers of input tensors. - ValueError: If the number of inputs per replica does not match - the number of formal parameters to `computation`. - ValueError: If the static `inputs` dimensions don't match with the values - given in `maximum_shapes`. - ValueError: If the structure of inputs per replica does not match - the structure of `maximum_shapes`. - """ - del name - inputs = [[]] if inputs is None else inputs - - metadata_kwargs = {} - if device_assignment is not None: - # Turn the Numpy array into a flattened list so we can pass it as an - # operator attribute. - metadata_kwargs = { - "topology": - device_assignment.topology.serialized(), - "device_assignment": - device_assignment.core_assignment.flatten().tolist() - } - # TODO(phawkins): remove this case after the forward compatibility window - # expires on 2018-10-5. - if api_compat.forward_compatible(2018, 10, 5): - metadata_kwargs["num_cores_per_replica"] = ( - device_assignment.num_cores_per_replica) - else: - metadata_kwargs["computation_shape"] = [ - device_assignment.num_cores_per_replica - ] - - if ((not isinstance(inputs, list)) or - any(not isinstance(inp, (list, tuple)) for inp in inputs)): - raise TypeError("tpu.replicate() inputs must be a list of lists/tuples") - - num_replicas = len(inputs) - - # No replicas? Nothing to do. - if num_replicas == 0: - return [] - - # Checks all replicas have the same structure. - for i in xrange(1, num_replicas): - nest.assert_same_structure(inputs[0], inputs[i]) - - # Flatten inputs. - flat_inputs = [ - nest.flatten(per_replica_input) for per_replica_input in inputs - ] - # Converts inputs to Tensors. - flat_inputs = [[ops.convert_to_tensor(x) for x in inp] for inp in flat_inputs] - - # Verifies that all replicas have matching numbers and types of inputs - flat_input_types = [x.dtype for x in flat_inputs[0]] - input_arity = len(inputs[0]) - flat_input_arity = len(flat_input_types) - for i in range(num_replicas): - if len(inputs[i]) != input_arity: - raise ValueError("Replicas must have the same number of inputs. " - "Replica 0 had {} inputs, replica {} had {} " - "inputs.".format(input_arity, i, len(inputs[i]))) - - types = [x.dtype for x in flat_inputs[i]] - if types != flat_input_types: - raise ValueError("Replicas must have matching input types. Replica 0 had " - "input types {}, replica {} had input types {}".format( - flat_input_types, i, types)) - - arg_error = xla.check_function_argument_count( - computation, input_arity, infeed_queue) - if arg_error is not None: - if infeed_queue is None: - raise TypeError( - "Supplied computation cannot be called with the specified inputs. " - "You specified %d inputs: %s, but the computation needs %s" % ( - input_arity, str([i.name for i in inputs[0]]), arg_error)) - else: - raise TypeError( - "Supplied computation cannot be called with the specified inputs. " - "You specified %d inputs: %s and %d additional inputs from infeed," - " but the computation needs %s" % (input_arity, str( - [i.name - for i in inputs[0]]), infeed_queue.number_of_tuple_elements, - arg_error)) - - if maximum_shapes: - if infeed_queue: - raise ValueError( - "Dynamic input shapes are not supported with infeed queues") - - # Make sure maximum_shapes has the same structure as inputs. - nest.assert_same_structure(inputs[0], maximum_shapes, check_types=False) - - # Flatten padded shapes. - flat_maximum_shapes = nest.flatten(maximum_shapes) - flat_maximum_shapes = [ - tensor_shape.TensorShape(s) for s in flat_maximum_shapes - ] - - flat_inputs, padding_maps = _pad_all_input(flat_inputs, flat_maximum_shapes) - - serialized_padding_maps = [] - for padding_map in padding_maps: - serialized_padding_maps.append(padding_map.SerializeToString()) - metadata_kwargs["padding_map"] = serialized_padding_maps - - metadata_kwargs["step_marker_location"] = getattr( - computation, "step_marker_location", "STEP_MARK_AT_ENTRY") - - graph = ops.get_default_graph() - - # Fan-in: Builds a TPUReplicatedInput node for each input. - flat_replicated_inputs = [] - for i in range(0, len(flat_inputs[0])): - replicas = [flat_inputs[replica][i] for replica in xrange(num_replicas)] - flat_replicated_inputs.append( - tpu_ops.tpu_replicated_input(replicas, name="input{}".format(i))) - - cluster_name = graph.unique_name("cluster") - pivot = control_flow_ops.no_op(name=cluster_name + "/pivot") - context = TPUReplicateContext( - name=cluster_name, num_replicas=num_replicas, pivot=pivot) - try: - context.Enter() - - metadata = tpu_ops.tpu_replicate_metadata( - num_replicas=num_replicas, use_tpu=use_tpu, **metadata_kwargs) - - with tpu_function.tpu_shard_context( - num_replicas), ops.control_dependencies([metadata]): - - # Add identity ops so even unused inputs are "consumed" by the - # computation. This is to avoid orphaned TPUReplicatedInput nodes. - # TODO(phawkins): consider instead pruning unused TPUReplicatedInput - # and eliding trivial TPUReplicatedInput/TPUReplicatedOutput pairs. - flat_replicated_inputs = [ - array_ops.identity(x, name="replicated_input_{}".format(i)) - for i, x in enumerate(flat_replicated_inputs) - ] - for i in flat_replicated_inputs: - # pylint: disable=protected-access - # Add an attribute to the identity node so that they could be removed in - # encapsulate TPU computation pass if unused. However we don't remove - # inputs when dynamic padding is enabled. - # TODO(rxsang): Use other ways except argument index in padding_map so - # outside compilation can work with dynamic padding correctly. - if maximum_shapes is None: - i.op._set_attr("_tpu_input_identity", - attr_value_pb2.AttrValue(b=True)) - # pylint: enable=protected-access - - # Unflatten the computation inputs to match original input structure. - computation_inputs = nest.pack_sequence_as( - structure=inputs[0], - flat_sequence=flat_replicated_inputs[:flat_input_arity]) - - # If there is an infeed queue, adds the dequeued values to the - # computation's inputs. - if infeed_queue is not None: - infeed_queue.set_number_of_shards(num_replicas) - for t in infeed_queue.generate_dequeue_op(): - computation_inputs.append(t) - - # Only resource variables work inside a TPU computation, so turn on - # resource variables for the computation. - # TODO(phawkins): consider removing this code. It will - # be less confusing to clients if they knowingly choose to use resource - # variables. - # Partitioned variables is not supported (b/112311320). - vscope = variable_scope.get_variable_scope() - saved_use_resource = vscope.use_resource - saved_custom_getter = vscope.custom_getter - - def custom_getter(getter, name, *args, **kwargs): - """Variables on TPU have a few restrictions.""" - partitioner = kwargs["partitioner"] - if partitioner is not None: - kwargs["partitioner"] = None - logging.warning( - "Partitioned variables are not supported on TPU. Got " - "`partitioner` that is {} for variable {}. " - "Setting `partitioner` to `None`." - .format(partitioner, name)) - if saved_custom_getter is None: - return getter(name, *args, **kwargs) - else: - return saved_custom_getter(getter, name, *args, **kwargs) - - vscope.set_use_resource(True) - vscope.set_custom_getter(custom_getter) - - outputs = computation(*computation_inputs) - - vscope.set_use_resource(saved_use_resource) - vscope.set_custom_getter(saved_custom_getter) - - outputs_is_flat = xla.is_flat(outputs) - if outputs_is_flat: - output_tensors, control_deps = _postprocess_flat_outputs(outputs) - else: - output_tensors, control_deps = _postprocess_non_flat_outputs(outputs) - - # tensor_tracer imports tpu.py. Local import to tensor_tracer to avoid - # import-cycle - # pylint: disable=g-import-not-at-top - from tensorflow.contrib.tpu.python.tpu import tensor_tracer - # pylint: enable=g-import-not-at-top - if tensor_tracer.TensorTracer.is_enabled(): - tt = tensor_tracer.TensorTracer() - output_tensors = tt.trace_tpu(ops.get_default_graph(), - output_tensors, control_deps, - num_replicas) - - context.ExitResult(output_tensors) - finally: - context.report_unsupported_operations() - context.Exit() - host_compute_core = context.HostComputeCore() - - if host_compute_core: - attr_value = attr_value_pb2.AttrValue() - attr_value.list.s.extend([compat.as_bytes(x) for x in host_compute_core]) - metadata._set_attr("host_compute_core", attr_value) # pylint: disable=protected-access - - with ops.control_dependencies([metadata]): - if use_tpu: - compile_status = tpu_ops.tpu_compilation_result() - op = compile_status.op - attr_value = attr_value_pb2.AttrValue(s=compat.as_bytes(cluster_name)) - op._set_attr(_TPU_COMPILATION_STATUS_ATTR, attr_value) # pylint: disable=protected-access - else: - compile_status = control_flow_ops.no_op(name="compilation_status") - - if not output_tensors: - # Returns a list of NoOps dependent on the replication Op, indexed by - # [replica_num]. - return [ - compile_status, - [ - control_flow_ops.group(control_deps, name="shard_%d" % i) - for i in range(num_replicas) - ] - ] - - # Fan-out: Builds a TPUReplicatedOutput node for each output. - replicated_outputs = [[] for i in xrange(num_replicas)] - for i, t in enumerate(output_tensors): - # Fan-out: Builds a TPUReplicatedOutput node for each output. - ys = tpu_ops.tpu_replicated_output( - t, num_replicas, name="output{}".format(i)) - - # Wraps the outputs in identity operators so the names of any possible - # `fetch` nodes are preserved by the replication rewrite. - with ops.control_dependencies(control_deps): - for replica in xrange(num_replicas): - replicated_outputs[replica].append( - array_ops.identity( - ys[replica], name="output_%d_shard_%d" % (i, replica))) - - if not outputs_is_flat: - replicated_outputs = [ - nest.pack_sequence_as(outputs, replica_outs) - for replica_outs in replicated_outputs - ] - - return [compile_status, replicated_outputs] - - -def _postprocess_flat_outputs(outputs): - """Validates non-flat outputs, add backs device assignments and other attrs. - - Args: - outputs: Output from `computation` inside `tpu.rewrite`. - - Returns: - Tensors and Operations extracted from outputs. - """ - # Following code segment is to preserve legacy behavior. Previously we only - # supported flat outputs and thus for consistency it was nice to convert even - # single element into a tuple. But now that we support arbitrary output - # structure, this is no longer necessary. - # TODO(b/121383831): Migrate all legacy use cases and delete this special - # case. - # If the computation returns `None`, make it an empty tuple. - if outputs is None: - outputs = tuple() - # If the computation only returned one value, makes it a tuple. - if not isinstance(outputs, collections.Sequence): - outputs = (outputs,) - - # Append `no_op` here so that fetching any return value of this function - # will trigger TPUExecute node. - outputs += (control_flow_ops.no_op(),) - try: - with ops.device(core(0)): - outputs = [ - o if isinstance(o, ops.Operation) else ops.convert_to_tensor(o) - for o in outputs - ] - except Exception as e: - raise ValueError( - "TPU function return values must all either be Operations or " - "convertible to Tensors. Got '%s'" % str(e)) - - # Separates the returned Operations and Tensors. - output_operations = [o for o in outputs if isinstance(o, ops.Operation)] - output_tensors = [o for o in outputs if not isinstance(o, ops.Operation)] - - if outputs != output_tensors + output_operations: - raise ValueError( - "TPU functions must return zero-or more Tensor values followed by " - "zero or more Operations.") - - # Wraps outputs in Identity ops. Otherwise a replicated input copied - # straight to an output would bypass the replicate(). This would be bad - # because the TPUReplicatedInput/TPUReplicatedOutput operator would not - # be rewritten away, leading to a runtime error. - # TODO(phawkins): extend the rewrite to elide these nodes instead. - new_output_tensors = [] - for t in output_tensors: - with ops.device(t.device if t.device else core(0)): - o = array_ops.identity(t) - # pylint: disable=protected-access - o.op._set_attr("_tpu_output_identity", attr_value_pb2.AttrValue(b=True)) - # pylint: enable=protected-access - new_output_tensors.append(o) - return new_output_tensors, output_operations - - -def _postprocess_non_flat_outputs(outputs): - """Validates non-flat outputs, add backs device assignments and other attrs. - - Args: - outputs: Output from `computation` inside `tpu.rewrite`. - - Returns: - Tensors extracted from outputs and an empty list because Operations are not - allowed in non-flat outputs.. - """ - - # Flatten output items. - flat_outputs = nest.flatten(outputs) - - # Convert all non-Operation outputs to Tensors. - for i, o in enumerate(flat_outputs): - if isinstance(o, ops.Operation): - raise ValueError( - "tpu.rewrite does not support Operation as return value in non-flat " - "output structure. You can set returned Operations as control " - "dependencies of returned Tensors so Operations are triggered when " - 'Tensors are evaluated. Operation found: "%s"' % o.name) - - try: - o = ops.convert_to_tensor(o) - except Exception as e: - raise ValueError( - "TPU function return values must all either be Operations or " - 'convertible to Tensors. Got error: "%s"' % str(e)) - - # Wraps outputs in Identity ops. Otherwise a replicated input copied - # straight to an output would bypass the replicate(). This would be bad - # because the TPUReplicatedInput/TPUReplicatedOutput operator would not - # be rewritten away, leading to a runtime error. - # TODO(phawkins): extend the rewrite to elide these nodes instead. - with ops.device(core(0)): - o = array_ops.identity(o) - # pylint: disable=protected-access - o.op._set_attr("_tpu_output_identity", attr_value_pb2.AttrValue(b=True)) - # pylint: enable=protected-access - flat_outputs[i] = array_ops.identity(o) - - # All flat_outputs are Tensors, and no Operations. - return flat_outputs, [] - - -def split_compile_and_shard(computation, - inputs=None, - num_shards=1, - input_shard_axes=None, - outputs_from_all_shards=True, - output_shard_axes=None, - infeed_queue=None, - device_assignment=None, - name=None): - """Shards `computation` for parallel execution. - - `inputs` must be a list of Tensors or None (equivalent to an empty list), each - of which has a corresponding split axis (from `input_shard_axes`). Each input - is split into `num_shards` pieces along the corresponding axis, and - computation is applied to each shard in parallel. - - Tensors are broadcast to all shards if they are lexically captured by - `computation`. e.g., - - x = tf.constant(7) - def computation(): - return x + 3 - ... = shard(computation, ...) - - If `outputs_from_all_shards` is true, the outputs from all shards of - `computation` are concatenated back together along their `output_shards_axes`. - Otherwise, each output is taken from an arbitrary shard. - - Inputs and outputs of the computation must be at least rank-1 Tensors. - - Args: - computation: A Python function that builds a computation to apply to each - shard of the input. - inputs: A list of input tensors or None (equivalent to an empty list). Each - input tensor has a corresponding shard axes, given by `input_shard_axes`, - which must have size divisible by `num_shards`. - num_shards: The number of shards. - input_shard_axes: A list of dimensions along which to shard `inputs`, or - `None`. `None` means "shard all inputs along dimension 0". If not `None`, - there must be one dimension per input. - outputs_from_all_shards: Boolean or list of boolean. For each output, if - `True`, outputs from all shards are concatenated along the corresponding - `output_shard_axes` entry. Otherwise, each output is taken - from an arbitrary shard. If the argument is a boolean, the argument's - value is used for each output. - output_shard_axes: A list of dimensions along which to concatenate the - outputs of `computation`, or `None`. `None` means "concatenate all outputs - along dimension 0". If not `None`, there must be one dimension per output. - Ignored if `outputs_from_all_shards` is False. - infeed_queue: If not `None`, the `InfeedQueue` to use to augment the inputs - of `computation`. - device_assignment: If not `None`, a `DeviceAssignment` describing the - mapping between logical cores in the computation with physical cores in - the TPU topology. Uses a default device assignment if `None`. The - `DeviceAssignment` may be omitted if each shard of the computation uses - only one core, and there is either only one shard, or the number of shards - is equal to the number of cores in the TPU system. - name: (Deprecated) Does nothing. - Returns: - A tuple of (compile op, [output tensors]). - Raises: - ValueError: If num_shards <= 0 - ValueError: If len(input_shard_axes) != len(inputs) - ValueError: If len(output_shard_axes) != len(outputs from `computation`) - """ - # TODO(phawkins): consider adding support for broadcasting Tensors passed as - # inputs. - - if num_shards <= 0: - raise ValueError("num_shards must be a positive integer.") - - inputs = [] if inputs is None else inputs - if not isinstance(inputs, list): - raise TypeError("tpu.shard()'s inputs must be a list of Tensors or None.") - - # Converts inputs to Tensors. - inputs = [ops.convert_to_tensor(x) for x in inputs] - - if input_shard_axes is None: - input_shard_axes = [0] * len(inputs) - if len(inputs) != len(input_shard_axes): - raise ValueError("Length of input_shard_axes must be equal to the number " - "of inputs.") - - if inputs: - # Splits the `inputs` along the corresponding `input_shard_axes`, giving - # lists with layout [input][shard] - split_inputs = [ - array_ops.split(x, num_shards, axis=axis) - for (axis, x) in zip(input_shard_axes, inputs)] - - # Transposes the input lists to have layout [shard][input] - transposed_inputs = [list(i) for i in zip(*split_inputs)] - else: - transposed_inputs = [[]] * num_shards - - compile_op, outputs = split_compile_and_replicate( - computation, - transposed_inputs, - infeed_queue=infeed_queue, - device_assignment=device_assignment, - name=name) - - # There must be at least one shard since num_shards > 0. - # TODO(b/36647078) remove disable when pylint bug is fixed. - # pylint: disable=indexing-exception - if isinstance(outputs[0], ops.Operation): - # pylint: enable=indexing-exception - # There were no outputs from the computation and replicate returned a list - # of NoOps with control dependencies on the computation. Return the first - # one so it can be used as a control dependency or fetch node. - # TODO(b/36647078) remove disable when pylint bug is fixed. - # pylint: disable=indexing-exception - return compile_op, [outputs[0]] - # pylint: enable=indexing-exception - - # TODO(b/36647078) remove disable when pylint bug is fixed. - # pylint: disable=indexing-exception - num_outputs = len(outputs[0]) - # pylint: enable=indexing-exception - - if output_shard_axes is None: - output_shard_axes = [0] * num_outputs - if num_outputs != len(output_shard_axes): - raise ValueError("Length of output_shard_axes must be equal to the number " - "of outputs.") - - if isinstance(outputs_from_all_shards, bool): - outputs_from_all_shards = [outputs_from_all_shards] * num_outputs - - if num_outputs != len(outputs_from_all_shards): - raise ValueError("Length of outputs_from_all_shards must be equal to the " - "number of outputs.") - - results = [] - for (axis, all_shards, x) in zip(output_shard_axes, outputs_from_all_shards, - zip(*outputs)): - if all_shards: - # Concatenate all of the outputs together (use stack for scalars). - shape = x[0].shape - is_scalar = shape is not None and (shape.ndims == 0) - results.append((array_ops.stack(list(x)) if is_scalar - else array_ops.concat(list(x), axis=axis))) - else: - # TODO(phawkins): use a smarter policy, e.g., round-robin across shards. - results.append(x[0]) - - return compile_op, results - - -def shard(computation, - inputs=None, - num_shards=1, - input_shard_axes=None, - outputs_from_all_shards=True, - output_shard_axes=None, - infeed_queue=None, - device_assignment=None, - name=None): - """Shards `computation` for parallel execution. - - `inputs` must be a list of Tensors or None (equivalent to an empty list), each - of which has a corresponding split axis (from `input_shard_axes`). Each input - is split into `num_shards` pieces along the corresponding axis, and - computation is applied to each shard in parallel. - - Tensors are broadcast to all shards if they are lexically captured by - `computation`. e.g., - - x = tf.constant(7) - def computation(): - return x + 3 - ... = shard(computation, ...) - - TODO(phawkins): consider adding support for broadcasting Tensors passed - as inputs. - - If `outputs_from_all_shards` is true, the outputs from all shards of - `computation` are concatenated back together along their `output_shards_axes`. - Otherwise, each output is taken from an arbitrary shard. - - Inputs and outputs of the computation must be at least rank-1 Tensors. - - Args: - computation: A Python function that builds a computation to apply to each - shard of the input. - inputs: A list of input tensors or None (equivalent to an empty list). Each - input tensor has a corresponding shard axes, given by `input_shard_axes`, - which must have size divisible by `num_shards`. - num_shards: The number of shards. - input_shard_axes: A list of dimensions along which to shard `inputs`, or - `None`. `None` means "shard all inputs along dimension 0". If not `None`, - there must be one dimension per input. - outputs_from_all_shards: Boolean or list of boolean. For each output, if - `True`, outputs from all shards are concatenated along the corresponding - `output_shard_axes` entry. Otherwise, each output is taken - from an arbitrary shard. If the argument is a boolean, the argument's - value is used for each output. - output_shard_axes: A list of dimensions along which to concatenate the - outputs of `computation`, or `None`. `None` means "concatenate all outputs - along dimension 0". If not `None`, there must be one dimension per output. - Ignored if `outputs_from_all_shards` is False. - infeed_queue: If not `None`, the `InfeedQueue` to use to augment the inputs - of `computation`. - device_assignment: If not `None`, a `DeviceAssignment` describing the - mapping between logical cores in the computation with physical cores in - the TPU topology. Uses a default device assignment if `None`. The - `DeviceAssignment` may be omitted if each shard of the computation uses - only one core, and there is either only one shard, or the number of shards - is equal to the number of cores in the TPU system. - name: (Deprecated) Does nothing. - Returns: - A list of output tensors. - Raises: - ValueError: If num_shards <= 0 - ValueError: If len(input_shard_axes) != len(inputs) - ValueError: If len(output_shard_axes) != len(outputs from `computation`) - """ - return split_compile_and_shard( - computation, - inputs=inputs, - num_shards=num_shards, - input_shard_axes=input_shard_axes, - outputs_from_all_shards=outputs_from_all_shards, - output_shard_axes=output_shard_axes, - infeed_queue=infeed_queue, - device_assignment=device_assignment, - name=name)[1] - - -def batch_parallel(computation, - inputs=None, - num_shards=1, - infeed_queue=None, - device_assignment=None, - name=None): - """Shards `computation` along the batch dimension for parallel execution. - - Convenience wrapper around shard(). - - `inputs` must be a list of Tensors or None (equivalent to an empty list). - Each input is split into `num_shards` pieces along the 0-th dimension, and - computation is applied to each shard in parallel. - - Tensors are broadcast to all shards if they are lexically captured by - `computation`. e.g., - - x = tf.constant(7) - def computation(): - return x + 3 - ... = shard(computation, ...) - - The outputs from all shards are concatenated back together along their 0-th - dimension. - - Inputs and outputs of the computation must be at least rank-1 Tensors. - - Args: - computation: A Python function that builds a computation to apply to each - shard of the input. - inputs: A list of input tensors or None (equivalent to an empty list). The - 0-th dimension of each Tensor must have size divisible by `num_shards`. - num_shards: The number of shards. - infeed_queue: If not `None`, the `InfeedQueue` from which to append a tuple - of arguments as inputs to `computation`. - device_assignment: If not `None`, a `DeviceAssignment` describing the - mapping between logical cores in the computation with physical cores in - the TPU topology. Uses a default device assignment if `None`. The - `DeviceAssignment` may be omitted if each shard of the computation uses - only one core, and there is either only one shard, or the number of shards - is equal to the number of cores in the TPU system. - name: (Deprecated) Does nothing. - Returns: - A list of output tensors. - Raises: - ValueError: If `num_shards <= 0` - """ - return shard( - computation, - inputs, - num_shards=num_shards, - infeed_queue=infeed_queue, - device_assignment=device_assignment, - name=name) - - -def rewrite(computation, - inputs=None, - infeed_queue=None, - device_assignment=None, - name=None): - """Rewrites `computation` for execution on a TPU system. - - Args: - computation: A Python function that builds a computation to apply to the - input. If the function takes n inputs, 'inputs' should be a list of n - tensors. - - `computation` may return a list of operations and tensors. Tensors must - come before operations in the returned list. The return value of - `rewrite` is a list of tensors corresponding to the tensors from the - output of `computation`. - - All `Operation`s constructed during `computation` will be executed when - evaluating any of the returned output tensors, not just the ones returned. - inputs: A list of input tensors or `None` (equivalent to an empty list). - Each input can be a nested structure containing values that are - convertible to tensors. Note that passing an N-dimension list of - compatible values will result in a N-dimention list of scalar tensors - rather than a single Rank-N tensors. If you need different behavior, - convert part of inputs to tensors with `tf.convert_to_tensor`. - infeed_queue: If not `None`, the `InfeedQueue` from which to append a tuple - of arguments as inputs to `computation`. - device_assignment: if not `None`, a `DeviceAssignment` describing the - mapping between logical cores in the computation with physical cores in - the TPU topology. May be omitted for a single-core computation, in which - case the core attached to task 0, TPU device 0 is used. - name: (Deprecated) Does nothing. - Returns: - Same data structure as if computation(*inputs) is called directly with some - exceptions for correctness. Exceptions include: - 1) None output: a NoOp would be returned which control-depends on - computation. - 2) Single value output: A tuple containing the value would be returned. - 3) Operation-only outputs: a NoOp would be returned which - control-depends on computation. - TODO(b/121383831): Investigate into removing these special cases. - """ - # TODO(b/36647078) remove disable when pylint bug is fixed. - # pylint: disable=indexing-exception - return replicate( - computation, - None if inputs is None else [inputs], - infeed_queue=infeed_queue, - device_assignment=device_assignment, - name=name)[0] - # pylint: enable=indexing-exception - - # Operations that indicate some error in the user's inference graph. -_BLACKLISTED_INFERENCE_OPS = set([ - "ReadVariableOp", - "AssignVariableOp", - "AssignAddVariableOp", - "AssignSubVariableOp", - "VarHandleOp", - "Variable", - "VariableV2", -]) - - -def under_tpu_inference_context(): - """Check if it is currently under `tpu.rewrite_for_inference()`.""" - graph = ops.get_default_graph() - - context = graph._get_control_flow_context() # pylint: disable=protected-access - while context: - if isinstance(context, _TPUInferenceContext): - return True - context = context.outer_context - - return False - - -class _TPUInferenceContext(control_flow_ops.XLAControlFlowContext): - """A `ControlFlowContext` for nodes inside a TPU inference computation. - - The primary role of `TPUReplicateContext` is to sanity check operators inside - a tpu.rewrite_for_inference() computation. - """ - - def __init__(self, name): - super(_TPUInferenceContext, self).__init__() - self._name = name - - def AddOp(self, op): - self._AddOpInternal(op) - - def _AddOpInternal(self, op): - # pylint: disable=protected-access - if op.type in _BLACKLISTED_INFERENCE_OPS: - raise NotImplementedError( - "Operation of type %s (%s) is not supported on the TPU for inference." - " Execution will fail if this op is used in the graph. Make sure your" - " variables are using variable_scope." % (op.type, op.name)) - if self._outer_context: - self._outer_context.AddInnerOp(op) - - def AddValue(self, val): - result = val - if self._outer_context: - result = self._outer_context.AddValue(val) - return result - - def AddInnerOp(self, op): - self._AddOpInternal(op) - - @property - def grad_state(self): - return None - - -@experimental -def validate_inference_rewrite_for_variables(graph): - """Validates whether rewrite_for_inference() 'worked' for variables. - - The rewrite_for_inference() method is supposed to append GuaranteeConstOps - after ReadVariableOps, but this mechanism works only if you are using - tf.get_variable() to create and access variables in your tpu computation. - This validation method can be called immediately after calling - tpu.rewrite_for_inference() to check whether GuaranteeConstOps where added - to the graph. - - Typical usages: - tpu.validate_inference_rewrite_for_variables(tf.get_default_graph()) - - tpu.validate_inference_rewrite_for_variables(sess.graph) - - Args: - graph: The graph which needs to be validated. - Raises: - RuntimeError: if validation failed. - """ - if not any(x.type == "GuaranteeConst" for x in graph.get_operations()): - raise RuntimeError( - "No GuaranteeConst ops found in the graph after running " - "tpu.rewrite_for_inference(...). Please check that you are using " - "tf.get_variable() to create and access variables in your tpu " - "computation.") - - -@experimental -def rewrite_for_inference(computation, - inputs=None, - infeed_queue=None, - device_assignment=None, - name=None): - """Rewrites `computation` for inference on a TPU system. - - Other than 'rewriting' the computation to run on a TPU, if using variables - in your computation, it moves the ReadVariableOps outside the TPU - computation, and adds GuaranteeConst ops just after the ReadVariableOps. - This mechanism works only if you are using tf.get_variable() to create and - access variables in your tpu computation. You can validate whether this - worked, by calling validate_inference_rewrite_for_variables() method - immediately after this method to check whether GuaranteeConstOps where - added to the graph. - - Args: - computation: A Python function that builds a computation to apply to the - input. If the function takes n inputs, 'inputs' should be a list of n - tensors. If the function returns m outputs, rewrite will return a list of - m tensors. - inputs: A list of input tensors or `None` (equivalent to an empty list). - infeed_queue: If not `None`, the `InfeedQueue` from which to append a tuple - of arguments as inputs to `computation`. - device_assignment: if not `None`, a `DeviceAssignment` describing the - mapping between logical cores in the computation with physical cores in - the TPU topology. May be omitted for a single-core computation, in which - case the core attached to task 0, TPU device 0 is used. - name: The name of the operator. - Returns: - A list of output tensors. - """ - - def guarantee_const_getter(getter, name, *args, **kwargs): - with ops.control_dependencies(None): - return array_ops.guarantee_const( - getter(name, *args, **kwargs), name=name + "/GuaranteeConst") - - def wrapped_computation(*args, **kwargs): - """Execute computation under `_TPUInferenceContext`.""" - context = _TPUInferenceContext( - name=ops.get_default_graph().unique_name("rewrite_for_inference")) - try: - context.Enter() - - vscope = variable_scope.get_variable_scope() - prev_custom_getter = vscope.custom_getter - prev_caching_device = vscope.caching_device - vscope.set_custom_getter(guarantee_const_getter) - vscope.set_caching_device(lambda op: op.device) - - result = computation(*args, **kwargs) - - vscope.set_custom_getter(prev_custom_getter) - vscope.set_caching_device(prev_caching_device) - finally: - context.Exit() - return result - - # pylint: disable=undefined-variable - return rewrite( - wrapped_computation, - inputs=inputs, - infeed_queue=infeed_queue, - device_assignment=device_assignment, - name=name) - # pylint: enable=undefined-variable +# pylint: disable=wildcard-import,unused-import,redefined-builtin +from tensorflow.python.tpu.tpu import * +# used by tests +from tensorflow.python.tpu.tpu import _TPU_REPLICATE_ATTR +# pylint: enable=wildcard-import,unused-import,redefined-builtin diff --git a/tensorflow/contrib/tpu/python/tpu/tpu_config.py b/tensorflow/contrib/tpu/python/tpu/tpu_config.py index 82ebb9a37f..c36aaa38c0 100644 --- a/tensorflow/contrib/tpu/python/tpu/tpu_config.py +++ b/tensorflow/contrib/tpu/python/tpu/tpu_config.py @@ -1,276 +1,23 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 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 +# 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 RunConfig subclass with TPU support.""" +# ============================================================================== +"""Stub file to maintain backwards compatibility.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function -import collections -import json -import os - -from tensorflow.contrib.tpu.python.tpu import util as util_lib -from tensorflow.core.protobuf import config_pb2 -from tensorflow.python.estimator import run_config as run_config_lib -from tensorflow.python.platform import tf_logging as logging - -# pylint: disable=protected-access -_TF_CONFIG_ENV = run_config_lib._TF_CONFIG_ENV -_SERVICE_KEY = run_config_lib._SERVICE_KEY -_TPU_WORKER_JOB_NAME = 'tpu_worker_job_name' -# 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 - BROADCAST = 4 - - -class TPUConfig( - collections.namedtuple('TPUConfig', [ - 'iterations_per_loop', - 'num_shards', - 'num_cores_per_replica', - 'per_host_input_for_training', - 'tpu_job_name', - 'initial_infeed_sleep_secs', - 'input_partition_dims', - ])): - r"""TPU related configuration required by `TPUEstimator`. - - Args: - iterations_per_loop: This is the number of train steps running in TPU - system before returning to CPU host for each `Session.run`. This means - global step is increased `iterations_per_loop` times in one `Session.run`. - It is recommended to be set as number of global steps for next checkpoint. - num_shards: (Deprecated, ignored by TPUEstimator). - The number of model replicas in the system. For non-model-parallelism - case, this number equals the total number of TPU cores. For - model-parallelism, the total number of TPU cores equals - num_cores_per_replica * num_shards. - num_cores_per_replica: Defaults to `None`, which disables model parallelism. - An integer which describes the number of TPU cores per model replica. This - is required by model-parallelism which enables partitioning - the model to multiple cores. Currently num_cores_per_replica must be - 1, 2, 4, or 8. - per_host_input_for_training: If `True`, `PER_HOST_V1`, or `PER_HOST_V2`, - `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. In `BROADCAST` mode, `input_fn` is only - invoked once on host 0 and the tensors are broadcasted to all other - replicas. The batch size equals to train_batch_size`. 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 - string. - initial_infeed_sleep_secs: The number of seconds the infeed thread should - wait before enqueueing the first batch. This helps avoid timeouts for - models that require a long compilation time. - input_partition_dims: A nested list to describe the partition dims - for all the tensors from input_fn(). The structure of - input_partition_dims must match the structure of `features` and - `labels` from input_fn(). The total number of partitions must match - `num_cores_per_replica`. For example, if input_fn() returns two tensors: - images with shape [N, H, W, C] and labels [N]. - input_partition_dims = [[1, 2, 2, 1], None] will split the images to 4 - pieces and feed into 4 TPU cores. labels tensor are directly broadcasted - to all the TPU cores since the partition dims is `None`. - Current limitations: This feature is only supported with the PER_HOST_V2 - input mode. - - Raises: - ValueError: If `num_cores_per_replica` is not 1, 2, 4, 8 or 16. - """ - - def __new__(cls, - iterations_per_loop=2, - num_shards=None, - num_cores_per_replica=None, - per_host_input_for_training=True, - tpu_job_name=None, - initial_infeed_sleep_secs=None, - input_partition_dims=None): - - # Check iterations_per_loop. - util_lib.check_positive_integer(iterations_per_loop, - 'TPUConfig iterations_per_loop') - - # Check num_shards. - if num_shards is not None: - util_lib.check_positive_integer(num_shards, 'TPUConfig num_shards') - - if input_partition_dims is not None: - if len(input_partition_dims) != 1 and len(input_partition_dims) != 2: - raise ValueError( - 'input_partition_dims must be a list/tuple with one or two' - ' elements.') - - if per_host_input_for_training is not InputPipelineConfig.PER_HOST_V2: - raise ValueError( - 'input_partition_dims is only supported in PER_HOST_V2 mode.') - - if num_cores_per_replica is None: - raise ValueError( - 'input_partition_dims requires setting num_cores_per_replica.') - - # Check num_cores_per_replica - if num_cores_per_replica is not None: - if num_cores_per_replica not in [1, 2, 4, 8, 16]: - raise ValueError( - 'num_cores_per_replica must be 1, 2, 4, 8, or 16; got {}'.format( - str(num_cores_per_replica))) - - # 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, - 'TPUConfig initial_infeed_sleep_secs') - - tpu_job_name = tpu_job_name or _get_tpu_job_name_from_tf_config() - - return super(TPUConfig, cls).__new__( - cls, - iterations_per_loop=iterations_per_loop, - num_shards=num_shards, - num_cores_per_replica=num_cores_per_replica, - per_host_input_for_training=per_host_input_for_training, - tpu_job_name=tpu_job_name, - initial_infeed_sleep_secs=initial_infeed_sleep_secs, - input_partition_dims=input_partition_dims) - - -class RunConfig(run_config_lib.RunConfig): - """RunConfig with TPU support.""" - - def __init__(self, - tpu_config=None, - evaluation_master=None, - master=None, - cluster=None, - **kwargs): - """Constructs a RunConfig. - - Args: - tpu_config: the TPUConfig that specifies TPU-specific configuration. - evaluation_master: a string. The address of the master to use for eval. - Defaults to master if not set. - master: a string. The address of the master to use for training. - cluster: a ClusterResolver - **kwargs: keyword config parameters. - - Raises: - ValueError: if cluster is not None and the provided session_config has a - cluster_def already. - """ - super(RunConfig, self).__init__(**kwargs) - self._tpu_config = tpu_config or TPUConfig() - self._cluster = cluster - - # If user sets master and/or evaluation_master explicitly, including empty - # string '', take it. Otherwise, take the values set by parent class. - if master is not None: - if cluster is not None: - raise ValueError('Both master and cluster are set.') - self._master = master - else: - if cluster: - self._master = cluster.master() - - if evaluation_master is not None: - self._evaluation_master = evaluation_master - elif (not self._evaluation_master and - self.task_type != run_config_lib.TaskType.EVALUATOR): - # If the task type is EVALUATOR, it means some cluster manager sets the - # TF_CONFIG. In that case, we respect the configuration in TF_CONFIG. - # - # Otherwise, it means user executes the code without external cluster - # manager. For that, we optimize the user experience by setting - # evaluation_master to master, unless user overwrites it. - self._evaluation_master = self._master - - # Set the ClusterSpec to use - if cluster: - self._cluster_spec = cluster.cluster_spec() - - # Merge the cluster_def into the ConfigProto. - if self._session_config is None: # pylint: disable=access-member-before-definition - self._session_config = config_pb2.ConfigProto( - allow_soft_placement=True, isolate_session_state=True) - if self._session_config.HasField('cluster_def'): - raise ValueError( - 'You cannot provide a ClusterResolver and ' - 'session_config.cluster_def.') - if self._cluster_spec: - self._session_config.cluster_def.CopyFrom( - self._cluster_spec.as_cluster_def()) - - def _maybe_overwrite_session_config_for_distributed_training(self): - # Overrides the parent class session_config overwrite for between-graph. TPU - # runs with in-graph, which should not have device filter. Doing nothing - # ("pass") basically disables it. - pass - - @property - def evaluation_master(self): - return self._evaluation_master - - @property - def master(self): - return self._master - - @property - def tpu_config(self): - return self._tpu_config - - @property - def cluster(self): - return self._cluster - - def replace(self, **kwargs): - if 'tpu_config' not in kwargs: - return super(RunConfig, self).replace(**kwargs) - - tpu_config = kwargs.pop('tpu_config') - new_instance = super(RunConfig, self).replace(**kwargs) - new_instance._tpu_config = tpu_config # pylint: disable=protected-access - return new_instance - - -def _get_tpu_job_name_from_tf_config(): - """Extracts the TPU job name from TF_CONFIG env variable.""" - # TODO(xiejw): Extends this to support both TF_CONFIG env variable and cluster - # spec propagation. - tf_config = json.loads(os.environ.get(_TF_CONFIG_ENV, '{}')) - tpu_job_name = tf_config.get(_SERVICE_KEY, {}).get(_TPU_WORKER_JOB_NAME) - if tpu_job_name: - logging.info('Load TPU job name from TF_CONFIG: %s', tpu_job_name) - return tpu_job_name +# pylint: disable=wildcard-import,unused-import +from tensorflow.python.tpu.tpu_config import * +# pylint: enable=wildcard-import,unused-import diff --git a/tensorflow/contrib/tpu/python/tpu/tpu_context.py b/tensorflow/contrib/tpu/python/tpu/tpu_context.py index ed1e0f0401..b77b010cba 100644 --- a/tensorflow/contrib/tpu/python/tpu/tpu_context.py +++ b/tensorflow/contrib/tpu/python/tpu/tpu_context.py @@ -1,763 +1,23 @@ -# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 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 +# 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. -# =================================================================== -"""TPU system metadata and associated tooling.""" +# ============================================================================== +"""Stub file to maintain backwards compatibility.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function -from contextlib import contextmanager -import copy - -from tensorflow.contrib.tpu.python.tpu import _tpu_estimator_embedding -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 - - -_DEFAULT_JOB_NAME = 'tpu_worker' -_DEFAULT_COORDINATOR_JOB_NAME = 'coordinator' -_LOCAL_MASTERS = ('', 'local') -_NUM_CORES_TO_COMPUTATION_SHAPE = { - 1: [1, 1, 1], - 2: [1, 1, 2], - 4: [1, 2, 2], - 8: [2, 2, 2], - 16: [4, 2, 2], -} - - -class TPUContext(object): - """A context that holds the current configuration of the TPU computation.""" - - def __init__(self, - internal_ctx, - input_device=None, - invocation_index=None, - call_from_input_fn=True): - self._internal_ctx = internal_ctx - self._input_device = input_device - self._invocation_index = invocation_index - self._call_from_input_fn = call_from_input_fn - - def current_input_fn_deployment(self): - """The configuration of the current input_fn invocation. - - The configuration depends on `TPUConfig.per_host_input_for_training`. See - `TPUConfig` for details. - - Only set in params dict of input_fn - - Returns: - A tuple of - 1. Device spec string: String, is the current CPU host where the - input_fn is invoked. - 2. Current invocation index: Int, 0-based index of the input_fn - invocation. See next item for details. - 3. Total invocation count: Int, the total number of times to invoke the - input_fn on all CPU hosts. Each invocation will be passed with a new - `TPUContext` instance with current invocation index set properly. - 4. Total number of replicas consumed by current_invocation: Int, the - number of replicas fed by the data returned by current input_fn. For - example, for per_core input pipeline deployment - and non-model-parallelism, total invocation count is equal to - the number of cores in the system and num replicas consumed by - current invocation is 1. For per-host v2 input pipeline deployment, - total invocation count is equal to the number of hosts in the system - and num replicas consumed by current invocation is equal to number of - cores per host. - - Raises: - RuntimeError: If this method must not be called from input_fn. - """ - if not self._call_from_input_fn: - raise RuntimeError('This TPUContext instance must not be called from' - ' model_fn.') - - if self._internal_ctx.is_input_sharded_per_core(): - total_invocation_count = (self._internal_ctx.num_hosts - * self._internal_ctx.num_of_replicas_per_host) - replicas_consumed = 1 - elif self._internal_ctx.is_input_broadcast_with_iterators(): - total_invocation_count = 1 - replicas_consumed = self._internal_ctx.num_replicas - else: - total_invocation_count = self._internal_ctx.num_hosts - replicas_consumed = self._internal_ctx.num_of_replicas_per_host - return (self._input_device, self._invocation_index, - total_invocation_count, replicas_consumed) - - @property - def num_replicas(self): - """The total number of replicas. - - For non-model-parallelism, num_replicas should be the total num of TPU - cores in the system. - - Returns: - The number of replicas. - """ - return self._internal_ctx.num_replicas - - @property - def num_hosts(self): - """The number of hosts for the TPU system.""" - return self._internal_ctx.num_hosts - - @property - def current_host(self): - """The current host index for the TPU system.""" - return self._invocation_index - - @property - def num_of_replicas_per_host(self): - """The number of replicas for each host.""" - if self._internal_ctx.model_parallelism_enabled: - raise ValueError( - 'num_of_replicas_per_host is not supported for model_parallelism') - return self._internal_ctx.num_of_replicas_per_host - - @property - def device_assignment(self): - """Returns device_assignment object.""" - if self._call_from_input_fn: - raise RuntimeError('This TPUContext instance must not be called from' - ' input_fn.') - return self._internal_ctx.device_assignment - - def device_for_replica(self, replica_id): - """Returns the tuple of (CPU device and device ordinal) for replica. - - This should be used for full replicate for non-model-parallelism. - - Args: - replica_id: Int, the replica index. - - Returns: - A tuple of device spec for CPU device and int device ordinal. - """ - # Note that: For the non-model parallelism, the mapping could be - # a random permutation. The order should not matter in most cases - # as far as model is replicated to all cores in the system. - return self._internal_ctx.device_for_replica(replica_id) - - @property - def tpu_host_placement_function(self): - """Returns the TPU host place function. - - The place function takes host_id as the input and returns the TF device - for the correspoding host. - """ - - def _placement_function(host_id): - """Return the host device given host_id.""" - return self._internal_ctx.tpu_host_placement_function(host_id=host_id) - - return _placement_function - - -class _InternalTPUContext(object): - """A context holds immutable states of TPU computation. - - This immutable object holds TPUEstimator config, train/eval batch size, and - `TPUEstimator.use_tpu`, which is expected to be passed around. It also - provides utility functions, based on the current state, to determine other - information commonly required by TPU computation, such as TPU device names, - TPU hosts, shard batch size, etc. - - if eval_on_tpu is False, then execution of eval on TPU is disabled. - if eval_on_tpu is True, but use_tpu is False, a warning is issued, - and TPU execution is disabled for all modes. - - N.B. As `mode` is not immutable state in Estimator, but essential to - distinguish between TPU training and evaluation, a common usage for - _InternalTPUContext with `mode` is as follows: - ``` - with _ctx.with_mode(mode) as ctx: - if ctx.is_running_on_cpu(): - ... - ``` - """ - - def __init__(self, - config, - train_batch_size, - eval_batch_size, - predict_batch_size, - use_tpu, - eval_on_tpu=True, - embedding_config_spec=None): - self._config = config - self._train_batch_size = train_batch_size - self._eval_batch_size = eval_batch_size - self._predict_batch_size = predict_batch_size - self._use_tpu = use_tpu - logging.info('_TPUContext: eval_on_tpu %s', eval_on_tpu) - if not use_tpu and eval_on_tpu: - logging.warning('eval_on_tpu ignored because use_tpu is False.') - - self._eval_on_tpu = eval_on_tpu - self._model_parallelism_enabled = ( - use_tpu and config.tpu_config.num_cores_per_replica) - self._mode = None - num_cores_per_replica = config.tpu_config.num_cores_per_replica - if self._model_parallelism_enabled: - self._computation_shape = _NUM_CORES_TO_COMPUTATION_SHAPE[ - num_cores_per_replica] - else: - self._computation_shape = None - self._lazy_tpu_system_metadata_dict = {} # key by master address - self._lazy_device_assignment_dict = {} # key by master address - self._lazy_validation_dict = {} # key by ModeKeys - self._embedding_config_spec = embedding_config_spec - self._lazy_embedding_config_dict = {} # key by master address - - def _assert_mode(self): - if self._mode is None: - raise RuntimeError( - '`mode` needs to be set via contextmanager `with_mode`.') - return self._mode - - @contextmanager - def with_mode(self, mode): - # NOTE(xiejw): Shallow copy is enough. It will share he lazy dictionaries, - # such as _lazy_tpu_system_metadata_dict between new copy and the original - # one. Note that all lazy states stored in properties _lazy_foo are sort of - # immutable as they should be same for the process lifetime. - new_ctx = copy.copy(self) - new_ctx._mode = mode # pylint: disable=protected-access - yield new_ctx - - @property - def mode(self): - return self._assert_mode() - - def _get_master_address(self): - mode = self._assert_mode() - config = self._config - master = ( - config.master - if mode != model_fn_lib.ModeKeys.EVAL else config.evaluation_master) - return master - - def _get_tpu_system_metadata(self): - """Gets the (maybe cached) TPU system metadata.""" - master = self._get_master_address() - tpu_system_metadata = self._lazy_tpu_system_metadata_dict.get(master) - if tpu_system_metadata is not None: - return tpu_system_metadata - - cluster_def = None - if (self._config.session_config and - self._config.session_config.cluster_def.job): - cluster_def = self._config.session_config.cluster_def - - # pylint: disable=protected-access - tpu_system_metadata = ( - tpu_system_metadata_lib._query_tpu_system_metadata( - master, - cluster_def=cluster_def, - query_topology=self.model_parallelism_enabled)) - - self._lazy_tpu_system_metadata_dict[master] = tpu_system_metadata - return tpu_system_metadata - - def _get_device_assignment(self): - """Gets the (maybe cached) TPU device assignment.""" - master = self._get_master_address() - device_assignment = self._lazy_device_assignment_dict.get(master) - if device_assignment is not None: - return device_assignment - - tpu_system_metadata = self._get_tpu_system_metadata() - - device_assignment = tpu_device_assignment.device_assignment( - tpu_system_metadata.topology, - computation_shape=self._computation_shape, - num_replicas=self.num_replicas) - - logging.info('num_cores_per_replica: %s', - str(self._config.tpu_config.num_cores_per_replica)) - logging.info('computation_shape: %s', str(self._computation_shape)) - logging.info('num_replicas: %d', self.num_replicas) - logging.info('device_assignment.topology.device_coordinates: %s', - str(device_assignment.topology.device_coordinates)) - logging.info('device_assignment.core_assignment: %s', - str(device_assignment.core_assignment)) - - self._lazy_device_assignment_dict[master] = device_assignment - return device_assignment - - @property - def embedding_config(self): - """Returns the embedding config based on current mode.""" - master = self._get_master_address() - if master in self._lazy_embedding_config_dict: - embedding_config = self._lazy_embedding_config_dict[master] - else: - embedding_config = None - if self._use_tpu and self._embedding_config_spec: - embedding_config = _tpu_estimator_embedding.EmbeddingConfig( - self._embedding_config_spec, self._train_batch_size, - self._eval_batch_size, self.num_hosts, self.num_cores, master) - if not embedding_config.has_embedding_tables(): - embedding_config = None - self._lazy_embedding_config_dict[master] = embedding_config - - if embedding_config is not None: - mode = self._assert_mode() - # Dynamically attach tpu_embedding based on mode. With - # this, we could keep embedding_config immutable but call site always - # accesses the unified API '.tpu_embedding'. - embedding_config.tpu_embedding = embedding_config.get_tpu_embedding(mode) - return embedding_config - - @property - def model_parallelism_enabled(self): - return self._model_parallelism_enabled - - @property - def input_partition_dims(self): - return self._config.tpu_config.input_partition_dims - - @property - def device_assignment(self): - return (self._get_device_assignment() - if self._model_parallelism_enabled else None) - - @property - def num_of_cores_per_host(self): - metadata = self._get_tpu_system_metadata() - return metadata.num_of_cores_per_host - - @property - def num_cores(self): - metadata = self._get_tpu_system_metadata() - return metadata.num_cores - - @property - def num_of_replicas_per_host(self): - """Return the number of replicas per host.""" - if self.model_parallelism_enabled: - return self.num_replicas // self.num_hosts - else: - return self.num_of_cores_per_host - - @property - def num_replicas(self): - num_cores_in_system = self.num_cores - - if self.model_parallelism_enabled: - num_cores_per_replica = self._config.tpu_config.num_cores_per_replica - if num_cores_per_replica > num_cores_in_system: - raise ValueError( - 'The num of cores required by the model parallelism, specified by ' - 'TPUConfig.num_cores_per_replica, is larger than the total num of ' - 'TPU cores in the system. num_cores_per_replica: {}, num cores ' - 'in the system: {}'.format(num_cores_per_replica, - num_cores_in_system)) - - if num_cores_in_system % num_cores_per_replica != 0: - raise RuntimeError( - 'The num of cores in the system ({}) is not divisible by the num ' - 'of cores ({}) required by the model parallelism, specified by ' - 'TPUConfig.num_cores_per_replica. This should never happen!'.format( - num_cores_in_system, num_cores_per_replica)) - - return num_cores_in_system // num_cores_per_replica - else: - return num_cores_in_system - - @property - def num_hosts(self): - metadata = self._get_tpu_system_metadata() - return metadata.num_hosts - - @property - def config(self): - return self._config - - def is_input_sharded_per_core(self): - """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 - (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_input_broadcast_with_iterators(self): - """Return true if input_fn should be run in the full_replicae config.""" - return (self._config.tpu_config.per_host_input_for_training is - tpu_config.InputPipelineConfig.BROADCAST) - - def is_running_on_cpu(self, is_export_mode=False): - """Determines whether the input_fn and model_fn should be invoked on CPU. - - This API also validates user provided configuration, such as batch size, - according the lazy initialized TPU system metadata. - - Args: - is_export_mode: Indicates whether the current mode is for exporting the - model, when mode == PREDICT. Only with this bool, we could - tell whether user is calling the Estimator.predict or - Estimator.export_savedmodel, which are running on TPU and CPU - respectively. Parent class Estimator does not distinguish these two. - - Returns: - bool, whether current input_fn or model_fn should be running on CPU. - - Raises: - ValueError: any configuration is invalid. - """ - - is_running_on_cpu = self._is_running_on_cpu(is_export_mode) - if not is_running_on_cpu: - self._validate_tpu_configuration() - return is_running_on_cpu - - def _is_running_on_cpu(self, is_export_mode): - """Determines whether the input_fn and model_fn should be invoked on CPU.""" - mode = self._assert_mode() - - if not self._use_tpu: - return True - - if mode == model_fn_lib.ModeKeys.EVAL and not self._eval_on_tpu: - logging.info('_is_running_on_cpu: eval_on_tpu disabled') - return True - - if is_export_mode: - return True - - return False - - @property - def global_batch_size(self): - mode = self._assert_mode() - if mode == model_fn_lib.ModeKeys.TRAIN: - return self._train_batch_size - elif mode == model_fn_lib.ModeKeys.EVAL: - return self._eval_batch_size - elif mode == model_fn_lib.ModeKeys.PREDICT: - return self._predict_batch_size - else: - return None - - @property - def batch_size_for_input_fn(self): - """Returns the shard batch size for `input_fn`.""" - global_batch_size = self.global_batch_size - - if (self.is_running_on_cpu() or self.is_input_broadcast_with_iterators()): - return global_batch_size - - # On TPU - if self.is_input_sharded_per_core() or ( - self.is_input_per_host_with_iterators()): - return global_batch_size // self.num_replicas - else: - return global_batch_size // self.num_hosts - - @property - def batch_size_for_model_fn(self): - """Returns the shard batch size for `model_fn`.""" - global_batch_size = self.global_batch_size - - if (self.is_running_on_cpu() or self.is_input_broadcast_with_iterators()): - return global_batch_size - - # On TPU. always sharded per shard. - return global_batch_size // self.num_replicas - - @property - def master_job(self): - """Returns the job name to use to place TPU computations on. - - Returns: - A string containing the job name, or None if no job should be specified. - - Raises: - ValueError: If the user needs to specify a tpu_job_name, because we are - unable to infer the job name automatically, or if the user-specified job - names are inappropriate. - """ - run_config = self._config - # If the user specifies the tpu_job_name, use that. - if run_config.tpu_config.tpu_job_name: - return run_config.tpu_config.tpu_job_name - - # The tpu job is determined by the run_config. Right now, this method is - # required as tpu_config is not part of the RunConfig. - mode = self._assert_mode() - master = ( - run_config.evaluation_master - if mode == model_fn_lib.ModeKeys.EVAL else run_config.master) - if master in _LOCAL_MASTERS: - return None - - if (not run_config.session_config or - not run_config.session_config.cluster_def.job): - return _DEFAULT_JOB_NAME - cluster_def = run_config.session_config.cluster_def - job_names = set([job.name for job in cluster_def.job]) - if _DEFAULT_JOB_NAME in job_names: - # b/37868888 tracks allowing ClusterSpec propagation to reuse job names. - raise ValueError('Currently, tpu_worker is not an allowed job name.') - if len(job_names) == 1: - return cluster_def.job[0].name - if len(job_names) == 2: - if _DEFAULT_COORDINATOR_JOB_NAME in job_names: - job_names.remove(_DEFAULT_COORDINATOR_JOB_NAME) - return job_names.pop() - # TODO(b/67716447): Include more sophisticated heuristics. - raise ValueError( - 'Could not infer TPU job name. Please specify a tpu_job_name as part ' - 'of your TPUConfig.') - - @property - def tpu_host_placement_function(self): - """Returns the TPU host place function.""" - - master = self.master_job - - def _placement_function(_sentinal=None, replica_id=None, host_id=None): # pylint: disable=invalid-name - """Return the host device given replica_id or host_id.""" - assert _sentinal is None - if replica_id is not None and host_id is not None: - raise RuntimeError( - 'replica_id and host_id can have only one non-None value.') - - if master is None: - return '/replica:0/task:0/device:CPU:0' - else: - if replica_id is not None: - if self.model_parallelism_enabled: - return self.device_assignment.host_device( - replica=replica_id, job=master) - else: - host_id = replica_id / self.num_of_cores_per_host - - return '/job:%s/task:%d/device:CPU:0' % (master, host_id) - - return _placement_function - - @property - def tpu_device_placement_function(self): - """Returns a TPU device placement Fn.""" - master = self.master_job - job_device = '' if master is None else ('/job:%s' % master) - - def _placement_function(i): - if self.model_parallelism_enabled: - return self.device_assignment.tpu_device(replica=i, job=master) - else: - num_of_cores_per_host = self.num_of_cores_per_host - host_id = i / num_of_cores_per_host - ordinal_id = i % num_of_cores_per_host - return '%s/task:%d/device:TPU:%d' % (job_device, host_id, ordinal_id) - - return _placement_function - - def tpu_ordinal_function(self, host_id): - """Returns the TPU ordinal fn.""" - - def _tpu_ordinal_function(shard_index_in_host): - """Return the TPU ordinal associated with a shard. - - Required because the enqueue ops are placed on CPU. - - Args: - shard_index_in_host: the shard index - - Returns: - The ordinal of the TPU device the shard's infeed should be placed on. - """ - if self.model_parallelism_enabled: - # We put both enqueue/dequeue ops at tpu.core(0) in each replica. - replica = self.device_assignment.lookup_replicas(host_id, - 0)[shard_index_in_host] - return self.device_assignment.tpu_ordinal(replica=replica) - else: - return shard_index_in_host % self.num_of_cores_per_host - - return _tpu_ordinal_function - - def _validate_tpu_configuration(self): - """Validates the configuration based on the TPU system metadata.""" - mode = self._assert_mode() - if self._lazy_validation_dict.get(mode): - return - - # All following information is obtained from TPU system metadata. - num_cores = self.num_cores - num_replicas = self.num_replicas - num_hosts = self.num_hosts - - if not num_cores: - tpu_system_metadata = self._get_tpu_system_metadata() - raise RuntimeError( - 'Cannot find any TPU cores in the system. Please double check ' - 'Tensorflow master address and TPU worker(s). Available devices ' - 'are {}.'.format(tpu_system_metadata.devices)) - - if self._config.tpu_config.num_shards: - user_provided_num_replicas = self._config.tpu_config.num_shards - if user_provided_num_replicas != num_replicas: - message = ( - 'TPUConfig.num_shards is not set correctly. According to TPU ' - 'system metadata for Tensorflow master ({}): num_replicas should ' - 'be ({}), got ({}). For non-model-parallelism, num_replicas should ' - 'be the total num of TPU cores in the system. For ' - 'model-parallelism, the total number of TPU cores should be ' - 'num_cores_per_replica * num_replicas. Please set it ' - 'accordingly or leave it as `None`'.format( - self._get_master_address(), num_replicas, - user_provided_num_replicas)) - - raise ValueError(message) - - if self._config.tpu_config.num_cores_per_replica: - num_cores_per_replica = self._config.tpu_config.num_cores_per_replica - num_cores_per_host = self._get_tpu_system_metadata().num_of_cores_per_host - if num_cores_per_replica > num_cores_per_host: - raise ValueError( - 'The num of cores required by the model parallelism, specified by ' - 'TPUConfig.num_cores_per_replica, is larger than the ' - 'num_cores_per_host. num_cores_per_replica: {}, ' - 'num_cores_per_host: {}'.format(num_cores_per_replica, - num_cores_per_host)) - - if mode == model_fn_lib.ModeKeys.TRAIN: - if (self._train_batch_size % num_replicas != 0 and - not self.is_input_broadcast_with_iterators()): - raise ValueError( - 'train batch size {} must be divisible by number of replicas {}' - .format(self._train_batch_size, num_replicas)) - - elif mode == model_fn_lib.ModeKeys.EVAL: - if self._eval_batch_size is None: - raise ValueError( - 'eval_batch_size in TPUEstimator constructor cannot be `None`' - 'if .evaluate is running on TPU.') - if (self._eval_batch_size % num_replicas != 0 and - not self.is_input_broadcast_with_iterators()): - raise ValueError( - 'eval batch size {} must be divisible by number of replicas {}' - .format(self._eval_batch_size, num_replicas)) - if num_hosts > 1 and not self.is_input_broadcast_with_iterators(): - raise ValueError( - 'TPUEstimator.evaluate should be running on single TPU' - ' instead of a Pod.') - else: - assert mode == model_fn_lib.ModeKeys.PREDICT - if self._predict_batch_size is None: - raise ValueError( - 'predict_batch_size in TPUEstimator constructor should not be ' - '`None` if .predict is running on TPU.') - if (self._predict_batch_size % num_replicas != 0 and - not self.is_input_broadcast_with_iterators()): - raise ValueError( - 'predict batch size {} must be divisible by number of replicas {}' - .format(self._predict_batch_size, num_replicas)) - if num_hosts > 1 and not self.is_input_broadcast_with_iterators(): - raise ValueError( - 'TPUEstimator.predict should be running on single TPU worker. ' - 'got {}.'.format(num_hosts)) - - # Record the state "validated" into lazy dictionary. - self._lazy_validation_dict[mode] = True - - def device_for_replica(self, replica_id): - """Returns the tuple of (CPU device and device ordinal) for replica. - - This should be used for full replicate for non-model-parallelism. - - Args: - replica_id: Int, the replica index. - - Returns: - A tuple of device spec for CPU device and int device ordinal. - """ - master = self.master_job - - if self.model_parallelism_enabled: - return (self.device_assignment.host_device( - replica=replica_id, job=master), - self.device_assignment.tpu_ordinal(replica=replica_id)) - - job_device = '' if master is None else ('/job:%s' % master) - - num_of_replicas_per_host = self.num_of_replicas_per_host - host_id = replica_id / num_of_replicas_per_host - ordinal_id = replica_id % num_of_replicas_per_host - - host_device = '%s/task:%d/device:CPU:0' % (job_device, host_id) - return (host_device, ordinal_id) - - -class _OneCoreTPUContext(_InternalTPUContext): - """Special _InternalTPUContext for one core usage.""" - - def __init__(self, config, train_batch_size, eval_batch_size, - predict_batch_size, use_tpu): - - super(_OneCoreTPUContext, self).__init__( - config, train_batch_size, eval_batch_size, - predict_batch_size, use_tpu) - - def _get_tpu_system_metadata(self): - """Gets the (maybe cached) TPU system metadata.""" - master = self._get_master_address() - tpu_system_metadata = self._lazy_tpu_system_metadata_dict.get(master) - if tpu_system_metadata is not None: - return tpu_system_metadata - - tpu_system_metadata = ( - tpu_system_metadata_lib._TPUSystemMetadata( # pylint: disable=protected-access - num_cores=1, - num_hosts=1, - num_of_cores_per_host=1, - topology=None, - devices=[])) - - self._lazy_tpu_system_metadata_dict[master] = tpu_system_metadata - return tpu_system_metadata - - -def _get_tpu_context(config, train_batch_size, eval_batch_size, - predict_batch_size, use_tpu, eval_on_tpu, - embedding_config_spec): - """Returns an instance of `_InternalTPUContext`.""" - - if (config.tpu_config.num_shards == 1 and - config.tpu_config.num_cores_per_replica is None): - if embedding_config_spec is not None: - raise ValueError('Setting TPUConfig.num_shards==1 is unsupported ' - 'when embedding_config_spec is not None.') - logging.warning( - 'Setting TPUConfig.num_shards==1 is an unsupported behavior. ' - 'Please fix as soon as possible (leaving num_shards as None.)') - return _OneCoreTPUContext(config, train_batch_size, eval_batch_size, - predict_batch_size, use_tpu) - - return _InternalTPUContext(config, train_batch_size, eval_batch_size, - predict_batch_size, use_tpu, eval_on_tpu, - embedding_config_spec) +# pylint: disable=wildcard-import,unused-import +from tensorflow.python.tpu.tpu_context import * +# pylint: enable=wildcard-import,unused-import diff --git a/tensorflow/contrib/tpu/python/tpu/tpu_embedding.py b/tensorflow/contrib/tpu/python/tpu/tpu_embedding.py index 1ba8017cda..cb38a8f1a6 100644 --- a/tensorflow/contrib/tpu/python/tpu/tpu_embedding.py +++ b/tensorflow/contrib/tpu/python/tpu/tpu_embedding.py @@ -1,10 +1,10 @@ -# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 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 +# 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, @@ -12,1097 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""TPU embedding APIs.""" +"""Stub file to maintain backwards compatibility.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function -import collections -import copy -import math -import re -import six - -from tensorflow.contrib.framework.python.framework import experimental -from tensorflow.contrib.tpu.python.ops import tpu_ops -from tensorflow.contrib.tpu.python.tpu import tpu_system_metadata as tpu_system_metadata_lib -from tensorflow.core.protobuf.tpu import optimization_parameters_pb2 -from tensorflow.core.protobuf.tpu import tpu_embedding_configuration_pb2 as elc -from tensorflow.python.framework import dtypes -from tensorflow.python.framework import ops -from tensorflow.python.framework import sparse_tensor -from tensorflow.python.ops import array_ops -from tensorflow.python.ops import control_flow_ops -from tensorflow.python.ops import init_ops -from tensorflow.python.ops import math_ops -from tensorflow.python.ops import partitioned_variables -from tensorflow.python.ops import state_ops -from tensorflow.python.ops import variable_scope - -TRAINING = elc.TPUEmbeddingConfiguration.TRAINING -INFERENCE = elc.TPUEmbeddingConfiguration.INFERENCE - - -class TableConfig( - collections.namedtuple( - 'TableConfig', - ['vocabulary_size', 'dimension', 'initializer', 'combiner'])): - """Embedding table configuration.""" - - @experimental - def __new__(cls, - vocabulary_size, - dimension, - initializer=None, - combiner='mean'): - """Embedding table configuration. - - Args: - vocabulary_size: Number of vocabulary (/rows) in the table. - dimension: The embedding dimension. - initializer: A variable initializer function to be used in embedding - variable initialization. If not specified, defaults to - `tf.truncated_normal_initializer` with mean `0.0` and standard deviation - `1/sqrt(dimension)`. - combiner: A string specifying how to reduce if there are multiple entries - in a single row. Currently 'mean', 'sqrtn' and 'sum' are supported, with - 'mean' the default. 'sqrtn' often achieves good accuracy, in particular - with bag-of-words columns. For more information, see - `tf.nn.embedding_lookup_sparse`. - - Returns: - `TableConfig`. - - Raises: - ValueError: if `vocabulary_size` is not positive integer. - ValueError: if `dimension` is not positive integer. - ValueError: if `initializer` is specified and is not callable. - ValueError: if `combiner` is not supported. - """ - if not isinstance(vocabulary_size, int) or vocabulary_size < 1: - raise ValueError('Invalid vocabulary_size {}.'.format(vocabulary_size)) - - if not isinstance(dimension, int) or dimension < 1: - raise ValueError('Invalid dimension {}.'.format(dimension)) - - if (initializer is not None) and (not callable(initializer)): - raise ValueError('initializer must be callable if specified.') - if initializer is None: - initializer = init_ops.truncated_normal_initializer( - mean=0.0, stddev=1 / math.sqrt(dimension)) - - if combiner not in ('mean', 'sum', 'sqrtn'): - raise ValueError('Invalid combiner {}'.format(combiner)) - - return super(TableConfig, cls).__new__(cls, vocabulary_size, dimension, - initializer, combiner) - - -AdamSlotVariableNames = collections.namedtuple( - 'AdamSlotVariableNames', ['m', 'v']) - -AdagradSlotVariableName = collections.namedtuple( - 'AdagradSlotVariableName', ['accumulator']) - -AdamSlotVariables = collections.namedtuple( - 'AdamSlotVariables', ['m', 'v']) - -AdagradSlotVariable = collections.namedtuple( - 'AdagradSlotVariable', ['accumulator']) - -VariablesAndOps = collections.namedtuple( - 'VariablesAndOps', - ['embedding_variables_by_table', 'slot_variables_by_table', - 'load_ops', 'retrieve_ops'] -) - - -# TODO(shizhiw): Factor `use_gradient_accumulation` and -# `pipeline_execution_with_tensor_core` out of `_OptimizationParameters`. -class _OptimizationParameters(object): - """Parameters common to all optimizations.""" - - def __init__(self, learning_rate, use_gradient_accumulation, - pipeline_execution_with_tensor_core): - self.learning_rate = learning_rate - self.use_gradient_accumulation = use_gradient_accumulation - self.pipeline_execution_with_tensor_core = ( - pipeline_execution_with_tensor_core) - - -class AdagradParameters(_OptimizationParameters): - """Optimization parameters for Adagrad.""" - - def __init__(self, learning_rate, initial_accumulator, - use_gradient_accumulation=False, - pipeline_execution_with_tensor_core=True): - """Optimization parameters for Adagrad. - - Args: - learning_rate: used for updating embedding table. - initial_accumulator: initial accumulator for Adagrad. - use_gradient_accumulation: setting this to `True` makes embedding - gradients calculation more accurate but slower. Please see - `optimization_parameters.proto` for details. - for details. - pipeline_execution_with_tensor_core: setting this to `True` makes training - faster, but trained model will be different if step N and step N+1 - involve the same set of embedding ID. Please see - `tpu_embedding_configuration.proto` for details. - """ - super(AdagradParameters, self).__init__(learning_rate, - use_gradient_accumulation, - pipeline_execution_with_tensor_core) - self.initial_accumulator = initial_accumulator - - -class AdamParameters(_OptimizationParameters): - """Optimization parameters for Adam.""" - - def __init__(self, learning_rate, - beta1=0.9, - beta2=0.999, - epsilon=1e-08, - lazy_adam=True, - sum_inside_sqrt=True, - use_gradient_accumulation=False, - pipeline_execution_with_tensor_core=True): - """Optimization parameters for Adam. - - Args: - learning_rate: a floating point value. The learning rate. - beta1: A float value. - The exponential decay rate for the 1st moment estimates. - beta2: A float value. - The exponential decay rate for the 2nd moment estimates. - epsilon: A small constant for numerical stability. - lazy_adam: Use lazy Adam instead of Adam. Lazy Adam trains faster. - Please see `optimization_parameters.proto` for details. - sum_inside_sqrt: This improves training speed. Please see - `optimization_parameters.proto` for details. - use_gradient_accumulation: setting this to `True` makes embedding - gradients calculation more accurate but slower. Please see - `optimization_parameters.proto` for details. - for details. - pipeline_execution_with_tensor_core: setting this to `True` makes training - faster, but trained model will be different if step N and step N+1 - involve the same set of embedding ID. Please see - `tpu_embedding_configuration.proto` for details. - """ - super(AdamParameters, self).__init__(learning_rate, - use_gradient_accumulation, - pipeline_execution_with_tensor_core) - self.beta1 = beta1 - self.beta2 = beta2 - self.epsilon = epsilon - self.lazy_adam = lazy_adam - self.sum_inside_sqrt = sum_inside_sqrt - - -class StochasticGradientDescentParameters(_OptimizationParameters): - """Optimization parameters for stochastic gradient descent. - - Args: - learning_rate: a floating point value. The learning rate. - use_gradient_accumulation: setting this to `True` makes embedding - gradients calculation more accurate but slower. Please see - `optimization_parameters.proto` for details. - pipeline_execution_with_tensor_core: setting this to `True` makes training - faster, but trained model will be different if step N and step N+1 - involve the same set of embedding ID. Please see - `tpu_embedding_configuration.proto` for details. - """ - - def __init__(self, learning_rate, use_gradient_accumulation=False, - pipeline_execution_with_tensor_core=True): - super(StochasticGradientDescentParameters, self).__init__( - learning_rate, use_gradient_accumulation, - pipeline_execution_with_tensor_core) - - -class TPUEmbedding(object): - """API for using TPU for embedding. - - Example: - ``` - table_config_user = tpu_embedding.TableConfig( - vocabulary_size=4, dimension=2, - initializer=initializer, combiner='mean') - table_to_config_dict = {'video': table_config_video, - 'user': table_config_user} - feature_to_table_dict = {'watched': 'video', - 'favorited': 'video', - 'friends': 'user'} - batch_size = 4 - num_hosts = 1 - optimization_parameters = tpu_embedding.AdagradParameters(1., 1.) - mode = tpu_embedding.TRAINING - embedding = tpu_embedding.TPUEmbedding( - table_to_config_dict, feature_to_table_dict, - batch_size, num_hosts, mode, optimization_parameters) - - batch_size_per_core = embedding.batch_size_per_core - sparse_features_list = [] - for host in hosts: - with ops.device(host): - for _ in range(embedding.num_cores_per_host): - sparse_features = {} - sparse_features['watched'] = sparse_tensor.SparseTensor(...) - sparse_features['favorited'] = sparse_tensor.SparseTensor(...) - sparse_features['friends'] = sparse_tensor.SparseTensor(...) - sparse_features_list.append(sparse_features) - - enqueue_ops = embedding.generate_enqueue_ops(sparse_features_list) - embedding_variables_and_ops = embedding.create_variables_and_ops() - - def computation(): - activations = embedding.get_activations() - loss = compute_loss(activations) - - base_optimizer = gradient_descent.GradientDescentOptimizer( - learning_rate=1) - cross_shard_optimizer = tpu_optimizer.CrossShardOptimizer( - base_optimizer) - - train_op = cross_shard_optimizer.minimize(loss) - gradients = ( - tpu_embedding_gradient.get_gradients_through_compute_gradients( - cross_shard_optimizer, loss, activations) - send_gradients_op = embedding.generate_send_gradients_op(gradients) - with ops.control_dependencies([train_op, send_gradients_op]): - loss = array_ops.identity(loss) - - loss = tpu.shard(computation, - num_shards=embedding.num_cores) - - with self.test_session() as sess: - sess.run(tpu.initialize_system(embedding_config= - embedding.config_proto)) - sess.run(variables.global_variables_initializer()) - sess.run(embedding_variables_and_ops.load_ops()) - sess.run(enqueue_ops) - loss_val = sess.run(loss) - ``` - """ - - # TODO(shizhiw): Instead of `feature_to_table_dict` which maps to table - # name, consider `feature_to_config_dict` which maps to `FeatureConfig`. - # `FeatureConfig` could have fields other than table name. For example, it - # could have a field to indicate that the feature should not be used to - # update embedding table (cr/204852758, cr/204940540). Also, this can support - # different combiners for different features within the same table. - # TODO(shizhiw, b/118512626): Remove `batch_size` from `__init__` and move it - # to `FeatureConfig`? - - # TODO(shizhiw): will it be cleaner to make `table_to_config_dict` and - # `feature_to_table_dict` lists of `TableSpec` and `FeatureSpec` respectively? - - # TODO(shizhiw): Consider adding `input_fn` as an option to remove boilerplate - # for-loops around construction of inputs. - - # `optimization_parameter` applies to all tables. If the need arises, - # we can add `optimization_parameters` to `TableConfig` to override this - # global setting. - @experimental - def __init__(self, - table_to_config_dict, - feature_to_table_dict, - batch_size, - mode, - master, - optimization_parameters=None): - """API for using TPU for embedding lookups. - - Args: - table_to_config_dict: A dictionary mapping from string of table name to - `TableConfig`. Table refers to an embedding table, e.g. `params` - argument to `tf.nn.embedding_lookup_sparse()`. - feature_to_table_dict: A dictionary mapping from string of feature name - to string of table name. Feature refers to ids to lookup in embedding - table, e.g. `sp_ids` argument to `tf.nn.embedding_lookup_sparse()`. - batch_size: An `int` representing the global batch size. - mode: `TRAINING` or `INFERENCE`. - master: A `string` representing the TensorFlow master to use. - optimization_parameters: `AdagradParameters`, `AdamParameters`, - `Stochasticgradientdescentparameters`. Must be set in training and must - be `None` in inference. - - Raises: - ValueError: if any input is invalid. - """ - _validate_table_to_config_dict(table_to_config_dict) - # Avoid nondeterminism from `Dict` iteration order by using `OrderedDict`. - self._table_to_config_dict = _create_ordered_dict(table_to_config_dict) - self._combiners = _create_combiners(self._table_to_config_dict) - - _validate_feature_to_table_dict(table_to_config_dict, feature_to_table_dict) - self._feature_to_table_dict = _create_ordered_dict(feature_to_table_dict) - self._table_to_features_dict = _create_table_to_features_dict( - self._feature_to_table_dict) - - self._batch_size = batch_size - - self._master = master - self._tpu_system_metadata = ( - tpu_system_metadata_lib._query_tpu_system_metadata(self._master)) # pylint: disable=protected-access - if self._tpu_system_metadata.num_cores == 0: - raise ValueError('TPUEmbedding needs TPUs, but master {} does not have ' - 'TPUs.'.format(self._master)) - self._num_hosts = self._tpu_system_metadata.num_hosts - self._hosts = [device.name for device in self._tpu_system_metadata.devices - if 'device:CPU:' in device.name] - self._num_cores_per_host = self._tpu_system_metadata.num_of_cores_per_host - self._num_cores = self._tpu_system_metadata.num_cores - - _validate_batch_size(self._batch_size, self._num_cores) - self._batch_size_per_core = self._batch_size // self._num_cores - - # TODO(shizhiw): remove `mode`? - if mode == TRAINING: - _validate_optimization_parameters(optimization_parameters) - self._optimization_parameters = optimization_parameters - elif mode == INFERENCE: - if optimization_parameters is not None: - raise ValueError('`optimization_parameters` should be `None` ' - 'for inference mode.') - self._optimization_parameters = ( - StochasticGradientDescentParameters(1.)) - else: - raise ValueError('`mode` only supports {} and {}; got {}.' - .format(TRAINING, INFERENCE, mode)) - self._mode = mode - - # TODO(shizhiw): move `optimization_parameters` into `_optimizer_handler` - # and create special handler for inference that inherits from - # StochasticGradientDescentHandler with more user-friendly error message - # on get_slot(). - self._optimizer_handler = _get_optimization_handler( - self._optimization_parameters) - - self._config_proto = self._create_config_proto() - - @property - def hosts(self): - """A list of device names for CPU hosts. - - Returns: - A list of device names for CPU hosts. - """ - return copy.copy(self._hosts) - - # TODO(shizhiw): change to num_tensor_cores_per_host to be more explicit and - # to be consistent with `tpu_embedding_configuration.proto`. - @property - def num_cores_per_host(self): - """Number of TPU cores on a CPU host. - - Returns: - Number of TPU cores on a CPU host. - """ - return self._num_cores_per_host - - @property - def num_cores(self): - """Total number of TPU cores on all hosts. - - Returns: - Total number of TPU cores on all hosts. - """ - return self._num_cores - - @property - def batch_size_per_core(self): - """Batch size for each TPU core. - - The sparse tensors in `sparse_features_list` to `generate_enqueue_ops` - must have batch dimension equal to this. - - Returns: - Batch size for each TPU core. - """ - return self._batch_size_per_core - - @property - def config_proto(self): - """Create embedding config proto for `tpu.initialize_system()`. - - Returns: - an `TPUEmbeddingConfiguration` proto describing the desired - configuration of the hardware embedding lookup tables, which - is passed to `tpu.initialize_system()`. - """ - return self._config_proto - - @property - def table_to_config_dict(self): - return copy.copy(self._table_to_config_dict) - - @property - def feature_to_table_dict(self): - return copy.copy(self._feature_to_table_dict) - - @property - def table_to_features_dict(self): - return copy.copy(self._table_to_features_dict) - - @property - def optimization_parameters(self): - return self._optimization_parameters - - def _create_config_proto(self): - """Create `TPUEmbeddingConfiguration`.""" - config_proto = elc.TPUEmbeddingConfiguration() - for table in self._table_to_config_dict: - table_descriptor = config_proto.table_descriptor.add() - table_descriptor.name = table - - table_config = self._table_to_config_dict[table] - table_descriptor.vocabulary_size = table_config.vocabulary_size - table_descriptor.dimension = table_config.dimension - - features_for_table = self._table_to_features_dict[table] - table_descriptor.num_features = len(features_for_table) - - table_descriptor.optimization_parameters.learning_rate.constant = ( - self._optimization_parameters.learning_rate) - table_descriptor.optimization_parameters.gradient_accumulation_status = ( - optimization_parameters_pb2.GradientAccumulationStatus.ENABLED - if self._optimization_parameters.use_gradient_accumulation else - optimization_parameters_pb2.GradientAccumulationStatus.DISABLED) - # For compatibility with old TPU workers. - table_descriptor.optimization_parameters.use_gradient_accumulation = ( - self._optimization_parameters.use_gradient_accumulation) - self._optimizer_handler.set_optimization_parameters(table_descriptor) - - config_proto.mode = self._mode - config_proto.batch_size_per_tensor_core = self._batch_size_per_core - config_proto.num_hosts = self._num_hosts - config_proto.num_tensor_cores = self._num_cores - config_proto.sharding_strategy = elc.TPUEmbeddingConfiguration.DIV_DEFAULT - config_proto.pipeline_execution_with_tensor_core = ( - self._optimization_parameters.pipeline_execution_with_tensor_core) - - return config_proto - - def create_variables_and_ops(self, embedding_variable_name_by_table=None, - slot_variable_names_by_table=None): - """Create embedding and slot variables, with ops to load and retrieve them. - - Args: - embedding_variable_name_by_table: A dictionary mapping from string of - table name to string of embedding variable name. If `None`, - defaults from `get_default_slot_variable_names()` will be used. - slot_variable_names_by_table: A dictionary mapping from string of table - name to `AdamSlotVariableNames`, `AdagradSlotVariableNames` etc. If - `None`, defaults from `get_default_slot_variable_names()` will be used. - - Returns: - `tpu_embedding.VariablesAndOps` with: - A dictionary mapping from string of table name to embedding variables, - A dictionary mapping from string of table name to AdagradSlotVariable, - AdamSlotVariables etc with slot variables, - A function which returns a list of ops to load embedding and slot - variables from TPU to CPU. - A function which returns a list of ops to retrieve embedding and slot - variables from TPU to CPU. - """ - embedding_variables_by_table = {} - slot_variables_by_table = {} - load_op_fns = [] - retrieve_op_fns = [] - for table in self._table_to_config_dict: - if embedding_variable_name_by_table: - embedding_variable_name = embedding_variable_name_by_table[table] - else: - embedding_variable_name = table - if slot_variable_names_by_table: - slot_variable_names = slot_variable_names_by_table[table] - else: - slot_variable_names = ( - self._optimizer_handler.get_default_slot_variable_names(table)) - - device_fn = _create_device_fn(self._hosts) - with ops.device(device_fn): - table_variables = _create_partitioned_variables( - name=embedding_variable_name, - num_hosts=self._num_hosts, - vocabulary_size=self._table_to_config_dict[table].vocabulary_size, - embedding_dimension=self._table_to_config_dict[table].dimension, - initializer=self._table_to_config_dict[table].initializer, - collections=[ops.GraphKeys.GLOBAL_VARIABLES]) - embedding_variables_by_table[table] = table_variables - - slot_variables_for_table, load_ops_fn, retrieve_ops_fn = ( - self._optimizer_handler.create_variables_and_ops( - table, slot_variable_names, self._num_hosts, - self._table_to_config_dict[table], table_variables) - ) - slot_variables_by_table[table] = slot_variables_for_table - load_op_fns.append(load_ops_fn) - retrieve_op_fns.append(retrieve_ops_fn) - - def load_ops(): - """Calls and returns the load ops for each embedding table. - - Returns: - A list of ops to load embedding and slot variables from CPU to TPU. - """ - load_ops_list = [] - for load_op_fn in load_op_fns: - load_ops_list.extend(load_op_fn()) - return load_ops_list - - def retrieve_ops(): - """Calls and returns the retrieve ops for each embedding table. - - Returns: - A list of ops to retrieve embedding and slot variables from TPU to CPU. - """ - retrieve_ops_list = [] - for retrieve_op_fn in retrieve_op_fns: - retrieve_ops_list.extend(retrieve_op_fn()) - return retrieve_ops_list - - return VariablesAndOps(embedding_variables_by_table, - slot_variables_by_table, - load_ops, retrieve_ops) - - def generate_enqueue_ops(self, sparse_features_list): - """Generate enqueue ops. - - Args: - sparse_features_list: a list of dictionary mapping from string - of feature names to sparse tensor. Each dictionary is for one - TPU core. Dictionaries for the same core should be contiguous - on the list. - - Returns: - Ops to enqueue to TPU for embedding. - """ - self._validate_generate_enqueue_ops_sparse_features_list( - sparse_features_list) - return [ - self._generate_enqueue_op( - sparse_features, device_ordinal=i % self._num_cores_per_host) - for i, sparse_features in enumerate(sparse_features_list) - ] - - def _validate_generate_enqueue_ops_sparse_features_list( - self, sparse_features_list): - """Validate `sparse_features_list`.""" - if len(sparse_features_list) != self._num_cores: - raise ValueError('Length of `sparse_features_list` should match the ' - 'number of cores; ' - '`len(sparse_features_list)` is {}, ' - 'number of cores is {}.'.format( - len(sparse_features_list), self._num_cores)) - - feature_set = set(self._feature_to_table_dict.keys()) - contiguous_device = None - for i, sparse_features in enumerate(sparse_features_list): - used_feature_set = set(sparse_features.keys()) - - # Check features are valid. - missing_feature_set = feature_set - used_feature_set - if missing_feature_set: - raise ValueError('`sparse_features_list[{}]` misses a feature that is ' - 'in `feature_to_config_dict`: {}.'.format( - i, missing_feature_set)) - - extra_feature_set = used_feature_set - feature_set - if extra_feature_set: - raise ValueError('`sparse_features_list[{}]` has a feature that is not ' - 'in `feature_to_config_dict`: {}.'.format( - i, extra_feature_set)) - - device = None - device_feature = None - for feature, tensor in six.iteritems(sparse_features): - if not isinstance(tensor, sparse_tensor.SparseTensor): - raise ValueError('`sparse_features_list[{}]` has a feature that is ' - 'not mapped to `SparseTensor`. ' - '`feature`: {}, type: {}'.format( - i, feature, type(tensor))) - - # Check all features are on the same device. - if device is None: - device = tensor.op.device - device_feature = feature - else: - if device != tensor.op.device: - raise ValueError('Devices are different between features in ' - '`sparse_features_list[{}]`; ' - 'devices: {}, {}; features: {}, {}.'.format( - i, device, tensor.op.device, feature, - device_feature)) - - if i % self._num_cores_per_host: - if device != contiguous_device: - raise ValueError('We expect the `sparse_features` which are on the ' - 'same host to be contiguous in ' - '`sparse_features_list`, ' - '`sparse_features_list[{}]` is on device {}, ' - 'but is expected to be on device {}.'.format( - i, device, contiguous_device)) - else: - contiguous_device = device - - def _generate_enqueue_op(self, sparse_features, device_ordinal): - with ops.colocate_with(list(sparse_features.values())[0]): - sample_idcs, embedding_idcs, aggregation_weights = ( - self._format_for_tpu_embedding_sparse_batch(sparse_features)) - return tpu_ops.enqueue_tpu_embedding_sparse_batch( - sample_idcs, - embedding_idcs, - aggregation_weights, - combiners=self._combiners, - device_ordinal=device_ordinal) - - def _format_for_tpu_embedding_sparse_batch(self, sparse_features): - """Format sparse features for `enqueue_tpu_embedding_sparse_batch()`. - - Args: - sparse_features: a `Dict` of `SparseTensor`s for embedding. - - Returns: - Arguments for `enqueue_tpu_embedding_sparse_batch()`. - """ - - sample_idcs, embedding_idcs, aggregation_weights = list(), list(), list() - for table in self._table_to_features_dict: - sample_t, indices_t, weights_t = list(), list(), list() - - features = self._table_to_features_dict[table] - for i, feature in enumerate(features): - tensor = sparse_features[feature] - sample_indices = tensor.indices[:, 0] - embedding_indices = tensor.values - weights = array_ops.ones_like(embedding_indices) - sample_t.append(i * self._batch_size_per_core + sample_indices) - indices_t.append(embedding_indices) - weights_t.append(weights) - - sample_idcs.append( - math_ops.cast(array_ops.concat(sample_t, axis=0), dtype=dtypes.int32)) - embedding_idcs.append( - math_ops.cast( - array_ops.concat(indices_t, axis=0), dtype=dtypes.int32)) - aggregation_weights.append( - math_ops.cast( - array_ops.concat(weights_t, axis=0), dtype=dtypes.float32)) - - return sample_idcs, embedding_idcs, aggregation_weights - - def get_activations(self): - """Get activations for features. - - This should be called within `computation` that is passed to - `tpu.replicate` and friends. - - Returns: - A dictionary mapping from `String` of feature name to `Tensor` - of activation. - """ - recv_activations = tpu_ops.recv_tpu_embedding_activations( - num_outputs=len(self._table_to_config_dict), - config=self._config_proto.SerializeToString()) - - activations = collections.OrderedDict() - for table_id, table in enumerate(self._table_to_features_dict): - features = self._table_to_features_dict[table] - for lookup_id, feature in enumerate(features): - start_row = lookup_id * self._batch_size_per_core - end_row = start_row + self._batch_size_per_core - activations[feature] = recv_activations[table_id][start_row:end_row, :] - return activations - - def generate_send_gradients_op(self, feature_to_gradient_dict): - """Send gradient to TPU embedding. - - Args: - feature_to_gradient_dict: dict mapping feature names to gradient wrt - activations. - - Returns: - SendTPUEmbeddingGradients Op. - - Raises: - RuntimeError: If `mode` is not `TRAINING`. - """ - if self._mode != TRAINING: - raise RuntimeError('Only in training mode gradients need to ' - 'be sent to TPU embedding; got mode {}.' - .format(self._mode)) - gradients = [] - for table in self._table_to_features_dict: - features = self._table_to_features_dict[table] - table_gradients = [ - feature_to_gradient_dict[feature] for feature in features - ] - concat_table_grads = array_ops.concat(table_gradients, axis=0) - gradients.append(concat_table_grads) - return tpu_ops.send_tpu_embedding_gradients( - inputs=gradients, config=self.config_proto.SerializeToString()) - - -def _validate_table_to_config_dict(table_to_config_dict): - """Validate `table_to_config_dict`.""" - for k, v in six.iteritems(table_to_config_dict): - if not isinstance(v, TableConfig): - raise ValueError('Value of `table_to_config_dict` must be of type ' - '`TableConfig`, got {} for {}.'.format(type(v), k)) - - -def _validate_feature_to_table_dict(table_to_config_dict, - feature_to_table_dict): - """Validate `feature_to_table_dict`.""" - used_table_set = set(feature_to_table_dict.values()) - table_set = set(table_to_config_dict.keys()) - - unused_table_set = table_set - used_table_set - if unused_table_set: - raise ValueError('`table_to_config_dict` specifies table that is not ' - 'used in `feature_to_table_dict`: {}.' - .format(unused_table_set)) - - extra_table_set = used_table_set - table_set - if extra_table_set: - raise ValueError('`feature_to_table_dict` refers to a table that is not ' - 'specified in `table_to_config_dict`: {}.' - .format(extra_table_set)) - - -def _validate_batch_size(batch_size, num_cores): - if batch_size % num_cores: - raise ValueError('`batch_size` is not a multiple of number of ' - 'cores. `batch_size`={}, `_num_cores`={}.'.format( - batch_size, num_cores)) - - -def _validate_optimization_parameters(optimization_parameters): - if not isinstance(optimization_parameters, _OptimizationParameters): - raise ValueError('`optimization_parameters` must inherit from ' - '`_OptimizationPramaters`. ' - '`type(optimization_parameters)`={}'.format( - type(optimization_parameters))) - - -class _OptimizerHandler(object): - """Interface class for handling optimizer specific logic.""" - - def __init__(self, optimization_parameters): - self._optimization_parameters = optimization_parameters - - def set_optimization_parameters(self, table_descriptor): - raise NotImplementedError() - - def get_default_slot_variable_names(self, table): - raise NotImplementedError() - - def create_variables_and_ops(self, table, slot_variable_names, num_hosts, - table_config, table_variables): - raise NotImplementedError() - - -class _AdagradHandler(_OptimizerHandler): - """Handles Adagrad specific logic.""" - - def __init__(self, optimization_parameters): - super(_AdagradHandler, self).__init__(optimization_parameters) - self._table_to_accumulator_variables_dict = {} - - def set_optimization_parameters(self, table_descriptor): - table_descriptor.optimization_parameters.adagrad.SetInParent() - - def get_default_slot_variable_names(self, table): - return AdagradSlotVariableName('{}/{}'.format(table, 'Adagrad')) - - def create_variables_and_ops(self, table, slot_variable_names, num_hosts, - table_config, table_variables): - accumulator_initializer = init_ops.constant_initializer( - self._optimization_parameters.initial_accumulator) - accumulator_variables = _create_partitioned_variables( - name=slot_variable_names.accumulator, - num_hosts=num_hosts, - vocabulary_size=table_config.vocabulary_size, - embedding_dimension=table_config.dimension, - collections=[ops.GraphKeys.GLOBAL_VARIABLES], - initializer=accumulator_initializer) - slot_variables = AdagradSlotVariable(accumulator_variables) - - def load_ops_fn(): - """Returns the retrieve ops for AdaGrad embedding tables. - - Returns: - A list of ops to load embedding and slot variables from CPU to TPU. - """ - load_op_list = [] - for host_id, table_variable, accumulator_variable in (zip( - range(num_hosts), table_variables, accumulator_variables)): - with ops.colocate_with(table_variable): - load_parameters_op = ( - tpu_ops.load_tpu_embedding_adagrad_parameters( - parameters=table_variable, - accumulators=accumulator_variable, - table_name=table, - num_shards=num_hosts, - shard_id=host_id)) - load_op_list.append(load_parameters_op) - return load_op_list - - def retrieve_ops_fn(): - """Returns the retrieve ops for AdaGrad embedding tables. - - Returns: - A list of ops to retrieve embedding and slot variables from TPU to CPU. - """ - retrieve_op_list = [] - for host_id, table_variable, accumulator_variable in (zip( - range(num_hosts), table_variables, accumulator_variables)): - with ops.colocate_with(table_variable): - retrieved_table, retrieved_accumulator = ( - tpu_ops.retrieve_tpu_embedding_adagrad_parameters( - table_name=table, - num_shards=num_hosts, - shard_id=host_id)) - retrieve_parameters_op = control_flow_ops.group( - state_ops.assign(table_variable, retrieved_table), - state_ops.assign(accumulator_variable, retrieved_accumulator)) - retrieve_op_list.append(retrieve_parameters_op) - return retrieve_op_list - - return slot_variables, load_ops_fn, retrieve_ops_fn - - -class _AdamHandler(_OptimizerHandler): - """Handles Adam specific logic.""" - - def __init__(self, optimization_parameters): - super(_AdamHandler, self).__init__(optimization_parameters) - self._table_to_m_variables_dict = {} - self._table_to_v_variables_dict = {} - - def set_optimization_parameters(self, table_descriptor): - table_descriptor.optimization_parameters.adam.beta1 = ( - self._optimization_parameters.beta1) - table_descriptor.optimization_parameters.adam.beta2 = ( - self._optimization_parameters.beta2) - table_descriptor.optimization_parameters.adam.epsilon = ( - self._optimization_parameters.epsilon) - table_descriptor.optimization_parameters.adam.use_non_lazy_adam = ( - not self._optimization_parameters.lazy_adam) - table_descriptor.optimization_parameters.adam.use_sum_inside_sqrt = ( - self._optimization_parameters.sum_inside_sqrt) - - def get_default_slot_variable_names(self, table): - return AdamSlotVariableNames('{}/{}/m'.format(table, 'Adam'), - '{}/{}/v'.format(table, 'Adam')) - - def create_variables_and_ops(self, table, slot_variable_names, num_hosts, - table_config, table_variables): - m_initializer = init_ops.zeros_initializer() - m_variables = _create_partitioned_variables( - name=slot_variable_names.m, - num_hosts=num_hosts, - vocabulary_size=table_config.vocabulary_size, - embedding_dimension=table_config.dimension, - collections=[ops.GraphKeys.GLOBAL_VARIABLES], - initializer=m_initializer) - v_initializer = init_ops.zeros_initializer() - v_variables = _create_partitioned_variables( - name=slot_variable_names.v, - num_hosts=num_hosts, - vocabulary_size=table_config.vocabulary_size, - embedding_dimension=table_config.dimension, - collections=[ops.GraphKeys.GLOBAL_VARIABLES], - initializer=v_initializer) - slot_variables = AdamSlotVariables(m_variables, v_variables) - - def load_ops_fn(): - """Returns the retrieve ops for AdaGrad embedding tables. - - Returns: - A list of ops to load embedding and slot variables from CPU to TPU. - """ - load_op_list = [] - for host_id, table_variable, m_variable, v_variable in (zip( - range(num_hosts), table_variables, - m_variables, v_variables)): - with ops.colocate_with(table_variable): - load_parameters_op = ( - tpu_ops.load_tpu_embedding_adam_parameters( - parameters=table_variable, - momenta=m_variable, - velocities=v_variable, - table_name=table, - num_shards=num_hosts, - shard_id=host_id)) - - load_op_list.append(load_parameters_op) - return load_op_list - - def retrieve_ops_fn(): - """Returns the retrieve ops for Adam embedding tables. - - Returns: - A list of ops to retrieve embedding and slot variables from TPU to CPU. - """ - - retrieve_op_list = [] - for host_id, table_variable, m_variable, v_variable in (zip( - range(num_hosts), table_variables, - m_variables, v_variables)): - with ops.colocate_with(table_variable): - retrieved_table, retrieved_m, retrieved_v = ( - tpu_ops.retrieve_tpu_embedding_adam_parameters( - table_name=table, - num_shards=num_hosts, - shard_id=host_id)) - retrieve_parameters_op = control_flow_ops.group( - state_ops.assign(table_variable, retrieved_table), - state_ops.assign(m_variable, retrieved_m), - state_ops.assign(v_variable, retrieved_v)) - - retrieve_op_list.append(retrieve_parameters_op) - return retrieve_op_list - - return slot_variables, load_ops_fn, retrieve_ops_fn - - -class _StochasticGradientDescentHandler(_OptimizerHandler): - """Handles stochastic gradient descent specific logic.""" - - def set_optimization_parameters(self, table_descriptor): - (table_descriptor.optimization_parameters.stochastic_gradient_descent - .SetInParent()) - - def get_default_slot_variable_names(self, table): - return None - - def create_variables_and_ops(self, table, slot_variable_names, num_hosts, - table_config, table_variables): - del table_config - - def load_ops_fn(): - """Returns the retrieve ops for AdaGrad embedding tables. - - Returns: - A list of ops to load embedding and slot variables from CPU to TPU. - """ - load_op_list = [] - for host_id, table_variable in (zip( - range(num_hosts), table_variables)): - with ops.colocate_with(table_variable): - load_parameters_op = ( - tpu_ops - .load_tpu_embedding_stochastic_gradient_descent_parameters( - parameters=table_variable, - table_name=table, - num_shards=num_hosts, - shard_id=host_id)) - - load_op_list.append(load_parameters_op) - return load_op_list - - def retrieve_ops_fn(): - """Returns the retrieve ops for SGD embedding tables. - - Returns: - A list of ops to retrieve embedding and slot variables from TPU to CPU. - """ - - retrieve_op_list = [] - for host_id, table_variable in (zip( - range(num_hosts), table_variables)): - with ops.colocate_with(table_variable): - retrieved_table = ( - tpu_ops - .retrieve_tpu_embedding_stochastic_gradient_descent_parameters( - table_name=table, - num_shards=num_hosts, - shard_id=host_id)) - retrieve_parameters_op = control_flow_ops.group( - state_ops.assign(table_variable, retrieved_table)) - - retrieve_op_list.append(retrieve_parameters_op) - return retrieve_op_list - - return None, load_ops_fn, retrieve_ops_fn - - -def _get_optimization_handler(optimization_parameters): - if isinstance(optimization_parameters, AdagradParameters): - return _AdagradHandler(optimization_parameters) - elif isinstance(optimization_parameters, AdamParameters): - return _AdamHandler(optimization_parameters) - elif isinstance(optimization_parameters, StochasticGradientDescentParameters): - return _StochasticGradientDescentHandler(optimization_parameters) - else: - return NotImplementedError() - - -def _create_ordered_dict(d): - """Create an OrderedDict from Dict.""" - return collections.OrderedDict((k, d[k]) for k in sorted(d)) - - -def _create_combiners(table_to_config_dict): - return [table_to_config_dict[t].combiner for t in table_to_config_dict] - - -def _create_table_to_features_dict(feature_to_table_dict): - """Create mapping from table to a list of its features.""" - table_to_features_dict_tmp = {} - for feature, table in six.iteritems(feature_to_table_dict): - if table in table_to_features_dict_tmp: - table_to_features_dict_tmp[table].append(feature) - else: - table_to_features_dict_tmp[table] = [feature] - - table_to_features_dict = collections.OrderedDict() - for table in sorted(table_to_features_dict_tmp): - table_to_features_dict[table] = sorted(table_to_features_dict_tmp[table]) - return table_to_features_dict - - -def _create_device_fn(hosts): - """Create device_fn() to use with _create_partitioned_variables().""" - - def device_fn(op): - """Returns the `device` for `op`.""" - part_match = re.match(r'.*/part_(\d+)(/|$)', op.name) - - if part_match: - idx = int(part_match.group(1)) - else: - raise RuntimeError('Internal Error: ' - 'Expected %s to contain /part_*.' % op.name) - - device = hosts[idx] - return device - - return device_fn - - -def _create_partitioned_variables(name, - num_hosts, - vocabulary_size, - embedding_dimension, - initializer, - collections=None): # pylint: disable=redefined-outer-name - """Creates ParitionedVariables based on `num_hosts` for `table`.""" - # TODO(shizhiw): automatically place embedding lookup elsewhere? - if vocabulary_size < num_hosts: - raise ValueError('`vocabulary_size`({}) is smaller than `num_hosts`({}). ' - 'As TPU embedding is not optimized for small tables, ' - 'please consider other ways for this embedding lookup.') - - return list(variable_scope.get_variable( - name, - shape=(vocabulary_size, embedding_dimension), - partitioner=partitioned_variables.fixed_size_partitioner(num_hosts), - dtype=dtypes.float32, - initializer=initializer, - collections=collections, - trainable=False)) +# pylint: disable=wildcard-import,unused-import +from tensorflow.python.tpu.tpu_embedding import * +# pylint: enable=wildcard-import,unused-import diff --git a/tensorflow/contrib/tpu/python/tpu/tpu_embedding_gradient.py b/tensorflow/contrib/tpu/python/tpu/tpu_embedding_gradient.py index dace0d801b..308adc77e9 100644 --- a/tensorflow/contrib/tpu/python/tpu/tpu_embedding_gradient.py +++ b/tensorflow/contrib/tpu/python/tpu/tpu_embedding_gradient.py @@ -1,153 +1,23 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 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 +# 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. -# =================================================================== -"""Optional helper for gradient handling.""" +# ============================================================================== +"""Stub file to maintain backwards compatibility.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function -import collections - -from tensorflow.contrib.tpu.python.ops import tpu_ops -from tensorflow.python.framework import dtypes -from tensorflow.python.framework import ops -from tensorflow.python.ops import variable_scope -from tensorflow.python.ops import variables - - -def get_gradients_through_compute_gradients(optimizer, loss, activations): - """Compute gradients to send to TPU embedding. - - Args: - optimizer: a subclass of optimizer.Optimizer, usually CrossShardOptimizer. - Used to call compute_gradients(). - loss: a Tensor to call optimizer.compute_gradients() on. - activations: an OrderedDict mapping feature_name to Tensors of activations. - - Returns: - An OrderedDict mapping from feature name Strings to Tensors of gradients of - the loss wrt the activations of the features. - """ - activation_list = activations.values() - grads_and_vars = optimizer.compute_gradients(loss, activation_list) - grads = [grad for grad, _ in grads_and_vars] - feature_to_gradient_dict = collections.OrderedDict( - zip(activations.keys(), grads)) - return feature_to_gradient_dict - - -def create_dummy_table_variables(tpu_embedding): - """Create dummy embedding table variables. - - The sole purpose of these dummy variables are to trigger gradient - calcuation wrt them so that the gradients wrt activation can be captured - and later sent to TPU embedding. - - Args: - tpu_embedding: TPUEmbedding, dummy table variables will be created for use - with tpu_embedding. - - Returns: - A tuple of dummy variables and their initializer. - - Raises: - RuntimeError: if collection to store gradients already exists and is not - empty. - """ - dummy_table_variables = collections.OrderedDict() - for table_id, table in enumerate(tpu_embedding.table_to_features_dict): - dummy_table_variables[table] = ( - # Explicitly specifying collections prevents this variable from - # being added to the GLOBAL_VARIABLES collection, so that Saver() - # ignores it. - # But Tensorflow optimizer creates slot variable for these dummy - # variable, e.g. tpu_embedding_dummy_table_variable_mlp_user/Adam{_1}, - # which will be in GLOBAL_VARIABLES collection, - variable_scope.get_variable( - 'tpu_embedding_dummy_table_variable_{}'.format(table), - dtype=dtypes.float32, - shape=[1], - use_resource=True, - trainable=True, - collections=['tpu_embedding_dummy_table_variables'])) - - g = ops.get_default_graph() - table_gradients = g.get_collection_ref( - 'tpu_embedding_gradients_table_{}'.format(table_id)) - if table_gradients: - raise RuntimeError( - 'tpu_embedding_gradients_table_{} is not empty.'.format(table_id)) - table_gradients.extend( - [None] * len(tpu_embedding.table_to_features_dict[table])) - - return (dummy_table_variables, - variables.variables_initializer( - dummy_table_variables.values(), - name='tpu_embedding_dummy_table_variables_init')) - - -def hook_dummy_table_variables_to_activations(tpu_embedding, activations, - dummy_table_variables): - """Have activations depend on dummy table variables for gradient intercept. - - Args: - tpu_embedding: TPUEmbedding, activations and dummy_table_variables are from - tpu_embedding. - activations: An OrderedDict of feature name String to activation tensors. - dummy_table_variables: An OrderedDict of table name String to dummy table - variables. - - Returns: - An OrderedDict of feature name String to activation tensors, which can be - used just as the activations input. - """ - new_activations = collections.OrderedDict() - for feature in activations: - table = tpu_embedding.feature_to_table_dict[feature] - new_activations[feature] = tpu_ops.tpu_embedding_activations( - dummy_table_variables[table], - activations[feature], - table_id=tpu_embedding.table_to_config_dict.keys().index(table), - lookup_id=tpu_embedding.table_to_features_dict[table].index(feature)) - return new_activations - - -def get_gradients_through_dummy_table_variables(tpu_embedding): - """Get gradients wrt the activations of each feature. - - Args: - tpu_embedding: TPUEmbedding, create dummy table variable to be used with - tpu_embedding. - - Returns: - An OrderedDict mapping feature name to gradient. - - Raises: - ValueError: if some gradients are not defined. - """ - g = ops.get_default_graph() - feature_to_gradient_dict = collections.OrderedDict() - for table_id, table in enumerate(tpu_embedding.table_to_config_dict): - table_gradients = g.get_collection( - 'tpu_embedding_gradients_table_{}'.format(table_id)) - if any(gradient is None for gradient in table_gradients): - raise ValueError( - 'Table {} with id {} has undefined gradients: this is probably ' - 'because the model asked TPUEmbedding to compute activations that ' - 'were not used.'.format(table, table_id)) - for feature, gradient in zip(tpu_embedding.table_to_features_dict[table], - table_gradients): - feature_to_gradient_dict[feature] = gradient - return feature_to_gradient_dict +# pylint: disable=wildcard-import,unused-import +from tensorflow.python.tpu.tpu_embedding_gradient import * +# pylint: enable=wildcard-import,unused-import diff --git a/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py b/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py index b2019c1083..71d1d0fdd4 100644 --- a/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py +++ b/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py @@ -1,3761 +1,30 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 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 +# 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. -# =================================================================== -"""TPUEstimator class.""" +# ============================================================================== +"""Stub file to maintain backwards compatibility.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function -import collections -import copy -import os -import signal -import sys -import threading -import time - -import numpy as np -import six -from six.moves import queue as Queue # pylint: disable=redefined-builtin -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_estimator_embedding -from tensorflow.contrib.tpu.python.tpu import error_handling -from tensorflow.contrib.tpu.python.tpu import functional as tpu_functional -from tensorflow.contrib.tpu.python.tpu import session_support -from tensorflow.contrib.tpu.python.tpu import tensor_tracer -from tensorflow.contrib.tpu.python.tpu import tpu -from tensorflow.contrib.tpu.python.tpu import tpu_config -from tensorflow.contrib.tpu.python.tpu import tpu_context -from tensorflow.contrib.tpu.python.tpu import tpu_embedding_gradient -from tensorflow.contrib.tpu.python.tpu import tpu_feed -from tensorflow.contrib.tpu.python.tpu import tpu_function -from tensorflow.contrib.tpu.python.tpu import training_loop -from tensorflow.contrib.tpu.python.tpu import util as util_lib -from tensorflow.contrib.tpu.python.tpu._tpu_estimator_embedding import AdamParameters # pylint: disable=unused-import -from tensorflow.contrib.tpu.python.tpu._tpu_estimator_embedding import EmbeddingConfigSpec # pylint: disable=unused-import -from tensorflow.contrib.training.python.training import hparam -from tensorflow.core.framework import variable_pb2 -from tensorflow.core.framework.summary_pb2 import Summary -from tensorflow.core.protobuf import config_pb2 -from tensorflow.core.protobuf.tpu import compilation_result_pb2 as tpu_compilation_result -from tensorflow.python.client import session as tf_session -from tensorflow.python.data.ops import dataset_ops -from tensorflow.python.data.util import nest as data_nest -from tensorflow.python.estimator import estimator as estimator_lib -from tensorflow.python.estimator import model_fn as model_fn_lib -from tensorflow.python.estimator.export import export_output as export_output_lib -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 -from tensorflow.python.framework import ops -from tensorflow.python.ops import array_ops -from tensorflow.python.ops import check_ops -from tensorflow.python.ops import control_flow_ops -from tensorflow.python.ops import init_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.ops import summary_ops_v2 as contrib_summary -from tensorflow.python.ops import variable_scope -from tensorflow.python.ops import variables -from tensorflow.python.platform import tf_logging as logging -from tensorflow.python.saved_model import tag_constants -from tensorflow.python.summary import summary -from tensorflow.python.training import basic_session_run_hooks -from tensorflow.python.training import evaluation -from tensorflow.python.training import session_run_hook -from tensorflow.python.training import training -from tensorflow.python.training import training_util -from tensorflow.python.util import function_utils -from tensorflow.python.util import nest -from tensorflow.python.util import tf_inspect - -_INITIAL_LOSS = 1e7 -_ZERO_LOSS = 0. -_TPU_ESTIMATOR = 'tpu_estimator' -_ITERATIONS_PER_LOOP_VAR = 'iterations_per_loop' -_BATCH_SIZE_KEY = 'batch_size' -_CTX_KEY = 'context' -_USE_TPU_KEY = 'use_tpu' -_CROSS_REPLICA_SUM_OP = 'CrossReplicaSum' -_ONE_GIGABYTE = 1024 * 1024 * 1024 -_TPU_ENQUEUE_OPS = '_tpu_enqueue_ops' -_TPU_TRAIN_OP = '_tpu_train_op' -_REWRITE_FOR_INFERENCE_MODE = '_rewrite_for_inference' -_KEY_WHEN_PREDICTIONS_IS_A_TENSOR = '_key_when_predictions_is_a_tensor' - -# Ideally _USE_TPU_KEY should be reserved as well. However there are already -# models that make use of this key, thus it can not be reserved now to prevent -# breakage. In the long run, we would like to mitigate this by migrating models -# off of using _USE_TPU_KEY. -_RESERVED_PARAMS_KEYS = [_BATCH_SIZE_KEY, _CTX_KEY] - -# TODO(b/65703635): Flip the value and remove all dead code. Currently, this is -# only used for per-core based deployments. For per-host based pipelines, if a -# user returns a Dataset instance it will be automatically wrapped in a -# tf.while_loop (This can be disabled by returning features and labels -# explicitly). -_WRAP_INPUT_FN_INTO_WHILE_LOOP = False - -ops.register_proto_function( - '{}_{}'.format(_TPU_ESTIMATOR, _ITERATIONS_PER_LOOP_VAR), - proto_type=variable_pb2.VariableDef, - to_proto=resource_variable_ops._to_proto_fn, # pylint: disable=protected-access - from_proto=resource_variable_ops._from_proto_fn) # pylint: disable=protected-access - - -def _is_iterable(obj): - """A Python 2 and 3 compatible util to check whether `obj` is iterable.""" - try: - iter(obj) - return True - except TypeError: - return False - - -class CatchInvalidHostcallFunctions(control_flow_ops.XLAControlFlowContext): - - def AddOp(self, op): - if op.type in [ - 'AudioSummary', 'AudioSummaryV2', 'HistogramSummary', 'ImageSummary', - 'MergeSummary', 'ScalarSummary', 'TensorSummary', 'TensorSummaryV2' - ]: - raise ValueError('Use tf.contrib.summary inside of host_calls.') - - -def _create_global_step(graph): - graph = graph or ops.get_default_graph() - if training.get_global_step(graph) is not None: - raise ValueError('"global_step" already exists.') - # Create in proper graph and base name_scope. - with graph.as_default() as g, g.name_scope(None): - return variable_scope.get_variable( - ops.GraphKeys.GLOBAL_STEP, - shape=[], - dtype=dtypes.int64, - initializer=init_ops.zeros_initializer(), - trainable=False, - use_resource=True, - collections=[ops.GraphKeys.GLOBAL_VARIABLES, ops.GraphKeys.GLOBAL_STEP]) - - -def _create_or_get_iterations_per_loop(): - """Creates or gets the iterations_per_loop variable. - - In TPUEstimator, the user provided computation, the model_fn, is wrapped - inside a tf.while_loop for peak performance. The iterations of the loop are - specified by this variable, which adjusts its value on the CPU after each TPU - program execution and before the next TPU execution. - - The purpose of using a variable, rather then a constant, is to allow - TPUEstimator adapt the TPU training iterations according to the final steps - specified by users. For example, if the user sets the iterations_per_loop as 4 - in TPUConfig and steps as 10 in TPUEstimator.train(), the iterations_per_loop - variable will have the following value before each TPU training. - - - 1-th TPU execution: iterations_per_loop = 4 - - 2-th TPU execution: iterations_per_loop = 4 - - 3-th TPU execution: iterations_per_loop = 2 - - As model_fn increases the global step once per train_op invocation, the global - step is 10 after all TPU executions, matching the steps=10 inputs passed in by - users. - - Returns: - A TF non-trainable resource variable. - - Raises: - RuntimeError: If multi iterations_per_loop variables were found. - """ - graph = ops.get_default_graph() - collection_name = '{}_{}'.format(_TPU_ESTIMATOR, _ITERATIONS_PER_LOOP_VAR) - iter_vars = graph.get_collection(collection_name) - if len(iter_vars) == 1: - return iter_vars[0] - elif len(iter_vars) > 1: - raise RuntimeError('Multiple iterations_per_loop_var in collection.') - - with ops.colocate_with(training_util.get_global_step()): - with variable_scope.variable_scope( - _TPU_ESTIMATOR, reuse=variable_scope.AUTO_REUSE): - return variable_scope.get_variable( - _ITERATIONS_PER_LOOP_VAR, - initializer=init_ops.zeros_initializer(), - shape=[], - dtype=dtypes.int32, - trainable=False, - collections=[collection_name, ops.GraphKeys.LOCAL_VARIABLES], - use_resource=True) - - -def _sync_variables_ops(ctx): - """Create varriables synchronization ops. - - Gets the variables back from TPU nodes. This means the variables updated - by TPU will now be *synced* to host memory. - In BROADCAST mode, we skip this sync since the variables are ususally too - big to transmit via RPC. - - Args: - ctx: A `_InternalTPUContext` instance with mode. - - Returns: - A list of sync ops. - """ - - if not ctx.is_input_broadcast_with_iterators(): - return [ - array_ops.check_numerics(v.read_value(), - 'Gradient for %s is NaN' % v.name).op - for v in variables.trainable_variables() - ] - else: - return [control_flow_ops.no_op()] - - -def _increase_eval_step_op(iterations_per_loop): - """Returns an op to increase the eval step for TPU evaluation. - - Args: - iterations_per_loop: Tensor. The number of eval steps running in TPU system - before returning to CPU host for each `Session.run`. - - Returns: - An operation - """ - eval_step = evaluation._get_or_create_eval_step() # pylint: disable=protected-access - # Estimator evaluate increases 1 by default. So, we increase the difference. - return state_ops.assign_add( - eval_step, - math_ops.cast(iterations_per_loop - 1, dtype=eval_step.dtype), - use_locking=True) - - -def _extract_key_names(tensor_or_dict): - if isinstance(tensor_or_dict, dict): - return sorted(tensor_or_dict.keys()) - return [] - - -class _SIGNAL(object): - """Signal used to control the thread of infeed/outfeed. - - All preserved signals must be negative numbers. Positive numbers are used to - indicate the number of iterations for next training/evaluation loop. - """ - NEXT_BATCH = -1 - STOP = -2 - - -class TPUEstimatorSpec(model_fn_lib._TPUEstimatorSpec): # pylint: disable=protected-access - """Ops and objects returned from a `model_fn` and passed to `TPUEstimator`. - - See `EstimatorSpec` for `mode`, `predictions`, `loss`, `train_op`, and - `export_outputs`. - - For evaluation, `eval_metrics `is a tuple of `metric_fn` and `tensors`, where - `metric_fn` runs on CPU to generate metrics and `tensors` represents the - `Tensor`s transferred from TPU system to CPU host and passed to `metric_fn`. - To be precise, TPU evaluation expects a slightly different signature from the - `tf.estimator.Estimator`. While `EstimatorSpec.eval_metric_ops` expects a - dict, `TPUEstimatorSpec.eval_metrics` is a tuple of `metric_fn` and `tensors`. - The `tensors` could be a list of `Tensor`s or dict of names to `Tensor`s. The - `tensors` usually specify the model logits, which are transferred back from - TPU system to CPU host. All tensors must have be batch-major, i.e., the batch - size is the first dimension. Once all tensors are available at CPU host from - all shards, they are concatenated (on CPU) and passed as positional arguments - to the `metric_fn` if `tensors` is list or keyword arguments if `tensors` is - a dict. `metric_fn` takes the `tensors` and returns a dict from metric string - name to the result of calling a metric function, namely a `(metric_tensor, - update_op)` tuple. See `TPUEstimator` for MNIST example how to specify the - `eval_metrics`. - - `scaffold_fn` is a function running on CPU to generate the `Scaffold`. This - function should not capture any Tensors in `model_fn`. - - `host_call` is a tuple of a `function` and a list or dictionary of `tensors` - to pass to that function and returns a list of Tensors. `host_call` currently - works for train() and evaluate(). The Tensors returned by the function is - executed on the CPU on every step, so there is communication overhead when - sending tensors from TPU to CPU. To reduce the overhead, try reducing the - size of the tensors. The `tensors` are concatenated along their major (batch) - dimension, and so must be >= rank 1. The `host_call` is useful for writing - summaries with `tf.contrib.summary.create_file_writer`. - """ - - def __new__(cls, - mode, - predictions=None, - loss=None, - train_op=None, - eval_metrics=None, - export_outputs=None, - scaffold_fn=None, - host_call=None, - training_hooks=None, - evaluation_hooks=None, - prediction_hooks=None): - """Creates a validated `TPUEstimatorSpec` instance.""" - host_calls = {} - if eval_metrics is not None: - host_calls['eval_metrics'] = eval_metrics - if host_call is not None: - host_calls['host_call'] = host_call - _OutfeedHostCall.validate(host_calls) - - training_hooks = tuple(training_hooks or []) - evaluation_hooks = tuple(evaluation_hooks or []) - prediction_hooks = tuple(prediction_hooks or []) - - for hook in training_hooks + evaluation_hooks + prediction_hooks: - if not isinstance(hook, session_run_hook.SessionRunHook): - raise TypeError('All hooks must be SessionRunHook instances, given: {}' - .format(hook)) - - return super(TPUEstimatorSpec, cls).__new__( - cls, - mode=mode, - predictions=predictions, - loss=loss, - train_op=train_op, - eval_metrics=eval_metrics, - export_outputs=export_outputs, - scaffold_fn=scaffold_fn, - host_call=host_call, - training_hooks=training_hooks, - evaluation_hooks=evaluation_hooks, - prediction_hooks=prediction_hooks) - - def as_estimator_spec(self): - """Creates an equivalent `EstimatorSpec` used by CPU train/eval.""" - host_calls = {} - if self.eval_metrics is not None: - host_calls['eval_metrics'] = self.eval_metrics - if self.host_call is not None: - host_calls['host_call'] = self.host_call - host_call_ret = _OutfeedHostCall.create_cpu_hostcall(host_calls) - eval_metric_ops = None - if self.eval_metrics is not None: - eval_metric_ops = host_call_ret['eval_metrics'] - hooks = None - if self.host_call is not None: - hooks = [_OutfeedHostCallHook(host_call_ret['host_call'])] - loss = self.loss - if tensor_tracer.TensorTracer.is_enabled() \ - and self.train_op is not None: - tt = tensor_tracer.TensorTracer() - loss = tt.trace_cpu(ops.get_default_graph(), loss, self.train_op) - - hooks = tuple(hooks or []) - scaffold = self.scaffold_fn() if self.scaffold_fn else None - return model_fn_lib.EstimatorSpec( - mode=self.mode, - predictions=self.predictions, - loss=loss, - train_op=self.train_op, - eval_metric_ops=eval_metric_ops, - export_outputs=self.export_outputs, - scaffold=scaffold, - training_hooks=self.training_hooks + hooks, - evaluation_hooks=self.evaluation_hooks + hooks, - prediction_hooks=self.prediction_hooks + hooks) - - -class _OpQueueContext(object): - """Manages work queue and thread for a infeed/outfeed thread.""" - - def __init__(self, name, target, args): - self._name = name - self._queue = Queue.Queue() - args = (self,) + args - self._thread = threading.Thread(name=name, target=target, args=args) - self._thread.daemon = True - self._thread.start() - - def stop(self): - self._queue.put(_SIGNAL.STOP) - - def send_next_batch_signal(self, iterations): - self._queue.put(iterations) - - def read_iteration_counts(self): - while True: - iterations = self._queue.get(block=True) - logging.debug('%s read iterations %s', self._name, iterations) - if iterations == _SIGNAL.STOP: - logging.info('%s received shutdown signal, stopping.', self._name) - return - yield iterations - - def join(self): - logging.info('Shutting down %s thread.', self._name) - self.stop() - self._thread.join() - - -class _OpSignalOnceQueueContext(_OpQueueContext): - """Manages work queue and thread for a infeed/outfeed thread. - - This subclass only signals once. - """ - - def __init__(self, name, target, args): - super(_OpSignalOnceQueueContext, self).__init__(name, target, args) - self._has_signaled = False - - def send_next_batch_signal(self, iterations): - if not self._has_signaled: - self._queue.put(iterations) - self._has_signaled = True - - -class TPUInfeedOutfeedSessionHook(session_run_hook.SessionRunHook): - """A Session hook setting up the TPU initialization, infeed, and outfeed. - - This hook does two major things: - 1. initialize and shutdown TPU system. - 2. launch and join the threads for infeed enqueue and (optional) outfeed - dequeue. - """ - - def __init__(self, - ctx, - enqueue_ops, - dequeue_ops, - tpu_compile_op, - run_infeed_loop_on_coordinator=True, - rendezvous=None, - master=None, - session_config=None, - tpu_init_ops=None): - self._master_job = ctx.master_job - self._enqueue_ops = enqueue_ops - self._dequeue_ops = dequeue_ops - self._rendezvous = rendezvous - self._master = master - self._session_config = session_config - self._init_ops = list(tpu_init_ops or []) - if ctx.embedding_config is None: - self._embedding_layer_config = None - else: - self._embedding_layer_config = ( - ctx.embedding_config.tpu_embedding.config_proto) - self._run_infeed_loop_on_coordinator = run_infeed_loop_on_coordinator - self._initial_infeed_sleep_secs = ( - ctx.config.tpu_config.initial_infeed_sleep_secs) - - self._feed_error = None - self._finished = False - # When using model parallelism, the TPU is pre-initialized at startup to - # fetch mesh information. We skip re-initializing it here to avoid - # suspected issues due to the mesh layout changing on the second - # initialization. - self._should_initialize_tpu = not ctx.model_parallelism_enabled - self._tpu_compile_op = tpu_compile_op - - def begin(self): - logging.info('TPU job name %s', self._master_job) - self._iterations_per_loop_var = _create_or_get_iterations_per_loop() - if self._should_initialize_tpu: - self._finalize_ops = [tpu.shutdown_system(job=self._master_job)] - else: - self._finalize_ops = [] - - summary_writer_init_ops = contrib_summary.summary_writer_initializer_op() - self._init_ops.extend(summary_writer_init_ops) - # Get all the writer resources from the initializer, so we know what to - # flush. - for op in summary_writer_init_ops: - self._finalize_ops.append(contrib_summary.flush(writer=op.inputs[0])) - - def _run_infeed(self, queue_ctx, session): - logging.info('Starting infeed thread controller.') - if self._initial_infeed_sleep_secs: - logging.info('Infeed thread sleeping for %d seconds.', - self._initial_infeed_sleep_secs) - time.sleep(self._initial_infeed_sleep_secs) - logging.info('Infeed thread starting after sleep') - - with self._rendezvous.catch_errors(source='infeed', session=session): - if self._run_infeed_loop_on_coordinator: - for count, steps in enumerate(queue_ctx.read_iteration_counts()): - for i in xrange(steps): - logging.debug('Infeed enqueue for iteration (%d, %d)', count, i) - session.run(self._enqueue_ops) - else: - for _ in queue_ctx.read_iteration_counts(): - session.run(self._enqueue_ops) - logging.info('Infeed thread finished, shutting down.') - - def _run_outfeed(self, queue_ctx, session): - logging.info('Starting outfeed thread controller.') - with self._rendezvous.catch_errors(source='outfeed', session=session): - for count, steps in enumerate(queue_ctx.read_iteration_counts()): - for i in xrange(steps): - logging.debug('Outfeed dequeue for iteration (%d, %d)', count, i) - session.run(self._dequeue_ops) - logging.info('Outfeed thread finished, shutting down.') - - def _create_infeed_controller(self, name, target, args): - return _OpQueueContext(name=name, target=target, args=args) - - def _assertCompilationSucceeded(self, result, coord): - proto = tpu_compilation_result.CompilationResultProto() - proto.ParseFromString(result) - if proto.status_error_message: - logging.error('Compilation failed: {}'.format(proto.status_error_message)) - coord.request_stop() - else: - logging.info('Compilation succeeded') - - def after_create_session(self, session, coord): - if self._should_initialize_tpu: - logging.info('Init TPU system') - start = time.time() - with ops.Graph().as_default(): - with tf_session.Session( - self._master, config=self._session_config) as sess: - sess.run( - tpu.initialize_system( - job=self._master_job, - embedding_config=self._embedding_layer_config)) - logging.info('Initialized TPU in %d seconds', time.time() - start) - - session.run(self._init_ops, - options=config_pb2.RunOptions(timeout_in_ms=5 * 60 * 1000)) - - if os.environ.get('TPU_SPLIT_COMPILE_AND_EXECUTE', '') == '1': - logging.info('Compiling user program: this may take a while...') - self._assertCompilationSucceeded(session.run(self._tpu_compile_op), coord) - - self._infeed_controller = self._create_infeed_controller( - name='InfeedController', target=self._run_infeed, args=(session,)) - - self._outfeed_controller = _OpQueueContext( - name='OutfeedController', target=self._run_outfeed, args=(session,)) - - # Enable the worker watchdog to terminate workers on coordinator exit. - watchdog_timeout = int(os.environ.get('TF_TPU_WATCHDOG_TIMEOUT', '0')) - if watchdog_timeout > 0: - session_support.start_worker_watchdog(session, - shutdown_timeout=watchdog_timeout) - - def before_run(self, run_context): - self._feed_error = None - - iterations = run_context.session.run(self._iterations_per_loop_var) - - logging.info('Enqueue next (%d) batch(es) of data to infeed.', iterations) - self._infeed_controller.send_next_batch_signal(iterations) - - logging.info('Dequeue next (%d) batch(es) of data from outfeed.', - iterations) - self._outfeed_controller.send_next_batch_signal(iterations) - - def end(self, session): - self._finished = True - logging.info('Stop infeed thread controller') - self._infeed_controller.join() - self._rendezvous.record_done('infeed') - - logging.info('Stop output thread controller') - self._outfeed_controller.join() - self._rendezvous.record_done('outfeed') - - logging.info('Shutdown TPU system.') - session.run(self._finalize_ops) - - -class TPUInfeedOutfeedSessionHookForPrediction(TPUInfeedOutfeedSessionHook): - - def __init__(self, ctx, enqueue_ops, dequeue_ops, tpu_compile_op, - rendezvous=None, master=None, session_config=None): - super(TPUInfeedOutfeedSessionHookForPrediction, self).__init__( - ctx, - enqueue_ops, - dequeue_ops, - tpu_compile_op=tpu_compile_op, - run_infeed_loop_on_coordinator=False, - rendezvous=rendezvous, - master=master, - session_config=session_config) - - def _create_infeed_controller(self, name, target, args): - return _OpSignalOnceQueueContext(name=name, target=target, args=args) - - -class _TPUStopAtStepHook(session_run_hook.SessionRunHook): - """Hook that requests stop at a specified step. - - This hook is similar to the `session_run_hook._StopAfterNEvalsHook` with - following differences for TPU training: - - 1. This hook sets the variable for iterations_per_loop, which is used by - `TPUInfeedOutfeedSessionHook` to control the iterations for infeed/outfeed. - As the hook execution order is not guaranteed, the variable update is - handled in `after_create_session` and `after_run` as - `TPUInfeedOutfeedSessionHook` reads the variable value in `before_run`. - - 2. For each training loop (session.run), the global step could be increased - multiple times on TPU. The global step tensor value will be explicitly read - again in `after_run` to ensure the latest value is retrieved to avoid race - condition. - """ - - def __init__(self, iterations, num_steps=None, last_step=None): - """Initializes a `StopAtStepHook`. - - Args: - iterations: The number of iterations to run optimizer per training loop. - num_steps: Number of steps to execute. - last_step: Step after which to stop. - - Raises: - ValueError: If one of the arguments is invalid. - """ - if num_steps is None and last_step is None: - raise ValueError('One of num_steps or last_step must be specified.') - if num_steps is not None and last_step is not None: - raise ValueError('Only one of num_steps or last_step can be specified.') - self._num_steps = num_steps - self._last_step = last_step - self._iterations = iterations - - def _next_iterations(self, global_step, last_step): - gap = last_step - global_step - return min(gap, self._iterations) - - def begin(self): - self._global_step_tensor = training_util.get_global_step() - if self._global_step_tensor is None: - raise RuntimeError('Global step should be created.') - - self._iterations_per_loop_var = _create_or_get_iterations_per_loop() - - def after_create_session(self, session, coord): - global_step = session.run(self._global_step_tensor) - if self._last_step is None: - self._last_step = global_step + self._num_steps - - iterations = self._next_iterations(global_step, self._last_step) - - self._iterations_per_loop_var.load(iterations, session=session) - - def after_run(self, run_context, run_values): - # Global step cannot be retrieved via SessionRunArgs and before_run due to - # race condition. - global_step = run_context.session.run(self._global_step_tensor) - if global_step >= self._last_step: - run_context.request_stop() - else: - iterations = self._next_iterations(global_step, self._last_step) - self._iterations_per_loop_var.load( - iterations, session=run_context.session) - - -class _SetEvalIterationsHook(session_run_hook.SessionRunHook): - """Hook that requests stop at a specified step.""" - - def __init__(self, num_steps): - """Initializes a `_SetEvalIterationsHook`. - - Args: - num_steps: Number of steps to execute. - """ - self._num_steps = num_steps - - def begin(self): - self._iterations_per_loop_var = _create_or_get_iterations_per_loop() - - def after_create_session(self, session, coord): - self._iterations_per_loop_var.load(self._num_steps, session=session) - - -class _StoppingPredictHook(session_run_hook.SessionRunHook): - """Hook that requests stop according to the stopping signal in prediction.""" - - def __init__(self, scalar_stopping_signal): - self._scalar_stopping_signal = scalar_stopping_signal - - def begin(self): - self._iterations_per_loop_var = _create_or_get_iterations_per_loop() - - def after_create_session(self, session, coord): - # This is not necessary as we do not run infeed enqueue and outfeed dequeue - # in side threads for prediction model. But it makes the - # TPUInfeedOutfeedSessionHook prints nice message. - self._iterations_per_loop_var.load(1, session=session) - - def before_run(self, run_context): - return session_run_hook.SessionRunArgs(self._scalar_stopping_signal) - - def after_run(self, run_context, run_values): - _ = run_context - scalar_stopping_signal = run_values.results - if _StopSignals.should_stop(scalar_stopping_signal): - # NOTE(xiejw): In prediction, stopping signals are inserted for each - # batch. And we append one more batch to signal the system it should stop. - # The data flow might look like - # - # batch 0: images, labels, stop = 0 (user provided) - # batch 1: images, labels, stop = 0 (user provided) - # ... - # batch 99: images, labels, stop = 0 (user provided) - # batch 100: images, labels, stop = 1 (TPUEstimator appended) - # - # where the final batch (id = 100) is appended by TPUEstimator, so we - # should drop it before returning the predictions to user. - # To achieve that, we throw the OutOfRangeError in after_run. Once - # Monitored Session sees this error in SessionRunHook.after_run, the - # "current" prediction, i.e., batch with id=100, will be discarded - # immediately - raise errors.OutOfRangeError(None, None, 'Stopped by stopping signal.') - - -def generate_per_core_enqueue_ops_fn_for_host( - ctx, input_fn, inputs_structure_recorder, host_device, host_id): - """Generates infeed enqueue ops for per-core input_fn on a single host.""" - captured_infeed_queue = _CapturedObject() - tpu_ordinal_function_impl = ctx.tpu_ordinal_function(host_id) - - def enqueue_ops_fn(): - """A fn returns enqueue_ops.""" - num_cores_per_host = ctx.num_of_cores_per_host - per_host_sharded_inputs = [] - for core_ordinal in range(num_cores_per_host): - with ops.name_scope('ordinal_%d' % (core_ordinal)): - user_context = tpu_context.TPUContext( - internal_ctx=ctx, - input_device=host_device, - invocation_index=host_id * ctx.num_of_cores_per_host + core_ordinal) - inputs = _Inputs.from_input_fn(input_fn(user_context)) - if inputs.is_dataset: - raise TypeError( - '`input_fn` returning `Dataset` is not yet supported in ' - 'per-Core input pipeline deployment yet. Please set ' - 'TPUConfig.per_host_input_for_training to True or return ' - '`features` and `labels` from `input_fn`') - features, labels = inputs.features_and_labels() - - inputs_structure_recorder.validate_and_record_structure( - features, labels) - flattened_inputs = ( - inputs_structure_recorder.flatten_features_and_labels( - features, labels)) - 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) - - per_host_enqueue_ops = infeed_queue.generate_enqueue_ops( - per_host_sharded_inputs, tpu_ordinal_function=tpu_ordinal_function_impl) - return per_host_enqueue_ops - - return enqueue_ops_fn, captured_infeed_queue - - -def generate_per_host_enqueue_ops_fn_for_host( - ctx, input_fn, inputs_structure_recorder, batch_axis, device, host_id): - """Generates infeed enqueue ops for per-host input_fn on a single host.""" - captured_infeed_queue = _CapturedObject() - - dataset_initializer = None - - with ops.device(device): - user_context = tpu_context.TPUContext( - internal_ctx=ctx, input_device=device, invocation_index=host_id) - inputs = _Inputs.from_input_fn(input_fn(user_context)) - - is_dataset = inputs.is_dataset - if ctx.mode == model_fn_lib.ModeKeys.PREDICT: - if not is_dataset: - raise TypeError( - 'For mode PREDICT, `input_fn` must return `Dataset` instead of ' - '`features` and `labels`.') - if batch_axis is not None: - raise TypeError('For mode PREDICT, batch_axis is not supported yet.') - inputs = _InputsWithStoppingSignals( - dataset=inputs.dataset, - batch_size=ctx.batch_size_for_input_fn, - add_padding=True) - - if is_dataset: - dataset_initializer = inputs.dataset_initializer() - - tpu_ordinal_function_impl = ctx.tpu_ordinal_function(host_id) - - def enqueue_ops_fn(): - """A Fn returning the TPU infeed enqueue ops. - - By providing as a Fn, it can be invoked inside the tf.while_loop such that - the input pipeline for multiple iterations can be executed by one - Session.run call. - - Returns: - list of dict of ops. - """ - with ops.device(device): - num_of_replicas_per_host = ctx.num_of_replicas_per_host - # Convert user input to features and labels. If the user returns a - # dataset, it is initialized and the features and labels extracted via - # `dataset.iterator.get_next()` - features, labels = inputs.features_and_labels() - signals = inputs.signals() - - inputs_structure_recorder.validate_and_record_structure(features, labels) - unsharded_tensor_list = ( - inputs_structure_recorder.flatten_features_and_labels( - features, labels, signals)) - - infeed_queue = tpu_feed.InfeedQueue( - tuple_types=[t.dtype for t in unsharded_tensor_list], - tuple_shapes=[t.shape for t in unsharded_tensor_list], - shard_dimensions=batch_axis) - captured_infeed_queue.capture(infeed_queue) - infeed_queue.set_number_of_shards(num_of_replicas_per_host) - per_host_enqueue_ops = ( - infeed_queue.split_inputs_and_generate_enqueue_ops( - unsharded_tensor_list, - placement_function=lambda x: device, - tpu_ordinal_function=tpu_ordinal_function_impl)) - if signals is None: - return per_host_enqueue_ops - else: - return { - 'ops': per_host_enqueue_ops, - 'signals': signals, - } - - return enqueue_ops_fn, captured_infeed_queue, dataset_initializer - - -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.""" - captured_infeed_queue = _CapturedObject() - dataset_initializer = None - - with ops.device(device): - user_context = tpu_context.TPUContext( - internal_ctx=ctx, input_device=device, invocation_index=host_id) - inputs = _Inputs.from_input_fn(input_fn(user_context)) - - 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: - inputs = _InputsWithStoppingSignals( - dataset=inputs.dataset, - batch_size=ctx.batch_size_for_input_fn, - add_padding=True, - num_invocations_per_step=ctx.num_of_replicas_per_host) - - dataset_initializer = inputs.dataset_initializer() - tpu_ordinal_function_impl = ctx.tpu_ordinal_function(host_id) - - def enqueue_ops_fn(): - """Generates the per_host enqueue ops.""" - control_deps = [] - per_host_sharded_inputs = [] - sparse_features_list = [] - num_replicas_per_host = ctx.num_of_replicas_per_host - cached_signals = None - 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() - signals = inputs.signals() - - # All the replicas share the replica 0's stopping singal. - # This avoids inconsistent state among different model replcias. - if cached_signals: - signals['stopping'] = cached_signals['stopping'] - else: - cached_signals = signals - - features, labels, sparse_features = ( - _tpu_estimator_embedding.split_inputs(ctx, features, labels)) - sparse_features_list.append(sparse_features) - - inputs_structure_recorder.validate_and_record_structure( - features, labels) - flattened_inputs = ( - inputs_structure_recorder.flatten_features_and_labels( - features, labels, signals)) - control_deps.extend(flattened_inputs) - per_host_sharded_inputs.append(flattened_inputs) - - if inputs_structure_recorder.flattened_input_dims: - input_partition_dims = inputs_structure_recorder.flattened_input_dims - if signals: - input_partition_dims += [None] * len(signals) - # pylint: disable=protected-access - infeed_queue = tpu_feed._PartitionedInfeedQueue( - number_of_tuple_elements=len(per_host_sharded_inputs[0]), - host_id=host_id, - input_partition_dims=input_partition_dims, - device_assignment=ctx.device_assignment) - per_host_enqueue_ops = infeed_queue.generate_enqueue_ops( - per_host_sharded_inputs) - else: - infeed_queue = tpu_feed.InfeedQueue( - number_of_tuple_elements=len(per_host_sharded_inputs[0])) - per_host_enqueue_ops = infeed_queue.generate_enqueue_ops( - per_host_sharded_inputs, - tpu_ordinal_function=tpu_ordinal_function_impl) - captured_infeed_queue.capture(infeed_queue) - - if ctx.embedding_config: - per_host_enqueue_ops.extend( - ctx.embedding_config.tpu_embedding.generate_enqueue_ops( - sparse_features_list)) - - if signals is None: - return per_host_enqueue_ops - else: - return { - 'ops': per_host_enqueue_ops, - 'signals': signals, - } - - return enqueue_ops_fn, captured_infeed_queue, dataset_initializer - - -def generate_broadcast_enqueue_ops_fn(ctx, input_fn, inputs_structure_recorder, - num_hosts): - """Generates infeed enqueue ops for one input_fn on all the hosts.""" - captured_infeed_queue = _CapturedObject() - dataset_initializer = None - device_0 = ctx.tpu_host_placement_function(host_id=0) - with ops.device(device_0): - user_context = tpu_context.TPUContext( - internal_ctx=ctx, input_device=device_0, invocation_index=0) - inputs = _Inputs.from_input_fn(input_fn(user_context)) - - is_dataset = inputs.is_dataset - if ctx.mode == model_fn_lib.ModeKeys.PREDICT: - if not is_dataset: - raise TypeError( - 'For mode PREDICT, `input_fn` must return `Dataset` instead of ' - '`features` and `labels`.') - - inputs = _InputsWithStoppingSignals( - dataset=inputs.dataset, - batch_size=ctx.batch_size_for_input_fn, - add_padding=True) - - if is_dataset: - dataset_initializer = inputs.dataset_initializer() - num_replicas_per_host = ctx.num_of_replicas_per_host - - def tpu_ordinal_function_impl(replica_id): - if ctx.device_assignment: - return ctx.device_assignment.tpu_ordinal(replica=replica_id) - else: - return replica_id % num_replicas_per_host - - def device_function_impl(replica_id): - return ctx.tpu_host_placement_function(replica_id=replica_id) - - def enqueue_ops_fn(): - """Generates enqueue ops for all the hosts.""" - broadcasted_inputs = [] - flattened_inputs = None # Cache result from input_fn. - signals = None - for host_id in xrange(num_hosts): - with ops.device(ctx.tpu_host_placement_function(host_id=host_id)): - for _ in xrange(ctx.num_of_replicas_per_host): - # Note: input_fn is only called once at host 0 for the first replica. - # The features and labels returned from that invocation are - # broadcasted to other replicas(including the replicas on other - # hosts). - if flattened_inputs is None: - features, labels = inputs.features_and_labels() # Calls get_next() - signals = inputs.signals() - - inputs_structure_recorder.validate_and_record_structure( - features, labels) - flattened_inputs = ( - inputs_structure_recorder.flatten_features_and_labels( - features, labels, signals)) - broadcasted_inputs.append(flattened_inputs) - - infeed_queue = tpu_feed.InfeedQueue( - number_of_tuple_elements=len(broadcasted_inputs[0])) - captured_infeed_queue.capture(infeed_queue) - enqueue_ops = infeed_queue.generate_enqueue_ops( - broadcasted_inputs, - tpu_ordinal_function=tpu_ordinal_function_impl, - placement_function=device_function_impl) - - if signals is None: - return enqueue_ops - else: - return { - 'ops': enqueue_ops, - 'signals': signals, - } - - return enqueue_ops_fn, captured_infeed_queue, dataset_initializer - - -class _InputPipeline(object): - """`_InputPipeline` handles invoking `input_fn` and piping to infeed queue. - - `_InputPipeline` abstracts the per-core/per-host `input_fn` invocation from - call site. To be precise, based on the configuration in - `_InternalTPUContext`, it invokes `input_fn` for all cores (usually - multi-host TPU training) or for one host (usually for single-host TPU - evaluation), and sends all `features` and `labels` returned by `input_fn` to - TPU infeed. For per-core invocation, `features` and `labels` are piped to - infeed directly, one tuple for each core. For per-host invocation, `features` - and `labels` are split at host (with respect to `batch_axis`) and piped to all - cores accordingly. - - In addition, flatten/unflatten are handled by `_InputPipeline` also. Model - inputs returned by the `input_fn` can have one of the following forms: - 1. features - 2. (features, labels) - 3. ((arbitrarily nested structure of features), labels) - - Internally, form 1 is reformed to `(features, None)` as features and labels - are passed separately to underlying methods. For TPU training, TPUEstimator - may expect multiple `features` and `labels` tuples one for each core. - - TPUEstimator allows various different structures for inputs (namely `features` - and `labels`). Both `features` and `labels` can be any nested sturcture - supported by TF nest (namely, dict, tuples, namedtuples or any nested - structure of such of Tensors). `labels` could be `None` as well. - - These are flattened before they are passed to the infeed/outfeed library - as that expectes flattend lists. - """ - - class InputsStructureRecorder(object): - """The recorder to record inputs structure.""" - - def __init__(self, input_partition_dims=None): - # Holds the structure of inputs - self._feature_structure = {} - self._flattened_input_dims = None - - if input_partition_dims: - # This should have been validated in TPUConfig. - assert len(input_partition_dims) <= 2, 'must have 1 or 2 elements.' - if len(input_partition_dims) == 2: - self._feature_dims, self._label_dims = input_partition_dims - else: - self._feature_dims = input_partition_dims[0] - self._label_dims = None - - assert self._feature_dims is not None, ('input_partition_dims[0] must ' - 'not be None') - else: - self._feature_dims = None - self._label_dims = None - - # Internal state. - self._initialized = False - - @property - def flattened_input_dims(self): - assert self._initialized, 'InputsStructureRecorder is not initialized.' - return self._flattened_input_dims - - def has_labels(self): - return 'labels' in self._feature_structure - - def _flatten_input_dims(self, feature_dims, feature_dims_names, label_dims, - label_dims_names, label_names, has_labels): - """Flatten input dims with the same order as flattened input tensors.""" - flattened_input_dims = [] - if feature_dims_names: - # We need a fixed ordering for matching the tensors in features. - flattened_input_dims.extend( - [feature_dims[name] for name in feature_dims_names]) - else: - flattened_input_dims.append(feature_dims) - - if label_dims_names: - # We need a fixed ordering for matching the tensors in labels. - flattened_input_dims.extend( - [label_dims[name] for name in label_dims_names]) - else: - if label_names: - num_tensors_in_label = len(label_names) - else: - num_tensors_in_label = int(has_labels) - # Setting `None` in input_partition_dims[1] will apply `None` to - # all the tensors in labels, regardless of internal structure. - flattened_input_dims.extend([label_dims] * num_tensors_in_label) - - return flattened_input_dims - - def validate_and_record_structure(self, features, labels): - """Validates and records the structure of `features` and `labels`.""" - # Extract structure. - has_labels = labels is not None - feature_names = _extract_key_names(features) - label_names = _extract_key_names(labels) - - if not self._initialized: - # Record structure. - self._initialized = True - if self._feature_dims is not None: - feature_dims_names = _extract_key_names(self._feature_dims) - if feature_dims_names != feature_names: - raise ValueError( - 'TPUConfig.input_partition_dims[0] mismatched feature' - ' keys. Expected {}, got {}'.format(feature_names, - feature_dims_names)) - - label_dims_names = _extract_key_names(self._label_dims) - if self._label_dims is not None and label_dims_names != label_names: - raise ValueError( - 'TPUConfig.input_partition_dims[1] mismatched label' - ' keys. Expected {}, got {}'.format(label_names, - label_dims_names)) - - self._flattened_input_dims = self._flatten_input_dims( - self._feature_dims, feature_dims_names, self._label_dims, - label_dims_names, label_names, has_labels) - - def flatten_features_and_labels(self, features, labels, signals=None): - """Flattens the `features` and `labels` to a single tensor list.""" - self._feature_structure['features'] = features - if labels is not None: - self._feature_structure['labels'] = labels - if signals is not None: - self._feature_structure['signals'] = signals - return data_nest.flatten(self._feature_structure) - - def unflatten_features_and_labels(self, flattened_inputs): - """Restores the flattened inputs to original features and labels form. - - Args: - flattened_inputs: Flattened inputs for each shard. - - Returns: - A tuple of (`features`, `labels`), where `labels` could be None. - Each one, if present, should have identical structure (single tensor vs - dict) as the one returned by input_fn. - - Raises: - ValueError: If the number of expected tensors from `flattened_inputs` - mismatches the recorded structure. - """ - - unflattened_inputs = data_nest.pack_sequence_as(self._feature_structure, - flattened_inputs) - return _Inputs( - unflattened_inputs['features'], - unflattened_inputs.get('labels'), - signals=unflattened_inputs.get('signals')) - - def __init__(self, input_fn, batch_axis, ctx): - """Constructor. - - Args: - input_fn: input fn for train or eval. - batch_axis: A python tuple of int values describing how each tensor - produced by the Estimator `input_fn` should be split across the TPU - compute shards. - ctx: A `_InternalTPUContext` instance with mode. - - Raises: - ValueError: If both `sharded_features` and `num_cores` are `None`. - """ - self._inputs_structure_recorder = _InputPipeline.InputsStructureRecorder( - ctx.input_partition_dims) - - self._sharded_per_core = ctx.is_input_sharded_per_core() - self._input_fn = input_fn - self._infeed_queue = None - self._ctx = ctx - self._batch_axis = batch_axis - - def generate_infeed_enqueue_ops_and_dequeue_fn(self): - """Generates infeed enqueue ops and dequeue_fn.""" - # While tf.while_loop is called, the body function, which invokes - # `enqueue_fn` passed in, is called to construct the graph. So, input_fn - # structure is recorded. - enqueue_ops, all_hooks, run_infeed_loop_on_coordinator = ( - self._invoke_input_fn_and_record_structure()) - - self._validate_input_pipeline() - - def dequeue_fn(): - """dequeue_fn is used by TPU to retrieve the tensors.""" - # 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. - 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) - - return (enqueue_ops, dequeue_fn, all_hooks, run_infeed_loop_on_coordinator) - - def _invoke_input_fn_and_record_structure(self): - """Deploys the input pipeline and record input structure.""" - enqueue_ops = [] - infeed_queues = [] - all_dataset_initializers = [] - num_hosts = self._ctx.num_hosts - tpu_host_placement_fn = self._ctx.tpu_host_placement_function - - run_infeed_loop_on_coordinator = True - - if self._sharded_per_core: - # Per-Core input pipeline deployment. - # Invoke input pipeline for each core and placed on the corresponding - # host. - for host_id in range(num_hosts): - 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 = ( - generate_per_core_enqueue_ops_fn_for_host( - self._ctx, self._input_fn, self._inputs_structure_recorder, - host_device, host_id)) - - if _WRAP_INPUT_FN_INTO_WHILE_LOOP: - run_infeed_loop_on_coordinator = False - enqueue_ops.append( - _wrap_computation_in_while_loop( - device=host_device, op_fn=enqueue_ops_fn)) - else: - enqueue_ops.append(enqueue_ops_fn()) - # Infeed_queue_getter must be called after enqueue_ops_fn is called. - infeed_queues.append(captured_infeed_queue.get()) - - elif self._ctx.is_input_broadcast_with_iterators(): - # Only calls input_fn in host 0. - host_device = tpu_host_placement_fn(host_id=0) - enqueue_ops_fn, captured_infeed_queue, dataset_initializer = ( - generate_broadcast_enqueue_ops_fn(self._ctx, self._input_fn, - self._inputs_structure_recorder, - num_hosts)) - if dataset_initializer: - all_dataset_initializers.append(dataset_initializer) - run_infeed_loop_on_coordinator = False - wrap_fn = ( - _wrap_computation_in_while_loop - if self._ctx.mode != model_fn_lib.ModeKeys.PREDICT else - _wrap_computation_in_while_loop_with_stopping_signals) - enqueue_ops.append(wrap_fn(device=host_device, op_fn=enqueue_ops_fn)) - else: - enqueue_ops.append(enqueue_ops_fn()) - infeed_queues.append(captured_infeed_queue.get()) - else: - for host_id in range(num_hosts): - 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)): - if self._ctx.is_input_per_host_with_iterators(): - enqueue_ops_fn, captured_infeed_queue, dataset_initializer = ( - 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, dataset_initializer = ( - generate_per_host_enqueue_ops_fn_for_host( - self._ctx, self._input_fn, - self._inputs_structure_recorder, self._batch_axis, - host_device, host_id)) - - # NOTE(xiejw): We dispatch here based on the return type of the - # users `input_fn`. - # - # 1. If input_fn returns a Dataset instance, we initialize the - # iterator outside of tf.while_loop, and call the iterator.get_next - # inside tf.while_loop. This should be always safe. - # - # 2. If input_fn returns (features, labels), it is too late to wrap - # them inside tf.while_loop, as resource initialization cannot be - # handled in TF control flow properly. In this case, we will use - # python loop to enqueue the data into TPU system. This may be - # slow compared to the previous case. - if dataset_initializer: - all_dataset_initializers.append(dataset_initializer) - run_infeed_loop_on_coordinator = False - wrap_fn = ( - _wrap_computation_in_while_loop - if self._ctx.mode != model_fn_lib.ModeKeys.PREDICT else - _wrap_computation_in_while_loop_with_stopping_signals) - enqueue_ops.append( - wrap_fn(device=host_device, op_fn=enqueue_ops_fn)) - else: - enqueue_ops.append(enqueue_ops_fn()) - infeed_queues.append(captured_infeed_queue.get()) - # infeed_queue is used to generate dequeue ops. The only thing it uses for - # dequeue is dtypes and types. So, any one can be used. Here, grab the - # first one. - self._infeed_queue = infeed_queues[0] - return enqueue_ops, [ - util_lib.MultiHostDatasetInitializerHook(all_dataset_initializers) - ], run_infeed_loop_on_coordinator - - def _validate_input_pipeline(self): - """Validates the input pipeline. - - Perform some sanity checks to log user friendly information. We should - error out to give users better error message. But, if - _WRAP_INPUT_FN_INTO_WHILE_LOOP is False (legacy behavior), we cannot break - user code, so, log a warning. - - Raises: - RuntimeError: If the validation failed. - """ - if ops.get_default_graph().get_collection(ops.GraphKeys.QUEUE_RUNNERS): - err_msg = ('Input pipeline contains one or more QueueRunners. ' - 'It could be slow and not scalable. Please consider ' - 'converting your input pipeline to use `tf.data` instead (see ' - 'https://www.tensorflow.org/guide/datasets for ' - 'instructions.') - if _WRAP_INPUT_FN_INTO_WHILE_LOOP: - raise RuntimeError(err_msg) - else: - logging.warn(err_msg) - - -def call_computation(computation, - experimental_exported_model_uses_all_cores=True): - """Call computation. - - computation uses a single-core for TPU inference. If - `experimental_exported_model_uses_all_cores` is `True`, this function will - round-robin - computation among all TPU cores visible to the host; otherwise, it will use - a single core. - - Args: - computation: A Python function that takes no inputs and builds computation - graph. If `computation` returns m outputs, this function will return a - list of m Tensors. - experimental_exported_model_uses_all_cores: Whether to round-robin among all - cores visible to the host, or to use a single core. - - Returns: - A list of output tensors. - """ - if experimental_exported_model_uses_all_cores: - # Using `TPUPartitionedCall` makes it possible to target a different - # TPU core with every `Session.run()` call. Note that the entire inference - # graph executes on a single core, and that invocations of this graph - # will round-robin among the cores attached to a host. - @function.Defun(capture_resource_var_by_value=False) - def tpu_subgraph(): - return computation() - - return tpu_functional.TPUPartitionedCall( - args=tpu_subgraph.captured_inputs, - device_ordinal=tpu_ops.tpu_ordinal_selector(), - Tout=[o.type for o in tpu_subgraph.definition.signature.output_arg], - f=tpu_subgraph) - else: - return computation() - - -class _ModelFnWrapper(object): - """A `model_fn` wrapper. - - This makes calling model_fn on CPU and TPU easier and more consistent and - performs necessary check and mutation required by TPU training and evaluation. - - In addition, this wrapper manages converting the `model_fn` to a single TPU - train and eval step. - """ - - def __init__(self, model_fn, config, params, ctx): - self._model_fn = model_fn - self._config = config - self._params = params - self._ctx = ctx - - def call_without_tpu(self, features, labels, is_export_mode): - return self._call_model_fn(features, labels, is_export_mode=is_export_mode) - - def _add_embedding_features(self, features, hook_dummy_table_variables): - """Add embedding features, optionally add hook to intercept gradient.""" - if self._ctx.embedding_config: - tpu_embedding_ = self._ctx.embedding_config.tpu_embedding - embedding_activations = tpu_embedding_.get_activations() - if hook_dummy_table_variables: - new_embedding_activations = ( - tpu_embedding_gradient.hook_dummy_table_variables_to_activations( - tpu_embedding_, embedding_activations, - self._ctx.embedding_config.dummy_table_variables)) - features.update(new_embedding_activations) - else: - features.update(embedding_activations) - - def convert_to_single_tpu_train_step(self, dequeue_fn): - """Converts user provided model_fn` as a single train step on TPU. - - The user provided `model_fn` takes input tuple - (features, labels) and produces the EstimatorSpec with train_op and loss for - train `mode`. This usually represents a single train computation on CPU. - - For TPU training, a train (computation) step is first wrapped in a - tf.while_loop control flow to repeat for many times and then replicated to - all TPU shards. Besides the input should be taken from TPU infeed rather - than input pipeline (input_fn) directly. To fit TPU loop and replicate - pattern, the original train computation should be reformed, which is the - returned `train_step`. - - Args: - dequeue_fn: The function to retrieve inputs, features and labels, from TPU - infeed dequeue channel. - - Returns: - A tuple of train_fn, host_calls, and captured scaffold_fn. The train_fn - representing the train step for TPU. - """ - - host_call = _OutfeedHostCall(self._ctx) - captured_scaffold_fn = _CapturedObject() - captured_training_hooks = _CapturedObject() - - def train_step(loss): - """Training step function for use inside a while loop.""" - del loss # unused; required in function signature. - inputs = dequeue_fn() - features, labels = inputs.features_and_labels() - self._add_embedding_features(features, True) - - estimator_spec = self._verify_estimator_spec( - self._call_model_fn(features, labels)) - loss, train_op = estimator_spec.loss, estimator_spec.train_op - - if isinstance(estimator_spec, model_fn_lib._TPUEstimatorSpec): # pylint: disable=protected-access - captured_scaffold_fn.capture(estimator_spec.scaffold_fn) - else: - captured_scaffold_fn.capture(None) - - captured_training_hooks.capture(estimator_spec.training_hooks) - - if self._ctx.embedding_config is None: - apply_sparse_grads = [] - else: - tpu_embedding_ = self._ctx.embedding_config.tpu_embedding - gradients = ( - tpu_embedding_gradient.get_gradients_through_dummy_table_variables( - tpu_embedding_) - ) - apply_sparse_grads = [ - tpu_embedding_.generate_send_gradients_op(gradients) - ] - - # We must run train_op to update the variables prior to running the - # outfeed. - with ops.control_dependencies([train_op] + apply_sparse_grads): - host_call_outfeed_ops = [] - if (isinstance(estimator_spec, model_fn_lib._TPUEstimatorSpec) # pylint: disable=protected-access - and estimator_spec.host_call is not None): - host_call.record({'host_call': estimator_spec.host_call}) - host_call_outfeed_ops = host_call.create_enqueue_op() - with ops.control_dependencies(host_call_outfeed_ops): - return array_ops.identity(loss) - - return (train_step, host_call, captured_scaffold_fn, - captured_training_hooks) - - def convert_to_single_tpu_eval_step(self, dequeue_fn): - """Converts user provided model_fn` as a single eval step on TPU. - - Similar to training, the user provided `model_fn` takes input tuple - (features, labels) and produces the TPUEstimatorSpec with eval_metrics for - eval `mode`. This usually represents a single evaluation computation on CPU. - - For TPU evaluation, a eval (computation) step is first wrapped in a - tf.while_loop control flow to repeat for many times and then replicated to - all TPU shards. Besides the input and output are slightly different. Input, - features and labels, should be taken from TPU infeed rather than input - pipeline (input_fn) directly. Output is managed in two stages. First, the - model outputs as the result of evaluation computation, usually model logits, - should be transferred from TPU system to CPU. Then, all model outputs are - concatenated first on CPU and sent to the metric_fn for metrics computation. - To fit TPU evaluation pattern, the original eval computation should be - reformed, which is the returned `eval_step`. - - Args: - dequeue_fn: The function to retrieve inputs, features and labels, from TPU - infeed dequeue channel. - - Returns: - A tuple of eval_fn, host_calls, and captured scaffold_fn. The eval_fn - representing the eval step for TPU. - """ - host_calls = _OutfeedHostCall(self._ctx) - captured_scaffold_fn = _CapturedObject() - captured_eval_hooks = _CapturedObject() - - def eval_step(total_loss): - """Evaluation step function for use inside a while loop.""" - inputs = dequeue_fn() - features, labels = inputs.features_and_labels() - self._add_embedding_features(features, False) - - tpu_estimator_spec = self._call_model_fn(features, labels) - if not isinstance(tpu_estimator_spec, model_fn_lib._TPUEstimatorSpec): # pylint: disable=protected-access - raise RuntimeError( - 'estimator_spec used by TPU evaluation must have type' - '`TPUEstimatorSpec`. Got {}'.format(type(tpu_estimator_spec))) - - loss = tpu_estimator_spec.loss - captured_scaffold_fn.capture(tpu_estimator_spec.scaffold_fn) - captured_eval_hooks.capture(tpu_estimator_spec.evaluation_hooks) - - to_record = {} - if tpu_estimator_spec.eval_metrics: - to_record['eval_metrics'] = tpu_estimator_spec.eval_metrics - if tpu_estimator_spec.host_call is not None: - # We assume that evaluate won't update global step, so we don't wrap - # this host_call. - to_record['host_call'] = tpu_estimator_spec.host_call - host_calls.record(to_record) - - with ops.control_dependencies(host_calls.create_enqueue_op()): - return math_ops.add(total_loss, loss) - - return eval_step, host_calls, captured_scaffold_fn, captured_eval_hooks - - def convert_to_single_tpu_predict_step(self, dequeue_fn): - """Converts user provided model_fn` as a single predict step on TPU. - - Args: - dequeue_fn: The function to retrieve inputs, features and labels, from TPU - infeed dequeue channel. - - Returns: - A tuple of predict_fn, host_calls, and captured scaffold_fn. The - predict_fn representing the predict step for TPU. - """ - host_calls = _OutfeedHostCall(self._ctx) - captured_scaffold_fn = _CapturedObject() - captured_predict_hooks = _CapturedObject() - - def predict_step(unused_scalar_stopping_signal): - """Evaluation step function for use inside a while loop.""" - inputs = dequeue_fn() - features, labels = inputs.features_and_labels() - stopping_signals = inputs.signals() - - assert stopping_signals is not None, ( - 'Internal Error: `signals` is missing.') - - tpu_estimator_spec = self._call_model_fn( - features, labels, is_export_mode=False) - if not isinstance(tpu_estimator_spec, model_fn_lib._TPUEstimatorSpec): # pylint: disable=protected-access - raise RuntimeError( - 'estimator_spec used by TPU prediction must have type' - '`TPUEstimatorSpec`. Got {}'.format(type(tpu_estimator_spec))) - - self._verify_tpu_spec_predictions(tpu_estimator_spec.predictions) - - captured_scaffold_fn.capture(tpu_estimator_spec.scaffold_fn) - captured_predict_hooks.capture(tpu_estimator_spec.prediction_hooks) - to_record = {} - identity_fn = lambda **kwargs: kwargs - to_record['predictions'] = [identity_fn, tpu_estimator_spec.predictions] - to_record['signals'] = [identity_fn, stopping_signals] - if tpu_estimator_spec.host_call is not None: - to_record['host_call'] = tpu_estimator_spec.host_call - host_calls.record(to_record) - - with ops.control_dependencies(host_calls.create_enqueue_op()): - return _StopSignals.as_scalar_stopping_signal(stopping_signals) - - return (predict_step, host_calls, captured_scaffold_fn, - captured_predict_hooks) - - def _verify_tpu_spec_predictions(self, predictions): - """Validates TPUEstimatorSpec.predictions dict.""" - # TODO(xiejw): Adds validation for prediction dictionrary. - # TODO(xiejw): Adds support for single tensor as predictions. - if not isinstance(predictions, dict): - raise TypeError('TPUEstimatorSpec.predictions must be dict of Tensors.') - - for (key, tensor) in predictions.items(): - if tensor.shape.dims[0].value is None: - raise ValueError( - 'The tensor with key ({}) in TPUEstimatorSpec.predictions has ' - 'dynamic shape (should be static). Tensor: {}'.format(key, tensor)) - return predictions - - def _validate_model_features_and_labels(self, features, labels, - is_export_mode): - """Validates that the features and labels for the model function are valid. - - A valid features/labels object is the one with: - - Type: A tensor or any nested structure of tensors supported by TF nest, - namely nested dictionary, tuple, namedtuple, or sequence of tensors. - - Static shape if is_export_mode is False. - - Args: - features: the features that would be input to the model function. - labels: the labels that would be input to the model function. - is_export_mode: boolean value specifying if in export mode. - - Raises: - TypeError: If features/labels are not of the correct type. - ValueError: If features/labels have dynamic shape. - """ - - def validate(obj, obj_name): - """Helper validate function.""" - if is_export_mode or self._ctx.is_running_on_cpu(is_export_mode): - return - if isinstance(obj, ops.Tensor): - if not obj.get_shape().is_fully_defined(): - raise ValueError( - 'The {} to the model returned by input_fn must have static shape.' - ' Tensor: {}'.format(obj_name, obj)) - else: - for tensor in data_nest.flatten(obj): - if not tensor.get_shape().is_fully_defined(): - raise ValueError( - ('The {} to the model returned by input_fn must have static ' - 'shape. Tensor: {}').format(obj_name, tensor)) - - validate(features, 'features') - if labels is not None: - validate(labels, 'labels') - - def _call_model_fn(self, features, labels, is_export_mode=False): - """Calls the model_fn with required parameters.""" - self._validate_model_features_and_labels(features, labels, is_export_mode) - model_fn_args = function_utils.fn_args(self._model_fn) - kwargs = {} - - # Makes deep copy with `config` and params` in case user mutates them. - config = copy.deepcopy(self._config) - params = copy.deepcopy(self._params) - - if 'labels' in model_fn_args: - kwargs['labels'] = labels - elif labels is not None: - raise ValueError( - 'model_fn does not take labels, but input_fn returns labels.') - if 'mode' in model_fn_args: - kwargs['mode'] = self._ctx.mode - if 'config' in model_fn_args: - kwargs['config'] = config - if 'params' in model_fn_args: - kwargs['params'] = params - - if 'params' not in model_fn_args: - raise ValueError('model_fn ({}) does not include params argument, ' - 'required by TPUEstimator to pass batch size as ' - 'params[\'batch_size\']'.format(self._model_fn)) - - if is_export_mode: - batch_size_for_model_fn = None - else: - batch_size_for_model_fn = self._ctx.batch_size_for_model_fn - - if batch_size_for_model_fn is not None: - _add_item_to_params(params, _BATCH_SIZE_KEY, batch_size_for_model_fn) - - running_on_cpu = self._ctx.is_running_on_cpu(is_export_mode) - _add_item_to_params(params, _USE_TPU_KEY, not running_on_cpu) - - if not running_on_cpu: - user_context = tpu_context.TPUContext( - internal_ctx=self._ctx, call_from_input_fn=False) - _add_item_to_params(params, _CTX_KEY, user_context) - - estimator_spec = self._model_fn(features=features, **kwargs) - if (running_on_cpu and - isinstance(estimator_spec, model_fn_lib._TPUEstimatorSpec)): # pylint: disable=protected-access - # The estimator_spec will be passed to `Estimator` directly, which expects - # type `EstimatorSpec`. - return estimator_spec.as_estimator_spec() - else: - return estimator_spec - - def _verify_estimator_spec(self, estimator_spec): - """Validates the estimator_spec.""" - if isinstance(estimator_spec, model_fn_lib._TPUEstimatorSpec): # pylint: disable=protected-access - return estimator_spec - - err_msg = '{} returned by EstimatorSpec is not supported in TPUEstimator.' - if estimator_spec.training_chief_hooks: - raise ValueError( - err_msg.format('training_chief_hooks') + 'If you want' + - ' to pass training hooks, please pass via training_hooks.') - - if estimator_spec.scaffold: - logging.warning('EstimatorSpec.Scaffold is ignored by TPU train/eval. ' - 'Please use TPUEstimatorSpec.') - return estimator_spec - - -class _OutfeedHostCall(object): - """Support for `eval_metrics` and `host_call` in TPUEstimatorSpec.""" - - def __init__(self, ctx): - self._ctx = ctx - self._names = [] - # All of these are dictionaries of lists keyed on the name. - self._host_fns = {} - self._tensor_keys = collections.defaultdict(list) - self._tensors = collections.defaultdict(list) - self._tensor_dtypes = collections.defaultdict(list) - self._tensor_shapes = collections.defaultdict(list) - - @staticmethod - def validate(host_calls): - """Validates the `eval_metrics` and `host_call` in `TPUEstimatorSpec`.""" - - for name, host_call in host_calls.items(): - if not isinstance(host_call, (tuple, list)): - raise ValueError('{} should be tuple or list'.format(name)) - if len(host_call) != 2: - raise ValueError('{} should have two elements.'.format(name)) - if not callable(host_call[0]): - raise TypeError('{}[0] should be callable.'.format(name)) - if not isinstance(host_call[1], (tuple, list, dict)): - raise ValueError('{}[1] should be tuple or list, or dict.'.format(name)) - - if isinstance(host_call[1], (tuple, list)): - fullargspec = tf_inspect.getfullargspec(host_call[0]) - fn_args = function_utils.fn_args(host_call[0]) - # wrapped_hostcall_with_global_step uses varargs, so we allow that. - if fullargspec.varargs is None and len(host_call[1]) != len(fn_args): - raise RuntimeError( - 'In TPUEstimatorSpec.{}, length of tensors {} does not match ' - 'method args of the function, which takes {}.'.format( - name, len(host_call[1]), len(fn_args))) - - @staticmethod - def create_cpu_hostcall(host_calls): - """Runs on the host_call on CPU instead of TPU when use_tpu=False.""" - - _OutfeedHostCall.validate(host_calls) - ret = {} - for name, host_call in host_calls.items(): - host_fn, tensors = host_call - if isinstance(tensors, (tuple, list)): - ret[name] = host_fn(*tensors) - else: - # Must be dict. - try: - ret[name] = host_fn(**tensors) - except TypeError as e: - logging.warning( - 'Exception while calling %s: %s. It is likely the tensors ' - '(%s[1]) do not match the ' - 'function\'s arguments', name, e, name) - raise - return ret - - def record(self, host_calls): - """Records the host_call structure.""" - - for name, host_call in host_calls.items(): - host_fn, tensor_list_or_dict = host_call - self._names.append(name) - self._host_fns[name] = host_fn - - if isinstance(tensor_list_or_dict, dict): - for (key, tensor) in six.iteritems(tensor_list_or_dict): - self._tensor_keys[name].append(key) - self._tensors[name].append(tensor) - self._tensor_dtypes[name].append(tensor.dtype) - self._tensor_shapes[name].append(tensor.shape) - else: - # List or tuple. - self._tensor_keys[name] = None - for tensor in tensor_list_or_dict: - self._tensors[name].append(tensor) - self._tensor_dtypes[name].append(tensor.dtype) - self._tensor_shapes[name].append(tensor.shape) - - def create_enqueue_op(self): - """Create the op to enqueue the recorded host_calls. - - Returns: - A list of enqueue ops, which is empty if there are no host calls. - """ - if not self._names: - return [] - - tensors = [] - # TODO(jhseu): Consider deduping tensors. - for name in self._names: - tensors.extend(self._tensors[name]) - - with ops.device(tpu.core(0)): - return [tpu_ops.outfeed_enqueue_tuple(tensors)] - - def create_tpu_hostcall(self): - """Sends the tensors through outfeed and runs the host_fn on CPU. - - The tensors are concatenated along dimension 0 to form a global tensor - across all shards. The concatenated function is passed to the host_fn and - executed on the first host. - - Returns: - A dictionary mapping name to the return type of the host_call by that - name. - - Raises: - RuntimeError: If outfeed tensor is scalar. - """ - if not self._names: - return {} - - ret = {} - # For each i, dequeue_ops[i] is a list containing the tensors from all - # shards. This list is concatenated later. - dequeue_ops = [] - tensor_dtypes = [] - tensor_shapes = [] - for name in self._names: - for _ in self._tensors[name]: - dequeue_ops.append([]) - for dtype in self._tensor_dtypes[name]: - tensor_dtypes.append(dtype) - for shape in self._tensor_shapes[name]: - tensor_shapes.append(shape) - - # Outfeed ops execute on each replica's first logical core. Note: we must - # constraint it such that we have at most one outfeed dequeue and enqueue - # per replica. - for i in xrange(self._ctx.num_replicas): - host_device, ordinal_id = self._ctx.device_for_replica(i) - with ops.device(host_device): - outfeed_tensors = tpu_ops.outfeed_dequeue_tuple( - dtypes=tensor_dtypes, - shapes=tensor_shapes, - device_ordinal=ordinal_id) - for j, item in enumerate(outfeed_tensors): - dequeue_ops[j].append(item) - - # Deconstruct dequeue ops. - flat_dequeue_ops = [] - for l in dequeue_ops: - flat_dequeue_ops.extend(l) - - dequeue_ops_by_name = {} - pos = 0 - for name in self._names: - dequeue_ops_by_name[name] = dequeue_ops[pos:pos + - len(self._tensors[name])] - pos += len(self._tensors[name]) - - def _call_host_fn(fn, *args, **kw): - context = CatchInvalidHostcallFunctions() - context.Enter() - result = fn(*args, **kw) - context.Exit() - context.ExitResult(result) - return result - - # It is assumed evaluation always happens on single host TPU system. So, - # place all ops on tpu host if possible. - # - # TODO(jhseu): Evaluate whether this is right for summaries. - with ops.device(self._ctx.tpu_host_placement_function(replica_id=0)): - for name in self._names: - dequeue_ops = dequeue_ops_by_name[name] - for i, item in enumerate(dequeue_ops): - if dequeue_ops[i][0].shape.ndims == 0: - raise RuntimeError( - 'All tensors outfed from TPU should preserve batch size ' - 'dimension, but got scalar {}'.format(dequeue_ops[i][0])) - # TODO(xiejw): Make the specification of the outfeed combinaton - # function more explicit and well-documented. We may want to give the - # user the option of concatenating along any axis. - if (self._ctx.config.tpu_config.per_host_input_for_training is - tpu_config.InputPipelineConfig.BROADCAST): - # If the infeed is in BROADCAST mode (each core recieving the same - # input), then we assume that the cores also produce identical - # copies of the same output, and we simply take the output from - # the first core. This mode is used by Mesh-TensorFlow. - with ops.control_dependencies(dequeue_ops[i]): - dequeue_ops[i] = array_ops.identity(dequeue_ops[i][0]) - else: - # Assume that the input has been batch-split and that axis 0 of the - # output tensors represents the batch size. Concatenate along - # the axis 0 to re-combine the batch. - dequeue_ops[i] = array_ops.concat(dequeue_ops[i], axis=0) - - if self._tensor_keys[name] is not None: - # The user-provided eval_metrics[1] is a dict. - dequeue_ops = dict(zip(self._tensor_keys[name], dequeue_ops)) - try: - ret[name] = _call_host_fn(self._host_fns[name], **dequeue_ops) - except TypeError as e: - logging.warning( - 'Exception while calling %s: %s. It is likely the tensors ' - '(%s[1]) do not match the ' - 'function\'s arguments', name, e, name) - raise - else: - ret[name] = _call_host_fn(self._host_fns[name], *dequeue_ops) - - # force all dequeue operations to be run if not consumed by the host calls - ret['__force_dequeue'] = control_flow_ops.group(*flat_dequeue_ops) - return ret - - -class _OutfeedHostCallHook(session_run_hook.SessionRunHook): - """Hook to run host calls when use_tpu=False.""" - - def __init__(self, tensors): - self._tensors = tensors - - def begin(self): - # We duplicate this code from the TPUInfeedOutfeedSessionHook rather than - # create a separate hook to guarantee execution order, because summaries - # need to be initialized before the outfeed thread starts. - # TODO(jhseu): Make a wrapper hook instead? - self._init_ops = contrib_summary.summary_writer_initializer_op() - # Get all the writer resources from the initializer, so we know what to - # flush. - self._finalize_ops = [] - for op in self._init_ops: - self._finalize_ops.append(contrib_summary.flush(writer=op.inputs[0])) - - def after_create_session(self, session, coord): - session.run(self._init_ops) - - def before_run(self, run_context): - return basic_session_run_hooks.SessionRunArgs(self._tensors) - - def end(self, session): - session.run(self._finalize_ops) - - -class ExamplesPerSecondHook(basic_session_run_hooks.StepCounterHook): - """Calculate and report global_step/sec and examples/sec during runtime.""" - - def __init__(self, - batch_size, - every_n_steps=100, - every_n_secs=None, - output_dir=None, - summary_writer=None): - self._batch_size = batch_size - super(ExamplesPerSecondHook, self).__init__( - every_n_steps=every_n_steps, - every_n_secs=every_n_secs, - output_dir=output_dir, - summary_writer=summary_writer) - - def _log_and_record(self, elapsed_steps, elapsed_time, global_step): - global_step_per_sec = elapsed_steps / elapsed_time - examples_per_sec = self._batch_size * global_step_per_sec - if self._summary_writer is not None: - global_step_summary = Summary(value=[ - Summary.Value(tag='global_step/sec', simple_value=global_step_per_sec) - ]) - example_summary = Summary(value=[ - Summary.Value(tag='examples/sec', simple_value=examples_per_sec) - ]) - self._summary_writer.add_summary(global_step_summary, global_step) - self._summary_writer.add_summary(example_summary, global_step) - logging.info('global_step/sec: %g', global_step_per_sec) - logging.info('examples/sec: %g', examples_per_sec) - - -class InstallSignalHandlerHook(session_run_hook.SessionRunHook): - """Change SIGINT (CTRL^C) handler to force quit the process. - - The default behavior often results in hanging processes. - The original handler is restored after training/evaluation. - """ - - def __init__(self): - self._signal_fn = signal.getsignal(signal.SIGINT) - - def before_run(self, run_context): - signal.signal(signal.SIGINT, signal.SIG_DFL) - - def end(self, session): - signal.signal(signal.SIGINT, self._signal_fn) - - -class TPUEstimator(estimator_lib.Estimator): - """Estimator with TPU support. - - TPUEstimator also supports training on CPU and GPU. You don't need to define - a separate `tf.estimator.Estimator`. - - TPUEstimator handles many of the details of running on TPU devices, such as - replicating inputs and models for each core, and returning to host - periodically to run hooks. - - TPUEstimator transforms a global batch size in params to a per-shard batch - size when calling the `input_fn` and `model_fn`. Users should specify - global batch size in constructor, and then get the batch size for each shard - in `input_fn` and `model_fn` by `params['batch_size']`. - - - For training, `model_fn` gets per-core batch size; `input_fn` may get - per-core or per-host batch size depending on `per_host_input_for_training` - in `TPUConfig` (See docstring for TPUConfig for details). - - - For evaluation and prediction, `model_fn` gets per-core batch size and - `input_fn` get per-host batch size. - - Evaluation - ========== - - `model_fn` should return `TPUEstimatorSpec`, which expects the `eval_metrics` - for TPU evaluation. If eval_on_tpu is False, the evaluation will execute on - CPU or GPU; in this case the following discussion on TPU evaluation does not - apply. - - `TPUEstimatorSpec.eval_metrics` is a tuple of `metric_fn` and `tensors`, where - `tensors` could be a list of any nested structure of `Tensor`s (See - `TPUEstimatorSpec` for details). `metric_fn` takes the `tensors` and returns - a dict from metric string name to the result of calling a metric function, - namely a `(metric_tensor, update_op)` tuple. - - One can set `use_tpu` to `False` for testing. All training, evaluation, and - predict will be executed on CPU. `input_fn` and `model_fn` will receive - `train_batch_size` or `eval_batch_size` unmodified as `params['batch_size']`. - - Current limitations: - -------------------- - - 1. TPU evaluation only works on a single host (one TPU worker) except - BROADCAST mode. - - 2. `input_fn` for evaluation should **NOT** raise an end-of-input exception - (`OutOfRangeError` or `StopIteration`). And all evaluation steps and all - batches should have the same size. - - Example (MNIST): - ---------------- - - ``` - # The metric Fn which runs on CPU. - def metric_fn(labels, logits): - predictions = tf.argmax(logits, 1) - return { - 'accuracy': tf.metrics.precision( - labels=labels, predictions=predictions), - } - - # Your model Fn which runs on TPU (eval_metrics is list in this example) - def model_fn(features, labels, mode, config, params): - ... - logits = ... - - if mode = tf.estimator.ModeKeys.EVAL: - return tpu_estimator.TPUEstimatorSpec( - mode=mode, - loss=loss, - eval_metrics=(metric_fn, [labels, logits])) - - # or specify the eval_metrics tensors as dict. - def model_fn(features, labels, mode, config, params): - ... - final_layer_output = ... - - if mode = tf.estimator.ModeKeys.EVAL: - return tpu_estimator.TPUEstimatorSpec( - mode=mode, - loss=loss, - eval_metrics=(metric_fn, { - 'labels': labels, - 'logits': final_layer_output, - })) - ``` - - Prediction - ========== - - Prediction on TPU is an experimental feature to support large batch inference. - It is not designed for latency-critical system. In addition, due to some - usability issues, for prediction with small dataset, CPU `.predict`, i.e., - creating a new `TPUEstimator` instance with `use_tpu=False`, might be more - convenient. - - Note: In contrast to TPU training/evaluation, the `input_fn` for prediction - *should* raise an end-of-input exception (`OutOfRangeError` or - `StopIteration`), which serves as the stopping signal to `TPUEstimator`. To be - precise, the ops created by `input_fn` produce one batch of the data. - The `predict()` API processes one batch at a time. When reaching the end of - the data source, an end-of-input exception should be raised by one of these - operations. The user usually does not need to do this manually. As long as the - dataset is not repeated forever, the `tf.data` API will raise an end-of-input - exception automatically after the last batch has been produced. - - Note: Estimator.predict returns a Python generator. Please consume all the - data from the generator so that TPUEstimator can shutdown the TPU system - properly for user. - - Current limitations: - -------------------- - 1. TPU prediction only works on a single host (one TPU worker). - - 2. `input_fn` must return a `Dataset` instance rather than `features`. In - fact, .train() and .evaluate() also support Dataset as return value. - - Example (MNIST): - ---------------- - ``` - height = 32 - width = 32 - total_examples = 100 - - def predict_input_fn(params): - batch_size = params['batch_size'] - - images = tf.random_uniform( - [total_examples, height, width, 3], minval=-1, maxval=1) - - dataset = tf.data.Dataset.from_tensor_slices(images) - dataset = dataset.map(lambda images: {'image': images}) - - dataset = dataset.batch(batch_size) - return dataset - - def model_fn(features, labels, params, mode): - # Generate predictions, called 'output', from features['image'] - - if mode == tf.estimator.ModeKeys.PREDICT: - return tf.contrib.tpu.TPUEstimatorSpec( - mode=mode, - predictions={ - 'predictions': output, - 'is_padding': features['is_padding'] - }) - - tpu_est = TPUEstimator( - model_fn=model_fn, - ..., - predict_batch_size=16) - - # Fully consume the generator so that TPUEstimator can shutdown the TPU - # system. - for item in tpu_est.predict(input_fn=input_fn): - # Filter out item if the `is_padding` is 1. - # Process the 'predictions' - ``` - - Exporting - ========= - - `export_savedmodel` exports 2 metagraphs, one with `tag_constants.SERVING`, - and another with `tag_constants.SERVING` and `tag_constants.TPU`. - At serving time, these tags are used to select metagraph to load. - - Before running the graph on TPU, TPU system needs to be initialized. If - TensorFlow Serving model-server is used, this is done automatically. If - not, please call `session.run(tpu.initialize_system())`. - - `tpu.outside_compilation` can be used to wrap TPU incompatible ops in - `model_fn`. - - Example: - ---------------- - - ``` - def model_fn(features, labels, mode, config, params): - ... - logits = ... - export_outputs = { - 'logits': export_output_lib.PredictOutput( - {'logits': logits}) - } - - def host_call(logits): - class_ids = math_ops.argmax(logits) - classes = string_ops.as_string(class_ids) - export_outputs['classes'] = - export_output_lib.ClassificationOutput(classes=classes) - - tpu.outside_compilation(host_call, logits) - - ... - ``` - - """ - - def __init__(self, - model_fn=None, - model_dir=None, - config=None, - params=None, - use_tpu=True, - train_batch_size=None, - eval_batch_size=None, - predict_batch_size=None, - batch_axis=None, - eval_on_tpu=True, - export_to_tpu=True, - export_to_cpu=True, - warm_start_from=None, - experimental_exported_model_uses_all_cores=False, - experimental_export_device_assignment=False, - experimental_embedding_config_spec=None): - """Constructs an `TPUEstimator` instance. - - Args: - model_fn: Model function as required by `Estimator` which returns - EstimatorSpec or TPUEstimatorSpec. `training_hooks`, 'evaluation_hooks', - and `prediction_hooks` must not capure any TPU Tensor inside the - model_fn. - 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. If `None`, the model_dir in - `config` will be used if set. If both are set, they must be same. If - both are `None`, a temporary directory will be used. - config: An `tpu_config.RunConfig` configuration object. Cannot be `None`. - params: An optional `dict` of hyper parameters that will be passed into - `input_fn` and `model_fn`. Keys are names of parameters, values are - basic python types. There are reserved keys for `TPUEstimator`, - including 'batch_size'. - use_tpu: A bool indicating whether TPU support is enabled. Currently, - - TPU training and evaluation respect this bit, but eval_on_tpu can - override execution of eval. See below. - Predict still happens on CPU. - train_batch_size: An int representing the global training batch size. - TPUEstimator transforms this global batch size to a per-shard batch - size, as params['batch_size'], when calling `input_fn` and `model_fn`. - Cannot be `None` if `use_tpu` is `True`. Must be divisible by total - number of replicas. - eval_batch_size: An int representing evaluation batch size. Must be - divisible by total number of replicas. - predict_batch_size: An int representing the prediction batch size. Must be - divisible by total number of replicas. - batch_axis: A python tuple of int values describing how each tensor - produced by the Estimator `input_fn` should be split across the TPU - compute shards. For example, if your input_fn produced (images, labels) - where the images tensor is in `HWCN` format, your shard dimensions would - be [3, 0], where 3 corresponds to the `N` dimension of your images - Tensor, and 0 corresponds to the dimension along which to split the - 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 or `PER_HOST_V2`, batch_axis is ignored. - eval_on_tpu: If False, evaluation runs on CPU or GPU. In this case, the - model_fn must return `EstimatorSpec` when called with `mode` as `EVAL`. - export_to_tpu: If True, `export_savedmodel()` exports a metagraph for - serving on TPU. Note that unsupported export modes such as EVAL will be - ignored. For those modes, only a CPU model will be exported. - Currently, export_to_tpu only supports PREDICT. - export_to_cpu: If True, `export_savedmodel()` exports a metagraph for - serving on CPU. - warm_start_from: Optional string filepath to a checkpoint or SavedModel to - warm-start from, or a `tf.estimator.WarmStartSettings` object to fully - configure warm-starting. If the string filepath is provided instead of - a `WarmStartSettings`, then all variables are warm-started, and it is - assumed that vocabularies and Tensor names are unchanged. - experimental_exported_model_uses_all_cores: Whether to round-robin among - all cores visible to the host which is serving the saved model, or to - use a single core. This is a temporary flag to enable using all TPU - cores for inference with TPUPartitionedCall(). Once outside compilation - is supported in TPUPartitionedCall(), this flag will be enabled by - default. - experimental_export_device_assignment: Whether to include the device - assignment in the exported model. Doing so is useful in case of model - parallel inference but will tie the exported model to the TPU topology - used to export the model. - experimental_embedding_config_spec: Optional EmbeddingConfigSpec instance - to support using TPU embedding. IT IS STILL WORK IN PROGRESS, SO PLEASE - DO NOT USE. - - Raises: - ValueError: `params` has reserved keys already. - """ - if config is None or not isinstance(config, tpu_config.RunConfig): - raise ValueError( - '`config` must be provided with type `tpu_config.RunConfig`') - - if params is not None and any(k in params for k in _RESERVED_PARAMS_KEYS): - raise ValueError('{} are reserved keys but existed in params {}.'.format( - _RESERVED_PARAMS_KEYS, params)) - - if use_tpu: - # Perform some very basic validations. More validations will be found in - # _InternalTPUContext. - if train_batch_size is None: - raise ValueError('`train_batch_size` cannot be `None`') - util_lib.check_positive_integer(train_batch_size, 'train_batch_size') - - if (config.tpu_config.per_host_input_for_training is - tpu_config.InputPipelineConfig.PER_SHARD_V1 and - config.tpu_config.num_cores_per_replica): - raise ValueError( - 'Model parallelism only supports per host input for training. ' - 'Please adjust TPURunconfig.per_host_input_for_training.') - - if eval_batch_size is not None: - util_lib.check_positive_integer(eval_batch_size, 'eval_batch_size') - - if predict_batch_size is not None: - util_lib.check_positive_integer(predict_batch_size, - 'predict_batch_size') - - # Verifies the model_fn signature according to Estimator framework. - estimator_lib._verify_model_fn_args(model_fn, params) # pylint: disable=protected-access - # We cannot store config and params in this constructor as parent - # constructor might change them, such as assigning a temp dir for - # config.model_dir. - model_function = self._augment_model_fn(model_fn, batch_axis) - - # Overwrite log_step_count_steps to disable TensorLoggingHook and - # StepCounterHook from being created in Estimator. TPUEstimator already - # added equivalent hooks in _augment_model_fn above. - self._log_every_n_steps = config.log_step_count_steps - config = config.replace(log_step_count_steps=None) - - # Passing non-None params as wrapped model_fn has it. - params = params or {} - super(TPUEstimator, self).__init__( - model_fn=model_function, - model_dir=model_dir, - config=config, - params=params, - warm_start_from=warm_start_from) - self._iterations_per_training_loop = ( - self._config.tpu_config.iterations_per_loop) - - # All properties passed to _InternalTPUContext are immutable. - # pylint: disable=protected-access - self._ctx = tpu_context._get_tpu_context( - self._config, train_batch_size, eval_batch_size, predict_batch_size, - use_tpu, eval_on_tpu, experimental_embedding_config_spec) - - self._export_to_cpu = export_to_cpu - self._export_to_tpu = export_to_tpu - self._experimental_exported_model_uses_all_cores = ( - experimental_exported_model_uses_all_cores) - self._experimental_export_device_assignment = ( - experimental_export_device_assignment) - if (experimental_exported_model_uses_all_cores and - experimental_export_device_assignment): - raise ValueError('experimental_exported_model_uses_all_cores and ' - 'experimental_export_device_assignment is not supported ' - 'at the same time.') - - self._is_input_fn_invoked = None - self._rendezvous = {} - - def _add_meta_graph_for_mode(self, - builder, - input_receiver_fn_map, - checkpoint_path, - save_variables=True, - mode=model_fn_lib.ModeKeys.PREDICT, - export_tags=None, - check_variables=True): - if self._export_to_tpu and mode != model_fn_lib.ModeKeys.PREDICT: - logging.warning('TPUEstimator only handles mode PREDICT for exporting ' - 'when `export_to_tpu` is `True`; Mode {} will be ignored ' - 'for TPU.'.format(mode)) - - if not self._export_to_cpu and not self._export_to_tpu: - raise ValueError('One of export_to_cpu and export_to_tpu must be true.') - - if self._export_to_cpu: - (super(TPUEstimator, self)._add_meta_graph_for_mode( - builder, - input_receiver_fn_map, - checkpoint_path, - save_variables, - mode=mode, - export_tags=export_tags, - check_variables=check_variables)) - - if self._export_to_tpu and mode == model_fn_lib.ModeKeys.PREDICT: - input_receiver_fn_map = { - _REWRITE_FOR_INFERENCE_MODE: input_receiver_fn_map[mode] - } - export_tags = [tag_constants.SERVING, tag_constants.TPU] - mode = _REWRITE_FOR_INFERENCE_MODE - - # See b/110052256 for why `check_variables` is `False`. - if not self._export_to_cpu: - check_variables = save_variables = True - else: - check_variables = save_variables = False - (super(TPUEstimator, self)._add_meta_graph_for_mode( - builder, - input_receiver_fn_map, - checkpoint_path, - save_variables=save_variables, - mode=mode, - export_tags=export_tags, - check_variables=check_variables)) - - def _call_model_fn(self, features, labels, mode, config): - if mode == _REWRITE_FOR_INFERENCE_MODE: - return self._call_model_fn_for_inference(features, labels, mode, config) - else: - return super(TPUEstimator, self)._call_model_fn(features, labels, mode, - config) - - def _call_model_fn_for_inference(self, features, labels, mode, config): - """Wraps `_call_model_fn` for `export_savedmodel`.""" - if mode != _REWRITE_FOR_INFERENCE_MODE: - raise ValueError('mode must be {}; ' - 'got {}.'.format(_REWRITE_FOR_INFERENCE_MODE, mode)) - - computation, capture = self._build_computation_for_inference( - features, labels, mode, config) - tensors = call_computation( - computation, - experimental_exported_model_uses_all_cores=self - ._experimental_exported_model_uses_all_cores) - estimator_spec, export_outputs_dict, predictions_dict, none_indices = ( - capture.get()) - predictions_list = tensors[:len(predictions_dict)] - export_outputs_list_without_none = tensors[len(predictions_dict):] - - # Reinsert `None`s which we've taken out in - # `_build_computation_for_inference()`. - export_outputs_list = [] - while none_indices or export_outputs_list_without_none: - if none_indices and none_indices[0] == len(export_outputs_list): - export_outputs_list.append(None) - none_indices.pop(0) - else: - export_outputs_list.append(export_outputs_list_without_none.pop(0)) - - # Reconstruct `export_outputs` with updated tensors. - new_export_outputs_dict = nest.pack_sequence_as(export_outputs_dict, - export_outputs_list) - export_outputs = estimator_spec.export_outputs - new_export_outputs = collections.OrderedDict( - (k, _clone_export_output_with_tensors(export_outputs[k], v)) - for k, v in six.iteritems(new_export_outputs_dict)) - # Reconstruct `predictions` with updated tensors. - new_predictions = nest.pack_sequence_as(predictions_dict, predictions_list) - if (len(new_predictions) == 1 and - _KEY_WHEN_PREDICTIONS_IS_A_TENSOR in new_predictions): - new_predictions = new_predictions[_KEY_WHEN_PREDICTIONS_IS_A_TENSOR] - - return estimator_spec._replace( - export_outputs=new_export_outputs, predictions=new_predictions) - - def _build_computation_for_inference(self, features, labels, mode, config): - capture = _CapturedObject() - - def computation(): - """Computation to be passed to `TPUPartitionedCall()`.""" - tpu_computation, tpu_capture = self._build_tpu_computation_for_inference( - features, labels, mode, config) - - if self._experimental_export_device_assignment: - # Export the device assignment as part of the model. This is useful for - # model parallel usecases where the model relies on the mapping between - # logical and physical devices. - with self._ctx.with_mode(mode) as ctx: - device_assignment = ctx.device_assignment - else: - device_assignment = None - - if self._experimental_exported_model_uses_all_cores: - tensors_on_cpu = tpu.rewrite( - tpu_computation, device_assignment=device_assignment) - else: - tensors_on_cpu = tpu.rewrite_for_inference( - tpu_computation, device_assignment=device_assignment) - - (estimator_spec, export_outputs_dict, export_outputs_list, - predictions_dict) = ( - tpu_capture.get()) - predictions_list = tensors_on_cpu[:len(predictions_dict)] - export_outputs_tpu_on_cpu_list = tensors_on_cpu[len(predictions_dict):] - - # Reconstruct tensors used in export_outputs, with TPU tensors replaced - # with their CPU counterpart returned from `rewrite_for_inference()`. - # `function.Defun()` does not like `None`s in return values, so we leave - # `None`s out but record their positions for later reconstruction. - export_outputs_list_without_none = [] - none_indices = [] - for i, t in enumerate(export_outputs_list): - if t is None: - none_indices.append(i) - else: - export_outputs_list_without_none.append( - export_outputs_tpu_on_cpu_list.pop(0)) - - capture.capture((estimator_spec, export_outputs_dict, predictions_dict, - none_indices)) - return predictions_list + export_outputs_list_without_none - - return computation, capture - - def _build_tpu_computation_for_inference(self, features, labels, mode, - config): - capture = _CapturedObject() - - def computation(): - """Compute tpu tensors used in export_outputs. - - Passed to rewrite_for_inference so that model_fn will be called under - the rewriting contexts. Only tpu tensors are returned, but export_outputs - and scaffold are captured. - - Returns: - A list of Tensors used in export_outputs and not marked for - outside_compilation. - """ - # We should only call model fn once and it should be inside `computation` - # so that building the graph will happen under `rewrite_for_inference`. - mode = model_fn_lib.ModeKeys.PREDICT - estimator_spec = self._call_model_fn(features, labels, mode, config) - - # We pick the TPU tensors out from `export_output` and later return them - # from `computation` for rewriting. - export_outputs_dict = collections.OrderedDict( - (k, _export_output_to_tensors(v)) - for k, v in six.iteritems(estimator_spec.export_outputs)) - export_outputs_list = nest.flatten(export_outputs_dict) - export_outputs_tpu_list = [ - t for t in export_outputs_list if t is not None - ] - - if isinstance(estimator_spec.predictions, dict): - predictions_dict = collections.OrderedDict( - (k, v) for k, v in six.iteritems(estimator_spec.predictions)) - else: - predictions_dict = { - _KEY_WHEN_PREDICTIONS_IS_A_TENSOR: estimator_spec.predictions - } - predictions_list = nest.flatten(predictions_dict) - - # We cannot return everything we want through the return values, so - # capture the rest here for later use. - capture.capture((estimator_spec, export_outputs_dict, export_outputs_list, - predictions_dict)) - return predictions_list + export_outputs_tpu_list - - return computation, capture - - def _create_global_step(self, graph): - """Creates a global step suitable for TPUs. - - Args: - graph: The graph in which to create the global step. - - Returns: - A global step `Tensor`. - - Raises: - ValueError: if the global step tensor is already defined. - """ - return _create_global_step(graph) - - def _convert_train_steps_to_hooks(self, steps, max_steps): - with self._ctx.with_mode(model_fn_lib.ModeKeys.TRAIN) as ctx: - if ctx.is_running_on_cpu(): - return super(TPUEstimator, self)._convert_train_steps_to_hooks( - steps, max_steps) - - # On TPU. - if steps is None and max_steps is None: - raise ValueError( - 'For TPU training, one of `steps` or `max_steps` must be set. ' - 'Cannot be both `None`.') - - # Estimator.train has explicit positiveness check. - if steps is not None: - util_lib.check_positive_integer(steps, 'Train steps') - if max_steps is not None: - util_lib.check_positive_integer(max_steps, 'Train max_steps') - - return [ - _TPUStopAtStepHook(self._iterations_per_training_loop, steps, max_steps) - ] - - def _convert_eval_steps_to_hooks(self, steps): - with self._ctx.with_mode(model_fn_lib.ModeKeys.EVAL) as ctx: - if ctx.is_running_on_cpu(): - return super(TPUEstimator, self)._convert_eval_steps_to_hooks(steps) - - if steps is None: - raise ValueError('Evaluate `steps` must be set on TPU. Cannot be `None`.') - - util_lib.check_positive_integer(steps, 'Eval steps') - - return [ - evaluation._StopAfterNEvalsHook( # pylint: disable=protected-access - num_evals=steps), - _SetEvalIterationsHook(steps) - ] - - def _call_input_fn(self, input_fn, mode): - """Calls the input function. - - Args: - input_fn: The input function. - mode: ModeKeys - - Returns: - In TPU mode, returns an input_fn to be called later in model_fn. - Otherwise, calls the input_fn and returns either fatures or - (features, labels). - - Raises: - ValueError: if input_fn takes invalid arguments or does not have `params`. - """ - input_fn_args = function_utils.fn_args(input_fn) - config = self.config # a deep copy. - kwargs = {} - if 'params' in input_fn_args: - kwargs['params'] = self.params # a deep copy. - else: - raise ValueError('input_fn ({}) does not include params argument, ' - 'required by TPUEstimator to pass batch size as ' - 'params["batch_size"]'.format(input_fn)) - if 'config' in input_fn_args: - kwargs['config'] = config - - if 'mode' in input_fn_args: - kwargs['mode'] = mode - - # Records the fact input_fn has been invoked. - self._is_input_fn_invoked = True - - with self._ctx.with_mode(mode) as ctx: - # Setting the batch size in params first. This helps user to have same - # input_fn for use_tpu=True/False. - batch_size_for_input_fn = ctx.batch_size_for_input_fn - if batch_size_for_input_fn is not None: - _add_item_to_params(kwargs['params'], _BATCH_SIZE_KEY, - batch_size_for_input_fn) - - # For export_savedmodel, input_fn is never passed to Estimator. So, - # `is_export_mode` must be False. - if ctx.is_running_on_cpu(is_export_mode=False): - with ops.device('/device:CPU:0'): - return input_fn(**kwargs) - - # For TPU computation, input_fn should be invoked in a tf.while_loop for - # performance. While constructing the tf.while_loop, the structure of - # inputs returned by the `input_fn` needs to be recorded. The structure - # includes whether features or labels is dict or single Tensor, dict keys, - # tensor shapes, and dtypes. The recorded structure is used to create the - # infeed dequeue ops, which must be wrapped and passed as a Fn, called - # inside the TPU computation, as the TPU computation is wrapped inside a - # tf.while_loop also. So, we either pass input_fn to model_fn or pass - # dequeue_fn to model_fn. Here, `input_fn` is passed directly as - # `features` in `model_fn` signature. - def _input_fn(ctx): - _add_item_to_params(kwargs['params'], _CTX_KEY, ctx) - return input_fn(**kwargs) - - return _input_fn - - def _validate_features_in_predict_input(self, result): - """Skip the validation. - - For TPUEstimator, we do not need to check the result type. `_InputPipeline` - has stronger check. Parent class's check generates confusing warning msg. - - Args: - result: `features` returned by input_fn. - """ - pass - - def train(self, - input_fn, - hooks=None, - steps=None, - max_steps=None, - saving_listeners=None): - rendezvous = error_handling.ErrorRendezvous(num_sources=3) - self._rendezvous[model_fn_lib.ModeKeys.TRAIN] = rendezvous - try: - return super(TPUEstimator, self).train( - input_fn=input_fn, - hooks=hooks, - steps=steps, - max_steps=max_steps, - saving_listeners=saving_listeners) - except Exception: # pylint: disable=broad-except - rendezvous.record_error('training_loop', sys.exc_info()) - finally: - rendezvous.record_done('training_loop') - rendezvous.raise_errors() - - def evaluate(self, - input_fn, - steps=None, - hooks=None, - checkpoint_path=None, - name=None): - rendezvous = error_handling.ErrorRendezvous(num_sources=3) - self._rendezvous[model_fn_lib.ModeKeys.EVAL] = rendezvous - try: - return super(TPUEstimator, self).evaluate( - input_fn, - steps=steps, - hooks=hooks, - checkpoint_path=checkpoint_path, - name=name) - except Exception: # pylint: disable=broad-except - rendezvous.record_error('evaluation_loop', sys.exc_info()) - finally: - rendezvous.record_done('evaluation_loop') - rendezvous.raise_errors() - - def predict(self, - input_fn, - predict_keys=None, - hooks=None, - checkpoint_path=None, - yield_single_examples=True): - rendezvous = error_handling.ErrorRendezvous(num_sources=3) - self._rendezvous[model_fn_lib.ModeKeys.PREDICT] = rendezvous - try: - for result in super(TPUEstimator, self).predict( - input_fn=input_fn, - predict_keys=predict_keys, - hooks=hooks, - checkpoint_path=checkpoint_path, - yield_single_examples=yield_single_examples): - yield result - except Exception: # pylint: disable=broad-except - rendezvous.record_error('prediction_loop', sys.exc_info()) - finally: - rendezvous.record_done('prediction_loop') - rendezvous.raise_errors() - - rendezvous.record_done('prediction_loop') - rendezvous.raise_errors() - - def _augment_model_fn(self, model_fn, batch_axis): - """Returns a new model_fn, which wraps the TPU support.""" - - def _model_fn(features, labels, mode, config, params): - """A Estimator `model_fn` for TPUEstimator.""" - with self._ctx.with_mode(mode) as ctx: - model_fn_wrapper = _ModelFnWrapper(model_fn, config, params, ctx) - - # `input_fn` is called in `train()`, `evaluate()`, and `predict()`, - # but not in `export_savedmodel()`. - if self._is_input_fn_invoked: - is_export_mode = False - else: - is_export_mode = True - - # Clear the bit. - self._is_input_fn_invoked = None - - # examples_hook is added to training_hooks for both CPU and TPU - # execution. - if self._log_every_n_steps is not None: - examples_hook = ExamplesPerSecondHook( - ctx.global_batch_size, - # pylint:disable=g-long-ternary - output_dir=(self.model_dir - if not config or config.save_summary_steps - else None), - # pylint:enable=g-long-ternary - every_n_steps=self._log_every_n_steps) - - if ctx.is_running_on_cpu(is_export_mode=is_export_mode): - logging.info('Running %s on CPU', mode) - estimator_spec = model_fn_wrapper.call_without_tpu( - features, labels, is_export_mode=is_export_mode) - if self._log_every_n_steps is not None: - estimator_spec = estimator_spec._replace( - training_hooks=estimator_spec.training_hooks + (examples_hook,)) - return estimator_spec - - assert labels is None, '`labels` passed to `model_fn` must be `None`.' - # TPUEstimator._call_input_fn passes `input_fn` as features to here. - assert callable(features), '`input_fn` is not callable.' - input_fn = features - - tpu_init_ops = [] - if ctx.embedding_config and mode == model_fn_lib.ModeKeys.TRAIN: - dummy_table_variables, dummy_table_variables_init = ( - tpu_embedding_gradient.create_dummy_table_variables( - ctx.embedding_config.tpu_embedding)) - ctx.embedding_config.dummy_table_variables = dummy_table_variables - tpu_init_ops.append(dummy_table_variables_init) - - input_holders = _InputPipeline(input_fn, batch_axis, ctx) - enqueue_ops, dequeue_fn, input_hooks, run_infeed_loop_on_coordinator = ( - input_holders.generate_infeed_enqueue_ops_and_dequeue_fn()) - - graph = ops.get_default_graph() - for enqueue_op in enqueue_ops: - if isinstance(enqueue_op, list): - graph.get_collection_ref(_TPU_ENQUEUE_OPS).extend(enqueue_op) - else: - graph.add_to_collection(_TPU_ENQUEUE_OPS, enqueue_op) - - if mode == model_fn_lib.ModeKeys.TRAIN: - compile_op, loss, host_call, scaffold, training_hooks = ( - _train_on_tpu_system(ctx, model_fn_wrapper, dequeue_fn)) - if ctx.embedding_config: - g = ops.get_default_graph() - table_to_config_dict = ( - ctx.embedding_config.tpu_embedding.table_to_config_dict) - optimization_parameters = ( - ctx.embedding_config.tpu_embedding.optimization_parameters) - embedding_variable_name_by_table, slot_variable_names_by_table = ( - _tpu_estimator_embedding.get_full_variable_names( - g, table_to_config_dict, optimization_parameters - ) - ) - embedding_variables_and_ops = ( - ctx.embedding_config.tpu_embedding.create_variables_and_ops( - embedding_variable_name_by_table, - slot_variable_names_by_table - )) - tpu_init_ops.extend(embedding_variables_and_ops.load_ops()) - - host_ops = host_call.create_tpu_hostcall() - if host_ops is None: - host_ops = [] - - shutdown_hooks = [] - shutdown_mode = os.environ.get('TF_TPU_GRACEFUL_SHUTDOWN_MODE', - 'shutdown_worker') - if shutdown_mode: - if shutdown_mode == 'shutdown_worker': - finalizer_hooks = [ - session_support.ShutdownLameWorkers(timeout_ms=60 * 1000), - ] - elif shutdown_mode == 'shutdown_computation': - finalizer_hooks = [ - session_support.RestartComputation(timeout_ms=60 * 1000), - ] - else: - raise ValueError( - 'Unknown TF_TPU_GRACEFUL_SHUTDOWN_MODE "%s"' % shutdown_mode) - - shutdown_hooks.append( - session_support.GracefulShutdownHook( - checkpoint_prefix=self.model_dir + '/model.ckpt', - on_shutdown_hooks=finalizer_hooks)) - - with ops.control_dependencies([loss]): - global_step = array_ops.identity(training.get_global_step()) - hooks = input_hooks + shutdown_hooks - hooks.extend([ - TPUInfeedOutfeedSessionHook( - ctx, - enqueue_ops, - host_ops, - tpu_compile_op=compile_op, - run_infeed_loop_on_coordinator=( - run_infeed_loop_on_coordinator), - rendezvous=self._rendezvous[mode], - master=self._config.master, - session_config=self._session_config, - tpu_init_ops=tpu_init_ops), - InstallSignalHandlerHook() - ]) - if self._log_every_n_steps is not None: - logging_hook_frequency = ( # Divide and round up - (self._log_every_n_steps + - self._config.tpu_config.iterations_per_loop - 1) // - self._config.tpu_config.iterations_per_loop) - hooks.append( - training.LoggingTensorHook({ - 'loss': array_ops.identity(loss), - 'step': global_step, - }, - every_n_iter=logging_hook_frequency)) - examples_hook._set_steps_per_run( # pylint: disable=protected-access - self._config.tpu_config.iterations_per_loop) - hooks.append(examples_hook) - - if training_hooks: - hooks.extend(training_hooks) - - chief_hooks = [] - if (self._config.save_checkpoints_secs or - self._config.save_checkpoints_steps): - checkpoint_hook = training.CheckpointSaverHook( - self.model_dir, - save_secs=self._config.save_checkpoints_secs, - save_steps=self._config.save_checkpoints_steps, - scaffold=scaffold) - checkpoint_hook._set_steps_per_run( # pylint: disable=protected-access - self._config.tpu_config.iterations_per_loop) - chief_hooks.append(checkpoint_hook) - - summary.scalar(model_fn_lib.LOSS_METRIC_KEY, loss) - with ops.control_dependencies([loss]): - update_ops = _sync_variables_ops(ctx) - if ctx.embedding_config: - update_ops.extend(embedding_variables_and_ops.retrieve_ops()) - - # Validate the TPU training graph to catch basic errors - _validate_tpu_training_graph() - - train_op = control_flow_ops.group(*update_ops) - graph.add_to_collection(_TPU_TRAIN_OP, train_op) - - return model_fn_lib.EstimatorSpec( - mode, - loss=loss, - training_chief_hooks=chief_hooks, - training_hooks=hooks, - train_op=train_op, - scaffold=scaffold) - - if mode == model_fn_lib.ModeKeys.EVAL: - compile_op, total_loss, host_calls, scaffold, eval_hooks = ( - _eval_on_tpu_system(ctx, model_fn_wrapper, dequeue_fn)) - iterations_per_loop_var = _create_or_get_iterations_per_loop() - mean_loss = math_ops.div( - total_loss, - math_ops.cast(iterations_per_loop_var, dtype=total_loss.dtype)) - - with ops.control_dependencies([mean_loss]): - # After TPU evaluation computation is done (the mean_loss tensor), - # reads all variables back from TPU and updates the eval step - # counter properly - internal_ops_to_run = _sync_variables_ops(ctx) - internal_ops_to_run.append( - _increase_eval_step_op(iterations_per_loop_var)) - - host_call_ret = host_calls.create_tpu_hostcall() - eval_metric_ops = {} - eval_update_ops = [] - - eval_metrics = host_call_ret.get('eval_metrics', {}) - if eval_metrics: - # Creates a dummy metric update_op for all metrics. Estimator - # expects all metrics in `eval_metric_ops` have update_op and calls - # them one by one. The real metric update_ops are invoked in a - # separated thread. So, here give Estimator the dummy op for all - # metrics. - with ops.control_dependencies(internal_ops_to_run): - dummy_update_op = control_flow_ops.no_op() - - for k, v in eval_metrics.items(): - eval_metric_ops[k] = (v[0], dummy_update_op) - eval_update_ops.append(v[1]) - else: - # If no eval metrics are passed, create an identity node for the - # loss and add `internal_ops_to_run` to its dependencies. So - # `internal_ops_to_run` can be executed. - with ops.control_dependencies(internal_ops_to_run): - mean_loss = array_ops.identity(mean_loss) - - if 'host_call' not in host_call_ret: - host_ops = [] - else: - host_ops = host_call_ret['host_call'] - hooks = [ - TPUInfeedOutfeedSessionHook( - ctx, - enqueue_ops, - eval_update_ops + host_ops, - tpu_compile_op=compile_op, - run_infeed_loop_on_coordinator=( - run_infeed_loop_on_coordinator), - rendezvous=self._rendezvous[mode], - master=self._config.evaluation_master, - session_config=self._session_config, - tpu_init_ops=tpu_init_ops) - ] + input_hooks - - if eval_hooks: - hooks.extend(eval_hooks) - - return model_fn_lib.EstimatorSpec( - mode, - loss=mean_loss, - evaluation_hooks=hooks, - eval_metric_ops=eval_metric_ops, - scaffold=scaffold) - - # Predict - assert mode == model_fn_lib.ModeKeys.PREDICT - - (compile_op, dummy_predict_op, host_calls, - scaffold, prediction_hooks) = _predict_on_tpu_system( - ctx, model_fn_wrapper, dequeue_fn) - with ops.control_dependencies([dummy_predict_op]): - internal_ops_to_run = _sync_variables_ops(ctx) - with ops.control_dependencies(internal_ops_to_run): - dummy_predict_op = control_flow_ops.no_op() - - # In train and evaluation, the main TPU program is passed to monitored - # training session to run. Infeed enqueue and outfeed dequeue are - # executed in side threads. This is not the configuration for - # prediction mode. - # - # For prediction, the Estimator executes the EstimatorSpec.predictions - # directly and yield the element (via generator) to call site. So, the - # outfeed based prediction must be passed to MonitoredSession directly. - # Other parts of the TPU execution are organized as follows. - # - # 1. All outfeed based Tensors must be grouped with predictions Tensors - # to form a single invocation. This avoid the issue we might trigger - # multiple outfeeds incorrectly. To achieve this, `host_call` is - # placed in control_dependencies of `stopping_signals`, and - # `stopping_signals` is passed into _StoppingPredictHook, which sets - # the `stopping_signals` as SessionRunArgs. MonitoredSession merges - # all SessionRunArgs with the fetch in session.run together. - # - # 2. The TPU program (dummy_predict_op) and enqueue_ops (infeed Enqueue) - # are grouped together. They will be launched once and only once in - # side threads and they quit naturally according to the SAME stopping - # condition. - enqueue_ops.append(dummy_predict_op) - - host_call_ret = host_calls.create_tpu_hostcall() - if 'host_call' not in host_call_ret: - host_ops = [] - else: - host_ops = host_call_ret['host_call'] - - predictions = host_call_ret['predictions'] - _verify_cross_hosts_transfer_size( - predictions, - message=( - 'The estimated size for TPUEstimatorSpec.predictions is too ' - 'large.')) - signals = host_call_ret['signals'] - - with ops.control_dependencies(host_ops): - host_ops = [] # Empty, we do do not need it anymore. - scalar_stopping_signal = _StopSignals.as_scalar_stopping_signal( - signals) - predictions = _PaddingSignals.slice_tensor_or_dict( - predictions, signals) - - hooks = [ - _StoppingPredictHook(scalar_stopping_signal), - TPUInfeedOutfeedSessionHookForPrediction( - ctx, enqueue_ops, host_ops, rendezvous=self._rendezvous[mode], - tpu_compile_op=compile_op, - master=self._config.master, - session_config=self._session_config), - ] + input_hooks - - if prediction_hooks: - hooks.extend(prediction_hooks) - - return model_fn_lib.EstimatorSpec( - mode, - prediction_hooks=hooks, - predictions=predictions, - scaffold=scaffold) - - return _model_fn - - -def _export_output_to_tensors(export_output): - """Get a list of `Tensors` used in `export_output`. - - Args: - export_output: an `ExportOutput` object such as `ClassificationOutput`, - `RegressionOutput`, or `PredictOutput`. - - Returns: - a list of tensors used in export_output. - - Raises: - ValueError: if `export_output` is not one of `ClassificationOutput`, - `RegressionOutput`, or `PredictOutput`. - """ - if isinstance(export_output, export_output_lib.ClassificationOutput): - return [export_output.scores, export_output.classes] - elif isinstance(export_output, export_output_lib.RegressionOutput): - return [export_output.value] - elif isinstance(export_output, export_output_lib.PredictOutput): - return list(export_output.outputs.values()) - else: - raise ValueError( - '`export_output` must be have type `ClassificationOutput`, ' - '`RegressionOutput`, or `PredictOutput`; got {}.'.format(export_output)) - - -def _clone_export_output_with_tensors(export_output, tensors): - """Clones `export_output` but with new `tensors`. - - Args: - export_output: an `ExportOutput` object such as `ClassificationOutput`, - `RegressionOutput`, or `PredictOutput`. - tensors: a list of `Tensors` used to construct a new `export_output`. - - Returns: - A dict similar to `export_output` but with `tensors`. - - Raises: - ValueError: if `export_output` is not one of `ClassificationOutput`, - `RegressionOutput`, or `PredictOutput`. - """ - if isinstance(export_output, export_output_lib.ClassificationOutput): - if len(tensors) != 2: - raise ValueError('tensors must be of length 2; ' - 'got {}.'.format(len(tensors))) - return export_output_lib.ClassificationOutput(*tensors) - elif isinstance(export_output, export_output_lib.RegressionOutput): - if len(tensors) != 1: - raise ValueError('tensors must be of length 1; ' - 'got {}'.format(len(tensors))) - return export_output_lib.RegressionOutput(*tensors) - elif isinstance(export_output, export_output_lib.PredictOutput): - return export_output_lib.PredictOutput( - dict(zip(export_output.outputs.keys(), tensors))) - else: - raise ValueError( - '`export_output` must be have type `ClassificationOutput`, ' - '`RegressionOutput`, or `PredictOutput`; got {}.'.format(export_output)) - - -def _eval_on_tpu_system(ctx, model_fn_wrapper, dequeue_fn): - """Executes `model_fn_wrapper` multiple times on all TPU shards.""" - iterations_per_loop_var = _create_or_get_iterations_per_loop() - - (single_tpu_eval_step, host_calls, captured_scaffold_fn, captured_eval_hooks - ) = model_fn_wrapper.convert_to_single_tpu_eval_step(dequeue_fn) - - def multi_tpu_eval_steps_on_single_shard(): - return training_loop.repeat(iterations_per_loop_var, single_tpu_eval_step, - [_ZERO_LOSS]) - - (compile_op, loss,) = tpu.split_compile_and_shard( - multi_tpu_eval_steps_on_single_shard, - inputs=[], - num_shards=ctx.num_replicas, - outputs_from_all_shards=False, - device_assignment=ctx.device_assignment) - - loss = loss[0] - scaffold = _get_scaffold(captured_scaffold_fn) - return compile_op, loss, host_calls, scaffold, captured_eval_hooks.get() - - -def _train_on_tpu_system(ctx, model_fn_wrapper, dequeue_fn): - """Executes `model_fn_wrapper` multiple times on all TPU shards.""" - iterations_per_loop_var = _create_or_get_iterations_per_loop() - - (single_tpu_train_step, host_call, captured_scaffold_fn, - captured_training_hooks) = ( - model_fn_wrapper.convert_to_single_tpu_train_step(dequeue_fn)) - - @tpu_function.on_device_training_loop - def multi_tpu_train_steps_on_single_shard(): - return training_loop.repeat(iterations_per_loop_var, single_tpu_train_step, - [_INITIAL_LOSS]) - - (compile_op, loss,) = tpu.split_compile_and_shard( - multi_tpu_train_steps_on_single_shard, - inputs=[], - num_shards=ctx.num_replicas, - outputs_from_all_shards=False, - device_assignment=ctx.device_assignment) - - loss = loss[0] - scaffold = _get_scaffold(captured_scaffold_fn) - return compile_op, loss, host_call, scaffold, captured_training_hooks.get() - - -def _predict_on_tpu_system(ctx, model_fn_wrapper, dequeue_fn): - """Executes `model_fn_wrapper` multiple times on all TPU shards.""" - (single_tpu_predict_step, host_calls, captured_scaffold_fn, - captured_predict_hooks - ) = model_fn_wrapper.convert_to_single_tpu_predict_step(dequeue_fn) - - @tpu_function.on_device_training_loop - def multi_tpu_predict_steps_on_single_shard(): - - def cond(scalar_stopping_signal): - return math_ops.logical_not( - _StopSignals.should_stop(scalar_stopping_signal)) - - inputs = [_StopSignals.NON_STOPPING_SIGNAL] - outputs = training_loop.while_loop( - cond, single_tpu_predict_step, inputs=inputs, name=b'loop') - return outputs - - (compile_op, dummy_predict_op,) = tpu.split_compile_and_shard( - multi_tpu_predict_steps_on_single_shard, - inputs=[], - num_shards=ctx.num_replicas, - outputs_from_all_shards=False, - device_assignment=ctx.device_assignment) - - dummy_predict_op = dummy_predict_op[0] - scaffold = _get_scaffold(captured_scaffold_fn) - return (compile_op, dummy_predict_op, host_calls, scaffold, - captured_predict_hooks.get()) - - -def _wrap_computation_in_while_loop(device, op_fn): - """Wraps the ops generated by `op_fn` in tf.while_loop.""" - - def computation(i): - with ops.control_dependencies(op_fn()): - return i + 1 - - iterations_per_loop_var = _create_or_get_iterations_per_loop() - # By setting parallel_iterations=1, the parallel execution in while_loop is - # basically turned off. - with ops.device(device): - iterations = array_ops.identity(iterations_per_loop_var) - return control_flow_ops.while_loop( - lambda i: i < iterations, - computation, [constant_op.constant(0)], - parallel_iterations=1) - - -def _wrap_computation_in_while_loop_with_stopping_signals(device, op_fn): - """Wraps the ops generated by `op_fn` in tf.while_loop.""" - - def cond(scalar_stopping_signal): - return math_ops.logical_not( - _StopSignals.should_stop(scalar_stopping_signal)) - - def computation(unused_scalar_stopping_signal): - return_value = op_fn() - execute_ops = return_value['ops'] - signals = return_value['signals'] - with ops.control_dependencies(execute_ops): - return _StopSignals.as_scalar_stopping_signal(signals) - - # By setting parallel_iterations=1, the parallel execution in while_loop is - # basically turned off. - with ops.device(device): - return control_flow_ops.while_loop( - cond, - computation, [_StopSignals.NON_STOPPING_SIGNAL], - parallel_iterations=1) - - -def _validate_tpu_training_graph(): - """Validate graph before running distributed training. - - Raises: - ValueError: If the graph seems invalid for running on device - """ - operations = ops.get_default_graph().get_operations() - - # Check if there is atleast one CrossReplicaSum operation in the graph - # This should be introduced by using the CrossShardOptimizer wrapper - cross_replica_sum_ops = [ - o for o in operations if o.type == _CROSS_REPLICA_SUM_OP - ] - if not cross_replica_sum_ops: - raise ValueError( - 'CrossShardOptimizer must be used for model training on TPUs.') - - -class _CapturedObject(object): - """A placeholder to capture an object. - - This is useful when we need to capture a Python object in the Tensorflow - control flow body function and use it outside the control flow. - """ - - def __init__(self): - self._object = None - self._captured = False - - def capture(self, o): - if self._captured: - raise RuntimeError( - 'InternalError: Object can capture only once. Please file bug.') - - self._captured = True - self._object = o - - def get(self): - if not self._captured: - raise RuntimeError( - 'InternalError: Object is not captured properly before `get`. ' - 'Please file bug.') - return self._object - - -def _get_scaffold(captured_scaffold_fn): - """Retrieves the Scaffold from `captured_scaffold_fn`.""" - with _CapturingContext(message='Inside scaffold_fn'): - scaffold_fn = captured_scaffold_fn.get() - if scaffold_fn: - scaffold = scaffold_fn() - if scaffold is None: - raise ValueError( - 'TPUEstimatorSpec.scaffold_fn returns None, which is not allowed') - else: - scaffold = None - - if scaffold: - wrapped_finalize = scaffold.finalize - - def _finalize(): - with _CapturingContext('Inside Scaffold.finalize'): - wrapped_finalize() - - scaffold.finalize = _finalize - return scaffold - - -class _CapturingContext(control_flow_ops.ControlFlowContext): - """Tracks references to Tensors defined in TPU replication.""" - - def __init__(self, message): - control_flow_ops.ControlFlowContext.__init__(self) - self._message = message - - def to_control_flow_context_def(self, context_def, export_scope=None): - # pylint: disable=useless-super-delegation - # NOTE(slebedev): the method is required by `ControlFlowContext`. - super(_CapturingContext, self).to_control_flow_context_def( - context_def, export_scope) - - def AddOp(self, op): # pylint: disable=invalid-name - for c in op.inputs: - if tpu._TPU_REPLICATE_ATTR in c.op.node_def.attr: # pylint: disable=protected-access - raise ValueError('{}: Op {} depends on TPU computation {}, ' - 'which is not allowed.'.format(self._message, op, c)) - - def __enter__(self): - # pylint: disable=protected-access - self._g = ops.get_default_graph() - self._old = self._g._get_control_flow_context() - self._g._set_control_flow_context(self) - # pylint: enable=protected-access - - def __exit__(self, _, __, ___): # pylint: disable=invalid-name - self._g._set_control_flow_context(self._old) # pylint: disable=protected-access - - -class _Inputs(object): - """A data structure representing the input_fn returned values. - - This also supports the returned value from input_fn as `Dataset`. - """ - - def __init__(self, features=None, labels=None, dataset=None, signals=None): - if dataset is not None and (features is not None or labels is not None or - signals is not None): - raise RuntimeError('Internal Error: Either (features and labels) or ' - 'dataset should be provided, not both. Please file ' - 'bug') - - self._features = features - self._labels = labels - self._signals = signals - - self._dataset = dataset - self._iterator = None - - @staticmethod - def from_input_fn(return_values): - """Returns an `_Inputs` instance according to `input_fn` return value.""" - if isinstance(return_values, dataset_ops.DatasetV2): - dataset = return_values - return _Inputs(dataset=dataset) - - features, labels = _Inputs._parse_inputs(return_values) - return _Inputs(features, labels) - - @staticmethod - def _parse_inputs(return_values): - if isinstance(return_values, tuple): - features, labels = return_values - else: - features, labels = return_values, None - return features, labels - - @property - def is_dataset(self): - """Returns True if the return value from input_fn is Dataset.""" - return self._dataset is not None - - def dataset_initializer(self): - """Returns the dataset's initializer. - - The initializer must be run before calling `features_and_labels`. - """ - self._iterator = dataset_ops.make_initializable_iterator(self._dataset) - return self._iterator.initializer - - def features_and_labels(self): - """Gets `features` and `labels`.""" - if self.is_dataset: - if self._iterator is None: - raise RuntimeError('Internal error: Must run dataset_initializer ' - 'before calling features_and_labels(). Please file ' - 'a bug!') - return _Inputs._parse_inputs(self._iterator.get_next()) - - return (self._features, self._labels) - - def signals(self): - return self._signals - - @property - def dataset(self): - return self._dataset - - -class _InputsWithStoppingSignals(_Inputs): - """Inputs with `_StopSignals` inserted into the dataset.""" - - def __init__(self, - dataset, - batch_size, - add_padding=False, - num_invocations_per_step=1): - - assert dataset is not None - user_provided_dataset = dataset.map( - _InputsWithStoppingSignals.insert_stopping_signal( - stop=False, batch_size=batch_size, add_padding=add_padding)) - if num_invocations_per_step == 1: - final_batch_dataset = dataset.take(1).map( - _InputsWithStoppingSignals.insert_stopping_signal( - stop=True, batch_size=batch_size, add_padding=add_padding)) - else: - # We append (2 * num_invocations_per_step - 1) batches for exhausting the - # user_provided_dataset and stop properly. - # For example, if num_invocations_per_step is 2, we append 3 additional - # padding batches: b1, b2, b3. - # If user_provided_dataset contains two batches: a1, a2 - # Step 1: [a1, a2] - # Step 2: [b1, b2] -> STOP - # If user_provided_dataset contains three batches: a1, a2, a3. - # The training loops: - # Step 1: [a1, a2] - # Step 2: [a3, b1] - # Step 3: [b2, b3] -> STOP. - final_batch_dataset = dataset.take(1).map( - _InputsWithStoppingSignals.insert_stopping_signal( - stop=True, batch_size=batch_size, add_padding=add_padding)) - final_batch_dataset = final_batch_dataset.repeat( - 2 * num_invocations_per_step - 1) - - def _set_mask(data_dict): - signals = data_dict['signals'] - signals['padding_mask'] = array_ops.ones_like(signals['padding_mask']) - data_dict['signals'] = signals - return data_dict - - # Mask out the extra batch. - final_batch_dataset = final_batch_dataset.map(_set_mask) - - dataset = user_provided_dataset.concatenate(final_batch_dataset).prefetch(2) - - super(_InputsWithStoppingSignals, self).__init__(dataset=dataset) - self._current_inputs = None - - def features_and_labels(self): - if self._current_inputs is not None: - raise RuntimeError( - 'Internal Error: The previous inputs have not been properly ' - 'consumed. First call features_and_labels, then call signals.') - - inputs_with_signals = self._iterator.get_next() - features = inputs_with_signals['features'] - labels = inputs_with_signals.get('labels') - - self._current_inputs = inputs_with_signals - return features, labels - - def signals(self): - """Returns the `Signals` from `_Inputs`.""" - if self._current_inputs is None: - raise RuntimeError( - 'Internal Error: The current inputs have not been properly ' - 'generated. First call features_and_labels, then call signals.') - signals = self._current_inputs['signals'] - self._current_inputs = None - return signals - - @staticmethod - def insert_stopping_signal(stop, batch_size, add_padding=False): - """Inserts stopping_signal into dataset via _map_fn. - - Here we change the data structure in the dataset, such that the return value - is a dictionary now and `features`, `labels`, and `signals` are three - distinguished keys in that dict. This provides a better structure, which - eases the process to decompose the inputs (see `features_and_labels`). - - Args: - stop: bool, state of current stopping signals. - batch_size: int, batch size. - add_padding: bool, whether to pad the tensor to full batch size. - - Returns: - A map_fn passed to dataset.map API. - """ - - def _map_fn(*args): - """The map fn to insert signals.""" - if len(args) == 1: - # Unpack the single Tensor/dict argument as features. This is required - # for the input_fn returns no labels. - args = args[0] - features, labels = _Inputs._parse_inputs(args) - new_input_dict = {} - - if add_padding: - padding_mask, features, labels = ( - _PaddingSignals.pad_features_and_labels(features, labels, - batch_size)) - - new_input_dict['features'] = features - if labels is not None: - new_input_dict['labels'] = labels - - else: - new_input_dict['features'] = features - if labels is not None: - new_input_dict['labels'] = labels - padding_mask = None - - new_input_dict['signals'] = _StopSignals( - stop=stop, batch_size=batch_size, - padding_mask=padding_mask).as_dict() - - return new_input_dict - - return _map_fn - - -class _StopSignals(object): - """Signals class holding all logic to handle TPU stopping condition.""" - - NON_STOPPING_SIGNAL = False - STOPPING_SIGNAL = True - - def __init__(self, stop, batch_size, padding_mask=None): - self._stop = stop - self._batch_size = batch_size - self._padding_mask = padding_mask - - def as_dict(self): - """Returns the signals as Python dict.""" - shape = [self._batch_size, 1] - dtype = dtypes.bool - - if self._stop: - stopping = array_ops.ones(shape=shape, dtype=dtype) - else: - stopping = array_ops.zeros(shape=shape, dtype=dtype) - - signals = {'stopping': stopping} - if self._padding_mask is not None: - signals['padding_mask'] = self._padding_mask - return signals - - @staticmethod - def as_scalar_stopping_signal(signals): - return array_ops.identity(signals['stopping'][0][0]) - - @staticmethod - def should_stop(scalar_stopping_signal): - """Detects whether scalar_stopping_signal indicates stopping.""" - if isinstance(scalar_stopping_signal, ops.Tensor): - # STOPPING_SIGNAL is a constant True. Here, the logical_and is just the TF - # way to express the bool check whether scalar_stopping_signal is True. - return math_ops.logical_and(scalar_stopping_signal, - _StopSignals.STOPPING_SIGNAL) - else: - # For non Tensor case, it is used in SessionRunHook. So, we cannot modify - # the graph anymore. Here, we use pure Python. - return bool(scalar_stopping_signal) - - -class _PaddingSignals(object): - """Signals class holding all logic to handle padding.""" - - @staticmethod - def pad_features_and_labels(features, labels, batch_size): - """Pads out the batch dimension of features and labels.""" - real_batch_size = array_ops.shape( - _PaddingSignals._find_any_tensor(features))[0] - - batch_size_tensor = constant_op.constant(batch_size, dtypes.int32) - - check_greater = check_ops.assert_greater_equal( - batch_size_tensor, - real_batch_size, - data=(batch_size_tensor, real_batch_size), - message='The real batch size should not be greater than batch_size.') - - with ops.control_dependencies([check_greater]): - missing_count = batch_size_tensor - real_batch_size - - def pad_single_tensor(tensor): - """Pads out the batch dimension of a tensor to the complete batch_size.""" - rank = len(tensor.shape) - assert rank > 0 - padding = array_ops.stack([[0, missing_count]] + [[0, 0]] * (rank - 1)) - padded_shape = (batch_size,) + tuple(tensor.shape[1:]) - padded_tensor = array_ops.pad(tensor, padding) - padded_tensor.set_shape(padded_shape) - return padded_tensor - - def nest_pad(tensor_or_dict): - return nest.map_structure(pad_single_tensor, tensor_or_dict) - - features = nest_pad(features) - if labels is not None: - labels = nest_pad(labels) - - padding_mask = _PaddingSignals._padding_mask(real_batch_size, missing_count, - batch_size) - - return padding_mask, features, labels - - @staticmethod - def slice_tensor_or_dict(tensor_or_dict, signals): - """Slice the real Tensors according to padding mask in signals.""" - - padding_mask = signals['padding_mask'] - batch_size = array_ops.shape(padding_mask)[0] - - def verify_batch_size(tensor): - check_batch_size = math_ops.equal(batch_size, tensor.shape[0]) - with ops.control_dependencies([check_batch_size]): - return array_ops.identity(tensor) - - def slice_single_tensor(tensor): - rank = len(tensor.shape) - assert rank > 0 - real_batch_size = batch_size - math_ops.reduce_sum(padding_mask) - return verify_batch_size(tensor)[0:real_batch_size] - - # As we split the Tensors to all TPU cores and concat them back, it is - # important to ensure the real data is placed before padded ones, i.e., - # order is preserved. By that, the sliced padding mask should have all 0's. - # If this assertion failed, # the slice logic here would not hold. - sliced_padding_mask = slice_single_tensor(padding_mask) - assert_padding_mask = math_ops.equal( - math_ops.reduce_sum(sliced_padding_mask), 0) - - with ops.control_dependencies([assert_padding_mask]): - should_stop = _StopSignals.should_stop( - _StopSignals.as_scalar_stopping_signal(signals)) - - is_full_batch = math_ops.equal(math_ops.reduce_sum(padding_mask), 0) - - def slice_fn(tensor): - # If the current batch is full batch or part of stopping signals, we do - # not need to slice to save performance. - return control_flow_ops.cond( - math_ops.logical_or(should_stop, is_full_batch), - (lambda: verify_batch_size(tensor)), - (lambda: slice_single_tensor(tensor))) - - return nest.map_structure(slice_fn, tensor_or_dict) - - @staticmethod - def _find_any_tensor(batch_features): - tensors = [ - x for x in nest.flatten(batch_features) if isinstance(x, ops.Tensor) - ] - if not tensors: - raise ValueError('Cannot find any Tensor in features dict.') - return tensors[0] - - @staticmethod - def _padding_mask(real_batch_size, missing_count, batch_size): - padding_mask = array_ops.concat([ - array_ops.zeros((real_batch_size,), dtype=dtypes.int32), - array_ops.ones((missing_count,), dtype=dtypes.int32) - ], - axis=0) - padding_mask.set_shape((batch_size,)) - return padding_mask - - -def _verify_cross_hosts_transfer_size(tensor_dict, message): - total_size = 0 - tensor_structure = {} - for key, tensor in tensor_dict.items(): - shape = tensor.shape - size = np.product(shape) * tensor.dtype.size - tensor_structure[key] = shape - total_size += size - if total_size >= _ONE_GIGABYTE: - raise ValueError( - '{} The transfer size is larger than the protobuf limit. Please ' - 'consider to use Tensors with smaller shapes or reduce batch ' - 'size. Given:\n' - '{}'.format( - message, '\n'.join([ - ' -- Key: {}, Shape: {}'.format(k, v) - for k, v in tensor_structure.items() - ]))) - - -def _add_item_to_params(params, key, value): - """Adds a new item into `params`.""" - if isinstance(params, hparam.HParams): - # For HParams, we need to use special API. - if key in params: - params.set_hparam(key, value) - else: - params.add_hparam(key, value) - else: - # Now params is Python dict. - params[key] = value - - -def export_estimator_savedmodel(estimator, - export_dir_base, - serving_input_receiver_fn, - assets_extra=None, - as_text=False, - checkpoint_path=None, - strip_default_attrs=False): - """Export `Estimator` trained model for TPU inference. - - Args: - estimator: `Estimator` with which model has been trained. - export_dir_base: A string containing a directory in which to create - timestamped subdirectories containing exported SavedModels. - serving_input_receiver_fn: A function that takes no argument and returns a - `ServingInputReceiver` or `TensorServingInputReceiver`. - assets_extra: A dict specifying how to populate the assets.extra directory - within the exported SavedModel, or `None` if no extra assets are needed. - as_text: whether to write the SavedModel proto in text format. - checkpoint_path: The checkpoint path to export. If `None` (the default), - the most recent checkpoint found within the model directory is chosen. - strip_default_attrs: Boolean. If `True`, default-valued attributes will be - removed from the NodeDefs. - - Returns: - The string path to the exported directory. - """ - # `TPUEstimator` requires `tpu_config.RunConfig`, so we cannot use - # `estimator.config`. - config = tpu_config.RunConfig(model_dir=estimator.model_dir) - est = TPUEstimator( - estimator._model_fn, # pylint: disable=protected-access - config=config, - params=estimator.params, - use_tpu=True, - train_batch_size=2048, # Does not matter. - eval_batch_size=2048, # Does not matter. - ) - return est.export_savedmodel(export_dir_base, serving_input_receiver_fn, - assets_extra, as_text, checkpoint_path, - strip_default_attrs) +# pylint: disable=wildcard-import,unused-import,redefined-builtin +from tensorflow.python.tpu.tpu_estimator import * +# used by tests +from tensorflow.python.tpu.tpu_estimator import _clone_export_output_with_tensors +from tensorflow.python.tpu.tpu_estimator import _create_global_step +from tensorflow.python.tpu.tpu_estimator import _export_output_to_tensors +from tensorflow.python.tpu.tpu_estimator import _get_scaffold +from tensorflow.python.tpu.tpu_estimator import _Inputs +from tensorflow.python.tpu.tpu_estimator import _TPU_ESTIMATOR +# pylint: enable=wildcard-import,unused-import,redefined-builtin diff --git a/tensorflow/contrib/tpu/python/tpu/tpu_feed.py b/tensorflow/contrib/tpu/python/tpu/tpu_feed.py index 97fddbc2ad..af2542ea85 100644 --- a/tensorflow/contrib/tpu/python/tpu/tpu_feed.py +++ b/tensorflow/contrib/tpu/python/tpu/tpu_feed.py @@ -1,919 +1,25 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 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 +# 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. -# =================================================================== - -"""Helper library for handling infeed between hosts and TPUs. -""" +# ============================================================================== +"""Stub file to maintain backwards compatibility.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function -import itertools - -import numpy as np -from six.moves import xrange # pylint: disable=redefined-builtin - -from tensorflow.compiler.xla.experimental.xla_sharding import xla_sharding -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 -from tensorflow.python.framework import ops -from tensorflow.python.framework import tensor_shape -from tensorflow.python.ops import array_ops -from tensorflow.python.util import nest - - -def partition_or_replicate_on_host(tensor, dims): - """Partitions or replicates the input tensor. - - The ops inside this function are placed on the host side. - - Args: - tensor: The input tensor which will be partioned or replicated. - dims: A list of integer describes how to partition the input tensor. - - Returns: - An iterator of `Tensor`s or a list of partioned tensors. - """ - if dims is None: - return itertools.repeat(tensor) - dims = np.array(dims) - output = [tensor] - shape_list = np.array(tensor.shape.as_list()) - quotients, remainders = np.divmod(shape_list, dims) - for axis, (quotient, remainder, dim, original_size) in enumerate( - zip(quotients, remainders, dims, shape_list)): - if dim <= 1: - continue - if remainder > 0: - # For each dimension, when it cannot be evenly partitioned, XLA assumes - # tensors are partitioned in a greedy manner by using - # ceil_ratio(size/dim) first. E.g. 2D tensor with shape (5, 14) and dims - # are (2, 4). Since 5 % 2 = 1 and 14 % 4 = 2, [5, 14] => - # [[(3, 4), (3, 4), (2, 4), (2, 2)], - # [(2, 4), (2, 4), (2, 4), (2, 2)]] - ceil_ratio = quotient + 1 - num_full_slots, left_over = np.divmod(original_size, ceil_ratio) - num_or_size_splits = [ceil_ratio] * num_full_slots + [left_over] - if len(num_or_size_splits) < dim: - num_or_size_splits += [0] * (dim - len(num_or_size_splits)) - new_output = [] - for x in output: - new_output.append( - array_ops.split( - x, num_or_size_splits=num_or_size_splits, axis=axis)) - output = new_output - else: - output = [array_ops.split(x, dim, axis=axis) for x in output] - output = nest.flatten(output) - return output - - -def _tag_sharding_attribute_for_dequeued_tensor(tensor, dims): - """Tags appropriate XLA sharding attribute to the dequeued tensor. - - Args: - tensor: The dequeued tensor on TPU. - dims: A list of integer describes how the tensor is partitioned. - - Returns: - The same tensor with the xla_sharding attribute. - """ - if dims is None: - return xla_sharding.replicate(tensor) - elif np.prod(dims) == 1: - return xla_sharding.assign_device(tensor, 0) - else: - tile_assignment = np.arange(np.prod(dims)).reshape(dims) - return xla_sharding.tile(tensor=tensor, tile_assignment=tile_assignment) - - -def tag_sharding_attribute_for_dequeued_tensors(dequeues, dims): - """Tags appropriate XLA sharding attribute to the dequeued tensors. - - Args: - dequeues: A list of dequeued tensors on TPU. - dims: A list of integer describes how the tensor is partitioned. - - Returns: - The same dequeues with appropriate xla_sharding attribute. - """ - nest.assert_shallow_structure(dequeues, dims) - return nest.map_structure_up_to( - dequeues, _tag_sharding_attribute_for_dequeued_tensor, dequeues, dims) - - -class InfeedQueue(object): - """A helper object to build a device infeed queue. - - The InfeedQueue builds the host-side and device-side Ops to enqueue and - dequeue elements, respectively, and ensures that their types and - shapes match. - """ - - def __init__(self, - number_of_tuple_elements=None, - tuple_types=None, - tuple_shapes=None, - shard_dimensions=None, - name=None): - """Creates a new InfeedQueue with the given configuration. - - The configuration need not be fully specified at creation since it - can be modified subsequently by methods that set the values - explicitly or infer them from the shapes of inputs. - - Args: - number_of_tuple_elements: the number of Tensors fed atomically through the - queue, must be present unless it can be inferred from other arguments. - tuple_types: if not None, a list of types of the elements of the queue. - tuple_shapes: if not None, a list of shapes of the elements of the queue. - shard_dimensions: if not None, a list of dimensions on which the - elements of the queue should be sharded during automatic - parallelization. - name: the name of the queue. - - Raises: - ValueError: if number_of_tuple_elements <= 0; or - number_of_tuple_arguments, tuple_types, tuple_shapes, and - shard_dimensions are all None; or the length of tuple_types, - tuple_shapes, or shard_dimensions is not equal to - number_of_tuple_elements; or any element of shard_dimensions - can't be converted to a Dimension. - TypeError: if any element of tuple_types or tuple_shapes can't - be converted to a dtype or TensorShape, respectively. - """ - self._frozen = False - self._generated_enqueue_ops = False - self._generated_dequeue_op = False - self._name = "InfeedQueue" if name is None else name - if number_of_tuple_elements is None: - if tuple_types is not None: - number_of_tuple_elements = len(tuple_types) - elif tuple_shapes is not None: - number_of_tuple_elements = len(tuple_shapes) - elif shard_dimensions is not None: - number_of_tuple_elements = len(shard_dimensions) - else: - raise ValueError( - "number of tuple elements cannot be inferred from InfeedQueue " - "constructor") - if number_of_tuple_elements <= 0: - raise ValueError("number_of_tuple_elements %d must be > 0" % - number_of_tuple_elements) - # Make an empty sharding policy for each tuple element. - self._sharding_policies = [ - tpu_sharding.ShardingPolicy() - for _ in xrange(number_of_tuple_elements) - ] - if tuple_types is not None: - self.set_tuple_types(tuple_types) - else: - self._tuple_types = None - if tuple_shapes is not None: - self.set_tuple_shapes(tuple_shapes) - else: - self._tuple_shapes = None - if shard_dimensions is not None: - self.set_shard_dimensions(shard_dimensions) - self._validate() - - def _validate(self): - """Checks that the configuration is self-consistent. - - Raises: - ValueError: if the shapes and sharding policies don't match. - """ - if self.tuple_shapes is not None: - for (policy, shape) in zip(self._sharding_policies, self._tuple_shapes): - # Raise an error if the policy is incompatible with the shape. - _ = policy.get_sharded_shape(shape) - - @property - def number_of_tuple_elements(self): - """Returns the number of InfeedQueue tuple elements.""" - return len(self._sharding_policies) - - @property - def tuple_types(self): - """Returns the types of the InfeedQueue tuple elements.""" - return self._tuple_types - - def set_tuple_types(self, tuple_types): - """Sets the type of each element of the queue. - - tuple_types must be a list of length - self.number_of_tuple_elements, and each element must be - convertible to a dtype. - - Args: - tuple_types: the types of each queue element. - - Raises: - ValueError: if tuple_types is not of length - self.number_of_tuple_elements. - TypeError: if an element of tuple_types cannot be converted to a - dtype. - """ - if len(tuple_types) != self.number_of_tuple_elements: - raise ValueError("tuple_types is %s, but must be a list of length %d" % - (str(tuple_types), self.number_of_tuple_elements)) - if self._frozen: - for (frozen, updated) in zip(self._tuple_types, tuple_types): - if frozen != updated: - raise ValueError( - "Trying to update InfeedQueue with frozen configuration with an " - "incompatible type. Frozen types are %s, updated types are %s" % ( - str(self._tuple_types), str(tuple_types))) - else: - try: - self._tuple_types = [dtypes.as_dtype(t) for t in tuple_types] - except (TypeError) as e: - raise TypeError( - "tuple_types is %s, but must be a list of elements each " - "convertible to dtype: got error %s" % (str(tuple_types), str(e))) - - @property - def tuple_shapes(self): - """Returns the shapes of the InfeedQueue tuple elements.""" - return self._tuple_shapes - - def set_tuple_shapes(self, tuple_shapes): - """Sets the shape of each element of the queue. - - tuple_shapes must be a list of length - self.number_of_tuple_elements, and each element must be - convertible to a TensorShape. - - Args: - tuple_shapes: the shapes of each queue element. - - Raises: - ValueError: if tuple_shapes is not of length - self.number_of_tuple_elements. - TypeError: if an element of tuple_shapes cannot be converted to - a TensorShape. - """ - if len(tuple_shapes) != self.number_of_tuple_elements: - raise ValueError("tuple_shapes is %s, but must be a list of length %d" % - (str(tuple_shapes), self.number_of_tuple_elements)) - try: - tuple_shapes = [tensor_shape.as_shape(shape) for shape in tuple_shapes] - except (ValueError, TypeError) as e: - raise TypeError( - "tuple_shapes is %s, but must be a list of elements each " - "convertible to TensorShape: got error %s" % (str(tuple_shapes), - str(e))) - if self._frozen: - for (frozen, updated) in zip(self._tuple_shapes, tuple_shapes): - if frozen != updated: - raise ValueError( - "Trying to update InfeedQueue with frozen configuration with an " - "incompatible shape. Frozen shapes are %s, updated shapes are %s" - % (str(self._tuple_shapes), str(tuple_shapes))) - else: - self._tuple_shapes = tuple_shapes - self._validate() - - @property - def sharding_policies(self): - """Returns the sharding policies of the InfeedQueue tuple elements.""" - return self._sharding_policies - - @property - def shard_dimensions(self): - """Gets the shard dimension of each tuple element. - - Returns: - A list of length number_of_tuple_elements, where each list entry - is the shard dimension of that tuple element or None if the - shard dimension has not been set. - """ - # The number of shards is always the same for all the policies. - return [policy.shard_dimension for policy in self._sharding_policies] - - def set_shard_dimensions(self, shard_dimensions): - """Sets the shard_dimension of each element of the queue. - - shard_dimensions must be a list of length - self.number_of_tuple_elements, and each element must be - convertible to a Dimension compatible with self.tuple_shapes. - - Args: - shard_dimensions: the dimensions of each queue element. - - Raises: - ValueError: if shard_dimensions is not of length - self.number_of_tuple_elements; or an element of - shard_dimensions cannot be converted to a Dimension; or an - element of shard_dimensions is a Dimension that is out of - range for the corresponding tuple element shape. - """ - if len(shard_dimensions) != self.number_of_tuple_elements: - raise ValueError("shard_dimensions is %s, but must be a list of length %d" - % (str(shard_dimensions), - self.number_of_tuple_elements)) - for (policy, dimension) in zip(self._sharding_policies, shard_dimensions): - policy.set_shard_dimension(dimension) - self._validate() - - @property - def number_of_shards(self): - """Gets the number of shards to use for the InfeedQueue. - - Returns: - Number of shards or None if the number of shards has not been set. - """ - # The number of shards is always the same for all the policies. - return self._sharding_policies[0].number_of_shards - - def set_number_of_shards(self, number_of_shards): - """Sets the number of shards to use for the InfeedQueue. - - Args: - number_of_shards: number of ways to shard the InfeedQueue. - - Raises: - ValueError: if number_of_shards is not > 0; or the policies have - been frozen and number_of_shards was already set to something - else. - """ - for policy in self._sharding_policies: - policy.set_number_of_shards(number_of_shards) - self._validate() - - def set_configuration_from_input_tensors(self, input_tensors): - """Sets the shapes and types of the queue tuple elements. - - input_tensors is a list of Tensors whose types and shapes are used - to set the queue configuration. - - Args: - input_tensors: list of Tensors of the same types and shapes as - the desired queue Tuple. - - Raises: - ValueError: if input_tensors is not a list of length - self.number_of_tuple_elements - """ - if len(input_tensors) != self.number_of_tuple_elements: - raise ValueError("input_tensors is %s, but should be a list of %d Tensors" - % (str(input_tensors), self.number_of_tuple_elements)) - self.set_tuple_shapes([t.shape for t in input_tensors]) - self.set_tuple_types([t.dtype for t in input_tensors]) - - def set_configuration_from_sharded_input_tensors(self, input_tensors): - """Sets the shapes and types of the queue tuple elements. - - input_tensors is a list of lists of Tensors whose types and shapes are used - to set the queue configuration. The length of the outer list is the number - of shards required, and each inner list is the tuple of Tensors to use to - determine the types and shapes of the corresponding shard. This method - depends on the shard dimension, and calling it freezes the shard policy. - - Args: - input_tensors: list of lists of Tensors. The outer list length corresponds - to the desired number of shards, and each inner list is the size - and shape of the desired configuration of the corresponding shard. - - Raises: - ValueError: if any inner list is not a list of length - self.number_of_tuple_elements; or the inner lists do not combine to - form a consistent unsharded shape. - TypeError: if the types of the Tensors in the inner lists do not match. - """ - if not self._frozen: - # Unset the tuple shapes in case the configuration becomes - # transiently inconsistent. - self._tuple_shapes = None - number_of_shards = len(input_tensors) - self.set_number_of_shards(number_of_shards) - for t in input_tensors: - if len(t) != self.number_of_tuple_elements: - raise ValueError( - "input_tensors is %s but must be a list of lists, where each inner" - " list has length number_of_tuple_elements=%d" % ( - str(input_tensors), self.number_of_tuple_elements)) - # Transpose the inputs to make a list of shard shapes for each tuple - # element. - sharded_shapes = [[t[i].shape for t in input_tensors] - for i in xrange(self.number_of_tuple_elements)] - # For each tuple, get the unsharded shape using that tuple's policy. - unsharded_shapes = [ - policy.get_unsharded_shape(s) - for (policy, s) in zip(self._sharding_policies, sharded_shapes) - ] - self.set_tuple_shapes(unsharded_shapes) - for i in xrange(1, self.number_of_shards): - for (t1, t2) in zip(input_tensors[0], input_tensors[i]): - if t1.dtype != t2.dtype: - raise TypeError( - "types of the tuple elements of input_tensors %s are not " - "consistent" % str(input_tensors)) - self.set_tuple_types([t.dtype for t in input_tensors[0]]) - - def freeze(self): - """Freezes the InfeedQueue so it can no longer be modified. - - The configuration is implicitly frozen before any host-side or - device-side Ops are generated. The configuration cannot be frozen - until the types and shapes of the tuple elements have been set. - - Raises: - ValueError: if the types or shapes of the tuple elements have not been - set. - """ - self._frozen = True - if self._tuple_types is None: - raise ValueError( - "Can't freeze an InfeedQueue without setting all tuple types.") - if self._tuple_shapes is None: - raise ValueError( - "Can't freeze an InfeedQueue without setting all tuple shapes.") - for shape in self._tuple_shapes: - if shape.dims is None: - raise ValueError( - "Can't freeze an InfeedQueue without setting all tuple shapes.") - for policy in self._sharding_policies: - policy.freeze() - self._validate() - - 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. - - Raises: - ValueError: if the types or shapes of the tuple elements have not been - set; or if a dequeue op has already been generated. - """ - self.freeze() - if self._generated_dequeue_op: - raise ValueError("Can't generate two dequeue Ops from the same queue") - self._generated_dequeue_op = True - full_name = "%s/dequeue" % self._name - sharded_shapes = [ - policy.get_sharded_shape(shape) - for (shape, policy) in zip(self._tuple_shapes, self._sharding_policies) - ] - 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, - name_prefix, - index, - device=None, - tpu_ordinal=-1): - """Generate a host-side Op to enqueue a tuple to the queue. - - If device is None the inputs are all required to have the same - device specification, and the enqueue Op is colocated with - inputs[0]. Otherwise the enqueue Op is placed on 'device'. - - Args: - inputs: a list of Tensors with the types and shapes of the tuple elements. - name_prefix: the base name for the Op. - index: the shard index, used to uniquify the Op name. - device: device to place the Op on, or None if it should be - colocated with the inputs. - tpu_ordinal: ordinal of the TPU device on the host to use for - infeed if device is a CPU device. Should be set to -1 if device - is a TPU device. - - Returns: - An Op corresponding to a shard of infeed enqueued at the host, - suitable for use within a replicated block. - - Raises: - ValueError: if device is None and inputs do not all have the - same device specification. - """ - full_name = "%s/%d" % (name_prefix, index) - shapes = [t.shape for t in inputs] - if device is None: - devices = [t.device for t in inputs] - for i in xrange(1, self.number_of_tuple_elements): - if devices[0] != devices[i]: - raise ValueError( - "input devices for shard %d are %s, but should all be the same" % - (index, str(devices))) - with ops.colocate_with(inputs[0]): - return tpu_ops.infeed_enqueue_tuple( - inputs=inputs, - shapes=shapes, - name=full_name, - device_ordinal=tpu_ordinal) - else: - with ops.device(device): - return tpu_ops.infeed_enqueue_tuple( - inputs=inputs, - shapes=shapes, - name=full_name, - device_ordinal=tpu_ordinal) - - def generate_enqueue_ops(self, - sharded_inputs, - tpu_ordinal_function=None, - placement_function=None): - """Generates the host-side Ops to enqueue the shards of a tuple. - - sharded_inputs is a list, one for each shard, of lists of - Tensors. sharded_inputs[0] is the tuple of Tensors to use to feed - shard 0 if the queue. Returns the host-side Ops that must be run to - enqueue the sharded tuple. The Op for shard i is colocated with the inputs - for shard i. - - Implicitly freezes the queue configuration if it is not already - frozen. If the configuration has already been frozen, and is not - compatible with the types and shapes of sharded_inputs, an error - will be raised. - - Args: - sharded_inputs: a list of lists of Tensors. The length of the outer list - determines the number of shards. Each inner list indicates the types - and shapes of the tuples in the corresponding shard. - tpu_ordinal_function: if not None, a function that takes the - shard index as input and returns the ordinal of the TPU device - the shard's infeed should be placed on. tpu_ordinal_function must be - set if the inputs are placed on CPU devices. - placement_function: if not None, a function that takes the shard index as - input and returns the host device where the enqueue op should be placed - on. - - Returns: - A list of host-side Ops, one for each shard, that when executed together - will enqueue a full-size element of infeed. - - Raises: - ValueError: if the queue configuration has previously been frozen and the - shapes of the elements of sharded_inputs are not compatible with the - frozen configuration; or if the shapes of the elements of sharded_inputs - don't form a consistent unsharded tuple; or if the elements of a tuple - have different device constraints. - TypeError: if the queue configuration has previously been frozen and the - types of the elements of sharded_inputs are not compatible with the - frozen configuration; or if the types of the elements of sharded_inputs - don't form a consistent unsharded tuple. - """ - self.set_configuration_from_sharded_input_tensors(sharded_inputs) - self.freeze() - if self._generated_enqueue_ops: - raise ValueError("Can't generate two enqueue Ops from the same queue") - self._generated_enqueue_ops = True - if tpu_ordinal_function is None: - tpu_ordinal_function = lambda index: -1 - name_prefix = "%s/enqueue" % self._name - return [ - self._generate_enqueue_op( - shard, - name_prefix, - index, - tpu_ordinal=tpu_ordinal_function(index), - device=placement_function(index) if placement_function else None) - for (shard, index) in zip(sharded_inputs, xrange(self.number_of_shards)) - ] - - # TODO(misard) Generalize this to the case of systems that don't - # have 8 devices per host, and figure out what to do with - # model-parallelism. - def _default_placement_function(self, index): - return "/task:%d/device:CPU:0" % (index / 8) - - def _default_ordinal_function(self, index): - return index % 8 - - # TODO(b/36470756) remove this from tutorials once we have a better story - # for automatic placement of input pipelines. - def split_inputs_and_generate_enqueue_ops(self, - inputs, - device_assignment=None, - placement_function=None, - tpu_ordinal_function=None): - """POORLY-PERFORMING ON MULTI-HOST SYSTEMS. - - Generates the host-side Ops to enqueue a tuple. - - This method performs poorly because it takes an entire input on a single - host, splits it, and distributes it to all of the cores. It is present only - to simplify tutorial examples. - - inputs is a list of Tensors to use to feed the queue. Each input is split - into self.number_of_shards shards. Returns an Op for each shard to enqueue - the shard. The Op for shard i is placed on device placement_function(i). - - Implicitly freezes the queue configuration if it is not already - frozen. If the configuration has already been frozen, and is not - compatible with the types and shapes of inputs, an error - will be raised. - - Args: - inputs: a list of Tensors which indicates the types and shapes of the - queue tuple. - device_assignment: if not `None`, a TPU `DeviceAssignment`. If - device_assignment is not `None`, but `placement_function` and - `ordinal_function` are None, then `device_assignment` will be used to - place infeeds on the first k TPU shards, where k is the number of shards - in the queue. If all three are `None`, then default placement and - ordinal functions are used. - placement_function: if not None, a function that takes the shard - index as input and returns a device string indicating which - device the shard's infeed should be placed on. If placement_function - and tpu_ordinal_function are None, inputs are sharded round-robin - across the devices in the system. - tpu_ordinal_function: if not None, a function that takes the - shard index as input and returns the ordinal of the TPU device - the shard's infeed should be placed on. If placement_function - and tpu_ordinal_function are None, inputs are sharded round-robin - across the devices in the system. - - Returns: - A list of host-side Ops, one for each shard, that when executed together - will enqueue a full-size element of infeed. - - Raises: - ValueError: if the queue configuration has previously been frozen and the - shapes of the elements of inputs are not compatible with the frozen - configuration. - TypeError: if the queue configuration has previously been frozen and the - types of the elements of inputs are not compatible with the frozen - configuration. - """ - if device_assignment is None: - if placement_function is None: - placement_function = self._default_placement_function - if tpu_ordinal_function is None: - tpu_ordinal_function = self._default_ordinal_function - else: - - def _placement_function_from_map(index): - return device_assignment.host_device(replica=index) - - def _ordinal_function_from_map(index): - return device_assignment.tpu_ordinal(replica=index) - - if placement_function is None: - placement_function = _placement_function_from_map - if tpu_ordinal_function is None: - tpu_ordinal_function = _ordinal_function_from_map - self.set_configuration_from_input_tensors(inputs) - self.freeze() - if self._generated_enqueue_ops: - raise ValueError("Can't generate two enqueue Ops from the same queue") - self._generated_enqueue_ops = True - split_name_prefix = "%s/split" % self._name - if self.number_of_shards == 1: - transposed_sharded_inputs = [[inp] for inp in inputs] - else: - - def split_fn(inp, num_shards, axis, name): - with ops.colocate_with(inp): - return array_ops.split(inp, num_shards, axis=axis, name=name) - - transposed_sharded_inputs = [ - split_fn( - inp, - self.number_of_shards, - axis=policy.shard_dimension, - name="%s/%d" % (split_name_prefix, index)) - for (inp, policy, index) in zip(inputs, self._sharding_policies, - xrange(self.number_of_tuple_elements)) - ] - sharded_inputs = [[shard[i] for shard in transposed_sharded_inputs] - for i in xrange(self.number_of_shards)] - name_prefix = "%s/enqueue" % self._name - return [ - self._generate_enqueue_op( - shard, - name_prefix, - index, - device=placement_function(index), - tpu_ordinal=tpu_ordinal_function(index)) - for (shard, index) in zip(sharded_inputs, xrange(self.number_of_shards)) - ] - - -class _PartitionedInfeedQueue(InfeedQueue): - """A helper object to build a device infeed queue with input partition. - - Args: - number_of_tuple_elements: the number of Tensors fed atomically through the - queue, must be present unless it can be inferred from other arguments. - device_assignment: A TPU `DeviceAssignment` which is used to place all the - partitions to different TPU infeed queues. - host_id: The id of the host machine. - input_partition_dims: A nested list/tuple of integers. Each inner - list/tuple describes how to partition the corresponding input tensor. - tuple_types: If not None, a list of types of the elements of the queue. - tuple_shapes: If not None, a list of shapes of the elements of the queue. - name: The name of the queue. - """ - - def __init__(self, - number_of_tuple_elements, - device_assignment, - host_id, - input_partition_dims=None, - tuple_types=None, - tuple_shapes=None, - name=None): - super(_PartitionedInfeedQueue, self).__init__( - number_of_tuple_elements=number_of_tuple_elements, - tuple_types=tuple_types, - tuple_shapes=None, - shard_dimensions=None, - name="PartitionedInfeedQueue" if name is None else name) - self._input_partition_dims = input_partition_dims - self._host_id = host_id - self._device_assignment = device_assignment - - def generate_dequeue_op(self, tpu_device=0): - """Generate TPU dequeue ops. - - Args: - tpu_device: The TPU device ordinal where the infeed instruction should be - placed. - - Returns: - A list of Outputs corresponding to a partition of infeed dequeued - into XLA, suitable for use within a replicated block. - - Raises: - ValueError: if the types or shapes of the tuple elements have not been - set; or if a dequeue op has already been generated. - """ - self.freeze() - if self._generated_dequeue_op: - raise ValueError("Can't generate two dequeue Ops from the same queue") - self._generated_dequeue_op = True - full_name = "%s/dequeue" % self._name - sharded_shapes = [ - policy.get_sharded_shape(shape) - for (shape, policy) in zip(self._tuple_shapes, self._sharding_policies) - ] - with ops.device(tpu.core(tpu_device)): - values = tpu_ops.infeed_dequeue_tuple( - dtypes=self._tuple_types, shapes=sharded_shapes, name=full_name) - return tag_sharding_attribute_for_dequeued_tensors( - values, self._input_partition_dims) - - def generate_enqueue_ops(self, per_host_sharded_inputs): - """Generates the host-side Ops to enqueue the partitioned inputs. - - per_host_sharded_inputs is a list, one for each replica, of lists of - Tensors. sharded_inputs[i] is the tuple of Tensors to use to feed - replica i. - sharded_inputs[i][j] is partitioned by self._input_partition_dims[j]. - - For example, if sharded_inputs[i][j] is a 2-D Tensor: - [[A, B, C, D], - [E ,F, G, H]] - self._input_partition_dims[j] is [2, 4]. - - sharded_inputs[i][j] will be partitioned and flattened into: - [A, B, C, D, E, F, G, H] and fed into the logical core ids: - [0, 1, 2, 3, 4, 5, 6, 7] respectively. - - Args: - per_host_sharded_inputs: a list of lists of Tensors. The length of the - outer list determines the number of shards. Each inner list indicates - the types and shapes of the tuples in the corresponding shard. - - Returns: - A list of host-side Ops, one for each shard, that when executed together - will enqueue a full-size element of infeed. - - Raises: - ValueError: if the queue configuration has previously been frozen and the - shapes of the elements of sharded_inputs are not compatible with the - frozen configuration; or if the shapes of the elements of sharded_inputs - don't form a consistent unsharded tuple; or if the elements of a tuple - have different device constraints; or if the partition dims are invalid. - TypeError: if the queue configuration has previously been frozen and the - types of the elements of sharded_inputs are not compatible with the - frozen configuration; or if the types of the elements of sharded_inputs - don't form a consistent unsharded tuple. - """ - self.set_configuration_from_sharded_input_tensors(per_host_sharded_inputs) - number_of_replicas_per_host = len(per_host_sharded_inputs) - number_of_tuple_elements = len(per_host_sharded_inputs[0]) - - assert len(self._input_partition_dims) == number_of_tuple_elements - per_host_enqueue_ops = [] - - for replica_index in range(number_of_replicas_per_host): - flattened_inputs = per_host_sharded_inputs[replica_index] - inputs_part_dims_flat = nest.flatten_up_to(flattened_inputs, - self._input_partition_dims) - inputs_parted_iters = [ - iter(self._check_dims_and_partition_or_replicate_on_host(x, dims)) - for x, dims in zip(per_host_sharded_inputs[replica_index], - inputs_part_dims_flat) - ] - - for logical_core in xrange(self._device_assignment.num_cores_per_replica): - # Places different partitions to different logic cores. - replica_id = self._device_assignment.lookup_replicas( - self._host_id, logical_core)[replica_index] - ordinal = self._device_assignment.tpu_ordinal( - replica=replica_id, logical_core=logical_core) - infeed_inputs = [] - for it in inputs_parted_iters: - input_for_device = next(it, None) - if input_for_device is not None: - infeed_inputs.append(input_for_device) - - if infeed_inputs: - per_host_enqueue_ops.append( - tpu_ops.infeed_enqueue_tuple( - inputs=infeed_inputs, - shapes=[x.shape for x in infeed_inputs], - name="enqueue/replica_{0}/input_{1}".format( - replica_index, logical_core), - device_ordinal=ordinal)) - return per_host_enqueue_ops - - def _check_input_partition_dims(self, tensor, dims): - """Checks that input partition dims are valid for the `Tensor`. - - Args: - tensor: Input tensor for partitioning. - dims: A list of integer describes how to partition the input tensor. - - Raises: - ValueError: If the tensor can't be partitioned by dims or the - num_cores_per_replica doesn't match the number of - partitions(dims.prod()). - """ - # No partitioning specified, so don't perform further checks. - if dims is None: - return - - dims = np.array(dims) - - if (dims < 1).any(): - raise ValueError("All input partition dims must be >= 1.") - - # No partitioning, so don't perform further checks. - if dims.prod() == 1: - return - - if dims.prod() != self._device_assignment.num_cores_per_replica: - raise ValueError( - "The product of each input parition dim should equal to " - "num_cores_per_replica. (dim = {}, num_cores_per_replica " - "= {})".format(dims, self._device_assignment.num_cores_per_replica)) - if dims.shape[0] != tensor.shape.ndims: - raise ValueError( - "Input partition dims must have the same number of dimensions " - "as the `Tensor` to be partitioned. (tensor shape = {}, input " - "partition dims = {}).".format(tensor.shape.as_list(), dims)) - - tensor.shape.assert_is_fully_defined() - - def _check_dims_and_partition_or_replicate_on_host(self, tensor, dims): - """Checks dims and partitions or replicates the input tensor. - - The ops inside this function are placed on the host side. - - Args: - tensor: The input tensor which will be partioned or replicated. - dims: A list of integer describes how to partition the input tensor. - - Returns: - An iterator of `Tensor`s or a list of partioned tensors. - """ - self._check_input_partition_dims(tensor, dims) - return partition_or_replicate_on_host(tensor, dims) +# pylint: disable=wildcard-import,unused-import,redefined-builtin +from tensorflow.python.tpu.tpu_feed import * +# used by tests +from tensorflow.python.tpu.tpu_feed import _PartitionedInfeedQueue +# pylint: enable=wildcard-import,unused-import,redefined-builtin diff --git a/tensorflow/contrib/tpu/python/tpu/tpu_function.py b/tensorflow/contrib/tpu/python/tpu/tpu_function.py index 422c7d3b26..f2755c6979 100644 --- a/tensorflow/contrib/tpu/python/tpu/tpu_function.py +++ b/tensorflow/contrib/tpu/python/tpu/tpu_function.py @@ -1,66 +1,23 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 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 +# 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. -# ============================================================================= - -"""Helper library for functions used during TPU compilation.""" +# ============================================================================== +"""Stub file to maintain backwards compatibility.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function -import contextlib - - -class TpuContext(object): - """A context object holding state about the TPU computation being built.""" - - def __init__(self): - """Creates a new TpuContext.""" - self._number_of_shards = None - - @property - def number_of_shards(self): - return self._number_of_shards - - def set_number_of_shards(self, number_of_shards): - self._number_of_shards = number_of_shards - - -# The Tpu context holds the number of shards when a sharded computation is -# being built, or None if no computation is being built. -_current_tpu_context = TpuContext() - - -@contextlib.contextmanager -def tpu_shard_context(number_of_shards): - if _current_tpu_context.number_of_shards is not None: - raise NotImplementedError("tpu_shard_context cannot be nested.") - try: - _current_tpu_context.set_number_of_shards(number_of_shards) - yield - finally: - _current_tpu_context.set_number_of_shards(None) - - -def get_tpu_context(): - return _current_tpu_context - - -# Decorator function for tpu computation func that was passed to tpu.rewrite() -# if there is an embedded training loop in this func, trace tools will generate -# step markers for each iteration. -def on_device_training_loop(func): - # Value for this attribute is from xla.DebugOptions.StepMarkerLocation. - setattr(func, "step_marker_location", "STEP_MARK_AT_TOP_LEVEL_WHILE_LOOP") - return func +# pylint: disable=wildcard-import,unused-import +from tensorflow.python.tpu.tpu_function import * +# pylint: enable=wildcard-import,unused-import diff --git a/tensorflow/contrib/tpu/python/tpu/tpu_optimizer.py b/tensorflow/contrib/tpu/python/tpu/tpu_optimizer.py index 1e11de6421..ca58e78d7b 100644 --- a/tensorflow/contrib/tpu/python/tpu/tpu_optimizer.py +++ b/tensorflow/contrib/tpu/python/tpu/tpu_optimizer.py @@ -1,203 +1,23 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 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 +# 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. -# ============================================================================= - -"""Optimizer that implements cross-shard gradient reduction for TPU.""" +# ============================================================================== +"""Stub file to maintain backwards compatibility.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function - -from tensorflow.contrib.tpu.python.ops import tpu_ops -from tensorflow.contrib.tpu.python.tpu import tpu_function -from tensorflow.python.framework import ops -from tensorflow.python.ops.losses import losses -from tensorflow.python.platform import tf_logging as logging -from tensorflow.python.training import optimizer - - -class CrossShardOptimizer(optimizer.Optimizer): - """An optimizer that averages gradients across TPU shards.""" - - def __init__(self, - opt, - reduction=losses.Reduction.MEAN, - name="CrossShardOptimizer", - group_assignment=None): - """Construct a new cross-shard optimizer. - - Args: - opt: An existing `Optimizer` to encapsulate. - reduction: The reduction to apply to the shard losses. - name: Optional name prefix for the operations created when applying - gradients. Defaults to "CrossShardOptimizer". - group_assignment: Optional 2d int32 lists with shape - [num_groups, num_replicas_per_group] which describles how to apply - optimizer to subgroups. - - Raises: - ValueError: If reduction is not a valid cross-shard reduction. - """ - if reduction not in (losses.Reduction.SUM, losses.Reduction.MEAN): - raise ValueError("Unsupported reduction: %s." % reduction) - - super(CrossShardOptimizer, self).__init__(False, name) - self._opt = opt - self._reduction = reduction - self._group_assignment = group_assignment - - def _verify_and_get_subgroup_size(self, group_assignment, num_shards): - """Verify group_assignment and get the subgroup size". - - Args: - group_assignment: list of group ids for applying the optimizer - to subgroups. - num_shards: The number of TPU shards. - - Returns: - The size of one subgroup in group_assignment. - - Raises: - ValueError: If group_assignment is invalid. - """ - if not group_assignment: - return None - if not (isinstance(group_assignment, list) and - all(isinstance(i, list) for i in group_assignment)): - raise ValueError("group_assignment must be a list of list. Got {}".format( - group_assignment)) - - replica_ids = set() - for g in group_assignment: - for i in g: - replica_ids.add(i) - - if set(range(num_shards)) != replica_ids: - raise ValueError("group_assignment must be a permutation of range({0})." - " Got group_assignment={1}".format( - num_shards, group_assignment)) - - subgroup_size_list = [len(group) for group in group_assignment] - if all(subgroup_size_list[0] == size for size in subgroup_size_list): - return subgroup_size_list[0] - else: - raise ValueError("The size of each subgroup in group_assignment must " - "be equal. Got group_assignment={}".format( - self._group_assignment)) - - def compute_gradients(self, loss, var_list=None, **kwargs): - """Compute gradients of "loss" for the variables in "var_list". - - This simply wraps the compute_gradients() from the real optimizer. The - gradients will be aggregated in the apply_gradients() so that user can - modify the gradients like clipping with per replica global norm if needed. - The global norm with aggregated gradients can be bad as one replica's huge - gradients can hurt the gradients from other replicas. - - Args: - loss: A Tensor containing the value to minimize. - 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 `GraphKey.TRAINABLE_VARIABLES`. - **kwargs: Keyword arguments for compute_gradients(). - - Returns: - A list of (gradient, variable) pairs. - - Raises: - ValueError: If not within a tpu_shard_context or group_assignment is - invalid. - """ - num_shards = tpu_function.get_tpu_context().number_of_shards - if num_shards is None: - logging.warning( - "CrossShardOptimizer should be used within a tpu_shard_context, but " - "got unset number_of_shards. Assuming 1.") - num_shards = 1 - - subgroup_size = self._verify_and_get_subgroup_size(self._group_assignment, - num_shards) - - if num_shards > 1 and self._reduction == losses.Reduction.MEAN: - if self._group_assignment: - scale = 1.0 / subgroup_size - else: - scale = 1.0 / num_shards - loss *= scale - - return self._opt.compute_gradients(loss, var_list=var_list, **kwargs) - - def apply_gradients(self, grads_and_vars, global_step=None, name=None): - """Apply gradients to variables. - - Calls tpu_ops.cross_replica_sum() to sum gradient contributions across - replicas, and then applies the real optimizer. - - 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 gradients. If `global_step` was not None, - that operation also increments `global_step`. - - Raises: - ValueError: If the grads_and_vars is malformed. - """ - summed_grads_and_vars = [] - for (grad, var) in grads_and_vars: - if grad is None: - summed_grads_and_vars.append((grad, var)) - else: - with ops.colocate_with(grad): - summed_grads_and_vars.append((tpu_ops.cross_replica_sum( - grad, self._group_assignment), var)) - return self._opt.apply_gradients(summed_grads_and_vars, global_step, name) - - def get_slot(self, *args, **kwargs): - """Return a slot named "name" created for "var" by the Optimizer. - - This simply wraps the get_slot() from the actual optimizer. - - Args: - *args: Arguments for get_slot(). - **kwargs: Keyword arguments for get_slot(). - - Returns: - The `Variable` for the slot if it was created, `None` otherwise. - """ - return self._opt.get_slot(*args, **kwargs) - - def get_slot_names(self, *args, **kwargs): - """Return a list of the names of slots created by the `Optimizer`. - - This simply wraps the get_slot_names() from the actual optimizer. - - Args: - *args: Arguments for get_slot(). - **kwargs: Keyword arguments for get_slot(). - - Returns: - A list of strings. - """ - return self._opt.get_slot_names(*args, **kwargs) - - def variables(self): - """Forwarding the variables from the underlying optimizer.""" - return self._opt.variables() +# pylint: disable=wildcard-import,unused-import +from tensorflow.python.tpu.tpu_optimizer import * +# pylint: enable=wildcard-import,unused-import diff --git a/tensorflow/contrib/tpu/python/tpu/tpu_sharding.py b/tensorflow/contrib/tpu/python/tpu/tpu_sharding.py index f5af03f33c..93c52335a5 100644 --- a/tensorflow/contrib/tpu/python/tpu/tpu_sharding.py +++ b/tensorflow/contrib/tpu/python/tpu/tpu_sharding.py @@ -1,253 +1,23 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 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 +# 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. -# ============================================================================= - -"""Helper library for sharding during TPU compilation.""" +# ============================================================================== +"""Stub file to maintain backwards compatibility.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function -from six.moves import xrange # pylint: disable=redefined-builtin - -from tensorflow.python.framework import tensor_shape - -_DEFAULT_NUMBER_OF_SHARDS = 1 -_DEFAULT_SHARD_DIMENSION = 0 - - -# TODO(b/36777903) change other parts of tpu.py to use this class. -class ShardingPolicy(object): - """An object use to hold the sharding policy for a Tensor. - """ - - def __init__(self): - self._number_of_shards = None - self._shard_dimension = None - self._frozen = False - - def __str__(self): - if self.number_of_shards is None or self.shard_dimension is None: - return "ShardingPolicy(unset)" - else: - return ("ShardingPolicy(%d shards dimension %d)" % - (self.number_of_shards, self.shard_dimension)) - - def _fill_default_values(self): - if self._number_of_shards is None: - self._number_of_shards = _DEFAULT_NUMBER_OF_SHARDS - if self._shard_dimension is None: - self._shard_dimension = tensor_shape.as_dimension( - _DEFAULT_SHARD_DIMENSION) - - def freeze(self): - """Prevents further modification to the sharding policy. - - Any values that have not been set when freeze is called are set to - defaults. If the ShardingPolicy is already frozen, this is a NoOp. - """ - if not self._frozen: - self._fill_default_values() - self._frozen = True - - @property - def number_of_shards(self): - """Returns the number of shards in the policy or None if unspecified.""" - return self._number_of_shards - - def set_number_of_shards(self, number_of_shards): - """Sets the number of shards for the current policy. - - If the policy has been frozen then number_of_shards must match the - existing setting. - - Args: - number_of_shards: The number of shards to use in the policy. - - Raises: - ValueError: If the policy has been frozen and number_of_shards - differs from the frozen value; or number_of_shards <= 0. - """ - if self._frozen: - if self._number_of_shards != number_of_shards: - raise ValueError( - "Can't set sharding policy to use %d shards since it has been " - "frozen to use %d." % (number_of_shards, self._number_of_shards)) - else: - if number_of_shards > 0: - self._number_of_shards = number_of_shards - else: - raise ValueError( - "Can't set sharding policy to use %s shards; value must be >0", - str(number_of_shards)) - - @property - def shard_dimension(self): - """Returns the shard dimension of the policy or None if unspecified.""" - return self._shard_dimension - - def set_shard_dimension(self, shard_dimension): - """Sets the shard dimension for the current policy. - - If the policy has been frozen then shard_dimension must match the - existing setting. - - Args: - shard_dimension: The shard dimension to use in the policy. - - Raises: - ValueError: If the policy has been frozen and shard_dimension - differs from the frozen value, or shard_dimension can't be - interpreted as a Dimension. - """ - if self._frozen: - if self._shard_dimension != shard_dimension: - raise ValueError( - "Can't set shard dimension to %d since it has been frozen to " - "use %d." % (shard_dimension, self._shard_dimension)) - else: - self._shard_dimension = tensor_shape.as_dimension(shard_dimension) - - def merge(self, other): - """Merges the policy of another policy into the current policy. - - Args: - other: The policy to merge into this one. - - Raises: - ValueError: If this policy has been frozen and the merge conflicts with - the frozen policy. - """ - if other.number_of_shards is not None: - self.set_number_of_shards(other.number_of_shards) - if other.shard_dimension is not None: - self.set_shard_dimension(other.shard_dimension) - - def get_sharded_shape(self, shape, shard_index=None): - """Returns the shape of a shard of a full Tensor. - - When given the shape of a 'full-size' Tensor, returns the shape of - the sub-Tensor after it has been sharded. Freezes the policy if it - has not yet been frozen. - - Args: - shape: The shape of the full-size Tensor to be sharded. - shard_index: The index of the shard whose shape should be returned. - shard_index can be None for sharding policies that use the same - shape for every shard. - freeze_config: - - Returns: - The shape of the sharded version of the Tensor. - - Raises: - ValueError: If shard_index is None when shards are of different - shapes; or shard_index is not None and - !(0<=shard_index= self.number_of_shards: - raise ValueError("shard_index %d, but must be in [0,%d)." % - (shard_index, self._number_of_shards)) - shape = tensor_shape.as_shape(shape) - if self._number_of_shards == 1: - # Don't do anything when there's only one shard. - return shape - ndims = shape.ndims - if ndims is None: - raise ValueError("shape must be a specified shape not Unknown") - if ndims <= self._shard_dimension: - raise ValueError("shape %s does not contain shard_dimension %d" % - (shape.as_list(), self._shard_dimension)) - dims = shape.as_list() - if dims[self._shard_dimension] is None: - raise ValueError("shape %s must have a fixed size for dimension %d " - "that is known at graph construction time." % - (shape.as_list(), self._shard_dimension)) - if (dims[self._shard_dimension] % self._number_of_shards) != 0: - raise ValueError("shape %s cannot be sharded %d ways along dimension %d" % - (shape.as_list(), self._number_of_shards, - self._shard_dimension)) - dims[self._shard_dimension] /= self._number_of_shards - return tensor_shape.as_shape(dims) - - def _unshard_shape(self, shape): - """Return the unsharded shape that would generate a given sharded shape. - - Args: - shape: the sharded shape to unshard - - Returns: - The unsharded shape. - - Raises: - ValueError: if shape is unknown or does not contain - self.shard_dimension - TypeError: if shape is not convertible to a TensorShape - """ - shape = tensor_shape.as_shape(shape) - if self._number_of_shards == 1: - # Don't do anything when there's only one shard. - return shape - ndims = shape.ndims - if ndims is None: - raise ValueError("shape must be a specified shape not Unknown") - if ndims <= self._shard_dimension: - raise ValueError("shape %s does not contain shard_dimension %d" % - (shape.as_list(), self._shard_dimension)) - dims = shape.as_list() - dims[self._shard_dimension] *= self._number_of_shards - return tensor_shape.as_shape(dims) - - def get_unsharded_shape(self, shapes): - """Returns the shape of an unsharded Tensor given a list of shards. - - When given a list of shapes of shards, returns the shape of the - unsharded Tensor that would generate the shards. Sets defaults for the - policy if number_of_shards or shard_dimension is None. - - Args: - shapes: The shapes of the Tensor shards to be combined. - - Returns: - The shape of the unsharded version of the Tensor. - - Raises: - ValueError: if shapes is not a list of length - self.number_of_shards; or any element of shapes is not a valid - shape consistent with the sharding policy; or the list of - shapes is not a valid sharding of a full shape. - TypeError: if an element of shapes is not convertible to a - TensorShape - """ - self._fill_default_values() - if len(shapes) != self.number_of_shards: - raise ValueError( - "shapes is %s but must be a list of length number_of_shards=%d" % ( - str(shapes), self.number_of_shards)) - unsharded_shapes = [self._unshard_shape(s) for s in shapes] - for i in xrange(self.number_of_shards - 1): - if not unsharded_shapes[i].is_compatible_with( - unsharded_shapes[self.number_of_shards - 1]): - raise ValueError( - "sharded shapes %s are not consistent shards of a full shape " - "sharded %d ways along dimension %d" % ( - str(shapes), self.number_of_shards, self.shard_dimension)) - return unsharded_shapes[0] +# pylint: disable=wildcard-import,unused-import,redefined-builtin +from tensorflow.python.tpu.tpu_sharding import * +# pylint: enable=wildcard-import,unused-import,redefined-builtin diff --git a/tensorflow/contrib/tpu/python/tpu/tpu_system_metadata.py b/tensorflow/contrib/tpu/python/tpu/tpu_system_metadata.py index d66ecfcf4a..258d34ddaf 100644 --- a/tensorflow/contrib/tpu/python/tpu/tpu_system_metadata.py +++ b/tensorflow/contrib/tpu/python/tpu/tpu_system_metadata.py @@ -1,156 +1,25 @@ -# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 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 +# 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. -# =================================================================== -"""TPU system metadata and associated tooling.""" +# ============================================================================== +"""Stub file to maintain backwards compatibility.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function -import collections -import re - -from tensorflow.contrib.tpu.python.tpu import tpu -from tensorflow.core.protobuf import config_pb2 -from tensorflow.python.client import session as session_lib -from tensorflow.python.framework import errors -from tensorflow.python.framework import ops -from tensorflow.python.platform import tf_logging as logging - -_PINGING_MASTER_TIMEOUT_IN_MS = 60 * 1000 # 1 min -_RETRY_TIMES = 120 -_INITIAL_TPU_SYSTEM_TIMEOUT_IN_MS = 300 * 1000 # 5 mins - -_TPU_DEVICE_REG = re.compile(r'.*task:(\d+)/.*device:TPU:(\d+)$') - -# _TPUSystemMetadata is used by TPUEstimator to hold TPU configuration, -# including num_cores and num_hosts. -_TPUSystemMetadata = collections.namedtuple('_TPUSystemMetadata', [ - 'num_cores', - 'num_hosts', - 'num_of_cores_per_host', - 'topology', - 'devices', -]) - - -def _query_tpu_system_metadata(master_address, cluster_def=None, - query_topology=False): - """Automatically detects the TPU system metadata in the system.""" - tpu_core_count = 0 - devices = [] - device_dict = collections.defaultdict(list) - - # TODO(b/120564445): Replace with standard library for retries. - retry_count = 1 - while True: - logging.info('Querying Tensorflow master (%s) for TPU system metadata.', - master_address) - try: - with ops.Graph().as_default(): - with session_lib.Session( - master_address, - config=get_session_config_with_timeout( - _PINGING_MASTER_TIMEOUT_IN_MS, - cluster_def)) as sess: - devices = sess.list_devices() - for device in devices: - match = _TPU_DEVICE_REG.match(device.name) - if match: - host_id = match.group(1) - core_id = match.group(2) - device_dict[host_id].append(core_id) - tpu_core_count += 1 - break - except errors.DeadlineExceededError: - 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 - # here. - if retry_count <= _RETRY_TIMES: - logging.warning('%s', msg) - logging.warning('Retrying (%d/%d).', retry_count, _RETRY_TIMES) - retry_count += 1 - else: - raise ValueError(msg) - - num_of_cores_per_host = 0 - if tpu_core_count: - num_cores_per_host_set = set( - [len(core_ids) for core_ids in device_dict.values()]) - if len(num_cores_per_host_set) != 1: - raise RuntimeError( - 'TPU cores on each host is not same. This should not happen!. ' - 'devices: {}'.format(devices)) - num_of_cores_per_host = num_cores_per_host_set.pop() - - topology = None - if query_topology: - if not tpu_core_count: - raise RuntimeError( - 'Cannot find any TPU cores in the system (master address {}). ' - 'This usually means the master address is incorrect or the ' - 'TPU worker has some problems. Available devices: {}'.format( - master_address, devices)) - - topology = _obtain_topology(master_address, cluster_def) - - metadata = _TPUSystemMetadata( - num_cores=tpu_core_count, - num_hosts=len(device_dict), - num_of_cores_per_host=num_of_cores_per_host, - topology=topology, - devices=devices) - - if tpu_core_count: - logging.info('Found TPU system:') - logging.info('*** Num TPU Cores: %d', metadata.num_cores) - logging.info('*** Num TPU Workers: %d', metadata.num_hosts) - logging.info('*** Num TPU Cores Per Worker: %d', - metadata.num_of_cores_per_host) - for device in metadata.devices: - logging.info('*** Available Device: %s', device) - else: - logging.info('Failed to find TPU: %s', metadata) - return metadata - - -def _obtain_topology(master_address, cluster_def): - """Obtains TPU fabric topology.""" - try: - logging.info('Initializing TPU system (master: %s) to fetch topology ' - 'for model parallelism. This might take a while.', - master_address) - with ops.Graph().as_default(): - session_config = get_session_config_with_timeout( - _INITIAL_TPU_SYSTEM_TIMEOUT_IN_MS, cluster_def) - with session_lib.Session( - master_address, config=session_config) as sess: - topology = sess.run(tpu.initialize_system()) - return topology - except errors.DeadlineExceededError: - raise ValueError( - 'Fail to initialize TPU system with master (%s). ' - 'Please double check the TPU system is functional.' % ( - master_address)) - - -def get_session_config_with_timeout(timeout_in_secs, cluster_def): - """Returns a session given a timeout and a cluster configuration.""" - config = config_pb2.ConfigProto( - operation_timeout_in_ms=timeout_in_secs, cluster_def=cluster_def) - return config +# pylint: disable=wildcard-import,unused-import +from tensorflow.python.tpu.tpu_system_metadata import * +# used by tests +from tensorflow.python.tpu.tpu_system_metadata import _query_tpu_system_metadata +# pylint: enable=wildcard-import,unused-import diff --git a/tensorflow/contrib/tpu/python/tpu/training_loop.py b/tensorflow/contrib/tpu/python/tpu/training_loop.py index 50848e83f0..673359b232 100644 --- a/tensorflow/contrib/tpu/python/tpu/training_loop.py +++ b/tensorflow/contrib/tpu/python/tpu/training_loop.py @@ -1,223 +1,23 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 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 +# 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 constructing a training loop, suitable for TPUs.""" +# ============================================================================== +"""Stub file to maintain backwards compatibility.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib.compiler import xla -from tensorflow.contrib.tpu.python.tpu import tensor_tracer -from tensorflow.contrib.tpu.python.tpu import tpu_function - -from tensorflow.python.framework import ops -from tensorflow.python.ops import array_ops -from tensorflow.python.ops import control_flow_ops - - -def while_loop(condition, body, inputs=None, infeed_queue=None, name=None): - """Builds a training loop for TPUs. - - The set of loop-carried tensors corresponds to `inputs`. Both - `condition` and `body` take the current value of the loop-carried - tensors. 'body' additionally takes a tuple of infeed from - infeed_queue if infeed_queue is not None. `condition` must return a - single boolean value that determines whether iteration - continues. `body` must return an updated list of values for the - loop-carried tensors. - - Args: - condition: a Python function that builds the loop condition. - body: a Python function that builds the loop body. - inputs: a list of initial values passed into the training loop, or - None (equivalent to an empty list). - infeed_queue: if not None, the infeed queue from which to append a tuple - of arguments as inputs to condition. - name: (Deprecated) Does nothing. - - Returns: - The final values of the loop-carried tensors. - - Raises: - TypeError: if body or condition has the wrong signature. - """ - del name - # Converts inputs to Tensors. - inputs = [] if inputs is None else [ops.convert_to_tensor(x) for - x in inputs] - input_types = [x.dtype for x in inputs] - input_arity = len(inputs) - - body_arg_error = xla.check_function_argument_count( - body, input_arity, infeed_queue) - if body_arg_error is not None: - if infeed_queue is None: - raise TypeError( - "Supplied loop body function cannot be called with the specified " - "inputs. You specified %d inputs: %s, but the loop body needs %s" % ( - input_arity, str([i.name for i in inputs]), body_arg_error)) - else: - raise TypeError( - "Supplied loop body function cannot be called with the specified " - "inputs. You specified %d inputs: %s and %d additional inputs from " - "infeed, but the computation needs %s" % (input_arity, str( - [i.name for i in inputs]), infeed_queue.number_of_tuple_elements, - body_arg_error)) - condition_arg_error = xla.check_function_argument_count( - condition, input_arity, None) - if condition_arg_error is not None: - if infeed_queue is None: - raise TypeError( - "Supplied loop condition function cannot be called with the " - "specified inputs. You specified %d inputs: %s, but the loop " - "condition needs %s" % (input_arity, str([i.name for i in inputs]), - condition_arg_error)) - else: - raise TypeError( - "Supplied loop condition function cannot be called with the " - "specified inputs. You specified %d inputs: %s, but the loop " - "condition needs %s. Note that infeed is not passed to the loop " - "condition." % (input_arity, str([i.name for i in inputs]), - condition_arg_error)) - - def condition_wrapper(*inputs): - # Discards the dummy output added for arity-0 loops. - if input_arity == 0: - inputs = [] - return condition(*inputs) - - def body_wrapper(*inputs): - """Wrapper around `body` that handles infeed queues and control deps.""" - inputs = list(inputs) - - # Discards the dummy output added for arity-0 loops. - if input_arity == 0: - inputs = [] - - # Runs `body` with the dequeue_ops appended. - if infeed_queue: - number_of_shards = tpu_function.get_tpu_context().number_of_shards - if number_of_shards is None: - raise ValueError("Can't build training loop with infeed when there is " - "no tpu_shard_context. Are you building a loop or " - "graph directly rather than from inside tpu.rewrite, " - "tpu.batch_parallel, tpu.shard, or tpu.replicate?") - infeed_queue.set_number_of_shards(number_of_shards) - dequeue_ops = [d for d in infeed_queue.generate_dequeue_op()] - else: - dequeue_ops = [] - outputs = body(*(inputs + dequeue_ops)) - - # If the computation only returned one value, make it a tuple. - if not isinstance(outputs, (list, tuple)): - outputs = (outputs,) - - outputs = [ - o if isinstance(o, ops.Operation) else ops.convert_to_tensor(o) - for o in outputs - ] - - # Separates the returned Operations and Tensors. - output_operations = [o for o in outputs if isinstance(o, ops.Operation)] - output_tensors = [o for o in outputs - if not isinstance(o, ops.Operation)] - - if outputs != output_tensors + output_operations: - raise ValueError( - "TPU training loop body must return zero or more Tensor values " - "followed by zero or more Operations.") - - output_types = [op.dtype for op in output_tensors] - if input_types != output_types: - raise TypeError( - "Mismatch between input types and output types for training loop " - "body: {} vs {}".format(input_types, output_types)) - - # Add the dequeue operations to output_operations to ensure they are run - # by the loop, even if the programmer's loop body does not use them. - output_operations += dequeue_ops - - # Add a dummy output, if needed. - if not output_tensors: - output_tensors = array_ops.constant(0) - - if output_operations: - # TODO(phawkins): in principle this is too restrictive since it serializes - # the training loop steps. In practice it does not matter since this loop - # will be compiled by XLA. - output_tensors = control_flow_ops.tuple(output_tensors, - control_inputs=output_operations) - - if tensor_tracer.TensorTracer.is_enabled(): - num_replicas = tpu_function.get_tpu_context().number_of_shards - if num_replicas is None: - num_replicas = 1 - tt = tensor_tracer.TensorTracer() - output_tensors = tt.trace_tpu(ops.get_default_graph(), - output_tensors, None, - num_replicas) - return output_tensors - - # If the body has arity 0, add a dummy loop-carried value to which we can add - # control dependencies from any side-effecting operations. - if input_arity == 0: - inputs = [array_ops.constant(0)] - return control_flow_ops.while_loop( - condition_wrapper, body_wrapper, inputs, name="", parallel_iterations=1) - - -def repeat(n, body, inputs=None, infeed_queue=None, name=None): - """Builds a training loop that executes a fixed number of iterations. - - The set of loop-carried tensors correspond to `inputs`. - `body` must be a function that takes and returns the values of the - loop-carried tensors. - - Args: - n: the number of loop iterations - body: a Python function that builds the loop body. - inputs: a list of initial values passed into the training loop or - None (equivalent to an empty list). - infeed_queue: if not None, the infeed queue from which to append a tuple - of arguments as inputs to condition. - name: (Deprecated) Does nothing. - Returns: - The final values of the loop-carried tensors. - Raises: - ValueError: if there is a type error. - """ - def _convert_to_list(xs): - if not isinstance(xs, (list, tuple)): - return [xs] - else: - return list(xs) - - def cond(i, *args): - del args - return i < n - - def body_wrapper(i, *args): - return [i + 1] + _convert_to_list(body(*args)) - - inputs = [0] if inputs is None else [0] + _convert_to_list(inputs) - outputs = while_loop( - cond, body_wrapper, inputs=inputs, infeed_queue=infeed_queue, name=name) - outputs = _convert_to_list(outputs) - if len(outputs) == 1: - # Returns the Op rather than an empty list. - return outputs[0].op - else: - return outputs[1:] +# pylint: disable=wildcard-import,unused-import +from tensorflow.python.tpu.training_loop import * +# pylint: enable=wildcard-import,unused-import diff --git a/tensorflow/contrib/tpu/python/tpu/util.py b/tensorflow/contrib/tpu/python/tpu/util.py index dfb8ce1d18..8d9b70d46e 100644 --- a/tensorflow/contrib/tpu/python/tpu/util.py +++ b/tensorflow/contrib/tpu/python/tpu/util.py @@ -1,51 +1,23 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 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 +# 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 the functionalities.""" +# ============================================================================== +"""Stub file to maintain backwards compatibility.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function -import time -import six - -from tensorflow.python.platform import tf_logging as logging -from tensorflow.python.training import training - -def check_positive_integer(value, name): - """Checks whether `value` is a positive integer.""" - if not isinstance(value, six.integer_types): - raise TypeError('{} must be int, got {}'.format(name, type(value))) - - if value <= 0: - raise ValueError('{} must be positive, got {}'.format(name, value)) - - -# TODO(b/118302029) Remove this copy of MultiHostDatasetInitializerHook after we -# release a tensorflow_estimator with MultiHostDatasetInitializerHook in -# python/estimator/util.py. -class MultiHostDatasetInitializerHook(training.SessionRunHook): - """Creates a SessionRunHook that initializes all passed iterators.""" - - def __init__(self, dataset_initializers): - self._initializers = dataset_initializers - - def after_create_session(self, session, coord): - del coord - start = time.time() - session.run(self._initializers) - logging.info('Initialized dataset iterators in %d seconds', - time.time() - start) +# pylint: disable=wildcard-import,unused-import +from tensorflow.python.tpu.util import * +# pylint: enable=wildcard-import,unused-import diff --git a/tensorflow/python/BUILD b/tensorflow/python/BUILD index 3702e64b3c..3b2311a233 100644 --- a/tensorflow/python/BUILD +++ b/tensorflow/python/BUILD @@ -2010,6 +2010,7 @@ tf_gen_op_wrapper_private_py( visibility = [ "//smartass/brain/configure/python:__pkg__", "//tensorflow/contrib/tpu:__pkg__", + "//tensorflow/python/tpu:__pkg__", ], deps = [ "//tensorflow/core:tpu_configuration_ops_op_lib", diff --git a/tensorflow/python/tpu/BUILD b/tensorflow/python/tpu/BUILD new file mode 100644 index 0000000000..a76c620564 --- /dev/null +++ b/tensorflow/python/tpu/BUILD @@ -0,0 +1,334 @@ +# Description: Operations defined for Cloud TPUs + +load( + "//tensorflow:tensorflow.bzl", + "tf_custom_op_library", + "tf_gen_op_libs", + "tf_gen_op_wrapper_py", + "tf_py_test", +) +load("//tensorflow:tensorflow.bzl", "tf_custom_op_py_library") + +licenses(["notice"]) # Apache 2.0 + +package( + default_visibility = [ + "//cloud/vmm/testing/tests/tpu:__subpackages__", + "//knowledge/cerebra/sense/im2query:__subpackages__", + "//learning/brain:__subpackages__", + "//learning/deepmind:__subpackages__", + "//medical/pathology:__subpackages__", + "//tensorflow:__subpackages__", + "//vr/perception:__subpackages__", + ], +) + +py_library( + name = "tpu_py", + srcs = ["ops/tpu_ops.py"], + srcs_version = "PY2AND3", + deps = [ + "//tensorflow/python:framework_for_generated_wrappers", + "//tensorflow/python:tpu_ops_gen", + ], +) + +py_library( + name = "async_checkpoint", + srcs = ["async_checkpoint.py"], + srcs_version = "PY2AND3", + deps = [ + "//tensorflow/python:array_ops", + "//tensorflow/python:control_flow_ops", + "//tensorflow/python:framework_for_generated_wrappers", + "//tensorflow/python:init_ops", + "//tensorflow/python:math_ops", + "//tensorflow/python:platform", + "//tensorflow/python:state_ops", + "//tensorflow/python:summary", + "//tensorflow/python:summary_ops_v2", + "//tensorflow/python:training", + "//tensorflow/python:variable_scope", + "//tensorflow/python:variables", + "//tensorflow/python/estimator:estimator_py", + ], +) + +py_library( + name = "tpu_estimator", + srcs = [ + "_tpu_estimator_embedding.py", + "error_handling.py", + "tpu_config.py", + "tpu_context.py", + "tpu_estimator.py", + "util.py", + ], + srcs_version = "PY2AND3", + deps = [ + ":async_checkpoint", + ":feature_column", + ":functional", + ":tpu_embedding", + ":tpu_lib", + "//tensorflow/core:protos_all_py", + "//tensorflow/python:array_ops", + "//tensorflow/python:control_flow_ops", + "//tensorflow/python:framework_for_generated_wrappers", + "//tensorflow/python:function", + "//tensorflow/python:init_ops", + "//tensorflow/python:math_ops", + "//tensorflow/python:platform", + "//tensorflow/python:session", + "//tensorflow/python:state_ops", + "//tensorflow/python:summary", + "//tensorflow/python:summary_ops_v2", + "//tensorflow/python:training", + "//tensorflow/python:variable_scope", + "//tensorflow/python:variables", + "//tensorflow/python/estimator:estimator_py", + "//tensorflow/python/estimator:util", + "@six_archive//:six", + ], +) + +py_library( + name = "functional", + srcs = ["functional.py"], + srcs_version = "PY2AND3", + visibility = [ + "//visibility:public", + ], + deps = [ + "//tensorflow/python:tpu_ops_gen", + ], +) + +py_library( + name = "tpu", + srcs = [ + "__init__.py", + ], + srcs_version = "PY2AND3", + deps = [ + ":feature_column", + ":tpu_embedding", + ":tpu_estimator", + ":tpu_lib", + ], +) + +py_library( + name = "tpu_lib", + srcs = [ + "__init__.py", + "bfloat16.py", + "device_assignment.py", + "session_support.py", + "tensor_tracer.py", + "topology.py", + "tpu.py", + "tpu_feed.py", + "tpu_function.py", + "tpu_optimizer.py", + "tpu_sharding.py", + "tpu_system_metadata.py", + "training_loop.py", + "xla.py", + ], + srcs_version = "PY2AND3", + deps = [ + ":datasets", + ":functional", + ":tpu_py", + "//tensorflow/compiler/xla/experimental/xla_sharding", + "//tensorflow/compiler/xla/python_api:xla_shape", + "//tensorflow/core:protos_all_py", + "//tensorflow/core/protobuf/tpu:compilation_result_proto_py", + "//tensorflow/core/protobuf/tpu:dynamic_padding_proto_py", + "//tensorflow/core/protobuf/tpu:optimization_parameters_proto_py", + "//tensorflow/core/protobuf/tpu:topology_proto_py", + "//tensorflow/core/protobuf/tpu:tpu_embedding_configuration_proto_py", + "//tensorflow/core/protobuf/tpu:tpu_embedding_output_layout_proto_py", + "//tensorflow/python:array_ops", + "//tensorflow/python:control_flow_ops", + "//tensorflow/python:control_flow_util", + "//tensorflow/python:dtypes", + "//tensorflow/python:framework", + "//tensorflow/python:framework_ops", + "//tensorflow/python:tensor_shape", + "//tensorflow/python:tpu_ops_gen", + "//tensorflow/python:training", + "//tensorflow/python:util", + "//tensorflow/python:variable_scope", + "//tensorflow/python/ops/losses", + "//tensorflow/python/tpu/profiler", + ], +) + +py_library( + name = "datasets", + srcs = [ + "datasets.py", + ], + srcs_version = "PY2AND3", + deps = [ + "//tensorflow/python:dtypes", + "//tensorflow/python:function", + "//tensorflow/python:functional_ops", + "//tensorflow/python/data/ops:dataset_ops", + "//tensorflow/python/data/ops:iterator_ops", + "//tensorflow/python/data/ops:readers", + ], +) + +tf_py_test( + name = "datasets_test", + size = "medium", + srcs = ["datasets_test.py"], + additional_deps = [ + "//tensorflow/python:client_testlib", + ":datasets", + ], + grpc_enabled = True, + shard_count = 4, + tags = ["no_oss"], +) + +tf_py_test( + name = "tpu_test", + size = "small", + srcs = ["tpu_test.py"], + additional_deps = [ + ":tpu", + "//tensorflow/python:client_testlib", + "//tensorflow/python:dtypes", + "//tensorflow/python:framework", + "//tensorflow/python:layers", + ], + tags = ["no_windows"], # TODO: needs investigation on Windows +) + +tf_py_test( + name = "tpu_sharding_test", + size = "small", + srcs = ["tpu_sharding_test.py"], + additional_deps = [ + ":tpu", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework", + ], +) + +tf_py_test( + name = "bfloat16_test", + size = "small", + srcs = ["bfloat16_test.py"], + additional_deps = [ + ":tpu", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework", + ], +) + +tf_py_test( + name = "tpu_infeed_test", + size = "small", + srcs = ["tpu_infeed_test.py"], + additional_deps = [ + ":tpu", + "//tensorflow/python:framework", + "//tensorflow/python:framework_test_lib", + ], +) + +tf_py_test( + name = "tpu_config_test", + size = "small", + srcs = ["tpu_config_test.py"], + additional_deps = [ + ":tpu_estimator", + "//tensorflow/python:framework", + "//tensorflow/python:framework_test_lib", + ], +) + +tf_py_test( + name = "tpu_estimator_signals_test", + size = "small", + srcs = ["tpu_estimator_signals_test.py"], + additional_deps = [ + ":tpu_estimator", + "//tensorflow/python:framework", + "//tensorflow/python:framework_test_lib", + ], + # TODO(jhseu): Remove. Fails in OSS on Python 3. + tags = ["no_oss"], +) + +tf_py_test( + name = "topology_test", + size = "medium", + srcs = ["topology_test.py"], + additional_deps = [ + ":tpu", + "//tensorflow/python:framework_test_lib", + ], +) + +py_library( + name = "tpu_embedding", + srcs = [ + "tpu_embedding.py", + "tpu_embedding_gradient.py", + ], + srcs_version = "PY2AND3", + deps = [ + ":tpu_lib", + "//tensorflow/core/protobuf/tpu:tpu_embedding_configuration_proto_py", + "//tensorflow/python:array_ops", + "//tensorflow/python:framework_for_generated_wrappers", + "//tensorflow/python:init_ops", + "//tensorflow/python:math_ops", + "//tensorflow/python:partitioned_variables", + "//tensorflow/python:tpu_ops_gen", + "//tensorflow/python:variable_scope", + "//tensorflow/python:variables", + "@six_archive//:six", + ], +) + +py_library( + name = "feature_column", + srcs = ["feature_column.py"], + deps = [ + ":tpu_lib", + "//tensorflow/python:framework_ops", + "//tensorflow/python:init_ops", + "//tensorflow/python:variable_scope", + "//tensorflow/python/feature_column", + "//tensorflow/python/feature_column:feature_column_py", + ], +) + +tf_py_test( + name = "feature_column_test", + srcs = [ + "feature_column_test.py", + ], + additional_deps = [ + ":feature_column", + "//third_party/py/numpy", + "//tensorflow/python:client_testlib", + "//tensorflow/python:dtypes", + "//tensorflow/python:framework_ops", + "//tensorflow/python:lookup_ops", + "//tensorflow/python:parsing_ops", + "//tensorflow/python:session", + "//tensorflow/python:sparse_tensor", + "//tensorflow/python:variables", + "//tensorflow/python/feature_column", + "//tensorflow/python/feature_column:feature_column_py", + ], + main = "feature_column_test.py", +) diff --git a/tensorflow/python/tpu/__init__.py b/tensorflow/python/tpu/__init__.py new file mode 100644 index 0000000000..0dffd7064b --- /dev/null +++ b/tensorflow/python/tpu/__init__.py @@ -0,0 +1,20 @@ +# 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. +# ============================================================================= + +"""Ops related to Tensor Processing Units.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function diff --git a/tensorflow/python/tpu/_tpu_estimator_embedding.py b/tensorflow/python/tpu/_tpu_estimator_embedding.py new file mode 100644 index 0000000000..08e0e968a6 --- /dev/null +++ b/tensorflow/python/tpu/_tpu_estimator_embedding.py @@ -0,0 +1,334 @@ +# 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. +# =================================================================== +"""Tooling for support TPU embedding in TPUEstimator.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import collections + +from tensorflow.python.estimator import model_fn as model_fn_lib +from tensorflow.python.feature_column import feature_column as core_fc +from tensorflow.python.feature_column import feature_column_lib as core_fc_lib +from tensorflow.python.tpu import feature_column as tpu_fc +from tensorflow.python.tpu import tpu_embedding + +# pylint: disable=protected-access +_TPU_EMBEDDING_COLUMN_CLASSES = (tpu_fc._TPUEmbeddingColumn, + tpu_fc._TPUSharedEmbeddingColumn) +_EMBEDDING_COLUMN_CLASSES = (core_fc._EmbeddingColumn, + core_fc_lib.EmbeddingColumn, + core_fc._SharedEmbeddingColumn) +_SUPPORTED_FEATURE_COLUMNS = (core_fc._NumericColumn, core_fc_lib.NumericColumn) + +# pylint: enable=protected-access + +_TABLE_NAME_PREFIX = 'tbl_' +_LEN_TABLE_NAME_PREFIX = len(_TABLE_NAME_PREFIX) + + +def _get_table_name_from_embedding_var_name(embedding_var_name): + return '{}{}'.format(_TABLE_NAME_PREFIX, embedding_var_name) + + +def _get_embedding_var_name_from_table_name(table_name): + return table_name[_LEN_TABLE_NAME_PREFIX:] + + +def _get_embedding_variable_name(scope_name, var_name): + return '{}/{}'.format(scope_name, var_name) + + +def _get_slot_variable_names(scope_name, var_name, optimization_parameters): + """Return embedding variable names which are consistent with CPU runs.""" + if isinstance(optimization_parameters, tpu_embedding.AdagradParameters): + return tpu_embedding.AdagradSlotVariableName( + '{}/{}/Adagrad'.format(scope_name, var_name) + ) + elif isinstance(optimization_parameters, tpu_embedding.AdamParameters): + return tpu_embedding.AdamSlotVariableNames( + '{}/{}/Adam/m'.format(scope_name, var_name), + '{}/{}/Adam/v'.format(scope_name, var_name) + ) + elif isinstance(optimization_parameters, + tpu_embedding.StochasticGradientDescentParameters): + return None + else: + raise ValueError('Support to infer full variable name ' + 'for optimization_parameter {} has not been added.' + .format(optimization_parameters)) + + +def get_full_variable_names( + graph, table_to_config_dict, optimization_parameters): + """Return embedding variable names and slot variables which are consistent with CPU runs.""" + collection = graph.get_collection_ref(tpu_fc._TPU_FC_TO_SCOPE) # pylint: disable=protected-access + if not collection: + raise RuntimeError( + 'Embedding feature column did not capture any thing. Make sure the ' + 'feature columns passed to TPUEstimator constructor is properly ' + 'used in model_fn.') + + embedding_variable_name_by_table = {} + slot_variable_names_by_table = {} + for table_name in table_to_config_dict: + embedding_var_name = _get_embedding_var_name_from_table_name(table_name) + (scope_name, var_name) = collection[0][embedding_var_name] + embedding_variable_name_by_table[table_name] = ( + _get_embedding_variable_name(scope_name, var_name)) + slot_variable_names_by_table[table_name] = _get_slot_variable_names( + scope_name, var_name, optimization_parameters) + + graph.clear_collection(tpu_fc._TPU_FC_TO_SCOPE) # pylint: disable=protected-access + return embedding_variable_name_by_table, slot_variable_names_by_table + + +def get_tpu_embedding_config_from_feature_columns(feature_columns): + """Create configs for TPUEmbedding from a list of feature columns. + + This function will place one embedding tensor per table and the return is + intended to be used as input to TPUEmbedding. + + Args: + feature_columns: a list of supported feature columns. + + Returns: + A pair of dicts, the first maps tables to their config, the second maps + features to tables. + """ + + allowed = (tpu_fc._TPUEmbeddingColumn, tpu_fc._TPUSharedEmbeddingColumn) # pylint: disable=protected-access + + for column in feature_columns: + if not isinstance(column, allowed): + raise TypeError( + 'Unsupported feature column {}. Supported types are {}.'.format( + type(column), allowed)) + + table_to_config = {} + feature_to_table = {} + for column in feature_columns: + feature_name = column.get_feature_key_name() + table_name = _get_table_name_from_embedding_var_name( + column.get_embedding_var_name()) + if feature_name in feature_to_table: + raise ValueError( + 'Feature column {} is used with multiple embeddings and this is ' + 'not supported.'.format(feature_name)) + feature_to_table[feature_name] = table_name + vocabulary_size, dimension = column.get_embedding_table_size() + table_to_config[table_name] = tpu_embedding.TableConfig( + vocabulary_size=vocabulary_size, + dimension=dimension, + initializer=column.get_initializer(), + combiner=column.get_combiner()) + + return table_to_config, feature_to_table + + +def _get_tpu_embedding_optimization_parameters(embedding_config_spec): + """Get tpu_embedding._OptimizationParameters from EmbeddingConfigSpec.""" + if embedding_config_spec.optimizer_type == 'adagrad': + return tpu_embedding.AdagradParameters( + embedding_config_spec.learning_rate, + embedding_config_spec.adagrad_initial_accumulator, + embedding_config_spec.use_gradient_accumulation) + elif embedding_config_spec.optimizer_type == 'sgd': + return tpu_embedding.StochasticGradientDescentParameters( + embedding_config_spec.learning_rate, + embedding_config_spec.use_gradient_accumulation) + elif embedding_config_spec.optimizer_type == 'adam': + return tpu_embedding.AdamParameters( + embedding_config_spec.learning_rate, + embedding_config_spec.adam_parameters.beta1, + embedding_config_spec.adam_parameters.beta2, + embedding_config_spec.adam_parameters.epsilon, + use_gradient_accumulation=embedding_config_spec + .use_gradient_accumulation) + else: + raise ValueError('optimizer_type must be adagrad or sgd or adam for now.') + + +AdamParameters = collections.namedtuple('AdamParameters', + ['beta1', 'beta2', 'epsilon']) + + +# TODO(shizhiw): Improve the API to support more optimizer parameters in API. +class EmbeddingConfigSpec( + collections.namedtuple('EmbeddingConfigSpec', [ + 'feature_columns', 'learning_rate', 'optimizer_type', + 'adagrad_initial_accumulator', 'clipping_limit', + 'use_gradient_accumulation', 'adam_parameters' + ])): + """Class to keep track of embedding config specification.""" + + def __new__(cls, + feature_columns, + learning_rate, + optimizer_type='adagrad', + adagrad_initial_accumulator=None, + clipping_limit=None, + use_gradient_accumulation=False, + adam_parameters=None): + """Creates an EmbeddingConfigSpec instance. + + Args: + feature_columns: All `FeatureColumn`s used by model. + learning_rate: embedding optimizer learning rate. + optimizer_type: (String) Name of the optimizer for embedding gradients + updates. Must be either 'adagrad' ( `tf.train.AdagradOptimizer`, default + value), 'sgd' (`tf.train.GradientDescentOptimizer`), or 'adam' + (`tf.contrib.opt.LazyAdamOptimizer`) for lazy Adam. This optimizer will + be applied to all embedding variables specified by `feature_columns`. + adagrad_initial_accumulator: Initial accumulator for Adagrad. Used when + optimizer_type is 'adagrad'. Default is `0.1`. + clipping_limit: (Optional) Clipping limit (absolute value). + use_gradient_accumulation: (Experimental) Whether to accumulate the + gradients across TPU embedding mini-batches. Gradient accumulation does + not affect SGD and therefore this is applicable only for Adagrad. + adam_parameters: AdamParameters. Used when optimizer_type is 'adam'. + Default is 0.9 for beta1, 0.999 for beta2 and 1e-8 for epsilon. + + Returns: + An EmbeddingConfigSpec instance. + + Raises: + ValueError: If the feature_columns are not specified. + TypeError: If the feature columns are not of ths correct type (one of + _SUPPORTED_FEATURE_COLUMNS, _TPU_EMBEDDING_COLUMN_CLASSES OR + _EMBEDDING_COLUMN_CLASSES). + ValueError: If use_gradient_accumulation is True for SGD. + ValueError: If `optimizer_type` is not one of "adagrad" or "sgd" or + "adam". + """ + if not feature_columns: + raise ValueError('`feature_columns` cannot be `None` or empty.') + + # It is unknown at this moment, whether the TPUEstimator is running in CPU + # or TPU mode. So allow non-TPU embedding columns also. + supported_classes = tuple( + list(_SUPPORTED_FEATURE_COLUMNS) + list(_TPU_EMBEDDING_COLUMN_CLASSES) + + list(_EMBEDDING_COLUMN_CLASSES)) + + for column in feature_columns: + if not isinstance(column, supported_classes): + raise TypeError( + 'All feature columns must be supported types in {}. Got {}'.format( + supported_classes, type(column))) + + if optimizer_type == 'adagrad': + if adagrad_initial_accumulator is None: + adagrad_initial_accumulator = 0.1 + if adagrad_initial_accumulator <= 0: + raise ValueError('Adagrad initial_accumulator must be positive') + elif optimizer_type == 'sgd': + if use_gradient_accumulation: + raise ValueError('Gradient accumulation makes sense for Adagrad only.') + elif optimizer_type == 'adam': + if adam_parameters is None: + adam_parameters = AdamParameters(0.9, 0.999, 1e-8) + if adam_parameters.beta1 < 0. or adam_parameters.beta1 >= 1.: + raise ValueError('beta1 must be between 0. and 1; got {}.'.format( + adam_parameters.beta1)) + if adam_parameters.beta2 < 0. or adam_parameters.beta2 >= 1.: + raise ValueError('beta2 must be between 0. and 1; got {}.'.format( + adam_parameters.beta2)) + if adam_parameters.epsilon <= 0.: + raise ValueError('epsilon must be positive; got {}.'.format( + adam_parameters.epsilon)) + else: + raise ValueError('optimizer_type must be adagrad or sgd or adam for now.') + + return super(EmbeddingConfigSpec, cls).__new__( + cls, + feature_columns=feature_columns, + learning_rate=learning_rate, + optimizer_type=optimizer_type, + adagrad_initial_accumulator=adagrad_initial_accumulator, + clipping_limit=clipping_limit, + use_gradient_accumulation=use_gradient_accumulation, + adam_parameters=adam_parameters) + + +class EmbeddingConfig(object): + """This is the internal immutable object for embedding config. + + `_EmbeddingConfig` is responsible to _translate_ user provided + `EmbeddingConfigSpec` to internal data structures, mostly constructor + arguments of `TPUEmbedding`. + """ + + def __init__(self, embedding_config_spec, train_batch_size, eval_batch_size, + num_hosts, num_cores, master): + self._embedding_config_spec = embedding_config_spec + self._train_batch_size = train_batch_size + self._eval_batch_size = eval_batch_size + self._num_hosts = num_hosts + self._num_cores = num_cores + self._master = master + + self._table_to_config_dict, self._feature_to_table_dict = ( + get_tpu_embedding_config_from_feature_columns( + embedding_config_spec.feature_columns)) + self._optimization_parameters = _get_tpu_embedding_optimization_parameters( + self._embedding_config_spec) + self._mode_to_tpu_embedding_dict = {} + self.dummy_table_variables = None + + def has_embedding_tables(self): + return bool(self._table_to_config_dict) + + def _create_tpu_embedding(self, mode): + """Create tpu_embedding.TPUEmbedding based on mode.""" + if mode == model_fn_lib.ModeKeys.TRAIN: + batch_size = self._train_batch_size + else: + batch_size = self._eval_batch_size + + if mode == model_fn_lib.ModeKeys.TRAIN: + tpu_embedding_mode = tpu_embedding.TRAINING + elif (mode == model_fn_lib.ModeKeys.EVAL or + mode == model_fn_lib.ModeKeys.PREDICT): + tpu_embedding_mode = tpu_embedding.INFERENCE + else: + raise ValueError('Mode {} is not supported.'.format(mode)) + + tpu_embedding_ = tpu_embedding.TPUEmbedding( + self._table_to_config_dict, + self._feature_to_table_dict, + batch_size, + tpu_embedding_mode, + self._master, + self._optimization_parameters, + ) + return tpu_embedding_ + + def get_tpu_embedding(self, mode): + if mode not in self._mode_to_tpu_embedding_dict: + self._mode_to_tpu_embedding_dict[mode] = ( + self._create_tpu_embedding(mode)) + return self._mode_to_tpu_embedding_dict[mode] + + +def split_inputs(ctx, features, labels): + """Splits the dense and sparse tensors inside the features and labels.""" + sparse_features = collections.OrderedDict() + if ctx.embedding_config: + tpu_embedding_ = ctx.embedding_config.tpu_embedding + for feature_key in tpu_embedding_.feature_to_table_dict: + sparse_features[feature_key] = features.pop(feature_key) + + return features, labels, sparse_features diff --git a/tensorflow/python/tpu/async_checkpoint.py b/tensorflow/python/tpu/async_checkpoint.py new file mode 100644 index 0000000000..1b09ce173a --- /dev/null +++ b/tensorflow/python/tpu/async_checkpoint.py @@ -0,0 +1,212 @@ +# 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. +# ====================================== +"""Hook for asynchronous checkpointing. + +This hook dispatches checkpoint writing operations in a separate thread to +allow execution to continue on the main thread. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import threading +import time + +from tensorflow.core.util.event_pb2 import SessionLog +from tensorflow.python.framework import meta_graph +from tensorflow.python.framework import ops +from tensorflow.python.platform import tf_logging as logging +from tensorflow.python.training import basic_session_run_hooks +from tensorflow.python.training import training_util +from tensorflow.python.training.session_run_hook import SessionRunArgs +from tensorflow.python.training.summary_io import SummaryWriterCache + + +class AsyncCheckpointSaverHook(basic_session_run_hooks.CheckpointSaverHook): + """Saves checkpoints every N steps or seconds.""" + + def __init__(self, + checkpoint_dir, + save_secs=None, + save_steps=None, + saver=None, + checkpoint_basename="model.ckpt", + scaffold=None, + listeners=None): + """Initializes a `CheckpointSaverHook`. + + Args: + checkpoint_dir: `str`, base directory for the checkpoint files. + save_secs: `int`, save every N secs. + save_steps: `int`, save every N steps. + saver: `Saver` object, used for saving. + checkpoint_basename: `str`, base name for the checkpoint files. + scaffold: `Scaffold`, use to get saver object. + listeners: List of `CheckpointSaverListener` subclass instances. Used for + callbacks that run immediately before or after this hook saves the + checkpoint. + + Raises: + ValueError: One of `save_steps` or `save_secs` should be set. + ValueError: At most one of `saver` or `scaffold` should be set. + """ + logging.info("Create AsyncCheckpointSaverHook.") + if saver is not None and scaffold is not None: + raise ValueError("You cannot provide both saver and scaffold.") + self._saver = saver + self._save_thread = None + self._write_graph_thread = None + self._checkpoint_dir = checkpoint_dir + self._save_path = os.path.join(checkpoint_dir, checkpoint_basename) + self._scaffold = scaffold + self._timer = basic_session_run_hooks.SecondOrStepTimer( + every_secs=save_secs, every_steps=save_steps) + self._listeners = listeners or [] + self._steps_per_run = 1 + self._summary_writer = None + self._global_step_tensor = None + + self._last_checkpoint_step = None + + def _set_steps_per_run(self, steps_per_run): + self._steps_per_run = steps_per_run + + def begin(self): + self._summary_writer = SummaryWriterCache.get(self._checkpoint_dir) + self._global_step_tensor = training_util._get_or_create_global_step_read() # pylint: disable=protected-access + if self._global_step_tensor is None: + raise RuntimeError( + "Global step should be created to use CheckpointSaverHook.") + for l in self._listeners: + l.begin() + + def after_create_session(self, session, coord): + global_step = session.run(self._global_step_tensor) + + # We do write graph and saver_def at the first call of before_run. + # We cannot do this in begin, since we let other hooks to change graph and + # add variables in begin. Graph is finalized after all begin calls. + def _write_graph_fn(self): + training_util.write_graph( + ops.get_default_graph().as_graph_def(add_shapes=True), + self._checkpoint_dir, "graph.pbtxt") + self._write_graph_thread = threading.Thread(target=_write_graph_fn, + args=[self]) + self._write_graph_thread.start() + + saver_def = self._get_saver().saver_def if self._get_saver() else None + graph = ops.get_default_graph() + meta_graph_def = meta_graph.create_meta_graph_def( + graph_def=graph.as_graph_def(add_shapes=True), saver_def=saver_def) + self._summary_writer.add_graph(graph) + self._summary_writer.add_meta_graph(meta_graph_def) + # The checkpoint saved here is the state at step "global_step". + self._save(session, global_step) + self._timer.update_last_triggered_step(global_step) + + def before_run(self, run_context): # pylint: disable=unused-argument + return SessionRunArgs(self._global_step_tensor) + + def after_run(self, run_context, run_values): + global_step = run_context.session.run(self._global_step_tensor) + if self._timer.should_trigger_for_step(global_step): + self._timer.update_last_triggered_step(global_step) + logging.info("Triggering checkpoint. %s", global_step) + if self._save(run_context.session, global_step): + run_context.request_stop() + + def end(self, session): + if self._save_thread: + logging.info("Waiting for any pending checkpoints to finish.") + self._save_thread.join() + if self._write_graph_thread: + logging.info("Waiting for any pending write_graph to finish.") + self._write_graph_thread.join() + + last_step = session.run(self._global_step_tensor) + + if self._last_checkpoint_step != last_step: + self._save(session, last_step, asynchronous=False) + + for l in self._listeners: + l.end(session, last_step) + + def _save(self, session, step, asynchronous=True): + """Saves the latest checkpoint, returns should_stop.""" + + # Skip saving on step 0 + if step == 0: + return + + def _save_fn(): + """Run the saver process.""" + logging.info("Saving checkpoints for %d into %s.", step, self._save_path) + + start_time = time.time() + for l in self._listeners: + l.before_save(session, step) + + self._get_saver().save(session, self._save_path, global_step=step) + self._summary_writer.add_session_log( + SessionLog( + status=SessionLog.CHECKPOINT, checkpoint_path=self._save_path), + step) + + for l in self._listeners: + l.after_save(session, step) + + end_time = time.time() + logging.info("Checkpoint actual writing time: (%.3f sec)", + end_time - start_time) + logging.info("Checkpoint finished for %d into %s.", step, self._save_path) + + if not asynchronous: + self._last_checkpoint_step = step + _save_fn() + return + + if self._save_thread is not None: + self._save_thread.join(timeout=0.1) + if self._save_thread.is_alive(): + logging.info("Saver thread still in progress, skipping checkpoint.") + return + + self._last_checkpoint_step = step + self._save_thread = threading.Thread(target=_save_fn) + self._save_thread.start() + + def _get_saver(self): + if self._saver is not None: + return self._saver + elif self._scaffold is not None: + return self._scaffold.saver + + # Get saver from the SAVERS collection if present. + collection_key = ops.GraphKeys.SAVERS + savers = ops.get_collection(collection_key) + if not savers: + raise RuntimeError( + "No items in collection {}. Please add a saver to the collection " + "or provide a saver or scaffold.".format(collection_key)) + elif len(savers) > 1: + raise RuntimeError( + "More than one item in collection {}. " + "Please indicate which one to use by passing it to the constructor." + .format(collection_key)) + + self._saver = savers[0] + return savers[0] diff --git a/tensorflow/python/tpu/bfloat16.py b/tensorflow/python/tpu/bfloat16.py new file mode 100644 index 0000000000..fa74f651aa --- /dev/null +++ b/tensorflow/python/tpu/bfloat16.py @@ -0,0 +1,77 @@ +# 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. +# ============================================================================= + +"""Helper context for running models with bfloat16.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.python.framework import dtypes +from tensorflow.python.ops import math_ops +from tensorflow.python.ops import variable_scope +from tensorflow.python.util import tf_contextlib + + +def _get_custom_getter(): + """Returns a custom getter that this class's methods must be called under. + + All methods of this class must be called under a variable scope that was + passed this custom getter. Example: + + ```python + network = ConvNetBuilder(...) + with tf.variable_scope('cg', custom_getter=network.get_custom_getter()): + network.conv(...) + # Call more methods of network here + ``` + + Currently, this custom getter only does anything if self.use_tf_layers is + True. In that case, it causes variables to be stored as dtype + self.variable_type, then casted to the requested dtype, instead of directly + storing the variable as the requested dtype. + """ + + def inner_custom_getter(getter, *args, **kwargs): + """Custom getter that forces variables to have type self.variable_type.""" + cast_to_bfloat16 = False + requested_dtype = kwargs['dtype'] + if requested_dtype == dtypes.bfloat16: + # Only change the variable dtype if doing so does not decrease variable + # precision. + kwargs['dtype'] = dtypes.float32 + cast_to_bfloat16 = True + var = getter(*args, **kwargs) + # This if statement is needed to guard the cast, because batch norm + # assigns directly to the return value of this custom getter. The cast + # makes the return value not a variable so it cannot be assigned. Batch + # norm variables are always in fp32 so this if statement is never + # triggered for them. + if cast_to_bfloat16: + var = math_ops.cast(var, dtypes.bfloat16) + return var + + return inner_custom_getter + + +@tf_contextlib.contextmanager +def bfloat16_scope(): + """Scope class for bfloat16 variables so that the model uses custom getter. + + This enables variables to be read as bfloat16 type when using get_variable. + """ + with variable_scope.variable_scope( + '', custom_getter=_get_custom_getter()) as varscope: + yield varscope diff --git a/tensorflow/contrib/tpu/python/tpu/bfloat16_test.py b/tensorflow/python/tpu/bfloat16_test.py similarity index 96% rename from tensorflow/contrib/tpu/python/tpu/bfloat16_test.py rename to tensorflow/python/tpu/bfloat16_test.py index 26fd376827..3308e01700 100644 --- a/tensorflow/contrib/tpu/python/tpu/bfloat16_test.py +++ b/tensorflow/python/tpu/bfloat16_test.py @@ -19,11 +19,10 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib.tpu.python.tpu import bfloat16 from tensorflow.python.framework import dtypes from tensorflow.python.ops import variable_scope - from tensorflow.python.platform import test +from tensorflow.python.tpu import bfloat16 class BFloat16ScopeTest(test.TestCase): diff --git a/tensorflow/python/tpu/datasets.py b/tensorflow/python/tpu/datasets.py new file mode 100644 index 0000000000..bc0cd41d21 --- /dev/null +++ b/tensorflow/python/tpu/datasets.py @@ -0,0 +1,191 @@ +# 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. +# ====================================== +"""Library of Cloud TPU helper functions for data loading.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.python.data.experimental.ops import batching +from tensorflow.python.data.experimental.ops import interleave_ops +from tensorflow.python.data.ops import dataset_ops +from tensorflow.python.data.ops import iterator_ops +from tensorflow.python.data.ops import readers +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import function +from tensorflow.python.framework import ops +from tensorflow.python.ops import functional_ops + + +def _TextLineDataset(filename): + buffer_size = 8 * 1024 * 1024 # 8 MiB per file + dataset = readers.TextLineDataset(filename, buffer_size=buffer_size) + return dataset + + +def _TFRecordDataset(filename): + buffer_size = 8 * 1024 * 1024 # 8 MiB per file + dataset = readers.TFRecordDataset(filename, buffer_size=buffer_size) + return dataset + + +_FILETYPE_MAP = { + 'tfrecord': _TFRecordDataset, + 'textline': _TextLineDataset, + 'text': _TextLineDataset, +} + + +def StreamingFilesDataset(files, + filetype=None, + file_reader_job=None, + worker_job=None, + num_epochs=None, + filename_shuffle_buffer_size=None, + num_parallel_reads=None, + batch_transfer_size=None, + sloppy=None): + """StreamingFilesDataset constructs a dataset to stream from workers (GCE VM). + + Because Cloud TPUs are allocated over the network, a Cloud TPU cannot read + files local to your GCE VM. In order to train using files stored on your local + VM (e.g. on local SSD for extreme performance), use the StreamingFilesDataset + helper to generate a dataset to feed your Cloud TPU with files from your GCE + VM. + + The resulting dataset may return an OutOfRangeError if there are no files + found as a result of the fileglob expansion. + + Note: StreamingFilesDataset assumes that the session is using a + TPUClusterResolver and has therefore a worker and a coordinator job. File + loading will be done on the coordinator job. + + Args: + files: A string glob to match files, or a `tf.data.Dataset` generating file + names. + filetype: A string (one of 'tfrecord', or 'textline') or a single-argument + TensorFlow function that when given a filename returns a dataset. + file_reader_job: An optional string that corresponds to the job that should + perform the file reads. + worker_job: An optional string that corresponds to the job that should + process the tensors (i.e. your GPU or TPU worker). + num_epochs: The number of epochs through the training set that should be + generated. By default, it will repeat infinitely. + filename_shuffle_buffer_size: An optional integer whose value controls the + shuffling of the file names. If you would like to read from the files in + the same order, set to 0 or False. + num_parallel_reads: An optional integer controlling the number of files to + read from concurrently. (Set to 1 for no parallelism.) + batch_transfer_size: An optional integer controlling the batching used to + amortize the remote function invocation overhead. Set to a very large + number to increase throughput. Set to a very small number to reduce memory + consumption. Set to False to skip batching. + sloppy: (Optional.) If `False`, read input data while maintaining a + deterministic order. (This may have significant performance impacts.) + sloppy defaults to: True. + Returns: + A `tf.data.Dataset` with an infinite stream of elements generated by a + parallel interleaving of the set of files matched (or generated) by `files` + with a type is the output of the dataset specified by `filetype`. + + Raises: + ValueError: if any argument is not of the expected type. + """ + if filetype is None: + filetype = 'tfrecord' + + if isinstance(filetype, str): + if filetype not in _FILETYPE_MAP: + raise ValueError('Unexpected filetype: %s' % filetype) + reader_fn = _FILETYPE_MAP[filetype] + elif callable(filetype): + reader_fn = filetype + else: + raise ValueError('filetype should be a string or a callable') + + file_reader_job = file_reader_job or 'coordinator' + + worker_job = worker_job or 'worker' + + if filename_shuffle_buffer_size is None: + filename_shuffle_buffer_size = 4096 + + num_parallel_reads = num_parallel_reads or 8 + + if batch_transfer_size is None: + batch_transfer_size = 256 + + if sloppy is None: + sloppy = True + + with ops.device('/job:%s' % file_reader_job): + if isinstance(files, str): + source_dataset = dataset_ops.Dataset.list_files(files) + elif isinstance(files, dataset_ops.DatasetV2): + source_dataset = files + else: + raise ValueError('files was not a string or a dataset: %s' % files) + + if filename_shuffle_buffer_size: + source_dataset = source_dataset.shuffle( + buffer_size=filename_shuffle_buffer_size) + + source_dataset = source_dataset.apply( + interleave_ops.parallel_interleave( + reader_fn, cycle_length=num_parallel_reads, sloppy=sloppy)) + + source_dataset = source_dataset.repeat(num_epochs) + + if batch_transfer_size: + source_dataset = source_dataset.batch(batch_transfer_size) + + source_dataset = source_dataset.prefetch(1) + + source_iterator = dataset_ops.make_one_shot_iterator(source_dataset) + source_handle = source_iterator.string_handle() + + @function.Defun(dtypes.string) + def LoadingFunc(h): + remote_iterator = iterator_ops.Iterator.from_string_handle( + h, source_dataset.output_types, source_dataset.output_shapes) + return remote_iterator.get_next() + + def MapFn(unused_input): + if isinstance(source_dataset.output_types, dtypes.DType): + output_types = [source_dataset.output_types] + elif isinstance(source_dataset.output_types, (list, tuple)): + output_types = source_dataset.output_types + else: + raise ValueError('source dataset has invalid output types') + remote_calls = functional_ops.remote_call( + args=[source_handle], + Tout=output_types, + f=LoadingFunc, + target='/job:%s/replica:0/task:0/cpu:0' % file_reader_job) + if len(remote_calls) == 1: + return remote_calls[0] + else: + return remote_calls + + with ops.device('/job:%s' % worker_job): + output_dataset = dataset_ops.Dataset.range(2).repeat().map( + MapFn, num_parallel_calls=4 if sloppy else None) + output_dataset = output_dataset.prefetch(1) + + if batch_transfer_size: + # Undo the batching used during the transfer. + output_dataset = output_dataset.apply(batching.unbatch()).prefetch(1) + + return output_dataset diff --git a/tensorflow/contrib/tpu/python/tpu/datasets_test.py b/tensorflow/python/tpu/datasets_test.py similarity index 99% rename from tensorflow/contrib/tpu/python/tpu/datasets_test.py rename to tensorflow/python/tpu/datasets_test.py index 8a94f527bb..416dd9496c 100644 --- a/tensorflow/contrib/tpu/python/tpu/datasets_test.py +++ b/tensorflow/python/tpu/datasets_test.py @@ -20,7 +20,6 @@ from __future__ import print_function import os -from tensorflow.contrib.tpu.python.tpu import datasets from tensorflow.core.protobuf import cluster_pb2 from tensorflow.core.protobuf import config_pb2 from tensorflow.python.client import session @@ -31,6 +30,7 @@ from tensorflow.python.framework import ops from tensorflow.python.framework import tensor_shape from tensorflow.python.lib.io import python_io from tensorflow.python.platform import test +from tensorflow.python.tpu import datasets from tensorflow.python.training import server_lib from tensorflow.python.util import compat diff --git a/tensorflow/python/tpu/device_assignment.py b/tensorflow/python/tpu/device_assignment.py new file mode 100644 index 0000000000..fd8a7a348c --- /dev/null +++ b/tensorflow/python/tpu/device_assignment.py @@ -0,0 +1,313 @@ +# 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. +# ====================================== +"""Library of TPU helper functions.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import math +import numpy as np +from six.moves import xrange # pylint: disable=redefined-builtin + +from tensorflow.python.tpu.topology import Topology + + +SINGLE_CORE_ASSIGNMENT = [[[0, 0, 0]]] + + +def _compute_task_and_cores_to_replicas(core_assignment, topology): + """Computes a nested dict which maps task and logical core to replicas.""" + task_and_cores_to_replicas = {} + for replica in xrange(core_assignment.shape[0]): + for logical_core in xrange(core_assignment.shape[1]): + coordinates = core_assignment[replica, logical_core, :] + task_id = topology.task_ordinal_at_coordinates(coordinates) + if task_id not in task_and_cores_to_replicas: + task_and_cores_to_replicas[task_id] = {} + if logical_core not in task_and_cores_to_replicas[task_id]: + task_and_cores_to_replicas[task_id][logical_core] = set() + + task_and_cores_to_replicas[task_id][logical_core].add(replica) + + task_to_sorted_replica_id = {} + + for task, core_to_replicas in task_and_cores_to_replicas.items(): + core_to_sorted_replicas = {} + for core, replicas in core_to_replicas.items(): + core_to_sorted_replicas[core] = sorted(replicas) + + task_to_sorted_replica_id[task] = core_to_sorted_replicas + return task_to_sorted_replica_id + + +class DeviceAssignment(object): + """Mapping from logical cores in a computation to the physical TPU topology. + + Prefer to use the `device_assignment()` helper to construct a + `DeviceAssignment`; it is easier if less flexible than constructing a + `DeviceAssignment` directly. + """ + + def __init__(self, topology, core_assignment): + """Constructs a `DeviceAssignment` object. + + Args: + topology: A `Topology` object that describes the physical TPU topology. + core_assignment: A logical to physical core mapping, represented as a + rank 3 numpy array. See the description of the `core_assignment` + property for more details. + + Raises: + ValueError: If `topology` is not `Topology` object. + ValueError: If `core_assignment` is not a rank 3 numpy array. + """ + if not isinstance(topology, Topology): + raise ValueError("topology must be a Topology object, got {}".format( + type(topology))) + core_assignment = np.asarray(core_assignment, dtype=np.int32) + + self._topology = topology + + if core_assignment.ndim != 3: + raise ValueError("core_assignment must be a rank 3 numpy array, " + "got shape {}".format(core_assignment.shape)) + + self._num_replicas = core_assignment.shape[0] + self._num_cores_per_replica = core_assignment.shape[1] + + if core_assignment.shape[-1] != topology.mesh_rank: + raise ValueError( + "minor dimension of core_assignment must have size equal to topology " + "rank ({}), got shape {}".format(topology.mesh_rank, + core_assignment.shape)) + + self._core_assignment = core_assignment + self._task_and_cores_to_replicas = _compute_task_and_cores_to_replicas( + self._core_assignment, topology) + + @property + def topology(self): + """A `Topology` that describes the TPU topology.""" + return self._topology + + @property + def num_cores_per_replica(self): + """The number of cores per replica.""" + return self._num_cores_per_replica + + @property + def num_replicas(self): + """The number of replicas of the computation.""" + return self._num_replicas + + @property + def core_assignment(self): + """The logical to physical core mapping. + + Returns: + An integer numpy array of rank 3, with shape + `[num_replicas, num_cores_per_replica, topology_rank]`. Maps + (replica, logical core) pairs to physical topology coordinates. + """ + return self._core_assignment + + def coordinates(self, replica, logical_core): + """Returns the physical topology coordinates of a logical core.""" + return tuple(self.core_assignment[replica, logical_core, :]) + + def lookup_replicas(self, task_id, logical_core): + """Lookup replica ids by task number and logical core. + + Args: + task_id: TensorFlow task number. + logical_core: An integer, identifying a logical core. + Returns: + A sorted list of the replicas that are attached to that task and + logical_core. + Raises: + ValueError: If no replica exists in the task which contains the logical + core. + """ + try: + return self._task_and_cores_to_replicas[task_id][logical_core] + except KeyError: + raise ValueError( + "Can not find any replica in task: {} contains logical_core: {} ". + format(task_id, logical_core)) + + def tpu_ordinal(self, replica=0, logical_core=0): + """Returns the ordinal of the TPU device assigned to a logical core.""" + coordinates = self.coordinates(replica, logical_core) + return self._topology.tpu_device_ordinal_at_coordinates(coordinates) + + def host_device(self, replica=0, logical_core=0, job=None): + """Returns the CPU device attached to a logical core.""" + coordinates = self.coordinates(replica, logical_core) + return self._topology.cpu_device_name_at_coordinates(coordinates, job=job) + + def tpu_device(self, replica=0, logical_core=0, job=None): + """Returns the name of the TPU device assigned to a logical core.""" + coordinates = self.coordinates(replica, logical_core) + return self._topology.tpu_device_name_at_coordinates(coordinates, job=job) + + +def device_assignment(topology, + computation_shape=None, + computation_stride=None, + num_replicas=1): + """Computes a device_assignment of a computation across a TPU topology. + + Attempts to choose a compact grid of cores for locality. + + Returns a `DeviceAssignment` that describes the cores in the topology assigned + to each core of each replica. + + `computation_shape` and `computation_stride` values should be powers of 2 for + optimal packing. + + Args: + topology: A `Topology` object that describes the TPU cluster topology. + To obtain a TPU topology, evaluate the `Tensor` returned by + `initialize_system` using `Session.run`. Either a serialized + `TopologyProto` or a `Topology` object may be passed. Note: you must + evaluate the `Tensor` first; you cannot pass an unevaluated `Tensor` here. + computation_shape: A rank 1 int32 numpy array with size equal to the + topology rank, describing the shape of the computation's block of cores. + If None, the `computation_shape` is `[1] * topology_rank`. + computation_stride: A rank 1 int32 numpy array of size `topology_rank`, + describing the inter-core spacing of the `computation_shape` cores in the + TPU topology. If None, the `computation_stride` is `[1] * topology_rank`. + num_replicas: The number of computation replicas to run. The replicas will + be packed into the free spaces of the topology. + + Returns: + A DeviceAssignment object, which describes the mapping between the logical + cores in each computation replica and the physical cores in the TPU + topology. + + Raises: + ValueError: If `topology` is not a valid `Topology` object. + ValueError: If `computation_shape` or `computation_stride` are not 1D int32 + numpy arrays with shape [3] where all values are positive. + ValueError: If computation's replicas cannot fit into the TPU topology. + """ + # Deserialize the Topology proto, if it is a string. + if isinstance(topology, bytes): + topology = Topology(serialized=topology) + + if not isinstance(topology, Topology): + raise ValueError("`topology` is not a Topology object; got {}".format( + type(topology))) + + topology_rank = len(topology.mesh_shape) + mesh_shape = topology.mesh_shape + if computation_shape is None: + computation_shape = np.array([1] * topology_rank, dtype=np.int32) + else: + computation_shape = np.asarray(computation_shape, dtype=np.int32) + + if computation_stride is None: + computation_stride = np.array([1] * topology_rank, dtype=np.int32) + else: + computation_stride = np.asarray(computation_stride, dtype=np.int32) + + if computation_shape.shape != (topology_rank,): + raise ValueError("computation_shape must have shape [{}]; got {}".format( + topology_rank, computation_shape.shape)) + if computation_stride.shape != (topology_rank,): + raise ValueError("computation_stride must have shape [{}]; got {}".format( + topology_rank, computation_stride.shape)) + + if any(computation_shape < 1): + raise ValueError( + "computation_shape must be positive; got computation_shape={}".format( + computation_shape)) + if any(computation_stride < 1): + raise ValueError( + "computation_stride must be positive; got computation_stride={}".format( + computation_stride)) + + # Computes the physical size of one computation instance. + computation_footprint = computation_shape * computation_stride + if any(computation_footprint > mesh_shape): + raise ValueError( + "computation footprint {} does not fit in TPU topology shape {}".format( + computation_footprint, mesh_shape)) + + # Computes how many copies of the computation footprint fit in the mesh. + block_counts = mesh_shape // computation_footprint + + replica_counts = block_counts * computation_stride + max_replicas = np.prod(replica_counts) + if num_replicas > max_replicas: + raise ValueError( + "requested {} replicas but only {} replicas with shape {} and " + "computation_stride {} fit in a TPU mesh of shape {}".format( + num_replicas, max_replicas, computation_shape, computation_stride, + mesh_shape)) + + def ceil_of_ratio(n, m): + return (n + m - 1) // m + + replica_shape = [0] * topology_rank + if num_replicas > 0: + remaining_replicas = num_replicas + remaining_dims = topology_rank + + # Choose dimensions as close to an equal cube as possible, in order of + # increasing dimension size. By visiting dimensions in increasing size, we + # assign the most constrained dimension first, so we won't make infeasible + # choices. + # + # As a secondary sort order, visit the dimensions in reverse order. This + # means we try to use both cores on the same chip in preference to two cores + # on different chips. + for x, ni in sorted(((x, -i) for (i, x) in enumerate(replica_counts))): + i = -ni + target_size = int(math.ceil(remaining_replicas**(1.0 / remaining_dims))) + replica_shape[i] = min(target_size, x) + remaining_replicas = ceil_of_ratio(remaining_replicas, replica_shape[i]) + remaining_dims -= 1 + + assert remaining_replicas == 1 and remaining_dims == 0 + + # Assigns an offset to each replica such that no two replicas overlap. + replica_offsets = np.full([num_replicas, topology_rank], -1, dtype=np.int32) + for replica in xrange(num_replicas): + # Chooses a replica number in each axis. + t = replica + pos = [] + for dim in replica_shape[::-1]: + pos.append(t % dim) + t //= dim + replica_pos = np.array(pos[::-1], dtype=np.int32) + + # Determines where that replica starts in each axis. + outer = replica_pos // computation_stride + inner = replica_pos % computation_stride + replica_offsets[replica, :] = outer * computation_footprint + inner + + # Computes a complete logical core -> physical core mapping for each replica. + indices = [ + np.arange(0, computation_shape[i] * computation_stride[i], + computation_stride[i]) for i in xrange(topology_rank) + ] + indices = np.concatenate( + [i[..., np.newaxis] for i in np.meshgrid(*indices, indexing="ij")], + axis=-1) + indices = indices.reshape((-1, topology_rank)) + assignment = indices + replica_offsets[:, np.newaxis, :] + return DeviceAssignment(topology, core_assignment=assignment) diff --git a/tensorflow/python/tpu/error_handling.py b/tensorflow/python/tpu/error_handling.py new file mode 100644 index 0000000000..52e1ea4237 --- /dev/null +++ b/tensorflow/python/tpu/error_handling.py @@ -0,0 +1,132 @@ +# 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. +# =================================================================== +"""ErrorRendezvous handler for collecting errors from multiple threads.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import contextlib +import sys +import threading +import time + +import six + +from tensorflow.python.framework import errors +from tensorflow.python.platform import tf_logging as logging + +_UNINTERESTING_ERRORS = (errors.CancelledError,) + + +class ErrorRendezvous(object): + """Resolve errors from multiple threads during TPU execution. + + TPU errors can occur on the infeed or outfeed threads as well as the main + training thread. + + Depending on which thread "wins" and receives the session error first, we may + end up showing users a confusing and non-actionable error message (session + cancelled) instead of a root cause (e.g. a bad filename). + + The rendezvous object provides a location to capture these errors until all + threads terminate. At that point we can choose the most informative error + to report. + """ + + def __init__(self, num_sources): + # string -> (message, traceback) + self._errors = {} + self._num_sources = num_sources + self._session_cancel_timer = None + + def record_error(self, source, exc_info, session=None): + """Report an exception from the given source. + + If a session is passed, a timer will be registered to close it after a few + seconds. This is necessary to ensure the main training loop does not hang + if an infeed/oufeed error occurs. We sleep a few seconds to allow a more + interesting error from another thread to propagate. + + Args: + source: string, source of the error + exc_info: Output from `sys.exc_info` (type, value, traceback) + session: Session to close after delay. + """ + _, value, _ = exc_info + self._errors[source] = exc_info + logging.info('Error recorded from %s: %s', source, value) + + if session is not None and self._session_cancel_timer is None: + + def _cancel_session(): + time.sleep(5) + try: + session.close() + except: # pylint: disable=bare-except + pass + + self._session_cancel_timer = threading.Thread(target=_cancel_session,) + self._session_cancel_timer.daemon = True + self._session_cancel_timer.start() + + def record_done(self, source): + """Mark execution source `source` as done. + + If an error was originally reported from `source` it is left intact. + + Args: + source: `str`, source being recorded + """ + logging.info('%s marked as finished', source) + if source not in self._errors: + self._errors[source] = None + + @contextlib.contextmanager + def catch_errors(self, source, session=None): + """Context manager to report any errors within a block.""" + try: + yield + except Exception: # pylint: disable=broad-except + self.record_error(source, sys.exc_info(), session) + + def raise_errors(self, timeout_sec=0): + """Wait for up to `timeout` seconds for all error sources to finish. + + Preferentially raise "interesting" errors (errors not in the + _UNINTERESTING_ERRORS) set. + + Args: + timeout_sec: Seconds to wait for other error sources. + """ + for _ in range(timeout_sec): + if len(self._errors) == self._num_sources: + break + time.sleep(1) + + kept_errors = [(k, v) for (k, v) in self._errors.items() if v is not None] + + # First check for any interesting errors, then fall back on the session + # cancelled errors etc. + for k, (typ, value, traceback) in kept_errors: + if isinstance(value, _UNINTERESTING_ERRORS): + continue + else: + logging.warn('Reraising captured error') + six.reraise(typ, value, traceback) + + for k, (typ, value, traceback) in kept_errors: + logging.warn('Reraising captured error') + six.reraise(typ, value, traceback) diff --git a/tensorflow/python/tpu/feature_column.py b/tensorflow/python/tpu/feature_column.py new file mode 100644 index 0000000000..2f7e93910c --- /dev/null +++ b/tensorflow/python/tpu/feature_column.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. +# =================================================================== +"""TPU Feature Column Library.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import math + +from tensorflow.python.feature_column import feature_column as fc +from tensorflow.python.feature_column import feature_column_lib as fc_lib +from tensorflow.python.framework import ops +from tensorflow.python.ops import init_ops +from tensorflow.python.ops import variable_scope +from tensorflow.python.tpu import tpu +from tensorflow.python.tpu import tpu_function +# pylint: disable=protected-access + + +_TPU_FC_TO_SCOPE = '_tpu_feature_column_scope' +_SUPPORTED_CATEGORICAL_COLUMNS = (fc._IdentityCategoricalColumn, + fc._VocabularyFileCategoricalColumn, + fc._VocabularyListCategoricalColumn, + fc._WeightedCategoricalColumn, + fc_lib.IdentityCategoricalColumn, + fc_lib.VocabularyFileCategoricalColumn, + fc_lib.VocabularyListCategoricalColumn, + fc_lib.WeightedCategoricalColumn) + + +def embedding_column(categorical_column, + dimension, + combiner='mean', + initializer=None): + """TPU embedding_column for `tf.feature_column.embedding_column`. + + Note that the interface for TPU embedding_column is different from the non-TPU + version. The following args available for the non-TPU version are NOT + supported: ckpt_to_load_from, tensor_name_in_ckp, max_norm and trainable. + + Args: + categorical_column: A categorical_column returned from + categorical_column_with_identity, weighted_categorical_column, + categorical_column_with_vocabulary_list or + categorical_column_with_vocabulary_file. + dimension: An integer specifying dimension of the embedding, must be > 0. + combiner: A string specifying how to reduce if there are multiple entries + in a single row. For more information, see + `tf.feature_column.embedding_column`. + initializer: A variable initializer function to be used in embedding + variable initialization. If not specified, defaults to + `tf.truncated_normal_initializer` with mean `0.0` and standard deviation + `1/sqrt(dimension)`. + + Returns: + A _TPUEmbeddingColumn. + + Raises: + ValueError: if `dimension` not > 0. + ValueError: if `initializer` is specified but not callable. + """ + if not isinstance(categorical_column, _SUPPORTED_CATEGORICAL_COLUMNS): + raise TypeError( + 'categorical_column for tpu ' + ' embedding_column must be type %s, got %s.' % (' or '.join([ + cc.__name__ for cc in _SUPPORTED_CATEGORICAL_COLUMNS + ]), type(categorical_column))) + if (dimension is None) or (dimension < 1): + raise ValueError('Invalid dimension {}.'.format(dimension)) + + if (initializer is not None) and (not callable(initializer)): + raise ValueError('initializer must be callable if specified. ' + 'Embedding of column_name: {}'.format( + categorical_column.name)) + if initializer is None: + initializer = init_ops.truncated_normal_initializer( + mean=0.0, stddev=1 / math.sqrt(dimension)) + + embedding_shape = categorical_column._num_buckets, dimension # pylint: disable=protected-access + + def _creator(weight_collections, scope): + embedding_column_layer = fc._EmbeddingColumnLayer( + embedding_shape=embedding_shape, + initializer=initializer, + weight_collections=weight_collections, + trainable=True, + name='embedding_column_layer') + return embedding_column_layer(None, scope=scope) # pylint: disable=not-callable + + column = _TPUEmbeddingColumn( + categorical_column=categorical_column, + dimension=dimension, + combiner=combiner, + layer_creator=_creator, + ckpt_to_load_from=None, + tensor_name_in_ckpt=None, + max_norm=None, + trainable=True) + # For Embedding column, the initializer is hidden inside the creator Fn, which + # is not accessiable later. So, we attach it to a speicial field. Also note + # that non-TPU Embedding column and non-TPU shared Embedding column handle the + # initializer differently. See shared_embedding_columns for details. + column._tpu_initializer = initializer + return column + + +def shared_embedding_columns(categorical_columns, + dimension, + combiner='mean', + initializer=None, + shared_embedding_collection_name=None): + """List of dense columns that convert from sparse, categorical input.""" + for categorical_column in categorical_columns: + if not isinstance(categorical_column, _SUPPORTED_CATEGORICAL_COLUMNS): + raise TypeError( + 'categorical_column for tpu ' + ' shared_embedding_columns must be type %s, got %s.' % (' or '.join([ + cc.__name__ for cc in _SUPPORTED_CATEGORICAL_COLUMNS + ]), type(categorical_column))) + columns = fc_lib.shared_embedding_columns( + categorical_columns, + dimension, + combiner=combiner, + initializer=initializer, + shared_embedding_collection_name=shared_embedding_collection_name, + ckpt_to_load_from=None, + tensor_name_in_ckpt=None, + max_norm=None, + trainable=True) + + # Use the initializer and shared_embedding_collection_name to create TPU + # version + initializer = columns[0].initializer + shared_embedding_collection_name = columns[0].shared_embedding_collection_name + tpu_columns = [] + + # Create the state (_SharedEmbeddingColumnLayer) here. + for categorical_column in categorical_columns: + column = _TPUSharedEmbeddingColumn( + categorical_column=categorical_column, + dimension=dimension, + combiner=combiner, + initializer=initializer, + shared_embedding_collection_name=shared_embedding_collection_name, + ckpt_to_load_from=None, + tensor_name_in_ckpt=None, + max_norm=None, + trainable=True) + tpu_columns.append(column) + + return tpu_columns + + +class _TPUBaseEmbeddingColumn(object): + """Base class for TPU Embedding Column.""" + + def __init__(self, categorical_column): + self._tpu_categorical_column = categorical_column + + def get_combiner(self): + """Returns the embedding combiner.""" + raise NotImplementedError('not implemented') + + def get_embedding_table_size(self): + """Returns the embedding table size, tuple of vocab size and dimension.""" + raise NotImplementedError('not implemented') + + def get_feature_key_name(self): + """Returns the feature key name in the features dict.""" + raise NotImplementedError('not impl') + + def get_weight_key_name(self): + """Return the key name for weights.""" + raise NotImplementedError('not impl') + + def get_embedding_var_name(self): + """Returns the embedding variable name. + + Feature key name and embedding variable name are usually one-to-one mapping. + But for shared embedding columns, it is many-to-one mapping. + """ + raise NotImplementedError('not impl') + + def get_initializer(self): + """Returns the initializer.""" + raise NotImplementedError('not impl') + + def is_categorical_column_weighted(self): + """Check if the categorical column of the embedding column is weighted.""" + raise NotImplementedError('not impl') + + +class _TPUEmbeddingColumn(_TPUBaseEmbeddingColumn, fc._EmbeddingColumn): + """Core Embedding Column.""" + + def __new__(cls, + categorical_column, + dimension, + combiner='mean', + layer_creator=None, + ckpt_to_load_from=None, + tensor_name_in_ckpt=None, + max_norm=None, + trainable=True): + # Note, args ckpt_to_load_from, tensor_name_in_ckpt, max_norm and trainable + # are not supported on TPU. They are solely for matching the signature of + # __new__ of parent class fc._EmbeddingColumn. + return fc._EmbeddingColumn.__new__( + cls, + categorical_column, + dimension, + combiner=combiner, + layer_creator=layer_creator, + ckpt_to_load_from=ckpt_to_load_from, + tensor_name_in_ckpt=tensor_name_in_ckpt, + max_norm=max_norm, + trainable=trainable) + + def __init__(self, + categorical_column, + dimension, + combiner='mean', + layer_creator=None, + ckpt_to_load_from=None, + tensor_name_in_ckpt=None, + max_norm=None, + trainable=True): + _TPUBaseEmbeddingColumn.__init__(self, categorical_column) + self._key = None + + def get_combiner(self): + return self.combiner + + def get_embedding_table_size(self): + """Returns num_ids and width.""" + return (self.categorical_column._num_buckets, self.dimension) + + def get_feature_key_name(self): + """get_feature_key_name.""" + if self.is_categorical_column_weighted(): + return self.categorical_column.categorical_column.name + return self.categorical_column.name + + def get_weight_key_name(self): + """get_weight_key_name.""" + if self.is_categorical_column_weighted(): + return self.categorical_column.weight_feature_key + return None + + def get_embedding_var_name(self): + """get_embedding_var_name.""" + return self.categorical_column.name + + def get_initializer(self): + return self._tpu_initializer + + def is_categorical_column_weighted(self): + """Check if the categorical column of the embedding column is weighted.""" + if isinstance( + self.categorical_column, + ( + fc._WeightedCategoricalColumn, # pylint: disable=protected-access + fc_lib.WeightedCategoricalColumn)): + return True + return False + + def _get_dense_tensor(self, inputs, weight_collections=None, trainable=None): + if tpu.under_tpu_inference_context(): + def host_computation(): + return fc._EmbeddingColumn._get_dense_tensor( + self, inputs, weight_collections, trainable) + return tpu.outside_compilation(host_computation) + + if _is_running_on_cpu(): + return fc._EmbeddingColumn._get_dense_tensor( + self, inputs, weight_collections, trainable) + + # TPU mode + # Get the embeddings from the LazyBuilder. + tensor = inputs.get(self.get_feature_key_name()) + + # Add to collection for _create_tpu_embedding_variables_and_ops + _record_variable_scope_and_name(self.get_embedding_var_name(), + 'embedding_weights') + + return tensor + + +class _TPUSharedEmbeddingColumn(_TPUBaseEmbeddingColumn, + fc._SharedEmbeddingColumn): + """Core Shared Embedding Column.""" + + def __new__(cls, + categorical_column, + dimension, + combiner='mean', + initializer=None, + shared_embedding_collection_name=None, + ckpt_to_load_from=None, + tensor_name_in_ckpt=None, + max_norm=None, + trainable=True): + return fc._SharedEmbeddingColumn.__new__( + cls, + categorical_column, + dimension, + combiner=combiner, + initializer=initializer, + shared_embedding_collection_name=shared_embedding_collection_name, + ckpt_to_load_from=ckpt_to_load_from, + tensor_name_in_ckpt=tensor_name_in_ckpt, + max_norm=max_norm, + trainable=trainable) + + def __init__(self, + categorical_column, + dimension, + combiner='mean', + initializer=None, + shared_embedding_collection_name=None, + ckpt_to_load_from=None, + tensor_name_in_ckpt=None, + max_norm=None, + trainable=True): + + _TPUBaseEmbeddingColumn.__init__(self, categorical_column) + self._key = None + + def get_combiner(self): + return self.combiner + + def get_embedding_table_size(self): + """Returns num_ids and width.""" + return (self.categorical_column._num_buckets, self.dimension) + + def get_feature_key_name(self): + """get_feature_key_name.""" + if self.is_categorical_column_weighted(): + return self.categorical_column.categorical_column.name + return self.categorical_column.name + + def get_weight_key_name(self): + """get_weight_key_name.""" + if self.is_categorical_column_weighted(): + return self.categorical_column.weight_feature_key + return None + + def get_embedding_var_name(self): + """get_embedding_var_name.""" + return self.shared_embedding_collection_name + + def get_initializer(self): + return self.initializer + + def is_categorical_column_weighted(self): + """Check if the categorical column of the embedding column is weighted.""" + if isinstance( + self.categorical_column, + ( + fc._WeightedCategoricalColumn, # pylint: disable=protected-access + fc_lib.WeightedCategoricalColumn)): + return True + return False + + def _get_dense_tensor(self, inputs, weight_collections=None, trainable=None): + if tpu.under_tpu_inference_context(): + def host_computation(): + return fc._SharedEmbeddingColumn._get_dense_tensor( + self, inputs, weight_collections, trainable) + return tpu.outside_compilation(host_computation) + + if _is_running_on_cpu(): + return fc._SharedEmbeddingColumn._get_dense_tensor( + self, inputs, weight_collections, trainable) + + # TPU mode + # Get the embeddings from the LazyBuilder. + tensor = inputs.get(self.get_feature_key_name()) + + # Add to collection for _create_tpu_embedding_variables_and_ops + _record_variable_scope_and_name( + self.get_embedding_var_name(), + 'embedding_weights', + is_shared_embedding=True) + return tensor + + +def _record_variable_scope_and_name(embedding_var_name, + embedding_var_name_in_fc, + is_shared_embedding=False): + """Add embedding variable name and scope to collection.""" + g = ops.get_default_graph() + collection = g.get_collection_ref(_TPU_FC_TO_SCOPE) + if not collection: + collection.append({}) + + var_def_dict = collection[0] + + captured_scope = variable_scope.get_variable_scope() + captured_scope_name = captured_scope.name + + if embedding_var_name in var_def_dict: + if (var_def_dict[embedding_var_name][0] != captured_scope_name + and not is_shared_embedding): + raise ValueError( + 'For embedding var name {}, the variable scope name is different, ' + 'got {}; expected {}'.format(embedding_var_name, + captured_scope_name, + var_def_dict[embedding_var_name][0])) + if var_def_dict[embedding_var_name][1] != embedding_var_name_in_fc: + raise ValueError( + 'For embedding var name {}, the embedding name is different, ' + 'got {}; expected {}'.format(embedding_var_name, + embedding_var_name_in_fc, + var_def_dict[embedding_var_name][1])) + else: + var_def_dict[embedding_var_name] = (captured_scope_name, + embedding_var_name_in_fc) + + +def _is_running_on_cpu(): + """Returns True if the current context is CPU model.""" + return tpu_function.get_tpu_context().number_of_shards is None diff --git a/tensorflow/contrib/tpu/python/tpu/feature_column_test.py b/tensorflow/python/tpu/feature_column_test.py similarity index 98% rename from tensorflow/contrib/tpu/python/tpu/feature_column_test.py rename to tensorflow/python/tpu/feature_column_test.py index 75164cce4c..6feeb0198d 100644 --- a/tensorflow/contrib/tpu/python/tpu/feature_column_test.py +++ b/tensorflow/python/tpu/feature_column_test.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # =================================================================== -"""Tests for contrib.tpu.python.tpu.feature_column.""" +"""Tests for python.tpu.feature_column.""" from __future__ import absolute_import from __future__ import division @@ -20,7 +20,6 @@ from __future__ import print_function import numpy as np -from tensorflow.contrib.tpu.python.tpu import feature_column as tpu_fc from tensorflow.python.client import session from tensorflow.python.feature_column import feature_column as fc from tensorflow.python.feature_column import feature_column_lib as fc_lib @@ -31,6 +30,7 @@ from tensorflow.python.ops import lookup_ops from tensorflow.python.ops import parsing_ops from tensorflow.python.ops import variables as variables_lib from tensorflow.python.platform import test +from tensorflow.python.tpu import feature_column as tpu_fc def _initialized_session(): diff --git a/tensorflow/python/tpu/functional.py b/tensorflow/python/tpu/functional.py new file mode 100644 index 0000000000..045ec523bb --- /dev/null +++ b/tensorflow/python/tpu/functional.py @@ -0,0 +1,23 @@ +# 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. +# ============================================================================= +"""Functional operations.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.python.tpu.ops import tpu_ops + +TPUPartitionedCall = tpu_ops.tpu_partitioned_call # pylint: disable=invalid-name diff --git a/tensorflow/python/tpu/ops/tpu_ops.py b/tensorflow/python/tpu/ops/tpu_ops.py new file mode 100644 index 0000000000..e6e411cbc9 --- /dev/null +++ b/tensorflow/python/tpu/ops/tpu_ops.py @@ -0,0 +1,418 @@ +# 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. +# ============================================================================= + +"""Operations for TPUs.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import platform + +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import ops +from tensorflow.python.ops import array_ops +from tensorflow.python.platform import tf_logging as logging +from tensorflow.python.tpu import tpu_function + +if platform.system() != "Windows": + # pylint: disable=wildcard-import,unused-import,g-import-not-at-top + from tensorflow.python.ops import gen_tpu_ops + from tensorflow.python.ops.gen_tpu_ops import * + # pylint: enable=wildcard-import,unused-import,g-import-not-at-top + + def _create_default_group_assignment(): + num_shards = tpu_function.get_tpu_context().number_of_shards + if num_shards is None: + logging.warning( + "cross_replica_sum should be used within a tpu_shard_context, but " + "got unset number_of_shards. Assuming 1.") + num_shards = 1 + group_assignment = [list(range(num_shards))] + return group_assignment + + def all_to_all(x, + concat_dimension, + split_dimension, + split_count, + group_assignment=None, + name=None): + """Exchange data across TPU replicas. + + Args: + x: The local tensor. + concat_dimension: The dimension number to concatenate. + split_dimension: The dimension number to split. + split_count: The number of splits, this number must equal to the sub-group + size(group_assignment.get_shape()[1]) + group_assignment: Optional 2d int32 lists with shape [num_groups, + num_replicas_per_group]. `group_assignment[i]` represents the replica + ids in the ith subgroup. + name: Optional op name. + + Returns: + A `Tensor` which is concatenated by data from different replicas. + """ + if group_assignment is None: + group_assignment = _create_default_group_assignment() + return gen_tpu_ops.all_to_all( + x, + group_assignment, + concat_dimension=concat_dimension, + split_dimension=split_dimension, + split_count=split_count, + name=name) + + @ops.RegisterGradient("AllToAll") + def _all_to_all_grad(op, grad): + # The gradient of a all-to-all is also a all-to-all but the + # split_dimension and concat_dimension is swapped. + # The graident with respect to group_assignment is None. + return [ + gen_tpu_ops.all_to_all( + grad, + op.inputs[1], + concat_dimension=op.get_attr("split_dimension"), + split_dimension=op.get_attr("concat_dimension"), + split_count=op.get_attr("split_count")), None + ] + + def cross_replica_sum(x, group_assignment=None, name=None): + """Sum the input tensor across replicas according to group_assignment. + + Args: + x: The local tensor to the sum. + group_assignment: Optional 2d int32 lists with shape [num_groups, + num_replicas_per_group]. `group_assignment[i]` represents the replica + ids in the ith subgroup. + name: Optional op name. + + Returns: + A `Tensor` which is summed across replicas. + """ + if group_assignment is None: + group_assignment = _create_default_group_assignment() + + return gen_tpu_ops.cross_replica_sum(x, group_assignment, name=name) + + def collective_permute(x, source_target_pairs, name=None): + """Permute the input tensor across replicas given source_target_pairs. + + For each source_target_pair , we send replica a's input to replica b. + Each replica id must only appear once in the source column. Also it must + only appear once in the target column. + For the replica id not in the target column, this op returns a zero tensor + with the same shape and dtype of the input x. + + For example, suppose there are 4 TPU instances: `[A, B, C, D]`. Passing + source_target_pairs=`[[0,1],[1,2],[2,3]]` gets the outputs: + `[0, A, B, C]`. + + Args: + x: The local tensor to be permuted. + source_target_pairs: 2d int lists with shape [num_pairs, 2]. + source_target_pairs[i][0] represents the source replica id and + source_target_pairs[i][1] represents the target replica id. + name: Optional op name. + + Returns: + A `Tensor` which is permuted. + """ + return gen_tpu_ops.collective_permute(x, source_target_pairs, name=name) + + @ops.RegisterGradient("CollectivePermute") + def _collective_permute_grad(op, grad): + # The gradient of a collective permute operation is also a collective + # permute, but with source/target pairs reversed. The gradient with respect + # to input argument `source_target_pairs` is `None`. + source_target_pairs = op.inputs[1][:, ::-1] + return [gen_tpu_ops.collective_permute(grad, source_target_pairs), None] + + @ops.RegisterGradient("CrossReplicaSum") + def _cross_replica_sum_grad(op, grad): + # The gradient of a cross replica sum is also a cross-replica sum. + # The gradient with respect to group_assignment is None. + return [gen_tpu_ops.cross_replica_sum(grad, op.inputs[1]), None] + + # This extra type checking exists to give a more helpful error message in + # the common case that uint8 and int64 values are infed. Remove when both + # types are supported. + + _SUPPORTED_INFEED_DTYPES = set([ + dtypes.bool, dtypes.int32, dtypes.int64, dtypes.bfloat16, dtypes.float32, + dtypes.complex64, dtypes.uint32 + ]) + + @ops.RegisterGradient("TPUEmbeddingActivations") + def _embedding_activations_grad(activations_op, grad_wrt_activations): + """Saves the gradient of embedding activations ops in a graph collection.""" + g = ops.get_default_graph() + table_id = activations_op.get_attr("table_id") + lookup_id = activations_op.get_attr("lookup_id") + table_gradients = g.get_collection_ref( + "tpu_embedding_gradients_table_%d" % table_id) + + if not table_gradients: + raise RuntimeError( + "Gradients for TPUEmbedding have been generated in non-training mode." + "This is not expected. Consider putting your Optimizer.minimize code " + "behind the training mode condition check. For Estimator, you can " + "do \n\n" + " if mode == tf.estimator.ModeKeys.TRAIN:\n" + " train_op = opt.minimize(loss)\n" + "\n") + + table_gradients[lookup_id] = array_ops.identity(grad_wrt_activations) + return [ + # RegisterGradient requires that value be returned for all inputs. Since + # the first argument (tpu_gradient_variable_{table_name}) has shape [1], + # we will return zeros(shape=[1]). The actual gradient w.r.t. the + # embedding activations (grad_wrt_activations) has the same shape as the + # activations returned by embedding_activations. + array_ops.zeros(arg.shape, dtype=dtypes.float32) + for arg in activations_op.inputs + ] + + def infeed_dequeue(dtype, shape, name=None): + """A placeholder op for a value that will be fed into the computation. + + Args: + dtype: A `tf.DType`. The type of elements in the tensor. + shape: A `tf.TensorShape` or list of `ints`. The shape of the tensor. + name: A name for the operation (optional). + + Returns: + A `Tensor` of type `dtype`. + A tensor that will be provided using the infeed mechanism. + + Raises: + TypeError: If 'dtype` is not a supported infeed type. + """ + if dtype not in _SUPPORTED_INFEED_DTYPES: + raise TypeError( + "{} is not a supported TPU infeed type. Supported types are: " + "{}".format(dtype, list(_SUPPORTED_INFEED_DTYPES))) + + return gen_tpu_ops.infeed_dequeue(dtype, shape, name=name) + + # pylint: disable=redefined-outer-name + def infeed_dequeue_tuple(dtypes, shapes, name=None): + """A placeholder op for values fed into the TPU simultaneously as a tuple. + + Args: + dtypes: A list of `tf.DType`s that has length `>= 1`. + The element types of each element in `outputs`. + shapes: A list of shapes (each a `tf.TensorShape` or list of `ints`). + The shapes of each tensor in `outputs`. + name: A name for the operation (optional). + + Returns: + A list of `Tensor` objects of type `dtypes`. + A list of tensors that will be provided using the infeed mechanism. + + Raises: + TypeError: If a type in 'dtypes` is not a supported infeed type. + """ + for dtype in dtypes: + if dtype not in _SUPPORTED_INFEED_DTYPES: + raise TypeError( + "{} is not a supported TPU infeed type. Supported types are: " + "{}".format(dtype, list(_SUPPORTED_INFEED_DTYPES))) + return gen_tpu_ops.infeed_dequeue_tuple(dtypes, shapes, name=name) + # pylint: enable=redefined-outer-name + + # pylint: disable=protected-access + def send_tpu_embedding_gradients(inputs, + config, + learning_rates=None, + name=None): + """A placeholder op for feeding per-sample gradients to the embedding layer. + + Args: + inputs: A TensorList of gradients with which to update embedding tables. + This argument has the same length and shapes as the return value of + RecvTPUEmbeddingActivations, but contains gradients of the model's + loss with respect to the embedding activations. The embedding tables + are updated from these gradients via the optimizers specified in the + TPU embedding configuration given to tpu.initialize_system. + config: Serialized TPUEmbeddingConfiguration proto. + learning_rates: A TensorList of float32 scalars, one for each dynamic + learning rate tag: see the comments in + //third_party/tensorflow/core/protobuf/tpu/ + optimization_parameters.proto. + Multiple tables can share the same dynamic learning rate tag as + specified in the configuration. If the learning rates for all tables + are constant, this list should be empty. + name: A name for the operation (optional). + + Returns: + A SendTPUEmbeddingGradients operation. + """ + if learning_rates is None: + learning_rates = [] + return gen_tpu_ops.send_tpu_embedding_gradients( + inputs=inputs, learning_rates=learning_rates, config=config, name=name) + + send_tpu_embedding_gradients.__doc__ = ( + gen_tpu_ops.send_tpu_embedding_gradients.__doc__) + + # pylint: disable=protected-access + def enqueue_tpu_embedding_integer_batch(batch, + device_ordinal, + mode_override=None, + name=None): + """A placeholder op for enqueueing embedding IDs to the TPU. + + Args: + batch: A list of 1D tensors, one for each embedding table, containing the + indices into the tables. + device_ordinal: The TPU device to use. Should be >= 0 and less than the + number of TPU cores in the task on which the node is placed. + mode_override: A string input that overrides the mode specified in the + TPUEmbeddingConfiguration. Supported values are {'unspecified', + 'inference', 'training', 'backward_pass_only'}. When set to + 'unspecified', the mode set in TPUEmbeddingConfiguration is used, + otherwise mode_override is used (optional). + name: A name for the operation (optional). + + Returns: + An EnqueueTPUEmbeddingIntegerBatch operation. + """ + if mode_override is None: + mode_override = "unspecified" + return gen_tpu_ops.enqueue_tpu_embedding_integer_batch( + batch=batch, + device_ordinal=device_ordinal, + mode_override=mode_override, + name=name) + + enqueue_tpu_embedding_integer_batch.__doc__ = ( + gen_tpu_ops.enqueue_tpu_embedding_integer_batch.__doc__) + + # pylint: disable=protected-access + def enqueue_tpu_embedding_sparse_batch(sample_indices, + embedding_indices, + aggregation_weights, + device_ordinal, + combiners=None, + mode_override=None, + name=None): + """A placeholder op for enqueueing embedding IDs to the TPU. + + Args: + sample_indices: A list of rank 1 Tensors specifying the training example + and feature to which the corresponding embedding_indices and + aggregation_weights values belong. sample_indices[i] must equal b * nf + + f, where nf is the number of features from the corresponding table, f is + in [0, nf), and b is in [0, batch size). + embedding_indices: A list of rank 1 Tensors, indices into the embedding + tables. + aggregation_weights: A list of rank 1 Tensors containing per sample -- + i.e. per (training example, feature) -- aggregation weights. + device_ordinal: The TPU device to use. Should be >= 0 and less than the + number of TPU cores in the task on which the node is placed. + combiners: A list of string scalars, one for each embedding table that + specify how to normalize the embedding activations after weighted + summation. Supported combiners are 'mean', 'sum', or 'sqrtn'. It is + invalid to have the sum of the weights be 0 for 'mean' or the sum of the + squared weights be 0 for 'sqrtn'. If combiners isn't passed, the default + is to use 'sum' for all tables (optional). + mode_override: A string input that overrides the mode specified in the + TPUEmbeddingConfiguration. Supported values are {'unspecified', + 'inference', 'training', 'backward_pass_only'}. When set to + 'unspecified', the mode set in TPUEmbeddingConfiguration is used, + otherwise mode_override is used (optional). + name: A name for the operation (optional). + + Returns: + An EnqueueTPUEmbeddingSparseBatch operation. + """ + if mode_override is None: + mode_override = "unspecified" + return gen_tpu_ops.enqueue_tpu_embedding_sparse_batch( + sample_indices=sample_indices, + embedding_indices=embedding_indices, + aggregation_weights=aggregation_weights, + device_ordinal=device_ordinal, + combiners=combiners, + mode_override=mode_override, + name=name) + + enqueue_tpu_embedding_sparse_batch.__doc__ = ( + gen_tpu_ops.enqueue_tpu_embedding_sparse_batch.__doc__) + + # pylint: disable=protected-access + def enqueue_tpu_embedding_sparse_tensor_batch(sample_indices, + embedding_indices, + aggregation_weights, + table_ids, + device_ordinal, + combiners=None, + mode_override=None, + name=None): + """A placeholder op for enqueueing embedding IDs to the TPU. + + Args: + sample_indices: A list of rank 1 Tensors specifying the training example + to which the corresponding embedding_indices and aggregation_weights + values belong. It corresponds to sp_ids.indices[:,0] in + embedding_lookup_sparse(). + embedding_indices: A list of rank 1 Tensors, indices into the embedding + tables. It corresponds to sp_ids.values in embedding_lookup_sparse(). + aggregation_weights: A list of rank 1 Tensors containing per training + example aggregation weights. It corresponds to sp_weights.values in + embedding_lookup_sparse(). + table_ids: A list of integers specifying the identifier of the embedding + table (offset of TableDescriptor in the TPUEmbeddingConfiguration) to + lookup the corresponding input. The ith input is looked up using + table_ids[i]. The size of the table_ids list must be equal to that of + sample_indices, embedding_indices and aggregation_weights. + device_ordinal: The TPU device to use. Should be >= 0 and less than the + number of TPU cores in the task on which the node is placed. + combiners: A list of string scalars, one for each embedding table that + specify how to normalize the embedding activations after weighted + summation. Supported combiners are 'mean', 'sum', or 'sqrtn'. It is + invalid to have the sum of the weights be 0 for 'mean' or the sum of the + squared weights be 0 for 'sqrtn'. If combiners isn't passed, the default + is to use 'sum' for all tables (optional). + mode_override: A string input that overrides the mode specified in the + TPUEmbeddingConfiguration. Supported values are {'unspecified', + 'inference', 'training', 'backward_pass_only'}. When set to + 'unspecified', the mode set in TPUEmbeddingConfiguration is used, + otherwise mode_override is used (optional). + name: A name for the operation (optional). + + Returns: + An EnqueueTPUEmbeddingSparseTensorBatch operation. + """ + if mode_override is None: + mode_override = "unspecified" + return gen_tpu_ops.enqueue_tpu_embedding_sparse_tensor_batch( + sample_indices=sample_indices, + embedding_indices=embedding_indices, + aggregation_weights=aggregation_weights, + table_ids=table_ids, + device_ordinal=device_ordinal, + combiners=combiners, + mode_override=mode_override, + name=name) + + enqueue_tpu_embedding_sparse_tensor_batch.__doc__ = ( + gen_tpu_ops.enqueue_tpu_embedding_sparse_tensor_batch.__doc__) + +else: + # We have already built the appropriate libraries into the binary via CMake + # if we have built contrib, so we don't need this + pass diff --git a/tensorflow/python/tpu/ops/tpu_ordinal_selector_op.py b/tensorflow/python/tpu/ops/tpu_ordinal_selector_op.py new file mode 100644 index 0000000000..1f2dce26cd --- /dev/null +++ b/tensorflow/python/tpu/ops/tpu_ordinal_selector_op.py @@ -0,0 +1,20 @@ +# 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. +# ============================================================================= + +"""Operations to select TPU core to run.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function diff --git a/tensorflow/python/tpu/profiler/BUILD b/tensorflow/python/tpu/profiler/BUILD new file mode 100644 index 0000000000..bfe79454cc --- /dev/null +++ b/tensorflow/python/tpu/profiler/BUILD @@ -0,0 +1,27 @@ +licenses(["notice"]) # Apache 2.0 + +package( + default_visibility = [ + "//tensorflow:__subpackages__", + ], +) + +py_library( + name = "profiler", + srcs = ["__init__.py"], + srcs_version = "PY2AND3", + deps = [ + ":tpu_profiler_analysis_pb2_grpc", + "//tensorflow/core/profiler:profiler_analysis_proto_py", + "//tensorflow/core/profiler:protos_all_py", + "//tensorflow/python:util", + ], +) + +py_library( + name = "tpu_profiler_analysis_pb2_grpc", + srcs = ["tpu_profiler_analysis_pb2_grpc.py"], + srcs_version = "PY2AND3", + visibility = ["//visibility:public"], + deps = ["//tensorflow/core/profiler:profiler_analysis_proto_py"], +) diff --git a/tensorflow/python/tpu/profiler/__init__.py b/tensorflow/python/tpu/profiler/__init__.py new file mode 100644 index 0000000000..0c183aaf53 --- /dev/null +++ b/tensorflow/python/tpu/profiler/__init__.py @@ -0,0 +1,31 @@ +# 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. +# ============================================================================= + +"""Classes for TPU trace events.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +# pylint: disable=wildcard-import,unused-import +from tensorflow.core.profiler.trace_events_pb2 import * +from tensorflow.core.profiler.profiler_analysis_pb2 import * +# pylint: enable=wildcard-import,unused-import + +from tensorflow.python.util.all_util import remove_undocumented + +_allowed_symbols = ['Trace', 'Resource', 'Device', 'TraceEvent'] + +remove_undocumented(__name__, _allowed_symbols) diff --git a/tensorflow/contrib/tpu/profiler/tpu_profiler_analysis_pb2_grpc.py b/tensorflow/python/tpu/profiler/tpu_profiler_analysis_pb2_grpc.py similarity index 100% rename from tensorflow/contrib/tpu/profiler/tpu_profiler_analysis_pb2_grpc.py rename to tensorflow/python/tpu/profiler/tpu_profiler_analysis_pb2_grpc.py diff --git a/tensorflow/python/tpu/session_support.py b/tensorflow/python/tpu/session_support.py new file mode 100644 index 0000000000..3df7185be0 --- /dev/null +++ b/tensorflow/python/tpu/session_support.py @@ -0,0 +1,438 @@ +# 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. +# ====================================== +"""Operations for handling session logging and shutdown notifications.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import threading + +import time +from google.protobuf import text_format + +from tensorflow.core.protobuf import config_pb2 +from tensorflow.core.util import event_pb2 +from tensorflow.python.client import session as session_lib +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import errors +from tensorflow.python.framework import ops +from tensorflow.python.ops import array_ops +from tensorflow.python.platform import tf_logging as logging +from tensorflow.python.tpu.ops import tpu_ops +from tensorflow.python.training import session_run_hook +from tensorflow.python.training import training_util + +_WATCHDOG = None + + +class CoordinatorShutdownException(Exception): + """Raised when the coordinator needs to shutdown.""" + pass + + +def _clone_session(session, graph=None): + return session_lib.Session( + target=session.sess_str, + config=session._config, # pylint: disable=protected-access + graph=graph if graph else session.graph) + + +def _make_heartbeat_op(session, device, request_ph): + """Return a heartbeat op or None if heartbeats are not supported by device.""" + try: + # Test if we can connect in a isolated graph + session + with ops.Graph().as_default(): + with _clone_session(session) as temp_session: + with ops.device(device): + heartbeat_op = tpu_ops.worker_heartbeat('') + options = config_pb2.RunOptions(timeout_in_ms=5000) + temp_session.run(heartbeat_op, options=options) + except errors.InvalidArgumentError as _: + logging.warning('Error running heartbeat on %s', device) + return None + except errors.DeadlineExceededError as _: + logging.warning('Timeout connecting to %s when testing heartbeat', device) + return None + + # If we successfully connected and pinged the worker, go ahead and construct + # the operation. + with ops.device(device): + return tpu_ops.worker_heartbeat(request_ph) + + +class WorkerHeartbeatManager(object): + """Manages the status/heartbeat monitor for a set of workers.""" + + def __init__(self, session, devices, heartbeat_ops, request_placeholder): + """Construct a new WorkerHeartbeatManager. + + (Prefer using `WorkerHeartbeatManager.from_devices` when possible.) + + Args: + session: `tf.Session`, session to use for heartbeat operations. + devices: `list[string]` Set of devices to connect to. + heartbeat_ops: `list[tf.Operation]` Heartbeat operations. + request_placeholder: `tf.Placeholder[String]` Placeholder used to specify + the WorkerHeartbeatRequest protocol buffer. + """ + self._session = session + self._devices = devices + self._ops = heartbeat_ops + self._request_placeholder = request_placeholder + + @staticmethod + def from_devices(session, devices): + """Construct a heartbeat manager for the given devices.""" + if not devices: + logging.error('Trying to create heartbeat manager with no devices?') + + logging.info('Creating heartbeat manager for %s', devices) + request_placeholder = array_ops.placeholder( + name='worker_heartbeat_request', dtype=dtypes.string) + + heartbeat_ops = [] + kept_devices = [] + for device in devices: + heartbeat_op = _make_heartbeat_op(session, device, request_placeholder) + if heartbeat_op is not None: + kept_devices.append(device) + heartbeat_ops.append(heartbeat_op) + else: + logging.warning('Heartbeat support not available for %s', device) + + return WorkerHeartbeatManager(session, kept_devices, heartbeat_ops, + request_placeholder) + + def num_workers(self): + return len(self._devices) + + def configure(self, message): + """Configure heartbeat manager for all devices. + + Args: + message: `event_pb2.WorkerHeartbeatRequest` + Returns: `None` + """ + logging.info('Configuring worker heartbeat: %s', + text_format.MessageToString(message)) + self._session.run(self._ops, + {self._request_placeholder: message.SerializeToString()}) + + def ping(self, request=None, timeout_in_ms=5000): + """Ping all workers, returning the parsed status results.""" + if request is None: + request = event_pb2.WorkerHeartbeatRequest() + + options = config_pb2.RunOptions(timeout_in_ms=timeout_in_ms) + results = self._session.run( + self._ops, + feed_dict={self._request_placeholder: request.SerializeToString()}, + options=options) + parsed_results = [ + event_pb2.WorkerHeartbeatResponse.FromString(res_pb) + for res_pb in results + ] + logging.debug('Ping results: %s', parsed_results) + return parsed_results + + def lame_workers(self): + """Ping all workers, returning manager containing lame workers (or None).""" + ping_results = self.ping() + lame_workers = [] + + for ping_response, device, op in zip(ping_results, self._devices, + self._ops): + if ping_response.health_status != event_pb2.OK: + lame_workers.append((device, op)) + + if not lame_workers: + return None + + bad_devices, bad_ops = zip(*lame_workers) + return WorkerHeartbeatManager(self._session, bad_devices, bad_ops, + self._request_placeholder) + + def __repr__(self): + return 'HeartbeatManager(%s)' % ','.join(self._devices) + + def shutdown(self, timeout_ms=10000): + """Shutdown all workers after `shutdown_timeout_secs`.""" + logging.info('Shutting down %s.', self) + req = event_pb2.WorkerHeartbeatRequest( + watchdog_config=event_pb2.WatchdogConfig(timeout_ms=timeout_ms), + shutdown_mode=event_pb2.WAIT_FOR_COORDINATOR) + self.configure(req) + + # Wait for workers to shutdown. This isn't strictly required + # but it avoids triggering multiple checkpoints with the same lame worker. + logging.info('Waiting %dms for worker shutdown.', timeout_ms) + time.sleep(timeout_ms / 1000) + + +def all_worker_devices(session): + """Return a list of devices for each worker in the system.""" + devices = session.list_devices() + return [ + device.name + for device in devices + if ':CPU:' in device.name and 'coordinator' not in device.name + ] + + +class WatchdogManager(threading.Thread): + """Configures worker watchdog timer and handles periodic pings. + + Usage: + # Ping workers every minute, shutting down workers if they haven't received + # a ping after 1 hour. + watchdog_manager = WatchdogManager( + ping_interval=60, shutdown_timeout=3600 + ) + + # Use as a context manager, resetting watchdog on context exit: + with watchdog_manager: + session.run(...) + + # Or setup globally; watchdog will remain active until program exit. + watchdog_manager.configure_and_run() + """ + + def __init__(self, + session, + devices=None, + ping_interval=60, + shutdown_timeout=3600): + """Initialize a watchdog manager. + + Args: + session: Session connected to worker devices. A cloned session and graph + will be created for managing worker pings. + devices: Set of devices to monitor. If none, all workers will be + monitored. + ping_interval: Time, in seconds, between watchdog pings. + shutdown_timeout: Time, in seconds, before watchdog timeout. + """ + threading.Thread.__init__(self) + self.ping_interval = ping_interval + self.shutdown_timeout = shutdown_timeout + self.daemon = True + self._config = session._config # pylint: disable=protected-access + self._target = session.sess_str + self._running = False + self._devices = devices + + self._graph = None + self._session = None + self._worker_manager = None + + def _reset_manager(self): + """Reset the graph, session and worker manager.""" + self._graph = ops.Graph() + self._session = session_lib.Session( + target=self._target, + graph=self._graph, + config=self._config, + ) + + if self._devices is None: + self._devices = all_worker_devices(self._session) + + with self._graph.as_default(): + self._worker_manager = WorkerHeartbeatManager.from_devices( + self._session, self._devices) + + self._worker_manager.configure( + event_pb2.WorkerHeartbeatRequest( + watchdog_config=event_pb2.WatchdogConfig( + timeout_ms=self.shutdown_timeout * 1000,), + shutdown_mode=event_pb2.WAIT_FOR_COORDINATOR)) + + def configure_and_run(self): + logging.info( + 'Enabling watchdog timer with %d second timeout ' + 'and %d second ping interval.', self.shutdown_timeout, + self.ping_interval) + self._reset_manager() + self._running = True + self.start() + + def stop(self): + logging.info('Stopping worker watchdog.') + self._worker_manager.configure( + event_pb2.WorkerHeartbeatRequest( + watchdog_config=event_pb2.WatchdogConfig(timeout_ms=-1,), + shutdown_mode=event_pb2.NOT_CONFIGURED)) + self._running = False + self.join() + + def __enter__(self): + self.configure_and_run() + + def __exit__(self, exc_type, exc_val, exc_tb): + self.stop() + + def run(self): + # Don't fetch logs or adjust timing: just ping the watchdog. + # + # If we hit an exception, reset our session as it is likely broken. + while self._running: + try: + self._worker_manager.ping(request=None) + time.sleep(self.ping_interval) + except errors.OpError as e: + # Catch any TF errors that occur so we don't stop sending heartbeats + logging.debug('Caught error while sending heartbeat: %s', e) + self._reset_manager() + + +def start_worker_watchdog(session, + devices=None, + ping_interval=60, + shutdown_timeout=3600): + """Start global worker watchdog to shutdown workers on coordinator exit.""" + global _WATCHDOG + if _WATCHDOG is None: + # Ensure we can send a few pings before we timeout! + ping_interval = min(shutdown_timeout / 10., ping_interval) + _WATCHDOG = WatchdogManager(session, devices, ping_interval, + shutdown_timeout) + _WATCHDOG.configure_and_run() + + +class GracefulShutdownHook(session_run_hook.SessionRunHook): + """Session hook that watches for shutdown events. + + If a shutdown is indicated, `saver.save(checkpoint_prefix)` is executed, and a + SystemShutdown exception is raised to terminate the main session. If `saver` + is None the `SAVERS` collection will be read to find a saver. + + `on_shutdown_hooks` is an optional list of functions that should be called + after checkpointing. The function is called with (`run_context`, + `all_workers`, `lame_workers`). + + If `heartbeat_group` is not specified, it will default to all CPU workers + in the system. + """ + + def __init__(self, checkpoint_prefix, saver=None, on_shutdown_hooks=None): + self._saver = saver + self._checkpoint_prefix = checkpoint_prefix + self._on_shutdown_hooks = on_shutdown_hooks if on_shutdown_hooks else [] + + # Worker heartbeats are managed independently of the main training graph. + self._graph = ops.Graph() + self._workers = None + self._session = None + self._heartbeat_supported = False + + def after_create_session(self, training_session, coord): # pylint: disable=unused-argument + # N.B. We have to pull the global step here to avoid it being unavailable + # at checkpoint time; the graph has been frozen at that point. + if training_util.get_global_step() is None and self.saver() is not None: + raise ValueError( + 'Saver defined but no global step. Run `get_or_create_global_step()`' + ' in your model definition to allow checkpointing.') + + with self._graph.as_default(): + logging.info('Installing graceful shutdown hook.') + self._session = _clone_session(training_session, self._graph) + self._workers = WorkerHeartbeatManager.from_devices( + self._session, all_worker_devices(self._session)) + self._heartbeat_supported = self._workers.num_workers() > 0 + if self._heartbeat_supported: + self._workers.configure( + event_pb2.WorkerHeartbeatRequest( + shutdown_mode=event_pb2.WAIT_FOR_COORDINATOR)) + else: + logging.warn( + 'No workers support hearbeats. Failure handling will be disabled.') + + def saver(self): + if self._saver: + return self._saver + + savers = ops.get_collection(ops.GraphKeys.SAVERS) + if not savers: + return None + + if not isinstance(savers, list): + return savers + + if len(savers) > 1: + logging.error( + 'Multiple savers in the SAVERS collection. On-demand checkpointing ' + 'will be disabled. Pass an explicit `saver` to the constructor to ' + 'override this behavior.') + return None + + return savers[0] + + def after_run(self, run_context, run_values): + del run_values + + if not self._heartbeat_supported: + return + + lame_workers = self._workers.lame_workers() + if lame_workers: + logging.info('ShutdownHook: lame workers found: %s', lame_workers) + + if self.saver(): + logging.info('ShutdownHook: saving checkpoint to %s', + self._checkpoint_prefix) + self.saver().save( + run_context.session, + self._checkpoint_prefix, + global_step=training_util.get_global_step(), + write_state=True, + ) + else: + logging.info('ShutdownHook: no Saver defined.') + + for fn in self._on_shutdown_hooks: + fn(run_context, self._workers, lame_workers) + + +class RestartComputation(object): + """Restart the entire computation. + + This hook shuts down all workers and returns control to the top-level by + throwing a CoordinatorShutdownException. + """ + + def __init__(self, timeout_ms=10000): + self.timeout_ms = timeout_ms + + def __call__(self, run_context, all_workers, lame_workers): + del run_context, lame_workers + all_workers.shutdown(timeout_ms=self.timeout_ms) + + logging.info('Terminating coordinator.') + raise CoordinatorShutdownException() + + +class ShutdownLameWorkers(object): + """Shutdown lamed workers. + + Processing will continue normally (typically by waiting for the down + workers to be restarted). + """ + + def __init__(self, timeout_ms=10000): + self.timeout_in_ms = timeout_ms + + def __call__(self, run_context, all_workers, lame_workers): + lame_workers.shutdown(timeout_ms=self.timeout_in_ms) diff --git a/tensorflow/python/tpu/tensor_tracer.py b/tensorflow/python/tpu/tensor_tracer.py new file mode 100644 index 0000000000..6d41d8768a --- /dev/null +++ b/tensorflow/python/tpu/tensor_tracer.py @@ -0,0 +1,1638 @@ +# 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 utility to trace tensor values on TPU.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import os.path +import re +import sys + +from tensorflow.python.framework import constant_op +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import graph_io +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 control_flow_ops +from tensorflow.python.ops import control_flow_util +from tensorflow.python.ops import gen_math_ops +from tensorflow.python.ops import init_ops +from tensorflow.python.ops import linalg_ops +from tensorflow.python.ops import logging_ops +from tensorflow.python.ops import math_ops +from tensorflow.python.ops import state_ops +from tensorflow.python.ops import variable_scope +from tensorflow.python.platform import gfile +from tensorflow.python.platform import tf_logging as logging +from tensorflow.python.tpu import tpu +from tensorflow.python.tpu.ops import tpu_ops + +_TRACER_LOG_PREFIX = ' [>>>TT>>>]' +_DEVICE_TYPE_TPU = 'tpu' +_DEVICE_TYPE_CPU = 'cpu' +_TRACE_MODE_NAN_INF = 'nan-inf' +_TRACE_MODE_PART_TENSOR = 'part-tensor' +_TRACE_MODE_PART_TENSOR_SIZE = 3 +_TRACE_MODE_FULL_TENSOR = 'full-tensor' +_TRACE_MODE_NORM = 'norm' +_TRACE_MODE_MAX_ABS = 'max-abs' +_SUBMODE_BRIEF = 'brief' +_SUBMODE_DETAILED = 'detailed' +_REASON_OUTSIDE_OP_RANGE = 'not-traced-outside-op-range' +_REASON_UNSAFE_OP = 'not-traced-unsafe-op' +_REASON_WHILELOOP_OP = 'not-traced-special-whileloop-op' +_REASON_UNSAFE_SCALAR = 'not-traced-unsafe-scalar' +_REASON_LESS_INTERESTING_OP = 'not-traced-less-interesting-op' +_REASON_DEVICE_MISMATCH = 'not-traced-device-mismatch' +_REASON_DYNAMIC_SHAPE = 'not-traced-dynamic-shape' +_REASON_SCALAR_GET_TRACED = 'traced-scalar' +_REASON_TENSOR_GET_TRACED = 'traced-tensor' +_REASON_USER_INCLUDED = 'traced-user-included' +_REASON_USER_EXCLUDED = 'not-traced-user-excluded' +_REASON_NOT_EXECUTED = 'not-traced-not-in-exec-path' +_REASON_NON_NUMERIC_TENSOR = 'not-traced-non-numeric-tensor' +_REASON_FEEDS_WHILELOOP_OP = 'not-traced-feeds-special-whileloop-op' +_MARKER_SECTION_BEGIN = '!!!!!!! section-begin:' +_MARKER_SECTION_END = '!!!!!!! section-end:' +_SECTION_NAME_CONFIG = 'configuration' +_SECTION_NAME_REASON = 'reason' +_SECTION_NAME_OP_LIST = 'op-list' +_SECTION_NAME_TENSOR_LIST = 'tensor-list' +_SECTION_NAME_CACHE_INDEX_MAP = 'cache-index-map' +_SECTION_NAME_GRAPH = 'graph' +_FIELD_NAME_VERSION = 'version:' +_FIELD_NAME_DEVICE = 'device:' +_FIELD_NAME_TRACE_MODE = 'trace-mode:' +_FIELD_NAME_SUBMODE = 'submode:' +_FIELD_NAME_NUM_REPLICAS = 'num-replicas:' +_FIELD_NAME_NUM_REPLICAS_PER_HOST = 'num-replicas-per-host:' +_FIELD_NAME_NUM_HOSTS = 'num-hosts:' +_FIELD_NAME_NUM_OPS = 'number-of-ops:' +_FIELD_NAME_NUM_TENSORS = 'number-of-tensors:' +_FIELD_NAME_NUM_CACHE_INDICES = 'number-of-indices:' +_FIELD_NAME_TOPOLOGICAL_SORT_SUCCEED = 'topological-sort-succeed:' +_FLAGS_ENV_VAR = 'TENSOR_TRACER_FLAGS' +_FLAG_SINGLE_QUOTE_PAT = re.compile(r"\s*--([^=]+)='([^']*)'") +_FLAG_DOUBLE_QUOTE_PAT = re.compile(r'\s*--([^=]+)="([^"]*)"') +_FLAG_NO_QUOTE_PAT = re.compile(r'\s*--([^=]+)=(\S*)') +_FLAG_NO_EQUAL_PAT = re.compile(r'\s*--([^=]+)\s*') +_FLAG_NAME_ENABLE = 'enable' +_FLAG_NAME_TRACE_MODE = 'trace_mode' +_FLAG_NAME_USE_COMPACT_TRACE = 'compact_trace' +_FLAG_NAME_SUBMODE = 'submode' +_FLAG_NAME_INCLUDE_LESS_INTERESTING_OPS = 'include_less_interesting_ops' +_FLAG_NAME_EXCLUDED_OPNAMES = 'excluded_opnames' +_FLAG_NAME_EXCLUDED_OPTYPES = 'excluded_optypes' +_FLAG_NAME_INCLUDED_OPNAMES = 'included_opnames' +_FLAG_NAME_INCLUDED_OPTYPES = 'included_optypes' +_FLAG_NAME_TRACE_DIR = 'trace_dir' +_FLAG_NAME_REPORT_FILE = 'report_file' +_FLAG_NAME_USE_TEST_UNDECLARED_OUTPUTS_DIR = 'use_test_undeclared_outputs_dir' +_FLAG_NAME_OP_RANGE = 'op_range' +# Folder to dump the pre (before tensor tracer updates) and post graphs (after +# tensor tracer updates). +_FLAG_DUMP_BEFORE_AFTER_GRAPHS = 'dump_graphs' +_OP_RANGE_PAT = re.compile(r'(\d+):(\d+)') +_OUTPUT_STREAM_ESCAPE = 'file://' +_TEST_UNDECLARED_OUTPUTS_DIR_ENV_VAR = 'TEST_UNDECLARED_OUTPUTS_DIR' +_TENSOR_TRACER_COLLECTION = 'tensor_tracer_variables' +_TENSOR_TRACER_CHECKPOINT = 'tensor_tracer_checkpoint' +_TRACE_FILE_NAME = 'trace.all' +_COMPACT_TRACE_FILE_PREFIX = 'compact_trace.' +_COMPACT_TRACE_ENTRY_INIT_VALUE = -1.0 +_TENSOR_TRACER_STORAGE = 'tensor_tracer_storage' +_TENSOR_VALUES_CACHE = 'tensor_values_cache' +_REPLICA_ID_TAG = '#replica-id: ' + + +def tensor_tracepoint(tensor, checkpoint_name): + """Adds a checkpoint with the given checkpoint name for the given tensor. + + The tensor will be added to the list of tensors that will be traced by the + tensor tracer. + + Args: + tensor: the tensor object for which the tracing is requested. + checkpoint_name: a string name for the checkpoint. This name has to be a + unique name if used within model comparison. The tensors that have the same + checkpoint identifier is compared in model comparison. + Returns: + The provided tensor. + """ + + tensor.graph.get_collection(_TENSOR_TRACER_COLLECTION) + tensor.graph.add_to_collection(_TENSOR_TRACER_COLLECTION, + (tensor, checkpoint_name)) + return tensor + + +def keras_layer_tracepoint(layer, checkpoint_name): + """An interface for adding the tensor outputs of a keras layer. + + Encapsulates tensor_tracepoint. + + Args: + layer: A keras layer. + checkpoint_name: a string name for the checkpoint. This name has to be a + unique name if used within model comparison. The tensors that have the same + checkpoint identifier is compared in model comparison. + + Returns: + The provided layer. + """ + try: + outputs = layer.output + if tensor_util.is_tensor(outputs): + tensor_tracepoint(outputs, '%s' % (checkpoint_name)) + else: + idx = 0 + for output_tensor in outputs: + if tensor_util.is_tensor(outputs): + tensor_tracepoint(output_tensor, '%s_%d' % (checkpoint_name, idx)) + idx += 1 + except AttributeError: + pass + except RuntimeError: + pass + return layer + + +def _trace_files_need_precreated(output_dir): + """Return True if trace files must be pre-created by users.""" + + if not output_dir.startswith('/'): + return False + if len(output_dir) < 5: + return False + if output_dir[2] != 'n': + return False + if output_dir[3] != 's': + return False + if output_dir[1] != 'c': + return False + if output_dir[4] != '/': + return False + return True + + +def _get_tensor_values_cache(graph=None): + """Returns the variable that implements tensor-value caching.""" + + graph = graph or ops.get_default_graph() + collection = graph.get_collection(_TENSOR_TRACER_STORAGE) + if len(collection) == 1: + return collection[0] + elif not collection: + raise RuntimeError('%s has not been created'%_TENSOR_VALUES_CACHE) + else: + raise RuntimeError('Multiple %s created'%_TENSOR_VALUES_CACHE) + return None + + +def _create_tensor_values_cache(graph, num_tensors): + """Creates a variable as the cache to store intermediate tensor values.""" + graph = graph or ops.get_default_graph() + # Create in proper graph and base name_scope. + with graph.as_default() as g, g.name_scope(None): + return variable_scope.get_variable( + _TENSOR_VALUES_CACHE, + shape=[num_tensors], + dtype=dtypes.float32, + initializer=init_ops.constant_initializer( + _COMPACT_TRACE_ENTRY_INIT_VALUE), + trainable=False, + use_resource=True, + collections=[_TENSOR_TRACER_STORAGE, ops.GraphKeys.GLOBAL_VARIABLES]) + + +class TensorTracer(object): + """A software construct for tracing tensor values in a TF graph on TPU. + + This utility is disabled by default. It can be enabled by setting + the TENSOR_TRACER_FLAGS env variable as: + export TENSOR_TRACER_FLAGS="--enable=1" + If it is enabled, it will trace the output tensor values of + selected Ops in the graph. It has two outputs: (1) the traces and (2) + a report. The traces are dumped to a specified local file on the TPU + host. The report is printed to the log.info of the TPU job. + By passing options via the env variable, users can change: + (1) the trace mode (e.g., detecting NaN/Inf, printing partial or + full tensor values) + (2) which Ops to be traced (via op.name or op.type) + (3) output trace file path. + """ + # The set of graphs that are rewritten by tensor tracer. + _traced_graphs = set() + @staticmethod + def _match_next_flag(flags, pos): + """Returns the match for the next TensorTracer flag. + + Args: + flags: a string that contains the flags. + pos: where in flags to start the search. + + Returns: + A pair where the first element is the regular-expression + match found and the second element indicates if the match + has a value. + """ + + match = _FLAG_DOUBLE_QUOTE_PAT.match(flags, pos) + if match: + return match, True + match = _FLAG_SINGLE_QUOTE_PAT.match(flags, pos) + if match: + return match, True + match = _FLAG_NO_QUOTE_PAT.match(flags, pos) + if match: + return match, True + match = _FLAG_NO_EQUAL_PAT.match(flags, pos) + if match: + # The flag is found but is not given a value. + return match, False + # The flag is not found. + return None, False + + @staticmethod + def validate_flag_names(): + """Validates if the TensorTrace flags passed are valid.""" + valid_flag_names = [_FLAG_NAME_ENABLE, _FLAG_NAME_TRACE_MODE, + _FLAG_NAME_USE_COMPACT_TRACE, + _FLAG_NAME_SUBMODE, + _FLAG_NAME_EXCLUDED_OPNAMES, + _FLAG_NAME_EXCLUDED_OPTYPES, + _FLAG_NAME_INCLUDED_OPNAMES, + _FLAG_NAME_INCLUDED_OPTYPES, + _FLAG_NAME_TRACE_DIR, + _FLAG_NAME_REPORT_FILE, + _FLAG_NAME_USE_TEST_UNDECLARED_OUTPUTS_DIR, + _FLAG_NAME_INCLUDE_LESS_INTERESTING_OPS, + _FLAG_NAME_OP_RANGE, + _FLAG_DUMP_BEFORE_AFTER_GRAPHS] + tensor_tracer_flags = os.environ.get(_FLAGS_ENV_VAR) + if not tensor_tracer_flags: + return + pos = 0 + while True: + match, _ = TensorTracer._match_next_flag(tensor_tracer_flags, pos) + if not match: + break + flag_name = match.group(1) + if flag_name not in valid_flag_names: + raise ValueError( + 'The flag name "%s" passed via the environment variable "%s" ' + 'is invalid. Valid flag names are:' + '\n%s'%(flag_name, _FLAGS_ENV_VAR, valid_flag_names)) + pos = match.end() + + @staticmethod + def print_flag_values(): + """Prints all TensorTracer flags passed via environment variables.""" + + tensor_tracer_flags = os.environ.get(_FLAGS_ENV_VAR) + if not tensor_tracer_flags: + return 'Env variable "%s" is not set'%_FLAGS_ENV_VAR + result = 'Env variable "%s" is set to "%s"\n'%(_FLAGS_ENV_VAR, + tensor_tracer_flags) + result += 'Individual flag value:\n' + pos = 0 + while True: + match, has_value = TensorTracer._match_next_flag( + tensor_tracer_flags, pos) + if not match: + break + flag_name = match.group(1) + if has_value: + flag_value = match.group(2) + else: + flag_value = None + result += ' %s: %s\n'%(flag_name, flag_value) + pos = match.end() + result += '\n' + return result + + @staticmethod + def get_flag_value(wanted_flag_name): + """Returns the value of a TensorTracer flags. + + Args: + wanted_flag_name: the name the the flag we are looking for. + + Returns: + A pair where the first element indicates if the flag is + found and the second element is the value of the flag. + + Raises: + RuntimeError: If supposedly deadcode is reached. + """ + + tensor_tracer_flags = os.getenv(_FLAGS_ENV_VAR) + if not tensor_tracer_flags: + return False, None + pos = 0 + while True: + match, has_value = TensorTracer._match_next_flag( + tensor_tracer_flags, pos) + if not match: + return False, None + flag_name = match.group(1) + if has_value: + flag_value = match.group(2) + else: + flag_value = None + if flag_name == wanted_flag_name: + return True, flag_value + pos = match.end() + raise RuntimeError('Should not reach here.') + + @staticmethod + def flag_value_to_re_list(flag_name): + """Converts list of strings to compiled RE.""" + + re_list = [] + found, flag_value = TensorTracer.get_flag_value(flag_name) + if not found or not flag_value: + return re_list + list_of_values = flag_value.split() + for v in list_of_values: + r = re.compile(v) + re_list.append(r) + return re_list + + @staticmethod + def _is_flag_on(flag_name): + """Returns True if the given flag is on.""" + + found, flag_value = TensorTracer.get_flag_value(flag_name) + if not found: + return False + if flag_value is None: + return True + # Depends on the flag value. + flag_value = flag_value.lower() + enabled = flag_value in ['1', 't', 'true', 'y', 'yes'] + return enabled + + @staticmethod + def is_enabled(): + """Returns True if TensorTracer is enabled.""" + + return TensorTracer._is_flag_on(_FLAG_NAME_ENABLE) + + @staticmethod + def use_test_undeclared_outputs_dir(): + """Decides the output directory of the report and trace files. + + Args: + None. + + Returns: + True if the output files should be written to the + test-undeclared-outputs-directory defined via an + env variable. + """ + + return TensorTracer._is_flag_on( + _FLAG_NAME_USE_TEST_UNDECLARED_OUTPUTS_DIR) + + @staticmethod + def use_compact_trace(): + return TensorTracer._is_flag_on( + _FLAG_NAME_USE_COMPACT_TRACE) + + @staticmethod + def check_device_type(device_type): + """Checks if the given device type is valid.""" + + if device_type not in [_DEVICE_TYPE_TPU, _DEVICE_TYPE_CPU]: + raise ValueError('Invalid device_type "%s"'%device_type) + + @staticmethod + def check_trace_mode(trace_mode): + """Checks if the given trace mode is valid.""" + + valid_trace_modes = [_TRACE_MODE_NAN_INF, _TRACE_MODE_PART_TENSOR, + _TRACE_MODE_FULL_TENSOR, _TRACE_MODE_NORM, + _TRACE_MODE_MAX_ABS] + if trace_mode not in valid_trace_modes: + raise ValueError('Invalid trace mode "%s" given to the Tensor_Tracer.' + 'Valid trace modes are: %s'%(trace_mode, + valid_trace_modes)) + + @staticmethod + def check_submode(submode): + """Checks if the given submode is valid.""" + + if not submode: + return + valid_submodes = [_SUBMODE_DETAILED, _SUBMODE_BRIEF] + if submode not in valid_submodes: + raise ValueError('Invalid submode "%s" given to the Tensor_Tracer.' + 'Valid submodes are: %s'%(submode, + valid_submodes)) + + @staticmethod + def loop_cond_op(op): + return op.type in ('LoopCond', 'RefLoopCond') + + @staticmethod + def while_loop_op(op): + """Returns true if op is one of the special ops of in a while loop. + + Args: + op: A tf.Operation. + + Returns: + True if the given op is one of [Switch, Merge, Enter, Exit, + NextIteration, LoopCond], which are all building blocks for TF while + loops. + """ + return (control_flow_util.IsLoopSwitch(op) or + control_flow_util.IsLoopMerge(op) or + control_flow_util.IsLoopEnter(op) or + control_flow_util.IsLoopExit(op) or + TensorTracer.loop_cond_op(op) or + op.type in ('RefNextIteration', 'NextIteration')) + + @staticmethod + def unsafe_op(op): + """Returns True if this op is not safe to be traced.""" + + if control_flow_util.IsInCond(op): + return True + # Reasons for not including following op types: + # Assign: cause incorrect result with CPU tracing. + if op.type in ['Assign']: + return True + return False + + @staticmethod + def device_mismatch(device_type, op): + if device_type == _DEVICE_TYPE_TPU: + # pylint: disable=protected-access + return tpu._TPU_REPLICATE_ATTR not in op.node_def.attr + # pylint: enable=protected-access + return False + + @staticmethod + def unsafe_scalar_trace(op): + """Return true if scalar output tensor from Op is not safe to be traced.""" + + # Tracing the following causes cycle in the graph on TPU. + if op.type in ['LoopCond', 'Enter', 'Merge', 'Const', + 'Switch', 'Less', 'ReadVariableOp']: + return True + # Tracing the following will cause casting-issue + # with the norm tracing mode or other compilation issues on CPU. + if op.type in ['VarHandleOp', 'IteratorToStringHandle', + 'IteratorGetNext', 'OneShotIterator', + 'IteratorV2', 'MakeIterator', + 'BatchDatasetV2', 'MapDataset', + 'FixedLengthRecordDataset', 'TakeDataset', 'ZipDataset', + 'Placeholder', 'PlaceholderWithDefault', 'StridedSlice']: + return True + return False + + @staticmethod + def less_interesting_op(op): + """Returns True if the given Op is not an interesting one to be traced.""" + + found, _ = TensorTracer.get_flag_value( + _FLAG_NAME_INCLUDE_LESS_INTERESTING_OPS) + if found: + # users force to include all ops. + return False + # Following ops are highly unlikey to cause bugs. + return op.type in ['Const', 'Identity', 'Cast', 'Shape'] + + @staticmethod + def reason(op_idx, details): + """Returns reason why the Op at op_idx is traced or not.""" + + return '%d %s'%(op_idx, details) + + @staticmethod + def topological_sort(g): + """Performs topological sort on the given graph. + + Args: + g: the graph. + + Returns: + A pair where the first element indicates if the topological + sort succeeded (True if there is no cycle found; False if a + cycle is found) and the second element is either the sorted + list of nodes or the cycle of nodes found. + """ + + def visit(op, cycle, permanently_marked_ops, + temporarily_marked_ops, sorted_ops): + """Recursively visits all Ops in a graph. + + Args: + op: the current Op being visited. + cycle: a cycle of Ops found. + permanently_marked_ops: the set of Ops that were already visited. + temporarily_marked_ops: the set of Ops that we have visited during + the current descent. + sorted_ops: the list of Ops sorted in topological order. + """ + + if cycle: + return + if op in permanently_marked_ops: + return + if op in temporarily_marked_ops: + cycle = temporarily_marked_ops + return + temporarily_marked_ops.add(op) + for i in range(len(op.outputs)): + out_tensor = op.outputs[i] + for consumer_op in out_tensor.consumers(): + visit(consumer_op, cycle, permanently_marked_ops, + temporarily_marked_ops, sorted_ops) + # pylint: disable=protected-access + for ctrl_output_op in op._control_outputs: + # pylint: enable=protected-access + visit(ctrl_output_op, cycle, permanently_marked_ops, + temporarily_marked_ops, sorted_ops) + temporarily_marked_ops.remove(op) + permanently_marked_ops.add(op) + sorted_ops.insert(0, op) + + graph_cycle = set([]) + sorted_ops = [] + permanently_marked_ops = set([]) + temporarily_marked_ops = set([]) + unsorted_ops = g.get_operations() + for op in unsorted_ops: + visit(op, graph_cycle, permanently_marked_ops, + temporarily_marked_ops, sorted_ops) + if graph_cycle: + return (False, graph_cycle) + else: + assert len(unsorted_ops) == len(sorted_ops) + return (True, sorted_ops) + + @staticmethod + def _make_op_and_tensor_maps(op_list): + """Creates various maps and lists from op_list. + + Args: + op_list: a list of Ops + + Returns: + opname_idx_map: a map from Op's name to its index in op_list. + tensor_list: a list of output tensors of the Ops in op_list. + tensorname_idx_map: a map from output tensor name to its index + in tensor_list. + """ + + opname_idx_map = {} + tensor_list = [] + tensorname_idx_map = {} + for op_id, op in enumerate(op_list): + if op.name in opname_idx_map: + raise ValueError('Duplicated Op name: %s'%op.name) + opname_idx_map[op.name] = op_id + for output_tensor in op.outputs: + if output_tensor.name not in tensorname_idx_map: + tensor_list.append(output_tensor) + tensorname_idx_map[output_tensor.name] = len(tensor_list)-1 + return (opname_idx_map, tensor_list, tensorname_idx_map) + + def __init__(self): + """Initializes a TensorTracer. + + Sets the various member fields from the flags (if given) or the defaults. + """ + self._version = 'use-outside-compilation' + self._device_type = None + TensorTracer.validate_flag_names() + found, self._trace_mode = TensorTracer.get_flag_value(_FLAG_NAME_TRACE_MODE) + if not found or not self._trace_mode: + self._trace_mode = _TRACE_MODE_NAN_INF + TensorTracer.check_trace_mode(self._trace_mode) + found, self._submode = TensorTracer.get_flag_value(_FLAG_NAME_SUBMODE) + if not found or not self._submode: + self._submode = _SUBMODE_DETAILED + TensorTracer.check_submode(self._submode) + self._part_tensor_size = _TRACE_MODE_PART_TENSOR_SIZE + self._instrument_records = {} + self._set_trace_dir() + self._set_report_file() + self._set_op_range() + self._set_excluded_opnames() + self._set_excluded_optypes() + self._set_included_opnames() + self._set_included_optypes() + self._num_replicas = None + self._num_replicas_per_host = None + self._num_hosts = None + self._replica_id = None + _, self._graph_dump_path = TensorTracer.get_flag_value( + _FLAG_DUMP_BEFORE_AFTER_GRAPHS) + + def _add_replica_id_to_graph(self): + """Adds nodes for computing the replica ID to the graph.""" + + if self._num_replicas: + with ops.control_dependencies(None): + # Uses None as dependency to run outside of TPU graph rewrites. + self._replica_id = tpu_ops.tpu_replicated_input( + list(range(self._num_replicas)), + name='tt_replica_id') + else: + self._replica_id = 'unknown' + + def _set_trace_dir(self): + found, self._trace_dir = TensorTracer.get_flag_value(_FLAG_NAME_TRACE_DIR) + if found and self._trace_dir \ + and TensorTracer.use_test_undeclared_outputs_dir(): + raise ValueError('Cannot not use --%s and --%s at the same time' + %(_FLAG_NAME_TRACE_DIR, + _FLAG_NAME_USE_TEST_UNDECLARED_OUTPUTS_DIR)) + if TensorTracer.use_test_undeclared_outputs_dir(): + self._trace_dir = os.environ.get(_TEST_UNDECLARED_OUTPUTS_DIR_ENV_VAR) + + def _set_report_file(self): + """Sets the path of the output report file.""" + + found, self._report_file_path = TensorTracer.get_flag_value( + _FLAG_NAME_REPORT_FILE) + if found and self._report_file_path \ + and TensorTracer.use_test_undeclared_outputs_dir(): + if os.path.isabs(self._report_file_path): + raise ValueError('If use_test_undeclared_outputs_dir is set,' + 'report_file_path cannot be an absolute path (%s)' + %self._report_file_path) + outputs_dir = os.environ.get(_TEST_UNDECLARED_OUTPUTS_DIR_ENV_VAR) + self._report_file_path = os.path.join(outputs_dir, + self._report_file_path) + if not self._report_file_path: + self._report_file = None + return + try: + self._report_file = gfile.Open(self._report_file_path, 'w') + except IOError as e: + raise e + + def _close_report_file(self): + if self._report_file: + self._report_file.close() + + def _set_op_range(self): + """Sets the index range of the Ops that we will consider tracing.""" + + found, op_range = TensorTracer.get_flag_value(_FLAG_NAME_OP_RANGE) + if not found or not op_range: + self._op_range = (-1, -1) # this means including all ops. + return + match = _OP_RANGE_PAT.match(op_range) + if not match: + self._op_range = (-1, -1) # this means including all ops. + return + self._op_range = (int(match.group(1)), int(match.group(2))) + + def _inside_op_range(self, idx): + """Return True if the given index is inside the selected range.""" + + if idx < self._op_range[0]: + return False + return self._op_range[1] < 0 or idx <= self._op_range[1] + + def _set_excluded_opnames(self): + self._excluded_opname_re_list = TensorTracer.flag_value_to_re_list( + _FLAG_NAME_EXCLUDED_OPNAMES) + + def _set_excluded_optypes(self): + self._excluded_optype_re_list = TensorTracer.flag_value_to_re_list( + _FLAG_NAME_EXCLUDED_OPTYPES) + + def _set_included_opnames(self): + self._included_opname_re_list = TensorTracer.flag_value_to_re_list( + _FLAG_NAME_INCLUDED_OPNAMES) + + def _set_included_optypes(self): + self._included_optype_re_list = TensorTracer.flag_value_to_re_list( + _FLAG_NAME_INCLUDED_OPTYPES) + + def _is_user_included_op(self, op): + for opname_re in self._included_opname_re_list: + if opname_re.match(op.name): + return True + for optype_re in self._included_optype_re_list: + if optype_re.match(op.type): + return True + return False + + def _is_user_excluded_op(self, op): + for opname_re in self._excluded_opname_re_list: + if opname_re.match(op.name): + return True + for optype_re in self._excluded_optype_re_list: + if optype_re.match(op.type): + return True + return False + + def _use_tensor_values_cache(self): + """Returns True if immediate tensors should be first saved to a cache.""" + + if self._trace_mode not in set([_TRACE_MODE_NAN_INF, + _TRACE_MODE_NORM, _TRACE_MODE_MAX_ABS]): + return False + if self._trace_dir and _trace_files_need_precreated(self._trace_dir): + return True + if TensorTracer.use_compact_trace(): + return True + return False + + def _save_tensor_value_to_cache_op(self, graph, cache_idx, updates): + """Returns an Op that will save the given updates to an entry in the cache.""" + + cache = _get_tensor_values_cache(graph) + indices = constant_op.constant([cache_idx]) + return state_ops.scatter_update(cache, indices, updates).op + + def _write_report(self, content): + """Writes the given content to the report.""" + + line = '%s %s'%(_TRACER_LOG_PREFIX, content) + if self._report_file: + self._report_file.write(line) + else: + logging.info(line) + + def _write_config_section(self): + """Writes the config section of the report.""" + + self._write_report('%s %s\n'%(_MARKER_SECTION_BEGIN, _SECTION_NAME_CONFIG)) + self._write_report('%s %s\n'%(_FIELD_NAME_VERSION, self._version)) + self._write_report('%s %s\n'%(_FIELD_NAME_DEVICE, self._device_type)) + self._write_report('%s %s\n'%(_FIELD_NAME_TRACE_MODE, self._trace_mode)) + self._write_report('%s %s\n'%(_FIELD_NAME_SUBMODE, self._submode)) + self._write_report('%s %s\n'%(_FIELD_NAME_NUM_REPLICAS, self._num_replicas)) + self._write_report('%s %s\n'%(_FIELD_NAME_NUM_REPLICAS_PER_HOST, + self._num_replicas_per_host)) + self._write_report('%s %s\n'%(_FIELD_NAME_NUM_HOSTS, self._num_hosts)) + self._write_report('%s %s\n'%(_MARKER_SECTION_END, _SECTION_NAME_CONFIG)) + + def _write_reason_section(self): + """Writes the reason section of the report.""" + + self._write_report('%s %s\n'%(_MARKER_SECTION_BEGIN, _SECTION_NAME_REASON)) + for key in sorted(self._instrument_records): + self._write_report('"%s" %s\n'%(key, self._instrument_records[key])) + self._write_report('%s %s\n'%(_MARKER_SECTION_END, _SECTION_NAME_REASON)) + + def _write_op_list_section(self, op_list): + """Writes the Op-list section of the report.""" + + self._write_report('%s %s\n'%(_MARKER_SECTION_BEGIN, _SECTION_NAME_OP_LIST)) + self._write_report('%s %d\n'%(_FIELD_NAME_NUM_OPS, len(op_list))) + for i in range(0, len(op_list)): + op = op_list[i] + line = '%d "%s" %s'%(i, op.name, op.type) + for out_tensor in op.outputs: + if out_tensor.name not in self._tensorname_idx_map: + raise ValueError( + 'out_tensor %s is not in tensorname_idx_map'%out_tensor.name) + line += ' %d'%self._tensorname_idx_map[out_tensor.name] + line += '\n' + self._write_report(line) + self._write_report('%s %s\n'%(_MARKER_SECTION_END, _SECTION_NAME_OP_LIST)) + + def _write_tensor_list_section(self, tensor_list, opname_idx_map): + """Writes the tensor-list section of the report.""" + + self._write_report('%s %s\n'%(_MARKER_SECTION_BEGIN, + _SECTION_NAME_TENSOR_LIST)) + self._write_report('%s %d\n'%(_FIELD_NAME_NUM_TENSORS, len(tensor_list))) + for i in range(0, len(tensor_list)): + tensor = tensor_list[i] + line = '%d "%s"'%(i, tensor.name) + for consumer_op in tensor.consumers(): + if consumer_op.name not in opname_idx_map: + raise ValueError( + 'consumer_op %s is not in opname_idx_map'%consumer_op.name) + line += ' %d'%opname_idx_map[consumer_op.name] + line += '\n' + self._write_report(line) + self._write_report('%s %s\n'%(_MARKER_SECTION_END, + _SECTION_NAME_TENSOR_LIST)) + + def _write_cache_index_map_section(self): + """Writes the mapping from cache index to tensor index to the report.""" + + self._write_report('%s %s\n'%(_MARKER_SECTION_BEGIN, + _SECTION_NAME_CACHE_INDEX_MAP)) + self._write_report('%s %d\n'%(_FIELD_NAME_NUM_CACHE_INDICES, + len(self._cache_idx_to_tensor_idx))) + for cache_idx in range(0, len(self._cache_idx_to_tensor_idx)): + tensor_idx = self._cache_idx_to_tensor_idx[cache_idx] + line = '%d %d\n'%(cache_idx, tensor_idx) + self._write_report(line) + self._write_report('%s %s\n'%(_MARKER_SECTION_END, + _SECTION_NAME_CACHE_INDEX_MAP)) + + def _write_graph_section(self, succeed, sorted_or_cycle): + """Writes the graph section of the report.""" + + self._write_report('%s %s\n'%(_MARKER_SECTION_BEGIN, _SECTION_NAME_GRAPH)) + self._write_report('%s %s\n'%(_FIELD_NAME_TOPOLOGICAL_SORT_SUCCEED, + succeed)) + l = list(sorted_or_cycle) + for i in range(0, len(l)): + self._write_report('%d "%s"\n'%(i, l[i].name)) + self._write_report('%s %s\n'%(_MARKER_SECTION_END, _SECTION_NAME_GRAPH)) + + def _preprocess_traced_tensor(self, tensor): + """Computes NAN/Norm/Max on TPUs before sending to CPU. + + Args: + tensor: The tensor to be traced. + Returns: + A tensor that should be input to the trace_function. + Raises: + RuntimeError: If the trace mode is invalid. + """ + + def _detect_nan_inf(tensor): + """Trace function for detecting any NaN/Inf in the tensor.""" + + if tensor.dtype.is_floating: + mask = math_ops.reduce_any( + gen_math_ops.logical_or( + gen_math_ops.is_nan(tensor), gen_math_ops.is_inf(tensor))) + output_tensor = control_flow_ops.cond(mask, + lambda: constant_op.constant(1.0), + lambda: constant_op.constant(0.0)) + else: + output_tensor = constant_op.constant(0.0) + # The shape has to be 1. Set it if it does not have the information. + output_tensor = array_ops.reshape(output_tensor, [1]) + return output_tensor + + def _show_norm(tensor): + tensor = math_ops.cast(tensor, dtypes.float32) + output_tensor = linalg_ops.norm(tensor) + # The shape has to be 1. Set it if it does not have the information. + output_tensor = array_ops.reshape(output_tensor, [1]) + return output_tensor + + def _show_max_abs(tensor): + tensor = math_ops.cast(tensor, dtypes.float32) + output_tensor = math_ops.reduce_max(math_ops.abs(tensor)) + zero = constant_op.constant(0, dtypes.float32) + output_tensor = gen_math_ops.maximum(zero, output_tensor) + # The shape has to be 1. Set it if it does not have the information. + output_tensor = array_ops.reshape(output_tensor, [1]) + return output_tensor + + if self._trace_mode == _TRACE_MODE_NAN_INF: + return _detect_nan_inf(tensor) + if self._trace_mode == _TRACE_MODE_PART_TENSOR: + return tensor + if self._trace_mode == _TRACE_MODE_FULL_TENSOR: + return tensor + if self._trace_mode == _TRACE_MODE_NORM: + return _show_norm(tensor) + if self._trace_mode == _TRACE_MODE_MAX_ABS: + return _show_max_abs(tensor) + raise RuntimeError( + 'Tensor trace fun for %s is not yet implemented' % self._trace_mode) + + def _make_tensor_trace_fun(self, tensor_name): + """Makes the tensor tracing function called by outside compilation. + + Args: + tensor_name: name of the tensor being traced. + + Returns: + A function to be passed as the first argument to outside compilation. + + Raises: + RuntimeError: If the trace mode is invalid. + """ + + def _print_tensor(tensor_name, num_elements, tensor, output_tensor): + """Prints a tensor value to a file. + + Args: + tensor_name: name of the tensor being traced. + num_elements: number of elements to print (-1 means print all). + tensor: the tensor needs to be returned. + output_tensor: the tensor needs to be printed. + + Returns: + The same tensor passed via the "tensor" argument. + + Raises: + ValueError: If tensor_name is not already in + self._tensorname_idx_map. + """ + + if self._submode == _SUBMODE_BRIEF: + if tensor_name not in self._tensorname_idx_map: + raise ValueError( + 'Tensor name %s is not in the tensorname_idx_map'%tensor_name) + msg = '%d'%self._tensorname_idx_map[tensor_name] + else: + msg = '"%s"'%tensor_name + + if self._trace_dir: + output_path = os.path.join(self._trace_dir, _TRACE_FILE_NAME) + output_stream = _OUTPUT_STREAM_ESCAPE + output_path + else: + output_stream = sys.stderr + return logging_ops.print_v2(msg, array_ops.shape(output_tensor), + '@', self._replica_id, + '\n', output_tensor, '\n', + summarize=num_elements, + output_stream=output_stream) + + def _show_part_tensor(tensor): + """Trace function for printing part of the tensor.""" + + return _print_tensor(tensor_name, self._part_tensor_size, + tensor, tensor) + + def _show_full_tensor(tensor): + """Trace function for printing the entire tensor.""" + + return _print_tensor(tensor_name, -1, tensor, tensor) + + if self._trace_mode == _TRACE_MODE_PART_TENSOR: + return _show_part_tensor + # The input tensor has a shape of "[1]" for _TRACE_MODE_NAN_INF, + # _TRACE_MODE_NORM, and _TRACE_MODE_MAX_ABS, as related computations are + # performed within TPUs and only their results are transferred to CPU. + # Simply, print the full tensor for these trace modes. + if self._trace_mode in [ + _TRACE_MODE_NAN_INF, _TRACE_MODE_NORM, _TRACE_MODE_FULL_TENSOR, + _TRACE_MODE_MAX_ABS + ]: + return _show_full_tensor + + raise RuntimeError('Tensor trace fun for %s is not yet implemented' + %self._trace_mode) + + def _skip_op(self, op_id, op, user_included, user_excluded, + in_exec_path=True): + """Returns True if we should not trace Op.""" + + if TensorTracer.while_loop_op(op): + self._instrument_records[op.name] = TensorTracer.reason( + op_id, _REASON_WHILELOOP_OP) + return True + if TensorTracer.unsafe_op(op): + self._instrument_records[op.name] = TensorTracer.reason( + op_id, _REASON_UNSAFE_OP) + return True + if TensorTracer.device_mismatch(self._device_type, op): + self._instrument_records[op.name] = TensorTracer.reason( + op_id, _REASON_DEVICE_MISMATCH) + return True + if not in_exec_path: + self._instrument_records[op.name] = TensorTracer.reason( + op_id, _REASON_NOT_EXECUTED) + return True + + if not self._inside_op_range(op_id): + self._instrument_records[op.name] = TensorTracer.reason( + op_id, _REASON_OUTSIDE_OP_RANGE) + return True + if TensorTracer.less_interesting_op(op): + self._instrument_records[op.name] = TensorTracer.reason( + op_id, _REASON_LESS_INTERESTING_OP) + return True + if user_included: + self._instrument_records[op.name] = TensorTracer.reason( + op_id, _REASON_USER_INCLUDED) + return False + if user_excluded: + self._instrument_records[op.name] = TensorTracer.reason( + op_id, _REASON_USER_EXCLUDED) + return True + return False + + def _skip_tensor(self, op_id, out_tensor, user_included, + user_excluded): + """Returns True if we should not trace out_tensor.""" + + # Skips a tensor if the tensor has a non-numeric type. + # Note: we cannot use check_ops.is_numeric_tensor(out_tensor) + # because it also excludes tensors with dtypes, bool, and + # float32_ref, which we actually want to trace. + non_numeric_tensor_types = set([dtypes.variant, dtypes.resource, + dtypes.string]) + if out_tensor.dtype in non_numeric_tensor_types: + self._instrument_records[out_tensor.name] = TensorTracer.reason( + op_id, _REASON_NON_NUMERIC_TENSOR) + return True + # Skip a tensor if it feeds a special while loop op. + if [consumer for consumer in out_tensor.consumers() if + TensorTracer.while_loop_op(consumer)]: + self._instrument_records[out_tensor.name] = TensorTracer.reason( + op_id, _REASON_FEEDS_WHILELOOP_OP) + return True + if user_included: + self._instrument_records[out_tensor.name] = TensorTracer.reason( + op_id, _REASON_USER_INCLUDED) + return False + if user_excluded: + self._instrument_records[out_tensor.name] = TensorTracer.reason( + op_id, _REASON_USER_EXCLUDED) + return True + if not out_tensor.get_shape().is_fully_defined(): + # If trace mode is nan-inf, norm or max, then the tensor will be reduced + # to a scalar before the outside compilation call. + if self._trace_mode in [ + _TRACE_MODE_NAN_INF, _TRACE_MODE_NORM, _TRACE_MODE_MAX_ABS + ]: + self._instrument_records[out_tensor.name] = TensorTracer.reason( + op_id, _REASON_TENSOR_GET_TRACED) + return False + else: + self._instrument_records[out_tensor.name] = TensorTracer.reason( + op_id, _REASON_DYNAMIC_SHAPE) + return True + rank = len(out_tensor.shape) + if rank < 1: + # scalar + if TensorTracer.unsafe_scalar_trace(out_tensor.op): + self._instrument_records[out_tensor.name] = TensorTracer.reason( + op_id, _REASON_UNSAFE_SCALAR) + return True + else: + self._instrument_records[out_tensor.name] = TensorTracer.reason( + op_id, _REASON_SCALAR_GET_TRACED) + return False + else: + # tensor + self._instrument_records[out_tensor.name] = TensorTracer.reason( + op_id, _REASON_TENSOR_GET_TRACED) + return False + + def _filter_execution_path_operations(self, operations, fetches): + """Returns the set of ops in the execution path to compute given fetches.""" + + # If no fetch provided, then return all operations. + if fetches is None: + return set(operations) + # Convert to list, if a single element is provided. + if not isinstance(fetches, (list, tuple)): + fetches = [fetches] + # If a tensor is given as fetch, convert it to op. + op_fetches = [] + for fetch in fetches: + if isinstance(fetch, ops.Operation): + op_fetches.append(fetch) + elif isinstance(fetch, ops.Tensor): + op_fetches.append(fetch.op) + else: + raise RuntimeError('Given fetch:%s is neither a tensor nor an op.' + %fetch) + + execution_path_operations = set(op_fetches) + traverse_stack = list(op_fetches) + while True: + if not traverse_stack: + break + head_op = traverse_stack.pop() + input_ops = [tensor_input.op for tensor_input in head_op.inputs] + input_ops.extend(head_op.control_inputs) + + for input_op in input_ops: + if input_op not in execution_path_operations: + # Filter out loop condition operations, tracing them causes a cycle. + # Trace only the loop-body. + if TensorTracer.loop_cond_op(input_op): + continue + execution_path_operations.add(input_op) + traverse_stack.append(input_op) + return execution_path_operations + + def _determine_traced_tensors(self, graph, ops_in_exec_path): + """Determines the tensors that will be traced.""" + + self._traced_tensorname_to_cache_idx_map = {} + self._cache_idx_to_tensor_idx = [] + operations = graph.get_operations() + checkpoint_operations = self._get_checkpoints(graph) + for op_id, op in enumerate(operations): + if checkpoint_operations and op.name not in checkpoint_operations: + continue + user_included = self._is_user_included_op(op) + user_excluded = self._is_user_excluded_op(op) + in_exec_path = op in ops_in_exec_path + if self._skip_op(op_id, op, user_included, user_excluded, in_exec_path): + continue + for i in range(len(op.outputs)): + out_tensor = op.outputs[i] + if self._skip_tensor(op_id, out_tensor, user_included, + user_excluded): + continue + tensor_name = out_tensor.name + if tensor_name in self._traced_tensorname_to_cache_idx_map: + raise ValueError( + 'Tensor name %s should not be already in ' + 'traced_tensorname_to_cache_idx_map'%tensor_name) + if tensor_name not in self._tensorname_idx_map: + raise ValueError( + 'Tensor name %s is not in the tensorname_idx_map'%tensor_name) + tensor_idx = self._tensorname_idx_map[tensor_name] + cache_idx = len(self._traced_tensorname_to_cache_idx_map) + self._traced_tensorname_to_cache_idx_map[tensor_name] = cache_idx + self._cache_idx_to_tensor_idx.append(tensor_idx) + if len(self._traced_tensorname_to_cache_idx_map) != len( + self._cache_idx_to_tensor_idx): + raise RuntimeError('len(self._traced_tensorname_to_cache_idx_map) != ' + 'len(self._cache_idx_to_tensor_idx') + + def _check_trace_files(self): + """Checks if any requirements for trace files are satisfied.""" + + if not self._trace_dir: + # traces will be written to stderr. No need to check trace files. + return + if _trace_files_need_precreated(self._trace_dir): + for replica_id in range(0, self._num_replicas): + trace_file_path = os.path.join( + self._trace_dir, + _COMPACT_TRACE_FILE_PREFIX) + '%d'%replica_id + if not gfile.Exists(trace_file_path): + raise RuntimeError( + '%s must be pre-created with the ' + 'appropriate properties.'%trace_file_path) + else: + if not gfile.Exists(self._trace_dir): + gfile.MkDir(self._trace_dir) + if not gfile.Exists(self._trace_dir): + raise RuntimeError('Failed to create %s'%self._trace_dir) + + def _pre_tracing(self, graph, fetches): + """Work needs to be done prior to TPU or CPU tracing.""" + + self._check_trace_files() + operations = graph.get_operations() + (opname_idx_map, tensor_list, self._tensorname_idx_map) = ( + TensorTracer._make_op_and_tensor_maps(operations)) + self._write_config_section() + self._write_op_list_section(operations) + self._write_tensor_list_section(tensor_list, opname_idx_map) + # Filter out the operations that won't be executed. + # if fetches=None, then ops_in_exec_path = set(operations) + ops_in_exec_path = self._filter_execution_path_operations(operations, + fetches) + self._determine_traced_tensors(graph, ops_in_exec_path) + self._write_cache_index_map_section() + # Does the topological sort before adding any nodes to the graph. + (succeed, sorted_or_cycle) = TensorTracer.topological_sort(graph) + if self._use_tensor_values_cache(): + _create_tensor_values_cache(graph, + len(self._cache_idx_to_tensor_idx)) + return (ops_in_exec_path, succeed, sorted_or_cycle) + + def _post_tracing(self, succeed, sorted_or_cycle): + """Work needs to be done after TPU or CPU tracing.""" + + self._write_reason_section() + self._write_graph_section(succeed, sorted_or_cycle) + self._close_report_file() + + def _get_checkpoints(self, graph): + """Returns the list of Ops that produce the tensors traced with API. + + Args: + graph: the graph of Ops. + + Returns: + A set of operation names which should be traced. + """ + + self._write_report('%s %s\n'%(_MARKER_SECTION_BEGIN, + _TENSOR_TRACER_CHECKPOINT)) + checkpoint_operations = set() + tensor_tracer_variables = graph.get_collection(_TENSOR_TRACER_COLLECTION) + for (tensor, checkpoint_name) in tensor_tracer_variables: + self._write_report('%s %s\n'%(tensor.name, checkpoint_name)) + checkpoint_operations.add(tensor.op.name) + self._write_report('%s %s\n'%(_MARKER_SECTION_END, + _TENSOR_TRACER_CHECKPOINT)) + return checkpoint_operations + + def _generate_flush_cache_op(self, graph, start_replica, on_tpu): + """Generates an Op that will flush the cache to file. + + Args: + graph: the graph of Ops + start_replica: the ID of the first replica being flushed by this Op. + on_tpu: if the graph is executed on TPU. + + Returns: + The Op to flush the cache to file. + """ + def _make_flush_fun(replica_id): + """Makes a function for flushing the cache for the given replica.""" + + def _fun(): + """A function that flushes the cache to a file.""" + + def _flush_fun(cache): + """Flushes the cache to a file.""" + + if isinstance(replica_id, str): + replica_id_str = replica_id + else: + replica_id_str = '%d'%replica_id + if self._trace_dir: + output_path = os.path.join(self._trace_dir, + _COMPACT_TRACE_FILE_PREFIX) \ + + replica_id_str + output_stream = _OUTPUT_STREAM_ESCAPE + output_path + else: + output_stream = sys.stderr + new_step_line = _REPLICA_ID_TAG + replica_id_str + print_op = logging_ops.print_v2( + new_step_line, '\n', + cache, '\n', + summarize=-1, + output_stream=output_stream) + with ops.control_dependencies([print_op]): + return constant_op.constant(0).op + + cache = _get_tensor_values_cache(graph) + if on_tpu: + flush_op = tpu.outside_compilation(_flush_fun, cache.value()) + else: + flush_op = _flush_fun(cache.value()) + with ops.control_dependencies([flush_op]): + reset_value = constant_op.constant(_COMPACT_TRACE_ENTRY_INIT_VALUE, + dtype=cache.dtype, + shape=cache.shape) + assign_op = state_ops.assign(cache, reset_value).op + with ops.control_dependencies([assign_op]): + return flush_op.outputs[0] + + return _fun + + def _f(replica_id): + return _make_flush_fun(replica_id) + def _eq(x): + return math_ops.equal(x, self._replica_id) + def _do_nothing(): + return constant_op.constant(0) + + return control_flow_ops.case({\ + _eq(start_replica): _f(start_replica), \ + _eq(start_replica+1): _f(start_replica+1), \ + _eq(start_replica+2): _f(start_replica+2), \ + _eq(start_replica+3): _f(start_replica+3), \ + _eq(start_replica+4): _f(start_replica+4), \ + _eq(start_replica+5): _f(start_replica+5), \ + _eq(start_replica+6): _f(start_replica+6), \ + _eq(start_replica+7): _f(start_replica+7), \ + }, + default=_do_nothing, + exclusive=True).op + + def _flush_tensor_values_cache(self, graph, tensor_fetches, op_fetches, + on_tpu): + """Flushes the intermediate tensor values in the graph to the cache. + + Args: + graph: the graph of Ops + tensor_fetches: list of tensor results returned by the model_fn. + op_fetches: list of ops that are returned by the model_fn, e.g., train_op. + on_tpu: if the graph is executed on TPU. + + Returns: + An identical copy of tensor_fetches. + """ + # Add a dependency to op and tensor fetches to make sure that all tracing + # ops are executed before flushing trace results. + with ops.control_dependencies(op_fetches + + [tensor.op for tensor in tensor_fetches]): + flush_cache_op_list = [] + for host in range(self._num_hosts): + start_replica = host * 8 + flush_op = self._generate_flush_cache_op(graph, start_replica, on_tpu) + flush_cache_op_list.append(flush_op) + return control_flow_ops.tuple(tensor_fetches, + control_inputs=flush_cache_op_list) + + def _process_tensor_fetches(self, tensor_fetches): + """Check that tensor_fetches is not empty and have valid tensors.""" + # If none or empty list. + if tensor_fetches is None: + raise RuntimeError('tensor_fetches provided to tensor_tracer cannot be ' + 'None.') + if not isinstance(tensor_fetches, (list, tuple)): + tensor_fetches = [tensor_fetches] + elif not tensor_fetches: + raise RuntimeError('tensor_fetches provided to tensor_tracer cannot be ' + 'empty list.') + fetches = [] + for fetch in tensor_fetches: + if isinstance(fetch, ops.Tensor): + fetches.append(fetch) + else: + raise RuntimeError('Given tensor_fetch:%s is not a tensor.' % fetch) + return fetches + + def _process_op_fetches(self, op_fetches): + """Check that op_fetches have valid ops.""" + if op_fetches is None: + return [] + + if not isinstance(op_fetches, (list, tuple)): + op_fetches = [op_fetches] + + fetches = [] + for fetch in op_fetches: + if isinstance(fetch, ops.Operation): + fetches.append(fetch) + else: + logging.warning('Ignoring the given op_fetch:%s, which is not an op.' % + fetch) + return fetches + + def _convert_fetches_to_input_format(self, input_fetches, current_fetches): + """Changes current_fetches' format, so that it matches input_fetches.""" + if isinstance(input_fetches, ops.Tensor): + if len(current_fetches) != 1: + raise RuntimeError('Tensor tracer input/output fetches do not match.') + return current_fetches[0] + else: + if len(current_fetches) != len(current_fetches): + raise RuntimeError('Tensor tracer input/output fetches do not match.') + elif isinstance(input_fetches, tuple): + return tuple(current_fetches) + else: + return current_fetches + + def _get_op_control_flow_context(self, op): + """Returns the control flow of the given op. + + Args: + op: tf.Operation for which the control flow context is requested. + Returns: + op_control_flow_context: which the is control flow context of the given + op. If the operation type is LoopExit, returns the outer control flow + context. + """ + # pylint: disable=protected-access + op_control_flow_context = op._control_flow_context + # pylint: enable=protected-access + if control_flow_util.IsLoopExit(op): + op_control_flow_context = op_control_flow_context.outer_context + return op_control_flow_context + + def _trace_execution(self, graph, + tensor_fetches, + op_fetches=None, + on_tpu=True): + """Commong tracing function for both CPU and TPUs. + + The caller function should set _device_type, _num_replicas, + _num_replicas_per_host, _num_hosts and _replica_id before calling + _trace_execution. + + + Args: + graph: the graph of Ops executed on the TPU. + tensor_fetches: a (list,tuple,or a single object) of tensor fetches + returned by model_fn given to session.run. Function must be provided + with as least one tensor to fetch. + op_fetches: A list of op fetches returned by model_fn given to + session.run. op_fetches and tensor_fetches are used to determine the + nodes that will be executed. Can be None. + on_tpu: True if executing on TPU. + + Returns: + tensor_fetches: an exact copy of tensor_fetches that has additional + dependencies. + Raises: + RuntimeError: If tensor_fetches is None or empty. + """ + def _cast_unsupported_dtypes(tensor): + """Casts tensor to a supported type.""" + + if tensor.dtype.__eq__(dtypes.int64): + # outside-compilation doesn't support int64 input yet. + return math_ops.cast(tensor, dtypes.int32) + if tensor.dtype.__eq__(dtypes.bfloat16) or tensor.dtype.__eq__( + dtypes.float16): + # Since host can't handle bf16, convert tensor to f32. + return math_ops.cast(tensor, dtypes.float32) + return tensor + + TensorTracer.check_device_type(self._device_type) + # Check in_tensor_fetches, and op_fetches and convert them to lists. + processed_t_fetches = self._process_tensor_fetches(tensor_fetches) + op_fetches = self._process_op_fetches(op_fetches) + all_fetches = op_fetches + [tensor.op for tensor in processed_t_fetches] + + # Filter the set of ops that will be executed, and topological sort. + (exec_op_set, succeed, sorted_or_cycle) = self._pre_tracing(graph, + all_fetches) + + tensor_fetch_set = set(processed_t_fetches) + tracing_ops = [] + + # pylint: disable=protected-access + current_control_flow_context = graph._get_control_flow_context() + # pylint: enable=protected-access + + # Trace ops only if they are in the execution path. + for op in exec_op_set: + for i in range(len(op.outputs)): + out_tensor = op.outputs[i] + tensor_name = out_tensor.name + if tensor_name not in self._traced_tensorname_to_cache_idx_map: + continue + # Create the list of consumers before calling _preprocess_traced_tensor. + # Otherwise, adding control input below, will introduce a cycle in the + # graph. + consumers = out_tensor.consumers() + # Not all consumers may be in the exec path. Filter out the consumers + # to keep the graph simpler. + consumers = [cop for cop in consumers if cop in exec_op_set] + + # If there is no consumer of the tensor, there is no need to trace it; + # unless the tensor itself is one of the fetches. + is_a_fetched_tensor = out_tensor in tensor_fetch_set + if (not consumers) and (not is_a_fetched_tensor): + continue + + op_control_flow_context = self._get_op_control_flow_context(op) + # pylint: disable=protected-access + graph._set_control_flow_context(op_control_flow_context) + # pylint: enable=protected-access + processed_out_tensor = self._preprocess_traced_tensor(out_tensor) + + if on_tpu: + processed_out_tensor = _cast_unsupported_dtypes(processed_out_tensor) + + if self._use_tensor_values_cache(): + cache_idx = self._traced_tensorname_to_cache_idx_map[tensor_name] + trace_op = self._save_tensor_value_to_cache_op(graph, + cache_idx, + processed_out_tensor) + elif on_tpu: + trace_op = tpu.outside_compilation( + self._make_tensor_trace_fun(tensor_name), processed_out_tensor) + else: + trace_fun = self._make_tensor_trace_fun(tensor_name) + trace_op = trace_fun(processed_out_tensor) + + if is_a_fetched_tensor: + tracing_ops.append(trace_op) + continue + # Add it to all consumers, as some consumers may not be executed if they + # are in a control flow. + for consumer_op in consumers: + # pylint: disable=protected-access + consumer_op._add_control_input(trace_op) + # pylint: enable=protected-access + + # pylint: disable=protected-access + graph._set_control_flow_context(current_control_flow_context) + # pylint: enable=protected-access + if tracing_ops: + # If we are tracing a fetched tensor, their dependency is stored in + # tracing_ops. + processed_t_fetches = control_flow_ops.tuple(processed_t_fetches, + control_inputs=tracing_ops) + if self._use_tensor_values_cache(): + processed_t_fetches = self._flush_tensor_values_cache(graph, + processed_t_fetches, + op_fetches, + on_tpu=on_tpu) + self._post_tracing(succeed, sorted_or_cycle) + # processed_t_fetches is a list at this point. Convert it to the same + # format as given in tensor_fetches. + return self._convert_fetches_to_input_format(tensor_fetches, + processed_t_fetches) + + def trace_tpu(self, graph, + tensor_fetches, + op_fetches=None, + num_replicas=None, + num_replicas_per_host=None, + num_hosts=None): + """Traces the tensors generated by TPU Ops in a TF graph. + + Args: + graph: the graph of Ops executed on the TPU. + tensor_fetches: a (list,tuple,or a single object) of tensor fetches + returned by model_fn given to session.run. Function must be provided + with as least one tensor to fetch. + op_fetches: A list of op fetches returned by model_fn given to + session.run. op_fetches and tensor_fetches are used to determine the + nodes that will be executed. Can be None. + num_replicas: number of replicas used on the TPU. + num_replicas_per_host: number of replicas per TPU host. + num_hosts: total number of TPU hosts. + + Returns: + tensor_fetches: an exact copy of tensor_fetches that has additional + dependencies. + Raises: + RuntimeError: If num_replicas_per_host > 8. + RuntimeError: If tensor_fetches is None or empty. + """ + + if graph in TensorTracer._traced_graphs: + logging.warning('Graph is already rewritten with tensor tracer, ignoring ' + 'multiple calls.') + return tensor_fetches + else: + TensorTracer._traced_graphs.add(graph) + self._device_type = _DEVICE_TYPE_TPU + self._num_replicas = num_replicas + self._num_replicas_per_host = num_replicas_per_host + self._num_hosts = num_hosts + if self._num_replicas is not None: + if self._num_replicas_per_host is None: + self._num_replicas_per_host = 8 + if self._num_hosts is None: + self._num_hosts = num_replicas // self._num_replicas_per_host + \ + (num_replicas % self._num_replicas_per_host > 0) + + if self._num_replicas_per_host > 8: + # Checks for the assumption in _generate_flush_cache_op(). + raise RuntimeError('num_replicas_per_host (%d) is ' + 'greater than 8'%self._num_replicas_per_host) + if self._graph_dump_path: + graph_io.write_graph(graph, self._graph_dump_path, + 'graph_before_tt.pbtxt') + with graph.as_default(): + self._add_replica_id_to_graph() + tensor_fetches = self._trace_execution(graph, tensor_fetches, op_fetches, + on_tpu=True) + if self._graph_dump_path: + graph_io.write_graph(graph, self._graph_dump_path, + 'graph_after_tt.pbtxt') + return tensor_fetches + + def trace_cpu(self, graph, tensor_fetches, op_fetches=None): + """Traces the tensors generated by CPU Ops in a TF graph. + + Args: + graph: the graph of Ops executed on the CPU. + tensor_fetches: a (list,tuple,or a single object) of tensor fetches + returned by model_fn given to session.run. Function must be provided + with as least one tensor to fetch. + op_fetches: A list of op fetches returned by model_fn given to + session.run. op_fetches and tensor_fetches are used to determine the + nodes that will be executed. Can be None. + + Returns: + tensor_fetches: an exact copy of tensor_fetches that has additional + dependencies. + Raises: + RuntimeError: If tensor_fetches is None or empty. + """ + + if graph in TensorTracer._traced_graphs: + logging.warning('Graph is already rewritten with tensor tracer, ignoring ' + 'multiple calls.') + return tensor_fetches + else: + TensorTracer._traced_graphs.add(graph) + + self._device_type = _DEVICE_TYPE_CPU + self._num_replicas = 1 + self._num_replicas_per_host = 1 + self._num_hosts = 1 + self._replica_id = 0 + if self._graph_dump_path: + graph_io.write_graph(graph, self._graph_dump_path, + 'graph_before_tt.pbtxt') + with graph.as_default(): + tensor_fetches = self._trace_execution(graph, tensor_fetches, op_fetches, + on_tpu=False) + if self._graph_dump_path: + graph_io.write_graph(graph, self._graph_dump_path, + 'graph_after_tt.pbtxt') + return tensor_fetches + + diff --git a/tensorflow/python/tpu/topology.py b/tensorflow/python/tpu/topology.py new file mode 100644 index 0000000000..00ee21e694 --- /dev/null +++ b/tensorflow/python/tpu/topology.py @@ -0,0 +1,220 @@ +# 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. +# ====================================== +"""Defines the `Topology` class, that describes a TPU fabric topology.""" + +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.core.protobuf.tpu import topology_pb2 + + +def _tpu_device_name(job, task, device): + """Returns the device name for the TPU `device` on `task` of `job`.""" + if job is None: + return "/task:%d/device:TPU:%d" % (task, device) + else: + return "/job:%s/task:%d/device:TPU:%d" % (job, task, device) + + +def _tpu_host_device_name(job, task): + """Returns the device name for the CPU device on `task` of `job`.""" + if job is None: + return "/task:%d/device:CPU:0" % task + else: + return "/job:%s/task:%d/device:CPU:0" % (job, task) + + +class Topology(object): + """Describes a set of TPU devices. + + Represents both the shape of the physical mesh, and the mapping between + TensorFlow TPU devices to physical mesh coordinates. + """ + + def __init__(self, serialized=None, mesh_shape=None, device_coordinates=None): + """Builds a Topology object. + + If `serialized` is not `None`, the topology is parsed from `serialized` and + the other arguments are ignored. Otherwise, the topology is computed from + `mesh_shape` and `device_coordinates`. + + Args: + serialized: A serialized `TopologyProto`, or `None`. If not `None`, the + serialized proto is parsed to discover the topology. + mesh_shape: A sequence of 3 positive integers, or `None`. If not `None`, + the shape of the TPU topology, in number of cores. Ignored if + `serialized` is not `None`. + device_coordinates: A rank 3 numpy array that describes the mapping from + TensorFlow TPU devices to TPU fabric coordinates, or `None`. Ignored + if `serialized is not `None`. + + Raises: + ValueError: If `serialized` does not describe a well-formed topology. + ValueError: If `serialized` is `None` and `mesh_shape` is not a sequence + of 3 positive integers. + ValueError: If `serialized` is `None` and `device_coordinates` is not a + rank 3 numpy int32 array that describes a valid coordinate mapping. + """ + + self._serialized = serialized + + if serialized: + self._parse_topology(serialized) + else: + self._mesh_shape = np.asarray(mesh_shape, dtype=np.int32) + self._device_coordinates = np.asarray(device_coordinates, np.int32) + if len(self._mesh_shape) != 3 or any(self._mesh_shape < 1): + raise ValueError("`mesh_shape` must be a sequence of 3 positive " + "entries; got {}".format(self._mesh_shape)) + + if (len(self._device_coordinates.shape) != 3 or + self._device_coordinates.shape[2] != len(self._mesh_shape)): + raise ValueError("`device_coordinates` must be a rank 3 int32 array " + "with minor dimension equal to the mesh shape rank") + + self._topology_tasks, self._topology_devices = self._invert_topology() + + def _parse_topology(self, serialized): + """Parses a serialized `TopologyProto` into `self`.""" + proto = topology_pb2.TopologyProto() + proto.ParseFromString(serialized) + + self._mesh_shape = np.array(proto.mesh_shape, dtype=np.int32) + if len(self._mesh_shape) != 3 or any(self._mesh_shape < 1): + raise ValueError("`mesh_shape` must be a vector of size 3 with positive " + "entries; got {}".format(self._mesh_shape)) + + if proto.num_tasks < 0: + raise ValueError("`num_tasks` must be >= 0; got {}".format( + proto.num_tasks)) + if proto.num_tpu_devices_per_task < 0: + raise ValueError("`num_tpu_devices_per_task` must be >= 0; got {}".format( + proto.num_tpu_devices_per_task)) + + expected_coordinates_size = ( + proto.num_tasks * proto.num_tpu_devices_per_task * len( + proto.mesh_shape)) + if len(proto.device_coordinates) != expected_coordinates_size: + raise ValueError("`device_coordinates` must have shape num_tasks ({}) * " + "num_tpu_devices_per_task ({}) * len(mesh_shape) ({}); " + "got shape {}".format(proto.num_tasks, + proto.num_tpu_devices_per_task, + proto.mesh_shape, + len(proto.device_coordinates))) + + coords = np.array(proto.device_coordinates, dtype=np.int32) + if any(coords < 0): + raise ValueError("`device_coordinates` must be >= 0") + coords = coords.reshape((proto.num_tasks, proto.num_tpu_devices_per_task, + len(proto.mesh_shape))) + self._device_coordinates = coords + + def _invert_topology(self): + """Inverts a [task,device,axis] topology to [x,y,z] -> task/device maps.""" + tasks = np.full(list(self.mesh_shape), -1, dtype=np.int32) + devices = np.full(list(self.mesh_shape), -1, dtype=np.int32) + for task in xrange(self.device_coordinates.shape[0]): + for device in xrange(self.device_coordinates.shape[1]): + x, y, z = self.device_coordinates[task, device, :] + tasks[x, y, z] = task + devices[x, y, z] = device + return tasks, devices + + @property + def mesh_shape(self): + """A rank 1 int32 array describing the shape of the TPU topology.""" + return self._mesh_shape + + @property + def mesh_rank(self): + """Returns the number of dimensions in the mesh.""" + return len(self._mesh_shape) + + @property + def device_coordinates(self): + """Describes the mapping from TPU devices to topology coordinates. + + Returns: + A rank 3 int32 array with shape `[tasks, devices, axis]`. + `tasks` is the number of tasks in the TPU cluster, `devices` is the number + of TPU devices per task, and `axis` is the number of axes in the TPU + cluster topology. Each entry gives the `axis`-th coordinate in the + topology of a task/device pair. TPU topologies are 3-dimensional, with + dimensions `(x, y, core number)`. + """ + return self._device_coordinates + + def task_ordinal_at_coordinates(self, device_coordinates): + """Returns the TensorFlow task number attached to `device_coordinates`. + + Args: + device_coordinates: An integer sequence describing a device's physical + coordinates in the TPU fabric. + + Returns: + Returns the TensorFlow task number that contains the TPU device with those + physical coordinates. + """ + return self._topology_tasks[tuple(device_coordinates)] + + def tpu_device_ordinal_at_coordinates(self, device_coordinates): + """Returns the TensorFlow device number at `device_coordinates`. + + Args: + device_coordinates: An integer sequence describing a device's physical + coordinates in the TPU fabric. + + Returns: + Returns the TensorFlow device number within the task corresponding to + attached to the device with those physical coordinates. + """ + return self._topology_devices[tuple(device_coordinates)] + + def cpu_device_name_at_coordinates(self, device_coordinates, job=None): + """Returns the CPU device attached to a logical core.""" + return _tpu_host_device_name( + job, self._topology_tasks[tuple(device_coordinates)]) + + def tpu_device_name_at_coordinates(self, device_coordinates, job=None): + """Returns the name of the TPU device assigned to a logical core.""" + return _tpu_device_name(job, + self._topology_tasks[tuple(device_coordinates)], + self._topology_devices[tuple(device_coordinates)]) + + @property + def num_tasks(self): + """Returns the number of TensorFlow tasks in the TPU slice.""" + return self._device_coordinates.shape[0] + + @property + def num_tpus_per_task(self): + """Returns the number of TPU devices per task in the TPU slice.""" + return self._device_coordinates.shape[1] + + def serialized(self): + """Returns the serialized form of the topology.""" + if self._serialized is None: + proto = topology_pb2.TopologyProto() + proto.mesh_shape[:] = list(self._mesh_shape) + proto.num_tasks = self._device_coordinates.shape[0] + proto.num_tpu_devices_per_task = self._device_coordinates.shape[1] + proto.device_coordinates.extend(list(self._device_coordinates.flatten())) + self._serialized = proto.SerializeToString() + + return self._serialized diff --git a/tensorflow/contrib/tpu/python/tpu/topology_test.py b/tensorflow/python/tpu/topology_test.py similarity index 96% rename from tensorflow/contrib/tpu/python/tpu/topology_test.py rename to tensorflow/python/tpu/topology_test.py index fafe3254d8..9e1b7de859 100644 --- a/tensorflow/contrib/tpu/python/tpu/topology_test.py +++ b/tensorflow/python/tpu/topology_test.py @@ -19,9 +19,8 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib.tpu.python.tpu import topology - from tensorflow.python.platform import test +from tensorflow.python.tpu import topology class TopologyTest(test.TestCase): diff --git a/tensorflow/python/tpu/tpu.py b/tensorflow/python/tpu/tpu.py new file mode 100644 index 0000000000..02489a9b10 --- /dev/null +++ b/tensorflow/python/tpu/tpu.py @@ -0,0 +1,1576 @@ +# 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. +# ====================================== + +"""Library of TPU helper functions.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import collections +from six.moves import xrange # pylint: disable=redefined-builtin + +from tensorflow.core.framework import attr_value_pb2 +from tensorflow.core.protobuf.tpu import dynamic_padding_pb2 as dynamic_padding +from tensorflow.python.compat import compat as api_compat +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 ops +from tensorflow.python.framework import tensor_shape +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 variable_scope +from tensorflow.python.platform import tf_logging as logging +from tensorflow.python.tpu import tpu_function +from tensorflow.python.tpu import xla +from tensorflow.python.tpu.ops import tpu_ops +from tensorflow.python.util import compat +from tensorflow.python.util import nest + + +# Operations that indicate some error in the users graph, e.g. a placeholder +# that's introduced outside of the infeed. +_BLACKLISTED_OPS = set([ + "Placeholder", +]) + +# XLA doesn't currently support reading of intermediate tensors, thus some ops +# are not supported. +_UNSUPPORTED_OPS = set([ + "AudioSummary", + "AudioSummaryV2", + "HistogramSummary", + "ImageSummary", + "MergeSummary", + "Print", + "ScalarSummary", + "TensorSummary", + "TensorSummaryV2", + ]) + +_MAX_WARNING_LINES = 5 + +_TPU_REPLICATE_ATTR = "_tpu_replicate" +_TPU_COMPILATION_STATUS_ATTR = "_tpu_compilation_status" +_OUTSIDE_COMPILATION_ATTR = "_xla_outside_compilation" + + +def _tpu_system_device_name(job): + """Returns the device name for the TPU_SYSTEM device of `job`.""" + if job is None: + return "/device:TPU_SYSTEM:0" + else: + return "/job:%s/device:TPU_SYSTEM:0" % job + + +def initialize_system(embedding_config=None, job=None): + """Initializes a distributed TPU system for use with TensorFlow. + + Args: + embedding_config: If not None, a `TPUEmbeddingConfiguration` proto + describing the desired configuration of the hardware embedding lookup + tables. If embedding_config is None, no hardware embeddings can be used. + job: The job (the XXX in TensorFlow device specification /job:XXX) that + contains the TPU devices that will be initialized. If job=None it is + assumed there is only one job in the TensorFlow flock, and an error will + be returned if this assumption does not hold. + Returns: + A serialized `TopologyProto` that describes the TPU system. Note: + the topology must be evaluated using `Session.run` before it can be used. + """ + config_string = ("" if embedding_config is None else + embedding_config.SerializeToString()) + with ops.device(_tpu_system_device_name(job)): + return tpu_ops.configure_distributed_tpu(embedding_config=config_string) + + +def shutdown_system(job=None): + """Shuts down a running a distributed TPU system.""" + with ops.device(_tpu_system_device_name(job)): + shutdown_distributed_tpu = tpu_ops.shutdown_distributed_tpu() + return shutdown_distributed_tpu + + +def core(num): + """Returns the device name for a core in a replicated TPU computation. + + Args: + num: the virtual core number within each replica to which operators should + be assigned. + Returns: + A device name, suitable for passing to `tf.device()`. + """ + return "device:TPU_REPLICATED_CORE:{}".format(num) + + +class TPUReplicateContext(control_flow_ops.XLAControlFlowContext): + """A `ControlFlowContext` for nodes inside a TPU computation. + + The primary role of `TPUReplicateContext` is to mark operators inside a + tpu.replicate() computation with the attribute "_tpu_replicate=XYZ", where XYZ + is a unique name. + + We use a `ControlFlowContext` to perform the annotation since it integrates + with Tensorflow constructs like ResourceVariables. For example, if a + `ResourceVariable` is constructed inside a tpu.replicate() block, the + `ResourceVariable` implementation can use + `with ops.control_dependencies(None)` to build the variable's definition + outside the replicated computation. + """ + + def __init__(self, name, num_replicas, pivot): + """Builds a new TPUReplicateContext. + + Args: + name: a unique name for the context, used to populate the `_tpu_replicate` + attribute. + num_replicas: an integer that gives the number of replicas for the + computation. + pivot: a pivot node. Nodes in the TPUReplicateContext that do not have any + inputs will have a control dependency on the pivot node. This ensures + that nodes are correctly included in any enclosing control flow + contexts. + """ + super(TPUReplicateContext, self).__init__() + self._num_replicas = num_replicas + self._outer_device_function_stack = None + self._oc_dev_fn_stack = None + self._outside_compilation_cluster = None + self._outside_compilation_counter = 0 + self._in_gradient_colocation = None + self._gradient_colocation_stack = [] + self._host_compute_core = [] + self._name = name + self._name_as_bytes = compat.as_bytes(name) + self._unsupported_ops = [] + self._pivot = pivot + self._replicated_vars = {} + + def get_replicated_var_handle(self, name, vars_): + """Returns a variable handle for replicated TPU variable 'var'. + + This is a method used by an experimental replicated variable implementation + and is not intended as a public API. + + Args: + name: The common name of the variable. + vars_: The replicated TPU variables. + + Returns: + The handle of the TPU replicated input node. + """ + handle = self._replicated_vars.get(name) + if handle is not None: + return handle + + # Builds a TPUReplicatedInput node for the variable, if one does not already + # exist. The TPUReplicatedInput node must belong to the enclosing + # control-flow scope of the TPUReplicateContext. + # TODO(phawkins): consider changing the contract of the TPU encapsulation + # so the TPUReplicatedInput nodes go inside the TPUReplicateContext scope + # instead. + + # pylint: disable=protected-access + graph = ops.get_default_graph() + saved_context = graph._get_control_flow_context() + graph._set_control_flow_context(self.outer_context) + handle = tpu_ops.tpu_replicated_input( + [v.handle for v in vars_], name=name + "/handle") + graph._set_control_flow_context(saved_context) + # pylint: enable=protected-access + self._replicated_vars[name] = handle + return handle + + def report_unsupported_operations(self): + if self._unsupported_ops: + op_str = "\n".join([" %s (%s)" % (op.type, op.name) + for op in self._unsupported_ops[:_MAX_WARNING_LINES]]) + logging.warning("%d unsupported operations found: \n%s", + len(self._unsupported_ops), op_str) + if len(self._unsupported_ops) > _MAX_WARNING_LINES: + logging.warning("... and %d more" % + (len(self._unsupported_ops) - _MAX_WARNING_LINES)) + + def EnterGradientColocation(self, op, gradient_uid): + if op is not None: + self._gradient_colocation_stack.append(op) + if not self._outside_compilation_cluster: + try: + outside_attr = op.get_attr(_OUTSIDE_COMPILATION_ATTR) + if self._in_gradient_colocation: + raise NotImplementedError( + "Cannot nest gradient colocation operations outside compilation" + ) + if gradient_uid == "__unsupported__": + raise NotImplementedError( + "No gradient_uid calling gradient within outside_compilation") + # When we take the gradient of an op X in an outside_compilation + # cluster C in a forward computation we would like to put the ops + # corresponding to the gradient of X into a new outside_compilation + # cluster C'. However, if we take the gradient of X twice, the second + # one should get yet another new outside_compilation cluster C''. + # + # The mechanism we adopt is to use a 'root_cluster' which is the + # cluster that X was in before we took gradients, and a 'gradient_uid' + # which is different for every invocation of gradients, and put the + # gradient of X in cluster 'root_cluster.gradient_uid'. + # + # When taking a gradient of a gradient, some ops will be colocated + # with Op in the forward pass (e.g., cluster root_cluster) and some in + # the backward pass (e.g., cluster root_cluster.initial_gradient_uid). + # We need all of the grad-of-grad ops to be in the same cluster to + # avoid cyclic dependencies between clusters. We adopt a heuristic + # that puts any op clustered with root_cluster. in + # root_cluster.gradient_uid, even if xxx was initial_gradient_uid. + self._in_gradient_colocation = op + parts = outside_attr.split(".") + cluster = parts[0] + "." + gradient_uid + self._EnterOutsideCompilationScope(cluster=cluster) + except ValueError: + # The attr was not present: do nothing. + pass + + def ExitGradientColocation(self, op, gradient_uid): + if op is not None: + if not self._gradient_colocation_stack: + raise errors.InternalError( + op.node_def, op, + "Badly nested gradient colocation: empty stack when popping Op " + + op.name) + last_op = self._gradient_colocation_stack.pop() + if op is last_op: + if op is self._in_gradient_colocation: + self._in_gradient_colocation = None + self._ExitOutsideCompilationScope() + else: + raise errors.InternalError( + op.node_def, op, "Badly nested gradient colocation, expected " + + last_op + ", got " + op.name) + + def _EnterOutsideCompilationScope(self, cluster=None): + + class FakeOp(object): + """A helper class to determine the current device. + + Supports only the type and device set/get methods needed to run the + graph's _apply_device_function method. + """ + + def __init__(self): + self._device = "" + + @property + def type(self): + return "FakeOp" + + @property + def device(self): + return self._device + + def _set_device(self, device): + if isinstance(device, pydev.DeviceSpec): + self._device = device.to_string() + else: + self._device = device + + if self._outside_compilation_cluster: + raise NotImplementedError("Cannot nest outside_compilation clusters") + if cluster: + self._outside_compilation_cluster = cluster + else: + self._outside_compilation_cluster = str(self._outside_compilation_counter) + self._outside_compilation_counter += 1 + graph = ops.get_default_graph() + fake_op = FakeOp() + graph._apply_device_functions(fake_op) # pylint: disable=protected-access + device = pydev.DeviceSpec.from_string(fake_op.device) + if (device.device_type == "TPU_REPLICATED_CORE" and + device.device_index is not None): + self._host_compute_core.append(self._outside_compilation_cluster + ":" + + str(device.device_index)) + self._oc_dev_fn_stack = graph._device_function_stack # pylint: disable=protected-access + graph._device_function_stack = self._outer_device_function_stack # pylint: disable=protected-access + + def _ExitOutsideCompilationScope(self): + if not self._outside_compilation_cluster: + raise NotImplementedError( + "Attempted to exit outside_compilation scope when not in scope") + self._outside_compilation_cluster = None + graph = ops.get_default_graph() + graph._device_function_stack = self._oc_dev_fn_stack # pylint: disable=protected-access + + def Enter(self): + if not self._outer_device_function_stack: + # Capture the device function stack at the time of first entry + # since that is the stack that will be used outside_compilation. + graph = ops.get_default_graph() + # pylint: disable=protected-access + self._outer_device_function_stack = graph._device_function_stack.copy() + # pylint: enable=protected-access + super(TPUReplicateContext, self).Enter() + + def HostComputeCore(self): + return self._host_compute_core + + def _RemoveExternalControlEdges(self, op): + """Remove any external control dependency on this op.""" + internal_control_inputs = [] + external_control_inputs = [] + for x in op.control_inputs: + # pylint: disable=protected-access + is_internal_op = False + ctxt = x._get_control_flow_context() + while ctxt is not None: + if ctxt == self: + is_internal_op = True + break + ctxt = ctxt._outer_context + if is_internal_op: + internal_control_inputs.append(x) + else: + external_control_inputs.append(x) + # pylint: enable=protected-access + # pylint: disable=protected-access + op._remove_all_control_inputs() + op._add_control_inputs(internal_control_inputs) + # pylint: enable=protected-access + return internal_control_inputs, external_control_inputs + + def AddOp(self, op): + # pylint: disable=protected-access + if op.type in _BLACKLISTED_OPS: + logging.error("Operation of type %s (%s) is not supported on the TPU. " + "Execution will fail if this op is used in the graph. " % + (op.type, op.name)) + + if op.type in _UNSUPPORTED_OPS: + self._unsupported_ops.append(op) + + if any(x.dtype._is_ref_dtype for x in op.inputs): + raise NotImplementedError( + "Non-resource Variables are not supported inside TPU computations " + "(operator name: %s)" % op.name) + if _TPU_REPLICATE_ATTR in op.node_def.attr: + raise ValueError("TPU computations cannot be nested") + op._set_attr(_TPU_REPLICATE_ATTR, + attr_value_pb2.AttrValue(s=self._name_as_bytes)) + if self._outside_compilation_cluster: + op._set_attr( + _OUTSIDE_COMPILATION_ATTR, + attr_value_pb2.AttrValue( + s=compat.as_bytes(self._outside_compilation_cluster))) + if self._num_replicas > 1 or not self._outside_compilation_cluster: + # Prevent feeding or fetching anything that is being compiled, + # and any replicated outside_compilation Op. + op.graph.prevent_feeding(op) + op.graph.prevent_fetching(op) + + # Remove any control edges from outer control flow contexts. These may cause + # mismatched frame errors. + (internal_control_inputs, + external_control_inputs) = self._RemoveExternalControlEdges(op) + + if not op.inputs: + # Add a control edge from the control pivot to this op. + if not internal_control_inputs: + # pylint: disable=protected-access + op._add_control_input(self.GetControlPivot()) + # pylint: enable=protected-access + else: + for index in xrange(len(op.inputs)): + x = op.inputs[index] + real_x = self.AddValue(x) + if real_x != x: + op._update_input(index, real_x) # pylint: disable=protected-access + + if external_control_inputs: + # Use an identity to pull control inputs as data inputs. Note that we + # ignore ops which don't have outputs. TODO(phawkins): fix that. + with ops.control_dependencies(None): + self.Enter() + external_control_inputs = [ + array_ops.identity(x.outputs[0]).op + for x in external_control_inputs + if x.outputs + ] + self.Exit() + # pylint: disable=protected-access + op._add_control_inputs(external_control_inputs) + # pylint: enable=protected-access + + # Mark op's outputs as seen by this context and any outer contexts. + output_names = [x.name for x in op.outputs] + context = self + while context is not None: + # pylint: disable=protected-access + context._values.update(output_names) + context = context._outer_context + # pylint: enable=protected-access + + if self._outer_context: + self._outer_context.AddInnerOp(op) + + def AddValue(self, val): + """Add `val` to the current context and its outer context recursively.""" + if val.name in self._values: + # Use the real value if it comes from outer context. + result = self._external_values.get(val.name) + return val if result is None else result + + result = val + self._values.add(val.name) + if self._outer_context: + result = self._outer_context.AddValue(val) + self._values.add(result.name) + + self._external_values[val.name] = result + + return result + + def AddInnerOp(self, op): + self.AddOp(op) + if self._outer_context: + self._outer_context.AddInnerOp(op) + + @property + def grad_state(self): + # Define the gradient loop state associated with the TPUReplicateContext to + # be None as the TPUReplicateContext does not get nested nor does the + # grad_state outside the TPUReplicateContext affect the graph inside so the + # grad_state should be as if this is the top-level gradient state. + return None + + @property + def back_prop(self): + """Forwards to the enclosing while context, if any.""" + if self.GetWhileContext(): + return self.GetWhileContext().back_prop + return False + + def GetControlPivot(self): + return self._pivot + + +def outside_compilation(computation, *args, **kwargs): + """Builds part of a computation outside any current TPU replicate scope. + + Args: + computation: A Python function that builds the computation to + place on the host. + *args: the positional arguments for the computation. + **kwargs: the keyword arguments for the computation. + + Returns: + The Tensors returned by computation. + """ + args = [] if args is None else args + graph = ops.get_default_graph() + + # If we are in a TPUReplicateContext, signal that we are now + # outside_compilation + initial_context = graph._get_control_flow_context() # pylint: disable=protected-access + context = initial_context + while context: + if isinstance(context, TPUReplicateContext): + context._EnterOutsideCompilationScope() # pylint: disable=protected-access + context = context.outer_context + + retval = computation(*args, **kwargs) + + # If we are in a TPUReplicateContext, signal that we are no longer + # outside_compilation + final_context = graph._get_control_flow_context() # pylint: disable=protected-access + if initial_context is not final_context: + raise NotImplementedError( + "Control-flow context cannot be different at start and end of an " + "outside_compilation scope") + context = initial_context + while context: + if isinstance(context, TPUReplicateContext): + context._ExitOutsideCompilationScope() # pylint: disable=protected-access + context = context.outer_context + + return retval + + +def replicate(computation, + inputs=None, + infeed_queue=None, + device_assignment=None, + name=None, + maximum_shapes=None): + """Builds a graph operator that runs a replicated TPU computation. + + Args: + computation: A Python function that builds the computation to replicate. + inputs: A list of lists of input tensors or `None` (equivalent to + `[[]]`), indexed by `[replica_num][input_num]`. All replicas must + have the same number of inputs. Each input can be a nested structure + containing values that are convertible to tensors. Note that passing an + N-dimension list of compatible values will result in a N-dimention list of + scalar tensors rather than a single Rank-N tensors. If you need different + behavior, convert part of inputs to tensors with `tf.convert_to_tensor`. + infeed_queue: If not `None`, the `InfeedQueue` from which to append a tuple + of arguments as inputs to computation. + device_assignment: If not `None`, a `DeviceAssignment` describing the + mapping between logical cores in the computation with physical cores in + the TPU topology. Uses a default device assignment if `None`. The + `DeviceAssignment` may be omitted if each replica of the computation uses + only one core, and there is either only one replica, or the number of + replicas is equal to the number of cores in the TPU system. + name: (Deprecated) Does nothing. + maximum_shapes: A nested structure of tf.TensorShape representing the shape + to which the respective component of each input element in each replica + should be padded. Any unknown dimensions (e.g. tf.Dimension(None) in a + tf.TensorShape or -1 in a tensor-like object) will be padded to the + maximum size of that dimension over all replicas. Note that if the input + dimension is already static, we won't do padding on it and we require the + maximum_shapes to have the same value or None on that dimension. The + structure of `maximum_shapes` needs to be the same as `inputs[0]`. + Returns: + A list of outputs, indexed by `[replica_num]` each output can be a nested + structure same as what computation() returns with a few exceptions. + + Exceptions include: + 1) None output: a NoOp would be returned which control-depends on + computation. + 2) Single value output: A tuple containing the value would be returned. + 3) Operation-only outputs: a NoOp would be returned which + control-depends on computation. + TODO(b/121383831): Investigate into removing these special cases. + + Raises: + ValueError: If all replicas do not have equal numbers of input tensors. + ValueError: If the number of inputs per replica does not match + the number of formal parameters to `computation`. + ValueError: If the static `inputs` dimensions don't match with the values + given in `maximum_shapes`. + ValueError: If the structure of inputs per replica does not match + the structure of `maximum_shapes`. + """ + return split_compile_and_replicate( + computation, + inputs, + infeed_queue, + device_assignment, + name, + maximum_shapes=maximum_shapes)[1] + + +def _pad_all_input(inputs, padded_shapes): + """Pad all input tensors given padded_shapes. + + The real shape tensors will be concatenated with the padded original inputs. + + Args: + inputs: The original inputs. + padded_shapes: A list of padded shapes for each input. + + Returns: + The padded inputs and a PaddingMap list which maps the padded input + dimension to the real shape argument index. + """ + input_shape_tensors = [] + for core_idx, inputs_per_core in enumerate(inputs): + for idx, input_tensor in enumerate(inputs_per_core): + if core_idx == 0: + input_shape_tensors.append([]) + input_shape_tensors[idx].append(array_ops.shape(input_tensor)) + + maximum_shapes = [] + for shapes_per_input in input_shape_tensors: + maximum_shapes.append( + math_ops.reduce_max(array_ops.stack(shapes_per_input), axis=0)) + + padded_inputs = [] + real_shapes = [] + padding_maps = [] + for core_idx, inputs_per_core in enumerate(inputs): + padded_inputs.append([]) + real_shapes.append([]) + real_shape_idx = len(inputs_per_core) - 1 + for idx, input_tensor in enumerate(inputs_per_core): + input_shape_tensor = input_shape_tensors[idx][core_idx] + input_shape = input_tensor.get_shape() + padded_shape = padded_shapes[idx] + + # The static shape of inputs should be compatible with the given padded + # shapes. + input_shape.assert_is_compatible_with(padded_shape) + + if input_shape.is_fully_defined(): + # Do nothing if the shape of the whole tensor is already static. + padded_inputs[core_idx].append(input_tensor) + else: + # Only pad the non static shape dimension. + for i, s in enumerate(input_shape): + if s.value is None: + if core_idx == 0: + real_shape_idx += 1 + padding_map = dynamic_padding.PaddingMap() + padding_map.arg_index = idx + padding_map.shape_index = i + padding_map.padding_arg_index = real_shape_idx + padding_maps.append(padding_map) + real_shapes[core_idx].append( + math_ops.cast(input_shape_tensor[i], dtypes.uint32)) + + paddings = [] + for i, s in enumerate(padded_shape): + if input_shape[i].value: + # Don't pad if input shape is already static. + padding = [0, 0] + else: + if s.value: + # Pad to the given maximum value. + padding = [0, s.value - input_shape_tensor[i]] + else: + # If maximum value is not given, then pad to the maximum dimension + # among all the cores. + padding = [0, maximum_shapes[idx][i] - input_shape_tensor[i]] + paddings.append(padding) + + padded_input = array_ops.pad(input_tensor, paddings) + padded_inputs[core_idx].append(padded_input) + + num_replicas = len(padded_inputs) + for i in range(num_replicas): + padded_inputs[i].extend(real_shapes[i]) + + return padded_inputs, padding_maps + + +def split_compile_and_replicate(computation, + inputs=None, + infeed_queue=None, + device_assignment=None, + name=None, + use_tpu=True, + maximum_shapes=None): + """Builds graph operators that runs compilation and replicated computation. + + This is a lower level interface than replicate that returns a separate compile + and execute output tensor. In the generated graph the compile op feeds into + the execute op and no additional compilation is incurred when running the + compile op before the execute op. The compile op returns additional + information about the compilation but does not return the compiled program. + + Args: + computation: A Python function that builds the computation to replicate. + inputs: A list of lists of input tensors or `None` (equivalent to + `[[]]`), indexed by `[replica_num][input_num]`. All replicas must + have the same number of inputs. Each input can be a nested structure + containing values that are convertible to tensors. Note that passing an + N-dimension list of compatible values will result in a N-dimention list of + scalar tensors rather than a single Rank-N tensors. If you need different + behavior, convert part of inputs to tensors with `tf.convert_to_tensor`. + infeed_queue: If not `None`, the `InfeedQueue` from which to append a tuple + of arguments as inputs to computation. + device_assignment: If not `None`, a `DeviceAssignment` describing the + mapping between logical cores in the computation with physical cores in + the TPU topology. Uses a default device assignment if `None`. The + `DeviceAssignment` may be omitted if each replica of the computation uses + only one core, and there is either only one replica, or the number of + replicas is equal to the number of cores in the TPU system. + name: (Deprecated) Does nothing. + use_tpu: When false, the input `computation` is executed on the XLA CPU/GPU + backends. Currently, only supports a default placement (computation is + placed on GPU if one is available, and on CPU if not). + maximum_shapes: A nested structure of tf.TensorShape representing the shape + to which the respective component of each input element in each replica + should be padded. Any unknown dimensions (e.g. tf.Dimension(None) in a + tf.TensorShape or -1 in a tensor-like object) will be padded to the + maximum size of that dimension over all replicas. Note that if the input + dimension is already static, we won't do padding on it and we require the + maximum_shapes to have the same value or None on that dimension. The + structure of `maximum_shapes` needs to be the same as `inputs[0]`. + + Returns: + A list of lists with the first list corresponding to the compile op and the + second a list of output tensors, indexed by `[replica_num][output_num]`. + Raises: + ValueError: If all replicas do not have equal numbers of input tensors. + ValueError: If the number of inputs per replica does not match + the number of formal parameters to `computation`. + ValueError: If the static `inputs` dimensions don't match with the values + given in `maximum_shapes`. + ValueError: If the structure of inputs per replica does not match + the structure of `maximum_shapes`. + """ + del name + inputs = [[]] if inputs is None else inputs + + metadata_kwargs = {} + if device_assignment is not None: + # Turn the Numpy array into a flattened list so we can pass it as an + # operator attribute. + metadata_kwargs = { + "topology": + device_assignment.topology.serialized(), + "device_assignment": + device_assignment.core_assignment.flatten().tolist() + } + # TODO(phawkins): remove this case after the forward compatibility window + # expires on 2018-10-5. + if api_compat.forward_compatible(2018, 10, 5): + metadata_kwargs["num_cores_per_replica"] = ( + device_assignment.num_cores_per_replica) + else: + metadata_kwargs["computation_shape"] = [ + device_assignment.num_cores_per_replica + ] + + if ((not isinstance(inputs, list)) or + any(not isinstance(inp, (list, tuple)) for inp in inputs)): + raise TypeError("tpu.replicate() inputs must be a list of lists/tuples") + + num_replicas = len(inputs) + + # No replicas? Nothing to do. + if num_replicas == 0: + return [] + + # Checks all replicas have the same structure. + for i in xrange(1, num_replicas): + nest.assert_same_structure(inputs[0], inputs[i]) + + # Flatten inputs. + flat_inputs = [ + nest.flatten(per_replica_input) for per_replica_input in inputs + ] + # Converts inputs to Tensors. + flat_inputs = [[ops.convert_to_tensor(x) for x in inp] for inp in flat_inputs] + + # Verifies that all replicas have matching numbers and types of inputs + flat_input_types = [x.dtype for x in flat_inputs[0]] + input_arity = len(inputs[0]) + flat_input_arity = len(flat_input_types) + for i in range(num_replicas): + if len(inputs[i]) != input_arity: + raise ValueError("Replicas must have the same number of inputs. " + "Replica 0 had {} inputs, replica {} had {} " + "inputs.".format(input_arity, i, len(inputs[i]))) + + types = [x.dtype for x in flat_inputs[i]] + if types != flat_input_types: + raise ValueError("Replicas must have matching input types. Replica 0 had " + "input types {}, replica {} had input types {}".format( + flat_input_types, i, types)) + + arg_error = xla.check_function_argument_count( + computation, input_arity, infeed_queue) + if arg_error is not None: + if infeed_queue is None: + raise TypeError( + "Supplied computation cannot be called with the specified inputs. " + "You specified %d inputs: %s, but the computation needs %s" % ( + input_arity, str([i.name for i in inputs[0]]), arg_error)) + else: + raise TypeError( + "Supplied computation cannot be called with the specified inputs. " + "You specified %d inputs: %s and %d additional inputs from infeed," + " but the computation needs %s" % (input_arity, str( + [i.name + for i in inputs[0]]), infeed_queue.number_of_tuple_elements, + arg_error)) + + if maximum_shapes: + if infeed_queue: + raise ValueError( + "Dynamic input shapes are not supported with infeed queues") + + # Make sure maximum_shapes has the same structure as inputs. + nest.assert_same_structure(inputs[0], maximum_shapes, check_types=False) + + # Flatten padded shapes. + flat_maximum_shapes = nest.flatten(maximum_shapes) + flat_maximum_shapes = [ + tensor_shape.TensorShape(s) for s in flat_maximum_shapes + ] + + flat_inputs, padding_maps = _pad_all_input(flat_inputs, flat_maximum_shapes) + + serialized_padding_maps = [] + for padding_map in padding_maps: + serialized_padding_maps.append(padding_map.SerializeToString()) + metadata_kwargs["padding_map"] = serialized_padding_maps + + metadata_kwargs["step_marker_location"] = getattr( + computation, "step_marker_location", "STEP_MARK_AT_ENTRY") + + graph = ops.get_default_graph() + + # Fan-in: Builds a TPUReplicatedInput node for each input. + flat_replicated_inputs = [] + for i in range(0, len(flat_inputs[0])): + replicas = [flat_inputs[replica][i] for replica in xrange(num_replicas)] + flat_replicated_inputs.append( + tpu_ops.tpu_replicated_input(replicas, name="input{}".format(i))) + + cluster_name = graph.unique_name("cluster") + pivot = control_flow_ops.no_op(name=cluster_name + "/pivot") + context = TPUReplicateContext( + name=cluster_name, num_replicas=num_replicas, pivot=pivot) + try: + context.Enter() + + metadata = tpu_ops.tpu_replicate_metadata( + num_replicas=num_replicas, use_tpu=use_tpu, **metadata_kwargs) + + with tpu_function.tpu_shard_context( + num_replicas), ops.control_dependencies([metadata]): + + # Add identity ops so even unused inputs are "consumed" by the + # computation. This is to avoid orphaned TPUReplicatedInput nodes. + # TODO(phawkins): consider instead pruning unused TPUReplicatedInput + # and eliding trivial TPUReplicatedInput/TPUReplicatedOutput pairs. + flat_replicated_inputs = [ + array_ops.identity(x, name="replicated_input_{}".format(i)) + for i, x in enumerate(flat_replicated_inputs) + ] + for i in flat_replicated_inputs: + # pylint: disable=protected-access + # Add an attribute to the identity node so that they could be removed in + # encapsulate TPU computation pass if unused. However we don't remove + # inputs when dynamic padding is enabled. + # TODO(rxsang): Use other ways except argument index in padding_map so + # outside compilation can work with dynamic padding correctly. + if maximum_shapes is None: + i.op._set_attr("_tpu_input_identity", + attr_value_pb2.AttrValue(b=True)) + # pylint: enable=protected-access + + # Unflatten the computation inputs to match original input structure. + computation_inputs = nest.pack_sequence_as( + structure=inputs[0], + flat_sequence=flat_replicated_inputs[:flat_input_arity]) + + # If there is an infeed queue, adds the dequeued values to the + # computation's inputs. + if infeed_queue is not None: + infeed_queue.set_number_of_shards(num_replicas) + for t in infeed_queue.generate_dequeue_op(): + computation_inputs.append(t) + + # Only resource variables work inside a TPU computation, so turn on + # resource variables for the computation. + # TODO(phawkins): consider removing this code. It will + # be less confusing to clients if they knowingly choose to use resource + # variables. + # Partitioned variables is not supported (b/112311320). + vscope = variable_scope.get_variable_scope() + saved_use_resource = vscope.use_resource + saved_custom_getter = vscope.custom_getter + + def custom_getter(getter, name, *args, **kwargs): + """Variables on TPU have a few restrictions.""" + partitioner = kwargs["partitioner"] + if partitioner is not None: + kwargs["partitioner"] = None + logging.warning( + "Partitioned variables are not supported on TPU. Got " + "`partitioner` that is {} for variable {}. " + "Setting `partitioner` to `None`." + .format(partitioner, name)) + if saved_custom_getter is None: + return getter(name, *args, **kwargs) + else: + return saved_custom_getter(getter, name, *args, **kwargs) + + vscope.set_use_resource(True) + vscope.set_custom_getter(custom_getter) + + outputs = computation(*computation_inputs) + + vscope.set_use_resource(saved_use_resource) + vscope.set_custom_getter(saved_custom_getter) + + outputs_is_flat = xla.is_flat(outputs) + if outputs_is_flat: + output_tensors, control_deps = _postprocess_flat_outputs(outputs) + else: + output_tensors, control_deps = _postprocess_non_flat_outputs(outputs) + + # tensor_tracer imports tpu.py. Local import to tensor_tracer to avoid + # import-cycle + # pylint: disable=g-import-not-at-top + from tensorflow.python.tpu import tensor_tracer + # pylint: enable=g-import-not-at-top + if tensor_tracer.TensorTracer.is_enabled(): + tt = tensor_tracer.TensorTracer() + output_tensors = tt.trace_tpu(ops.get_default_graph(), + output_tensors, control_deps, + num_replicas) + + context.ExitResult(output_tensors) + finally: + context.report_unsupported_operations() + context.Exit() + host_compute_core = context.HostComputeCore() + + if host_compute_core: + attr_value = attr_value_pb2.AttrValue() + attr_value.list.s.extend([compat.as_bytes(x) for x in host_compute_core]) + metadata._set_attr("host_compute_core", attr_value) # pylint: disable=protected-access + + with ops.control_dependencies([metadata]): + if use_tpu: + compile_status = tpu_ops.tpu_compilation_result() + op = compile_status.op + attr_value = attr_value_pb2.AttrValue(s=compat.as_bytes(cluster_name)) + op._set_attr(_TPU_COMPILATION_STATUS_ATTR, attr_value) # pylint: disable=protected-access + else: + compile_status = control_flow_ops.no_op(name="compilation_status") + + if not output_tensors: + # Returns a list of NoOps dependent on the replication Op, indexed by + # [replica_num]. + return [ + compile_status, + [ + control_flow_ops.group(control_deps, name="shard_%d" % i) + for i in range(num_replicas) + ] + ] + + # Fan-out: Builds a TPUReplicatedOutput node for each output. + replicated_outputs = [[] for i in xrange(num_replicas)] + for i, t in enumerate(output_tensors): + # Fan-out: Builds a TPUReplicatedOutput node for each output. + ys = tpu_ops.tpu_replicated_output( + t, num_replicas, name="output{}".format(i)) + + # Wraps the outputs in identity operators so the names of any possible + # `fetch` nodes are preserved by the replication rewrite. + with ops.control_dependencies(control_deps): + for replica in xrange(num_replicas): + replicated_outputs[replica].append( + array_ops.identity( + ys[replica], name="output_%d_shard_%d" % (i, replica))) + + if not outputs_is_flat: + replicated_outputs = [ + nest.pack_sequence_as(outputs, replica_outs) + for replica_outs in replicated_outputs + ] + + return [compile_status, replicated_outputs] + + +def _postprocess_flat_outputs(outputs): + """Validates non-flat outputs, add backs device assignments and other attrs. + + Args: + outputs: Output from `computation` inside `tpu.rewrite`. + + Returns: + Tensors and Operations extracted from outputs. + """ + # Following code segment is to preserve legacy behavior. Previously we only + # supported flat outputs and thus for consistency it was nice to convert even + # single element into a tuple. But now that we support arbitrary output + # structure, this is no longer necessary. + # TODO(b/121383831): Migrate all legacy use cases and delete this special + # case. + # If the computation returns `None`, make it an empty tuple. + if outputs is None: + outputs = tuple() + # If the computation only returned one value, makes it a tuple. + if not isinstance(outputs, collections.Sequence): + outputs = (outputs,) + + # Append `no_op` here so that fetching any return value of this function + # will trigger TPUExecute node. + outputs += (control_flow_ops.no_op(),) + try: + with ops.device(core(0)): + outputs = [ + o if isinstance(o, ops.Operation) else ops.convert_to_tensor(o) + for o in outputs + ] + except Exception as e: + raise ValueError( + "TPU function return values must all either be Operations or " + "convertible to Tensors. Got '%s'" % str(e)) + + # Separates the returned Operations and Tensors. + output_operations = [o for o in outputs if isinstance(o, ops.Operation)] + output_tensors = [o for o in outputs if not isinstance(o, ops.Operation)] + + if outputs != output_tensors + output_operations: + raise ValueError( + "TPU functions must return zero-or more Tensor values followed by " + "zero or more Operations.") + + # Wraps outputs in Identity ops. Otherwise a replicated input copied + # straight to an output would bypass the replicate(). This would be bad + # because the TPUReplicatedInput/TPUReplicatedOutput operator would not + # be rewritten away, leading to a runtime error. + # TODO(phawkins): extend the rewrite to elide these nodes instead. + new_output_tensors = [] + for t in output_tensors: + with ops.device(t.device if t.device else core(0)): + o = array_ops.identity(t) + # pylint: disable=protected-access + o.op._set_attr("_tpu_output_identity", attr_value_pb2.AttrValue(b=True)) + # pylint: enable=protected-access + new_output_tensors.append(o) + return new_output_tensors, output_operations + + +def _postprocess_non_flat_outputs(outputs): + """Validates non-flat outputs, add backs device assignments and other attrs. + + Args: + outputs: Output from `computation` inside `tpu.rewrite`. + + Returns: + Tensors extracted from outputs and an empty list because Operations are not + allowed in non-flat outputs.. + """ + + # Flatten output items. + flat_outputs = nest.flatten(outputs) + + # Convert all non-Operation outputs to Tensors. + for i, o in enumerate(flat_outputs): + if isinstance(o, ops.Operation): + raise ValueError( + "tpu.rewrite does not support Operation as return value in non-flat " + "output structure. You can set returned Operations as control " + "dependencies of returned Tensors so Operations are triggered when " + 'Tensors are evaluated. Operation found: "%s"' % o.name) + + try: + o = ops.convert_to_tensor(o) + except Exception as e: + raise ValueError( + "TPU function return values must all either be Operations or " + 'convertible to Tensors. Got error: "%s"' % str(e)) + + # Wraps outputs in Identity ops. Otherwise a replicated input copied + # straight to an output would bypass the replicate(). This would be bad + # because the TPUReplicatedInput/TPUReplicatedOutput operator would not + # be rewritten away, leading to a runtime error. + # TODO(phawkins): extend the rewrite to elide these nodes instead. + with ops.device(core(0)): + o = array_ops.identity(o) + # pylint: disable=protected-access + o.op._set_attr("_tpu_output_identity", attr_value_pb2.AttrValue(b=True)) + # pylint: enable=protected-access + flat_outputs[i] = array_ops.identity(o) + + # All flat_outputs are Tensors, and no Operations. + return flat_outputs, [] + + +def split_compile_and_shard(computation, + inputs=None, + num_shards=1, + input_shard_axes=None, + outputs_from_all_shards=True, + output_shard_axes=None, + infeed_queue=None, + device_assignment=None, + name=None): + """Shards `computation` for parallel execution. + + `inputs` must be a list of Tensors or None (equivalent to an empty list), each + of which has a corresponding split axis (from `input_shard_axes`). Each input + is split into `num_shards` pieces along the corresponding axis, and + computation is applied to each shard in parallel. + + Tensors are broadcast to all shards if they are lexically captured by + `computation`. e.g., + + x = tf.constant(7) + def computation(): + return x + 3 + ... = shard(computation, ...) + + If `outputs_from_all_shards` is true, the outputs from all shards of + `computation` are concatenated back together along their `output_shards_axes`. + Otherwise, each output is taken from an arbitrary shard. + + Inputs and outputs of the computation must be at least rank-1 Tensors. + + Args: + computation: A Python function that builds a computation to apply to each + shard of the input. + inputs: A list of input tensors or None (equivalent to an empty list). Each + input tensor has a corresponding shard axes, given by `input_shard_axes`, + which must have size divisible by `num_shards`. + num_shards: The number of shards. + input_shard_axes: A list of dimensions along which to shard `inputs`, or + `None`. `None` means "shard all inputs along dimension 0". If not `None`, + there must be one dimension per input. + outputs_from_all_shards: Boolean or list of boolean. For each output, if + `True`, outputs from all shards are concatenated along the corresponding + `output_shard_axes` entry. Otherwise, each output is taken + from an arbitrary shard. If the argument is a boolean, the argument's + value is used for each output. + output_shard_axes: A list of dimensions along which to concatenate the + outputs of `computation`, or `None`. `None` means "concatenate all outputs + along dimension 0". If not `None`, there must be one dimension per output. + Ignored if `outputs_from_all_shards` is False. + infeed_queue: If not `None`, the `InfeedQueue` to use to augment the inputs + of `computation`. + device_assignment: If not `None`, a `DeviceAssignment` describing the + mapping between logical cores in the computation with physical cores in + the TPU topology. Uses a default device assignment if `None`. The + `DeviceAssignment` may be omitted if each shard of the computation uses + only one core, and there is either only one shard, or the number of shards + is equal to the number of cores in the TPU system. + name: (Deprecated) Does nothing. + Returns: + A tuple of (compile op, [output tensors]). + Raises: + ValueError: If num_shards <= 0 + ValueError: If len(input_shard_axes) != len(inputs) + ValueError: If len(output_shard_axes) != len(outputs from `computation`) + """ + # TODO(phawkins): consider adding support for broadcasting Tensors passed as + # inputs. + + if num_shards <= 0: + raise ValueError("num_shards must be a positive integer.") + + inputs = [] if inputs is None else inputs + if not isinstance(inputs, list): + raise TypeError("tpu.shard()'s inputs must be a list of Tensors or None.") + + # Converts inputs to Tensors. + inputs = [ops.convert_to_tensor(x) for x in inputs] + + if input_shard_axes is None: + input_shard_axes = [0] * len(inputs) + if len(inputs) != len(input_shard_axes): + raise ValueError("Length of input_shard_axes must be equal to the number " + "of inputs.") + + if inputs: + # Splits the `inputs` along the corresponding `input_shard_axes`, giving + # lists with layout [input][shard] + split_inputs = [ + array_ops.split(x, num_shards, axis=axis) + for (axis, x) in zip(input_shard_axes, inputs)] + + # Transposes the input lists to have layout [shard][input] + transposed_inputs = [list(i) for i in zip(*split_inputs)] + else: + transposed_inputs = [[]] * num_shards + + compile_op, outputs = split_compile_and_replicate( + computation, + transposed_inputs, + infeed_queue=infeed_queue, + device_assignment=device_assignment, + name=name) + + # There must be at least one shard since num_shards > 0. + # TODO(b/36647078) remove disable when pylint bug is fixed. + # pylint: disable=indexing-exception + if isinstance(outputs[0], ops.Operation): + # pylint: enable=indexing-exception + # There were no outputs from the computation and replicate returned a list + # of NoOps with control dependencies on the computation. Return the first + # one so it can be used as a control dependency or fetch node. + # TODO(b/36647078) remove disable when pylint bug is fixed. + # pylint: disable=indexing-exception + return compile_op, [outputs[0]] + # pylint: enable=indexing-exception + + # TODO(b/36647078) remove disable when pylint bug is fixed. + # pylint: disable=indexing-exception + num_outputs = len(outputs[0]) + # pylint: enable=indexing-exception + + if output_shard_axes is None: + output_shard_axes = [0] * num_outputs + if num_outputs != len(output_shard_axes): + raise ValueError("Length of output_shard_axes must be equal to the number " + "of outputs.") + + if isinstance(outputs_from_all_shards, bool): + outputs_from_all_shards = [outputs_from_all_shards] * num_outputs + + if num_outputs != len(outputs_from_all_shards): + raise ValueError("Length of outputs_from_all_shards must be equal to the " + "number of outputs.") + + results = [] + for (axis, all_shards, x) in zip(output_shard_axes, outputs_from_all_shards, + zip(*outputs)): + if all_shards: + # Concatenate all of the outputs together (use stack for scalars). + shape = x[0].shape + is_scalar = shape is not None and (shape.ndims == 0) + results.append((array_ops.stack(list(x)) if is_scalar + else array_ops.concat(list(x), axis=axis))) + else: + # TODO(phawkins): use a smarter policy, e.g., round-robin across shards. + results.append(x[0]) + + return compile_op, results + + +def shard(computation, + inputs=None, + num_shards=1, + input_shard_axes=None, + outputs_from_all_shards=True, + output_shard_axes=None, + infeed_queue=None, + device_assignment=None, + name=None): + """Shards `computation` for parallel execution. + + `inputs` must be a list of Tensors or None (equivalent to an empty list), each + of which has a corresponding split axis (from `input_shard_axes`). Each input + is split into `num_shards` pieces along the corresponding axis, and + computation is applied to each shard in parallel. + + Tensors are broadcast to all shards if they are lexically captured by + `computation`. e.g., + + x = tf.constant(7) + def computation(): + return x + 3 + ... = shard(computation, ...) + + TODO(phawkins): consider adding support for broadcasting Tensors passed + as inputs. + + If `outputs_from_all_shards` is true, the outputs from all shards of + `computation` are concatenated back together along their `output_shards_axes`. + Otherwise, each output is taken from an arbitrary shard. + + Inputs and outputs of the computation must be at least rank-1 Tensors. + + Args: + computation: A Python function that builds a computation to apply to each + shard of the input. + inputs: A list of input tensors or None (equivalent to an empty list). Each + input tensor has a corresponding shard axes, given by `input_shard_axes`, + which must have size divisible by `num_shards`. + num_shards: The number of shards. + input_shard_axes: A list of dimensions along which to shard `inputs`, or + `None`. `None` means "shard all inputs along dimension 0". If not `None`, + there must be one dimension per input. + outputs_from_all_shards: Boolean or list of boolean. For each output, if + `True`, outputs from all shards are concatenated along the corresponding + `output_shard_axes` entry. Otherwise, each output is taken + from an arbitrary shard. If the argument is a boolean, the argument's + value is used for each output. + output_shard_axes: A list of dimensions along which to concatenate the + outputs of `computation`, or `None`. `None` means "concatenate all outputs + along dimension 0". If not `None`, there must be one dimension per output. + Ignored if `outputs_from_all_shards` is False. + infeed_queue: If not `None`, the `InfeedQueue` to use to augment the inputs + of `computation`. + device_assignment: If not `None`, a `DeviceAssignment` describing the + mapping between logical cores in the computation with physical cores in + the TPU topology. Uses a default device assignment if `None`. The + `DeviceAssignment` may be omitted if each shard of the computation uses + only one core, and there is either only one shard, or the number of shards + is equal to the number of cores in the TPU system. + name: (Deprecated) Does nothing. + Returns: + A list of output tensors. + Raises: + ValueError: If num_shards <= 0 + ValueError: If len(input_shard_axes) != len(inputs) + ValueError: If len(output_shard_axes) != len(outputs from `computation`) + """ + return split_compile_and_shard( + computation, + inputs=inputs, + num_shards=num_shards, + input_shard_axes=input_shard_axes, + outputs_from_all_shards=outputs_from_all_shards, + output_shard_axes=output_shard_axes, + infeed_queue=infeed_queue, + device_assignment=device_assignment, + name=name)[1] + + +def batch_parallel(computation, + inputs=None, + num_shards=1, + infeed_queue=None, + device_assignment=None, + name=None): + """Shards `computation` along the batch dimension for parallel execution. + + Convenience wrapper around shard(). + + `inputs` must be a list of Tensors or None (equivalent to an empty list). + Each input is split into `num_shards` pieces along the 0-th dimension, and + computation is applied to each shard in parallel. + + Tensors are broadcast to all shards if they are lexically captured by + `computation`. e.g., + + x = tf.constant(7) + def computation(): + return x + 3 + ... = shard(computation, ...) + + The outputs from all shards are concatenated back together along their 0-th + dimension. + + Inputs and outputs of the computation must be at least rank-1 Tensors. + + Args: + computation: A Python function that builds a computation to apply to each + shard of the input. + inputs: A list of input tensors or None (equivalent to an empty list). The + 0-th dimension of each Tensor must have size divisible by `num_shards`. + num_shards: The number of shards. + infeed_queue: If not `None`, the `InfeedQueue` from which to append a tuple + of arguments as inputs to `computation`. + device_assignment: If not `None`, a `DeviceAssignment` describing the + mapping between logical cores in the computation with physical cores in + the TPU topology. Uses a default device assignment if `None`. The + `DeviceAssignment` may be omitted if each shard of the computation uses + only one core, and there is either only one shard, or the number of shards + is equal to the number of cores in the TPU system. + name: (Deprecated) Does nothing. + Returns: + A list of output tensors. + Raises: + ValueError: If `num_shards <= 0` + """ + return shard( + computation, + inputs, + num_shards=num_shards, + infeed_queue=infeed_queue, + device_assignment=device_assignment, + name=name) + + +def rewrite(computation, + inputs=None, + infeed_queue=None, + device_assignment=None, + name=None): + """Rewrites `computation` for execution on a TPU system. + + Args: + computation: A Python function that builds a computation to apply to the + input. If the function takes n inputs, 'inputs' should be a list of n + tensors. + + `computation` may return a list of operations and tensors. Tensors must + come before operations in the returned list. The return value of + `rewrite` is a list of tensors corresponding to the tensors from the + output of `computation`. + + All `Operation`s constructed during `computation` will be executed when + evaluating any of the returned output tensors, not just the ones returned. + inputs: A list of input tensors or `None` (equivalent to an empty list). + Each input can be a nested structure containing values that are + convertible to tensors. Note that passing an N-dimension list of + compatible values will result in a N-dimention list of scalar tensors + rather than a single Rank-N tensors. If you need different behavior, + convert part of inputs to tensors with `tf.convert_to_tensor`. + infeed_queue: If not `None`, the `InfeedQueue` from which to append a tuple + of arguments as inputs to `computation`. + device_assignment: if not `None`, a `DeviceAssignment` describing the + mapping between logical cores in the computation with physical cores in + the TPU topology. May be omitted for a single-core computation, in which + case the core attached to task 0, TPU device 0 is used. + name: (Deprecated) Does nothing. + Returns: + Same data structure as if computation(*inputs) is called directly with some + exceptions for correctness. Exceptions include: + 1) None output: a NoOp would be returned which control-depends on + computation. + 2) Single value output: A tuple containing the value would be returned. + 3) Operation-only outputs: a NoOp would be returned which + control-depends on computation. + TODO(b/121383831): Investigate into removing these special cases. + """ + # TODO(b/36647078) remove disable when pylint bug is fixed. + # pylint: disable=indexing-exception + return replicate( + computation, + None if inputs is None else [inputs], + infeed_queue=infeed_queue, + device_assignment=device_assignment, + name=name)[0] + # pylint: enable=indexing-exception + + # Operations that indicate some error in the user's inference graph. +_BLACKLISTED_INFERENCE_OPS = set([ + "ReadVariableOp", + "AssignVariableOp", + "AssignAddVariableOp", + "AssignSubVariableOp", + "VarHandleOp", + "Variable", + "VariableV2", +]) + + +def under_tpu_inference_context(): + """Check if it is currently under `tpu.rewrite_for_inference()`.""" + graph = ops.get_default_graph() + + context = graph._get_control_flow_context() # pylint: disable=protected-access + while context: + if isinstance(context, _TPUInferenceContext): + return True + context = context.outer_context + + return False + + +class _TPUInferenceContext(control_flow_ops.XLAControlFlowContext): + """A `ControlFlowContext` for nodes inside a TPU inference computation. + + The primary role of `TPUReplicateContext` is to sanity check operators inside + a tpu.rewrite_for_inference() computation. + """ + + def __init__(self, name): + super(_TPUInferenceContext, self).__init__() + self._name = name + + def AddOp(self, op): + self._AddOpInternal(op) + + def _AddOpInternal(self, op): + # pylint: disable=protected-access + if op.type in _BLACKLISTED_INFERENCE_OPS: + raise NotImplementedError( + "Operation of type %s (%s) is not supported on the TPU for inference." + " Execution will fail if this op is used in the graph. Make sure your" + " variables are using variable_scope." % (op.type, op.name)) + if self._outer_context: + self._outer_context.AddInnerOp(op) + + def AddValue(self, val): + result = val + if self._outer_context: + result = self._outer_context.AddValue(val) + return result + + def AddInnerOp(self, op): + self._AddOpInternal(op) + + @property + def grad_state(self): + return None + + +def validate_inference_rewrite_for_variables(graph): + """Validates whether rewrite_for_inference() 'worked' for variables. + + The rewrite_for_inference() method is supposed to append GuaranteeConstOps + after ReadVariableOps, but this mechanism works only if you are using + tf.get_variable() to create and access variables in your tpu computation. + This validation method can be called immediately after calling + tpu.rewrite_for_inference() to check whether GuaranteeConstOps where added + to the graph. + + Typical usages: + tpu.validate_inference_rewrite_for_variables(tf.get_default_graph()) + + tpu.validate_inference_rewrite_for_variables(sess.graph) + + Args: + graph: The graph which needs to be validated. + Raises: + RuntimeError: if validation failed. + """ + if not any(x.type == "GuaranteeConst" for x in graph.get_operations()): + raise RuntimeError( + "No GuaranteeConst ops found in the graph after running " + "tpu.rewrite_for_inference(...). Please check that you are using " + "tf.get_variable() to create and access variables in your tpu " + "computation.") + + +def rewrite_for_inference(computation, + inputs=None, + infeed_queue=None, + device_assignment=None, + name=None): + """Rewrites `computation` for inference on a TPU system. + + Other than 'rewriting' the computation to run on a TPU, if using variables + in your computation, it moves the ReadVariableOps outside the TPU + computation, and adds GuaranteeConst ops just after the ReadVariableOps. + This mechanism works only if you are using tf.get_variable() to create and + access variables in your tpu computation. You can validate whether this + worked, by calling validate_inference_rewrite_for_variables() method + immediately after this method to check whether GuaranteeConstOps where + added to the graph. + + Args: + computation: A Python function that builds a computation to apply to the + input. If the function takes n inputs, 'inputs' should be a list of n + tensors. If the function returns m outputs, rewrite will return a list of + m tensors. + inputs: A list of input tensors or `None` (equivalent to an empty list). + infeed_queue: If not `None`, the `InfeedQueue` from which to append a tuple + of arguments as inputs to `computation`. + device_assignment: if not `None`, a `DeviceAssignment` describing the + mapping between logical cores in the computation with physical cores in + the TPU topology. May be omitted for a single-core computation, in which + case the core attached to task 0, TPU device 0 is used. + name: The name of the operator. + Returns: + A list of output tensors. + """ + + def guarantee_const_getter(getter, name, *args, **kwargs): + with ops.control_dependencies(None): + return array_ops.guarantee_const( + getter(name, *args, **kwargs), name=name + "/GuaranteeConst") + + def wrapped_computation(*args, **kwargs): + """Execute computation under `_TPUInferenceContext`.""" + context = _TPUInferenceContext( + name=ops.get_default_graph().unique_name("rewrite_for_inference")) + try: + context.Enter() + + vscope = variable_scope.get_variable_scope() + prev_custom_getter = vscope.custom_getter + prev_caching_device = vscope.caching_device + vscope.set_custom_getter(guarantee_const_getter) + vscope.set_caching_device(lambda op: op.device) + + result = computation(*args, **kwargs) + + vscope.set_custom_getter(prev_custom_getter) + vscope.set_caching_device(prev_caching_device) + finally: + context.Exit() + return result + + # pylint: disable=undefined-variable + return rewrite( + wrapped_computation, + inputs=inputs, + infeed_queue=infeed_queue, + device_assignment=device_assignment, + name=name) + # pylint: enable=undefined-variable diff --git a/tensorflow/python/tpu/tpu_config.py b/tensorflow/python/tpu/tpu_config.py new file mode 100644 index 0000000000..a9038a9522 --- /dev/null +++ b/tensorflow/python/tpu/tpu_config.py @@ -0,0 +1,276 @@ +# 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. +# =================================================================== + +"""A RunConfig subclass with TPU support.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import collections +import json +import os + +from tensorflow.core.protobuf import config_pb2 +from tensorflow.python.estimator import run_config as run_config_lib +from tensorflow.python.platform import tf_logging as logging +from tensorflow.python.tpu import util as util_lib + +# pylint: disable=protected-access +_TF_CONFIG_ENV = run_config_lib._TF_CONFIG_ENV +_SERVICE_KEY = run_config_lib._SERVICE_KEY +_TPU_WORKER_JOB_NAME = 'tpu_worker_job_name' +# 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 + BROADCAST = 4 + + +class TPUConfig( + collections.namedtuple('TPUConfig', [ + 'iterations_per_loop', + 'num_shards', + 'num_cores_per_replica', + 'per_host_input_for_training', + 'tpu_job_name', + 'initial_infeed_sleep_secs', + 'input_partition_dims', + ])): + r"""TPU related configuration required by `TPUEstimator`. + + Args: + iterations_per_loop: This is the number of train steps running in TPU + system before returning to CPU host for each `Session.run`. This means + global step is increased `iterations_per_loop` times in one `Session.run`. + It is recommended to be set as number of global steps for next checkpoint. + num_shards: (Deprecated, ignored by TPUEstimator). + The number of model replicas in the system. For non-model-parallelism + case, this number equals the total number of TPU cores. For + model-parallelism, the total number of TPU cores equals + num_cores_per_replica * num_shards. + num_cores_per_replica: Defaults to `None`, which disables model parallelism. + An integer which describes the number of TPU cores per model replica. This + is required by model-parallelism which enables partitioning + the model to multiple cores. Currently num_cores_per_replica must be + 1, 2, 4, or 8. + per_host_input_for_training: If `True`, `PER_HOST_V1`, or `PER_HOST_V2`, + `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. In `BROADCAST` mode, `input_fn` is only + invoked once on host 0 and the tensors are broadcasted to all other + replicas. The batch size equals to train_batch_size`. 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 + string. + initial_infeed_sleep_secs: The number of seconds the infeed thread should + wait before enqueueing the first batch. This helps avoid timeouts for + models that require a long compilation time. + input_partition_dims: A nested list to describe the partition dims + for all the tensors from input_fn(). The structure of + input_partition_dims must match the structure of `features` and + `labels` from input_fn(). The total number of partitions must match + `num_cores_per_replica`. For example, if input_fn() returns two tensors: + images with shape [N, H, W, C] and labels [N]. + input_partition_dims = [[1, 2, 2, 1], None] will split the images to 4 + pieces and feed into 4 TPU cores. labels tensor are directly broadcasted + to all the TPU cores since the partition dims is `None`. + Current limitations: This feature is only supported with the PER_HOST_V2 + input mode. + + Raises: + ValueError: If `num_cores_per_replica` is not 1, 2, 4, 8 or 16. + """ + + def __new__(cls, + iterations_per_loop=2, + num_shards=None, + num_cores_per_replica=None, + per_host_input_for_training=True, + tpu_job_name=None, + initial_infeed_sleep_secs=None, + input_partition_dims=None): + + # Check iterations_per_loop. + util_lib.check_positive_integer(iterations_per_loop, + 'TPUConfig iterations_per_loop') + + # Check num_shards. + if num_shards is not None: + util_lib.check_positive_integer(num_shards, 'TPUConfig num_shards') + + if input_partition_dims is not None: + if len(input_partition_dims) != 1 and len(input_partition_dims) != 2: + raise ValueError( + 'input_partition_dims must be a list/tuple with one or two' + ' elements.') + + if per_host_input_for_training is not InputPipelineConfig.PER_HOST_V2: + raise ValueError( + 'input_partition_dims is only supported in PER_HOST_V2 mode.') + + if num_cores_per_replica is None: + raise ValueError( + 'input_partition_dims requires setting num_cores_per_replica.') + + # Check num_cores_per_replica + if num_cores_per_replica is not None: + if num_cores_per_replica not in [1, 2, 4, 8, 16]: + raise ValueError( + 'num_cores_per_replica must be 1, 2, 4, 8, or 16; got {}'.format( + str(num_cores_per_replica))) + + # 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, + 'TPUConfig initial_infeed_sleep_secs') + + tpu_job_name = tpu_job_name or _get_tpu_job_name_from_tf_config() + + return super(TPUConfig, cls).__new__( + cls, + iterations_per_loop=iterations_per_loop, + num_shards=num_shards, + num_cores_per_replica=num_cores_per_replica, + per_host_input_for_training=per_host_input_for_training, + tpu_job_name=tpu_job_name, + initial_infeed_sleep_secs=initial_infeed_sleep_secs, + input_partition_dims=input_partition_dims) + + +class RunConfig(run_config_lib.RunConfig): + """RunConfig with TPU support.""" + + def __init__(self, + tpu_config=None, + evaluation_master=None, + master=None, + cluster=None, + **kwargs): + """Constructs a RunConfig. + + Args: + tpu_config: the TPUConfig that specifies TPU-specific configuration. + evaluation_master: a string. The address of the master to use for eval. + Defaults to master if not set. + master: a string. The address of the master to use for training. + cluster: a ClusterResolver + **kwargs: keyword config parameters. + + Raises: + ValueError: if cluster is not None and the provided session_config has a + cluster_def already. + """ + super(RunConfig, self).__init__(**kwargs) + self._tpu_config = tpu_config or TPUConfig() + self._cluster = cluster + + # If user sets master and/or evaluation_master explicitly, including empty + # string '', take it. Otherwise, take the values set by parent class. + if master is not None: + if cluster is not None: + raise ValueError('Both master and cluster are set.') + self._master = master + else: + if cluster: + self._master = cluster.master() + + if evaluation_master is not None: + self._evaluation_master = evaluation_master + elif (not self._evaluation_master and + self.task_type != run_config_lib.TaskType.EVALUATOR): + # If the task type is EVALUATOR, it means some cluster manager sets the + # TF_CONFIG. In that case, we respect the configuration in TF_CONFIG. + # + # Otherwise, it means user executes the code without external cluster + # manager. For that, we optimize the user experience by setting + # evaluation_master to master, unless user overwrites it. + self._evaluation_master = self._master + + # Set the ClusterSpec to use + if cluster: + self._cluster_spec = cluster.cluster_spec() + + # Merge the cluster_def into the ConfigProto. + if self._session_config is None: # pylint: disable=access-member-before-definition + self._session_config = config_pb2.ConfigProto( + allow_soft_placement=True, isolate_session_state=True) + if self._session_config.HasField('cluster_def'): + raise ValueError( + 'You cannot provide a ClusterResolver and ' + 'session_config.cluster_def.') + if self._cluster_spec: + self._session_config.cluster_def.CopyFrom( + self._cluster_spec.as_cluster_def()) + + def _maybe_overwrite_session_config_for_distributed_training(self): + # Overrides the parent class session_config overwrite for between-graph. TPU + # runs with in-graph, which should not have device filter. Doing nothing + # ("pass") basically disables it. + pass + + @property + def evaluation_master(self): + return self._evaluation_master + + @property + def master(self): + return self._master + + @property + def tpu_config(self): + return self._tpu_config + + @property + def cluster(self): + return self._cluster + + def replace(self, **kwargs): + if 'tpu_config' not in kwargs: + return super(RunConfig, self).replace(**kwargs) + + tpu_config = kwargs.pop('tpu_config') + new_instance = super(RunConfig, self).replace(**kwargs) + new_instance._tpu_config = tpu_config # pylint: disable=protected-access + return new_instance + + +def _get_tpu_job_name_from_tf_config(): + """Extracts the TPU job name from TF_CONFIG env variable.""" + # TODO(xiejw): Extends this to support both TF_CONFIG env variable and cluster + # spec propagation. + tf_config = json.loads(os.environ.get(_TF_CONFIG_ENV, '{}')) + tpu_job_name = tf_config.get(_SERVICE_KEY, {}).get(_TPU_WORKER_JOB_NAME) + if tpu_job_name: + logging.info('Load TPU job name from TF_CONFIG: %s', tpu_job_name) + return tpu_job_name diff --git a/tensorflow/contrib/tpu/python/tpu/tpu_config_test.py b/tensorflow/python/tpu/tpu_config_test.py similarity index 98% rename from tensorflow/contrib/tpu/python/tpu/tpu_config_test.py rename to tensorflow/python/tpu/tpu_config_test.py index b2fe0a6888..22fb303216 100644 --- a/tensorflow/contrib/tpu/python/tpu/tpu_config_test.py +++ b/tensorflow/python/tpu/tpu_config_test.py @@ -20,10 +20,10 @@ from __future__ import print_function import json -from tensorflow.contrib.tpu.python.tpu import tpu_config as tpu_config_lib from tensorflow.core.protobuf import config_pb2 from tensorflow.python.estimator import run_config as run_config_lib from tensorflow.python.platform import test +from tensorflow.python.tpu import tpu_config as tpu_config_lib def _set_tf_config_env_variable(tf_config): diff --git a/tensorflow/python/tpu/tpu_context.py b/tensorflow/python/tpu/tpu_context.py new file mode 100644 index 0000000000..2511e427a2 --- /dev/null +++ b/tensorflow/python/tpu/tpu_context.py @@ -0,0 +1,763 @@ +# 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. +# =================================================================== +"""TPU system metadata and associated tooling.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from contextlib import contextmanager +import copy + +from tensorflow.python.estimator import model_fn as model_fn_lib +from tensorflow.python.platform import tf_logging as logging +from tensorflow.python.tpu import _tpu_estimator_embedding +from tensorflow.python.tpu import device_assignment as tpu_device_assignment +from tensorflow.python.tpu import tpu_config +from tensorflow.python.tpu import tpu_system_metadata as tpu_system_metadata_lib + + +_DEFAULT_JOB_NAME = 'tpu_worker' +_DEFAULT_COORDINATOR_JOB_NAME = 'coordinator' +_LOCAL_MASTERS = ('', 'local') +_NUM_CORES_TO_COMPUTATION_SHAPE = { + 1: [1, 1, 1], + 2: [1, 1, 2], + 4: [1, 2, 2], + 8: [2, 2, 2], + 16: [4, 2, 2], +} + + +class TPUContext(object): + """A context that holds the current configuration of the TPU computation.""" + + def __init__(self, + internal_ctx, + input_device=None, + invocation_index=None, + call_from_input_fn=True): + self._internal_ctx = internal_ctx + self._input_device = input_device + self._invocation_index = invocation_index + self._call_from_input_fn = call_from_input_fn + + def current_input_fn_deployment(self): + """The configuration of the current input_fn invocation. + + The configuration depends on `TPUConfig.per_host_input_for_training`. See + `TPUConfig` for details. + + Only set in params dict of input_fn + + Returns: + A tuple of + 1. Device spec string: String, is the current CPU host where the + input_fn is invoked. + 2. Current invocation index: Int, 0-based index of the input_fn + invocation. See next item for details. + 3. Total invocation count: Int, the total number of times to invoke the + input_fn on all CPU hosts. Each invocation will be passed with a new + `TPUContext` instance with current invocation index set properly. + 4. Total number of replicas consumed by current_invocation: Int, the + number of replicas fed by the data returned by current input_fn. For + example, for per_core input pipeline deployment + and non-model-parallelism, total invocation count is equal to + the number of cores in the system and num replicas consumed by + current invocation is 1. For per-host v2 input pipeline deployment, + total invocation count is equal to the number of hosts in the system + and num replicas consumed by current invocation is equal to number of + cores per host. + + Raises: + RuntimeError: If this method must not be called from input_fn. + """ + if not self._call_from_input_fn: + raise RuntimeError('This TPUContext instance must not be called from' + ' model_fn.') + + if self._internal_ctx.is_input_sharded_per_core(): + total_invocation_count = (self._internal_ctx.num_hosts + * self._internal_ctx.num_of_replicas_per_host) + replicas_consumed = 1 + elif self._internal_ctx.is_input_broadcast_with_iterators(): + total_invocation_count = 1 + replicas_consumed = self._internal_ctx.num_replicas + else: + total_invocation_count = self._internal_ctx.num_hosts + replicas_consumed = self._internal_ctx.num_of_replicas_per_host + return (self._input_device, self._invocation_index, + total_invocation_count, replicas_consumed) + + @property + def num_replicas(self): + """The total number of replicas. + + For non-model-parallelism, num_replicas should be the total num of TPU + cores in the system. + + Returns: + The number of replicas. + """ + return self._internal_ctx.num_replicas + + @property + def num_hosts(self): + """The number of hosts for the TPU system.""" + return self._internal_ctx.num_hosts + + @property + def current_host(self): + """The current host index for the TPU system.""" + return self._invocation_index + + @property + def num_of_replicas_per_host(self): + """The number of replicas for each host.""" + if self._internal_ctx.model_parallelism_enabled: + raise ValueError( + 'num_of_replicas_per_host is not supported for model_parallelism') + return self._internal_ctx.num_of_replicas_per_host + + @property + def device_assignment(self): + """Returns device_assignment object.""" + if self._call_from_input_fn: + raise RuntimeError('This TPUContext instance must not be called from' + ' input_fn.') + return self._internal_ctx.device_assignment + + def device_for_replica(self, replica_id): + """Returns the tuple of (CPU device and device ordinal) for replica. + + This should be used for full replicate for non-model-parallelism. + + Args: + replica_id: Int, the replica index. + + Returns: + A tuple of device spec for CPU device and int device ordinal. + """ + # Note that: For the non-model parallelism, the mapping could be + # a random permutation. The order should not matter in most cases + # as far as model is replicated to all cores in the system. + return self._internal_ctx.device_for_replica(replica_id) + + @property + def tpu_host_placement_function(self): + """Returns the TPU host place function. + + The place function takes host_id as the input and returns the TF device + for the correspoding host. + """ + + def _placement_function(host_id): + """Return the host device given host_id.""" + return self._internal_ctx.tpu_host_placement_function(host_id=host_id) + + return _placement_function + + +class _InternalTPUContext(object): + """A context holds immutable states of TPU computation. + + This immutable object holds TPUEstimator config, train/eval batch size, and + `TPUEstimator.use_tpu`, which is expected to be passed around. It also + provides utility functions, based on the current state, to determine other + information commonly required by TPU computation, such as TPU device names, + TPU hosts, shard batch size, etc. + + if eval_on_tpu is False, then execution of eval on TPU is disabled. + if eval_on_tpu is True, but use_tpu is False, a warning is issued, + and TPU execution is disabled for all modes. + + N.B. As `mode` is not immutable state in Estimator, but essential to + distinguish between TPU training and evaluation, a common usage for + _InternalTPUContext with `mode` is as follows: + ``` + with _ctx.with_mode(mode) as ctx: + if ctx.is_running_on_cpu(): + ... + ``` + """ + + def __init__(self, + config, + train_batch_size, + eval_batch_size, + predict_batch_size, + use_tpu, + eval_on_tpu=True, + embedding_config_spec=None): + self._config = config + self._train_batch_size = train_batch_size + self._eval_batch_size = eval_batch_size + self._predict_batch_size = predict_batch_size + self._use_tpu = use_tpu + logging.info('_TPUContext: eval_on_tpu %s', eval_on_tpu) + if not use_tpu and eval_on_tpu: + logging.warning('eval_on_tpu ignored because use_tpu is False.') + + self._eval_on_tpu = eval_on_tpu + self._model_parallelism_enabled = ( + use_tpu and config.tpu_config.num_cores_per_replica) + self._mode = None + num_cores_per_replica = config.tpu_config.num_cores_per_replica + if self._model_parallelism_enabled: + self._computation_shape = _NUM_CORES_TO_COMPUTATION_SHAPE[ + num_cores_per_replica] + else: + self._computation_shape = None + self._lazy_tpu_system_metadata_dict = {} # key by master address + self._lazy_device_assignment_dict = {} # key by master address + self._lazy_validation_dict = {} # key by ModeKeys + self._embedding_config_spec = embedding_config_spec + self._lazy_embedding_config_dict = {} # key by master address + + def _assert_mode(self): + if self._mode is None: + raise RuntimeError( + '`mode` needs to be set via contextmanager `with_mode`.') + return self._mode + + @contextmanager + def with_mode(self, mode): + # NOTE(xiejw): Shallow copy is enough. It will share he lazy dictionaries, + # such as _lazy_tpu_system_metadata_dict between new copy and the original + # one. Note that all lazy states stored in properties _lazy_foo are sort of + # immutable as they should be same for the process lifetime. + new_ctx = copy.copy(self) + new_ctx._mode = mode # pylint: disable=protected-access + yield new_ctx + + @property + def mode(self): + return self._assert_mode() + + def _get_master_address(self): + mode = self._assert_mode() + config = self._config + master = ( + config.master + if mode != model_fn_lib.ModeKeys.EVAL else config.evaluation_master) + return master + + def _get_tpu_system_metadata(self): + """Gets the (maybe cached) TPU system metadata.""" + master = self._get_master_address() + tpu_system_metadata = self._lazy_tpu_system_metadata_dict.get(master) + if tpu_system_metadata is not None: + return tpu_system_metadata + + cluster_def = None + if (self._config.session_config and + self._config.session_config.cluster_def.job): + cluster_def = self._config.session_config.cluster_def + + # pylint: disable=protected-access + tpu_system_metadata = ( + tpu_system_metadata_lib._query_tpu_system_metadata( + master, + cluster_def=cluster_def, + query_topology=self.model_parallelism_enabled)) + + self._lazy_tpu_system_metadata_dict[master] = tpu_system_metadata + return tpu_system_metadata + + def _get_device_assignment(self): + """Gets the (maybe cached) TPU device assignment.""" + master = self._get_master_address() + device_assignment = self._lazy_device_assignment_dict.get(master) + if device_assignment is not None: + return device_assignment + + tpu_system_metadata = self._get_tpu_system_metadata() + + device_assignment = tpu_device_assignment.device_assignment( + tpu_system_metadata.topology, + computation_shape=self._computation_shape, + num_replicas=self.num_replicas) + + logging.info('num_cores_per_replica: %s', + str(self._config.tpu_config.num_cores_per_replica)) + logging.info('computation_shape: %s', str(self._computation_shape)) + logging.info('num_replicas: %d', self.num_replicas) + logging.info('device_assignment.topology.device_coordinates: %s', + str(device_assignment.topology.device_coordinates)) + logging.info('device_assignment.core_assignment: %s', + str(device_assignment.core_assignment)) + + self._lazy_device_assignment_dict[master] = device_assignment + return device_assignment + + @property + def embedding_config(self): + """Returns the embedding config based on current mode.""" + master = self._get_master_address() + if master in self._lazy_embedding_config_dict: + embedding_config = self._lazy_embedding_config_dict[master] + else: + embedding_config = None + if self._use_tpu and self._embedding_config_spec: + embedding_config = _tpu_estimator_embedding.EmbeddingConfig( + self._embedding_config_spec, self._train_batch_size, + self._eval_batch_size, self.num_hosts, self.num_cores, master) + if not embedding_config.has_embedding_tables(): + embedding_config = None + self._lazy_embedding_config_dict[master] = embedding_config + + if embedding_config is not None: + mode = self._assert_mode() + # Dynamically attach tpu_embedding based on mode. With + # this, we could keep embedding_config immutable but call site always + # accesses the unified API '.tpu_embedding'. + embedding_config.tpu_embedding = embedding_config.get_tpu_embedding(mode) + return embedding_config + + @property + def model_parallelism_enabled(self): + return self._model_parallelism_enabled + + @property + def input_partition_dims(self): + return self._config.tpu_config.input_partition_dims + + @property + def device_assignment(self): + return (self._get_device_assignment() + if self._model_parallelism_enabled else None) + + @property + def num_of_cores_per_host(self): + metadata = self._get_tpu_system_metadata() + return metadata.num_of_cores_per_host + + @property + def num_cores(self): + metadata = self._get_tpu_system_metadata() + return metadata.num_cores + + @property + def num_of_replicas_per_host(self): + """Return the number of replicas per host.""" + if self.model_parallelism_enabled: + return self.num_replicas // self.num_hosts + else: + return self.num_of_cores_per_host + + @property + def num_replicas(self): + num_cores_in_system = self.num_cores + + if self.model_parallelism_enabled: + num_cores_per_replica = self._config.tpu_config.num_cores_per_replica + if num_cores_per_replica > num_cores_in_system: + raise ValueError( + 'The num of cores required by the model parallelism, specified by ' + 'TPUConfig.num_cores_per_replica, is larger than the total num of ' + 'TPU cores in the system. num_cores_per_replica: {}, num cores ' + 'in the system: {}'.format(num_cores_per_replica, + num_cores_in_system)) + + if num_cores_in_system % num_cores_per_replica != 0: + raise RuntimeError( + 'The num of cores in the system ({}) is not divisible by the num ' + 'of cores ({}) required by the model parallelism, specified by ' + 'TPUConfig.num_cores_per_replica. This should never happen!'.format( + num_cores_in_system, num_cores_per_replica)) + + return num_cores_in_system // num_cores_per_replica + else: + return num_cores_in_system + + @property + def num_hosts(self): + metadata = self._get_tpu_system_metadata() + return metadata.num_hosts + + @property + def config(self): + return self._config + + def is_input_sharded_per_core(self): + """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 + (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_input_broadcast_with_iterators(self): + """Return true if input_fn should be run in the full_replicae config.""" + return (self._config.tpu_config.per_host_input_for_training is + tpu_config.InputPipelineConfig.BROADCAST) + + def is_running_on_cpu(self, is_export_mode=False): + """Determines whether the input_fn and model_fn should be invoked on CPU. + + This API also validates user provided configuration, such as batch size, + according the lazy initialized TPU system metadata. + + Args: + is_export_mode: Indicates whether the current mode is for exporting the + model, when mode == PREDICT. Only with this bool, we could + tell whether user is calling the Estimator.predict or + Estimator.export_savedmodel, which are running on TPU and CPU + respectively. Parent class Estimator does not distinguish these two. + + Returns: + bool, whether current input_fn or model_fn should be running on CPU. + + Raises: + ValueError: any configuration is invalid. + """ + + is_running_on_cpu = self._is_running_on_cpu(is_export_mode) + if not is_running_on_cpu: + self._validate_tpu_configuration() + return is_running_on_cpu + + def _is_running_on_cpu(self, is_export_mode): + """Determines whether the input_fn and model_fn should be invoked on CPU.""" + mode = self._assert_mode() + + if not self._use_tpu: + return True + + if mode == model_fn_lib.ModeKeys.EVAL and not self._eval_on_tpu: + logging.info('_is_running_on_cpu: eval_on_tpu disabled') + return True + + if is_export_mode: + return True + + return False + + @property + def global_batch_size(self): + mode = self._assert_mode() + if mode == model_fn_lib.ModeKeys.TRAIN: + return self._train_batch_size + elif mode == model_fn_lib.ModeKeys.EVAL: + return self._eval_batch_size + elif mode == model_fn_lib.ModeKeys.PREDICT: + return self._predict_batch_size + else: + return None + + @property + def batch_size_for_input_fn(self): + """Returns the shard batch size for `input_fn`.""" + global_batch_size = self.global_batch_size + + if (self.is_running_on_cpu() or self.is_input_broadcast_with_iterators()): + return global_batch_size + + # On TPU + if self.is_input_sharded_per_core() or ( + self.is_input_per_host_with_iterators()): + return global_batch_size // self.num_replicas + else: + return global_batch_size // self.num_hosts + + @property + def batch_size_for_model_fn(self): + """Returns the shard batch size for `model_fn`.""" + global_batch_size = self.global_batch_size + + if (self.is_running_on_cpu() or self.is_input_broadcast_with_iterators()): + return global_batch_size + + # On TPU. always sharded per shard. + return global_batch_size // self.num_replicas + + @property + def master_job(self): + """Returns the job name to use to place TPU computations on. + + Returns: + A string containing the job name, or None if no job should be specified. + + Raises: + ValueError: If the user needs to specify a tpu_job_name, because we are + unable to infer the job name automatically, or if the user-specified job + names are inappropriate. + """ + run_config = self._config + # If the user specifies the tpu_job_name, use that. + if run_config.tpu_config.tpu_job_name: + return run_config.tpu_config.tpu_job_name + + # The tpu job is determined by the run_config. Right now, this method is + # required as tpu_config is not part of the RunConfig. + mode = self._assert_mode() + master = ( + run_config.evaluation_master + if mode == model_fn_lib.ModeKeys.EVAL else run_config.master) + if master in _LOCAL_MASTERS: + return None + + if (not run_config.session_config or + not run_config.session_config.cluster_def.job): + return _DEFAULT_JOB_NAME + cluster_def = run_config.session_config.cluster_def + job_names = set([job.name for job in cluster_def.job]) + if _DEFAULT_JOB_NAME in job_names: + # b/37868888 tracks allowing ClusterSpec propagation to reuse job names. + raise ValueError('Currently, tpu_worker is not an allowed job name.') + if len(job_names) == 1: + return cluster_def.job[0].name + if len(job_names) == 2: + if _DEFAULT_COORDINATOR_JOB_NAME in job_names: + job_names.remove(_DEFAULT_COORDINATOR_JOB_NAME) + return job_names.pop() + # TODO(b/67716447): Include more sophisticated heuristics. + raise ValueError( + 'Could not infer TPU job name. Please specify a tpu_job_name as part ' + 'of your TPUConfig.') + + @property + def tpu_host_placement_function(self): + """Returns the TPU host place function.""" + + master = self.master_job + + def _placement_function(_sentinal=None, replica_id=None, host_id=None): # pylint: disable=invalid-name + """Return the host device given replica_id or host_id.""" + assert _sentinal is None + if replica_id is not None and host_id is not None: + raise RuntimeError( + 'replica_id and host_id can have only one non-None value.') + + if master is None: + return '/replica:0/task:0/device:CPU:0' + else: + if replica_id is not None: + if self.model_parallelism_enabled: + return self.device_assignment.host_device( + replica=replica_id, job=master) + else: + host_id = replica_id / self.num_of_cores_per_host + + return '/job:%s/task:%d/device:CPU:0' % (master, host_id) + + return _placement_function + + @property + def tpu_device_placement_function(self): + """Returns a TPU device placement Fn.""" + master = self.master_job + job_device = '' if master is None else ('/job:%s' % master) + + def _placement_function(i): + if self.model_parallelism_enabled: + return self.device_assignment.tpu_device(replica=i, job=master) + else: + num_of_cores_per_host = self.num_of_cores_per_host + host_id = i / num_of_cores_per_host + ordinal_id = i % num_of_cores_per_host + return '%s/task:%d/device:TPU:%d' % (job_device, host_id, ordinal_id) + + return _placement_function + + def tpu_ordinal_function(self, host_id): + """Returns the TPU ordinal fn.""" + + def _tpu_ordinal_function(shard_index_in_host): + """Return the TPU ordinal associated with a shard. + + Required because the enqueue ops are placed on CPU. + + Args: + shard_index_in_host: the shard index + + Returns: + The ordinal of the TPU device the shard's infeed should be placed on. + """ + if self.model_parallelism_enabled: + # We put both enqueue/dequeue ops at tpu.core(0) in each replica. + replica = self.device_assignment.lookup_replicas(host_id, + 0)[shard_index_in_host] + return self.device_assignment.tpu_ordinal(replica=replica) + else: + return shard_index_in_host % self.num_of_cores_per_host + + return _tpu_ordinal_function + + def _validate_tpu_configuration(self): + """Validates the configuration based on the TPU system metadata.""" + mode = self._assert_mode() + if self._lazy_validation_dict.get(mode): + return + + # All following information is obtained from TPU system metadata. + num_cores = self.num_cores + num_replicas = self.num_replicas + num_hosts = self.num_hosts + + if not num_cores: + tpu_system_metadata = self._get_tpu_system_metadata() + raise RuntimeError( + 'Cannot find any TPU cores in the system. Please double check ' + 'Tensorflow master address and TPU worker(s). Available devices ' + 'are {}.'.format(tpu_system_metadata.devices)) + + if self._config.tpu_config.num_shards: + user_provided_num_replicas = self._config.tpu_config.num_shards + if user_provided_num_replicas != num_replicas: + message = ( + 'TPUConfig.num_shards is not set correctly. According to TPU ' + 'system metadata for Tensorflow master ({}): num_replicas should ' + 'be ({}), got ({}). For non-model-parallelism, num_replicas should ' + 'be the total num of TPU cores in the system. For ' + 'model-parallelism, the total number of TPU cores should be ' + 'num_cores_per_replica * num_replicas. Please set it ' + 'accordingly or leave it as `None`'.format( + self._get_master_address(), num_replicas, + user_provided_num_replicas)) + + raise ValueError(message) + + if self._config.tpu_config.num_cores_per_replica: + num_cores_per_replica = self._config.tpu_config.num_cores_per_replica + num_cores_per_host = self._get_tpu_system_metadata().num_of_cores_per_host + if num_cores_per_replica > num_cores_per_host: + raise ValueError( + 'The num of cores required by the model parallelism, specified by ' + 'TPUConfig.num_cores_per_replica, is larger than the ' + 'num_cores_per_host. num_cores_per_replica: {}, ' + 'num_cores_per_host: {}'.format(num_cores_per_replica, + num_cores_per_host)) + + if mode == model_fn_lib.ModeKeys.TRAIN: + if (self._train_batch_size % num_replicas != 0 and + not self.is_input_broadcast_with_iterators()): + raise ValueError( + 'train batch size {} must be divisible by number of replicas {}' + .format(self._train_batch_size, num_replicas)) + + elif mode == model_fn_lib.ModeKeys.EVAL: + if self._eval_batch_size is None: + raise ValueError( + 'eval_batch_size in TPUEstimator constructor cannot be `None`' + 'if .evaluate is running on TPU.') + if (self._eval_batch_size % num_replicas != 0 and + not self.is_input_broadcast_with_iterators()): + raise ValueError( + 'eval batch size {} must be divisible by number of replicas {}' + .format(self._eval_batch_size, num_replicas)) + if num_hosts > 1 and not self.is_input_broadcast_with_iterators(): + raise ValueError( + 'TPUEstimator.evaluate should be running on single TPU' + ' instead of a Pod.') + else: + assert mode == model_fn_lib.ModeKeys.PREDICT + if self._predict_batch_size is None: + raise ValueError( + 'predict_batch_size in TPUEstimator constructor should not be ' + '`None` if .predict is running on TPU.') + if (self._predict_batch_size % num_replicas != 0 and + not self.is_input_broadcast_with_iterators()): + raise ValueError( + 'predict batch size {} must be divisible by number of replicas {}' + .format(self._predict_batch_size, num_replicas)) + if num_hosts > 1 and not self.is_input_broadcast_with_iterators(): + raise ValueError( + 'TPUEstimator.predict should be running on single TPU worker. ' + 'got {}.'.format(num_hosts)) + + # Record the state "validated" into lazy dictionary. + self._lazy_validation_dict[mode] = True + + def device_for_replica(self, replica_id): + """Returns the tuple of (CPU device and device ordinal) for replica. + + This should be used for full replicate for non-model-parallelism. + + Args: + replica_id: Int, the replica index. + + Returns: + A tuple of device spec for CPU device and int device ordinal. + """ + master = self.master_job + + if self.model_parallelism_enabled: + return (self.device_assignment.host_device( + replica=replica_id, job=master), + self.device_assignment.tpu_ordinal(replica=replica_id)) + + job_device = '' if master is None else ('/job:%s' % master) + + num_of_replicas_per_host = self.num_of_replicas_per_host + host_id = replica_id / num_of_replicas_per_host + ordinal_id = replica_id % num_of_replicas_per_host + + host_device = '%s/task:%d/device:CPU:0' % (job_device, host_id) + return (host_device, ordinal_id) + + +class _OneCoreTPUContext(_InternalTPUContext): + """Special _InternalTPUContext for one core usage.""" + + def __init__(self, config, train_batch_size, eval_batch_size, + predict_batch_size, use_tpu): + + super(_OneCoreTPUContext, self).__init__( + config, train_batch_size, eval_batch_size, + predict_batch_size, use_tpu) + + def _get_tpu_system_metadata(self): + """Gets the (maybe cached) TPU system metadata.""" + master = self._get_master_address() + tpu_system_metadata = self._lazy_tpu_system_metadata_dict.get(master) + if tpu_system_metadata is not None: + return tpu_system_metadata + + tpu_system_metadata = ( + tpu_system_metadata_lib._TPUSystemMetadata( # pylint: disable=protected-access + num_cores=1, + num_hosts=1, + num_of_cores_per_host=1, + topology=None, + devices=[])) + + self._lazy_tpu_system_metadata_dict[master] = tpu_system_metadata + return tpu_system_metadata + + +def _get_tpu_context(config, train_batch_size, eval_batch_size, + predict_batch_size, use_tpu, eval_on_tpu, + embedding_config_spec): + """Returns an instance of `_InternalTPUContext`.""" + + if (config.tpu_config.num_shards == 1 and + config.tpu_config.num_cores_per_replica is None): + if embedding_config_spec is not None: + raise ValueError('Setting TPUConfig.num_shards==1 is unsupported ' + 'when embedding_config_spec is not None.') + logging.warning( + 'Setting TPUConfig.num_shards==1 is an unsupported behavior. ' + 'Please fix as soon as possible (leaving num_shards as None.)') + return _OneCoreTPUContext(config, train_batch_size, eval_batch_size, + predict_batch_size, use_tpu) + + return _InternalTPUContext(config, train_batch_size, eval_batch_size, + predict_batch_size, use_tpu, eval_on_tpu, + embedding_config_spec) diff --git a/tensorflow/python/tpu/tpu_embedding.py b/tensorflow/python/tpu/tpu_embedding.py new file mode 100644 index 0000000000..ee2658f914 --- /dev/null +++ b/tensorflow/python/tpu/tpu_embedding.py @@ -0,0 +1,1105 @@ +# 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. +# ============================================================================== +"""TPU embedding APIs.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import collections +import copy +import math +import re +import six + +from tensorflow.core.protobuf.tpu import optimization_parameters_pb2 +from tensorflow.core.protobuf.tpu import tpu_embedding_configuration_pb2 as elc +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import ops +from tensorflow.python.framework import sparse_tensor +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import control_flow_ops +from tensorflow.python.ops import init_ops +from tensorflow.python.ops import math_ops +from tensorflow.python.ops import partitioned_variables +from tensorflow.python.ops import state_ops +from tensorflow.python.ops import variable_scope +from tensorflow.python.tpu import tpu_system_metadata as tpu_system_metadata_lib +from tensorflow.python.tpu.ops import tpu_ops + +TRAINING = elc.TPUEmbeddingConfiguration.TRAINING +INFERENCE = elc.TPUEmbeddingConfiguration.INFERENCE + + +class TableConfig( + collections.namedtuple( + 'TableConfig', + ['vocabulary_size', 'dimension', 'initializer', 'combiner'])): + """Embedding table configuration.""" + + def __new__(cls, + vocabulary_size, + dimension, + initializer=None, + combiner='mean'): + """Embedding table configuration. + + Args: + vocabulary_size: Number of vocabulary (/rows) in the table. + dimension: The embedding dimension. + initializer: A variable initializer function to be used in embedding + variable initialization. If not specified, defaults to + `tf.truncated_normal_initializer` with mean `0.0` and standard deviation + `1/sqrt(dimension)`. + combiner: A string specifying how to reduce if there are multiple entries + in a single row. Currently 'mean', 'sqrtn' and 'sum' are supported, with + 'mean' the default. 'sqrtn' often achieves good accuracy, in particular + with bag-of-words columns. For more information, see + `tf.nn.embedding_lookup_sparse`. + + Returns: + `TableConfig`. + + Raises: + ValueError: if `vocabulary_size` is not positive integer. + ValueError: if `dimension` is not positive integer. + ValueError: if `initializer` is specified and is not callable. + ValueError: if `combiner` is not supported. + """ + if not isinstance(vocabulary_size, int) or vocabulary_size < 1: + raise ValueError('Invalid vocabulary_size {}.'.format(vocabulary_size)) + + if not isinstance(dimension, int) or dimension < 1: + raise ValueError('Invalid dimension {}.'.format(dimension)) + + if (initializer is not None) and (not callable(initializer)): + raise ValueError('initializer must be callable if specified.') + if initializer is None: + initializer = init_ops.truncated_normal_initializer( + mean=0.0, stddev=1 / math.sqrt(dimension)) + + if combiner not in ('mean', 'sum', 'sqrtn'): + raise ValueError('Invalid combiner {}'.format(combiner)) + + return super(TableConfig, cls).__new__(cls, vocabulary_size, dimension, + initializer, combiner) + + +AdamSlotVariableNames = collections.namedtuple( + 'AdamSlotVariableNames', ['m', 'v']) + +AdagradSlotVariableName = collections.namedtuple( + 'AdagradSlotVariableName', ['accumulator']) + +AdamSlotVariables = collections.namedtuple( + 'AdamSlotVariables', ['m', 'v']) + +AdagradSlotVariable = collections.namedtuple( + 'AdagradSlotVariable', ['accumulator']) + +VariablesAndOps = collections.namedtuple( + 'VariablesAndOps', + ['embedding_variables_by_table', 'slot_variables_by_table', + 'load_ops', 'retrieve_ops'] +) + + +# TODO(shizhiw): Factor `use_gradient_accumulation` and +# `pipeline_execution_with_tensor_core` out of `_OptimizationParameters`. +class _OptimizationParameters(object): + """Parameters common to all optimizations.""" + + def __init__(self, learning_rate, use_gradient_accumulation, + pipeline_execution_with_tensor_core): + self.learning_rate = learning_rate + self.use_gradient_accumulation = use_gradient_accumulation + self.pipeline_execution_with_tensor_core = ( + pipeline_execution_with_tensor_core) + + +class AdagradParameters(_OptimizationParameters): + """Optimization parameters for Adagrad.""" + + def __init__(self, learning_rate, initial_accumulator, + use_gradient_accumulation=False, + pipeline_execution_with_tensor_core=True): + """Optimization parameters for Adagrad. + + Args: + learning_rate: used for updating embedding table. + initial_accumulator: initial accumulator for Adagrad. + use_gradient_accumulation: setting this to `True` makes embedding + gradients calculation more accurate but slower. Please see + `optimization_parameters.proto` for details. + for details. + pipeline_execution_with_tensor_core: setting this to `True` makes training + faster, but trained model will be different if step N and step N+1 + involve the same set of embedding ID. Please see + `tpu_embedding_configuration.proto` for details. + """ + super(AdagradParameters, self).__init__(learning_rate, + use_gradient_accumulation, + pipeline_execution_with_tensor_core) + self.initial_accumulator = initial_accumulator + + +class AdamParameters(_OptimizationParameters): + """Optimization parameters for Adam.""" + + def __init__(self, learning_rate, + beta1=0.9, + beta2=0.999, + epsilon=1e-08, + lazy_adam=True, + sum_inside_sqrt=True, + use_gradient_accumulation=False, + pipeline_execution_with_tensor_core=True): + """Optimization parameters for Adam. + + Args: + learning_rate: a floating point value. The learning rate. + beta1: A float value. + The exponential decay rate for the 1st moment estimates. + beta2: A float value. + The exponential decay rate for the 2nd moment estimates. + epsilon: A small constant for numerical stability. + lazy_adam: Use lazy Adam instead of Adam. Lazy Adam trains faster. + Please see `optimization_parameters.proto` for details. + sum_inside_sqrt: This improves training speed. Please see + `optimization_parameters.proto` for details. + use_gradient_accumulation: setting this to `True` makes embedding + gradients calculation more accurate but slower. Please see + `optimization_parameters.proto` for details. + for details. + pipeline_execution_with_tensor_core: setting this to `True` makes training + faster, but trained model will be different if step N and step N+1 + involve the same set of embedding ID. Please see + `tpu_embedding_configuration.proto` for details. + """ + super(AdamParameters, self).__init__(learning_rate, + use_gradient_accumulation, + pipeline_execution_with_tensor_core) + self.beta1 = beta1 + self.beta2 = beta2 + self.epsilon = epsilon + self.lazy_adam = lazy_adam + self.sum_inside_sqrt = sum_inside_sqrt + + +class StochasticGradientDescentParameters(_OptimizationParameters): + """Optimization parameters for stochastic gradient descent. + + Args: + learning_rate: a floating point value. The learning rate. + use_gradient_accumulation: setting this to `True` makes embedding + gradients calculation more accurate but slower. Please see + `optimization_parameters.proto` for details. + pipeline_execution_with_tensor_core: setting this to `True` makes training + faster, but trained model will be different if step N and step N+1 + involve the same set of embedding ID. Please see + `tpu_embedding_configuration.proto` for details. + """ + + def __init__(self, learning_rate, use_gradient_accumulation=False, + pipeline_execution_with_tensor_core=True): + super(StochasticGradientDescentParameters, self).__init__( + learning_rate, use_gradient_accumulation, + pipeline_execution_with_tensor_core) + + +class TPUEmbedding(object): + """API for using TPU for embedding. + + Example: + ``` + table_config_user = tpu_embedding.TableConfig( + vocabulary_size=4, dimension=2, + initializer=initializer, combiner='mean') + table_to_config_dict = {'video': table_config_video, + 'user': table_config_user} + feature_to_table_dict = {'watched': 'video', + 'favorited': 'video', + 'friends': 'user'} + batch_size = 4 + num_hosts = 1 + optimization_parameters = tpu_embedding.AdagradParameters(1., 1.) + mode = tpu_embedding.TRAINING + embedding = tpu_embedding.TPUEmbedding( + table_to_config_dict, feature_to_table_dict, + batch_size, num_hosts, mode, optimization_parameters) + + batch_size_per_core = embedding.batch_size_per_core + sparse_features_list = [] + for host in hosts: + with ops.device(host): + for _ in range(embedding.num_cores_per_host): + sparse_features = {} + sparse_features['watched'] = sparse_tensor.SparseTensor(...) + sparse_features['favorited'] = sparse_tensor.SparseTensor(...) + sparse_features['friends'] = sparse_tensor.SparseTensor(...) + sparse_features_list.append(sparse_features) + + enqueue_ops = embedding.generate_enqueue_ops(sparse_features_list) + embedding_variables_and_ops = embedding.create_variables_and_ops() + + def computation(): + activations = embedding.get_activations() + loss = compute_loss(activations) + + base_optimizer = gradient_descent.GradientDescentOptimizer( + learning_rate=1) + cross_shard_optimizer = tpu_optimizer.CrossShardOptimizer( + base_optimizer) + + train_op = cross_shard_optimizer.minimize(loss) + gradients = ( + tpu_embedding_gradient.get_gradients_through_compute_gradients( + cross_shard_optimizer, loss, activations) + send_gradients_op = embedding.generate_send_gradients_op(gradients) + with ops.control_dependencies([train_op, send_gradients_op]): + loss = array_ops.identity(loss) + + loss = tpu.shard(computation, + num_shards=embedding.num_cores) + + with self.test_session() as sess: + sess.run(tpu.initialize_system(embedding_config= + embedding.config_proto)) + sess.run(variables.global_variables_initializer()) + sess.run(embedding_variables_and_ops.load_ops()) + sess.run(enqueue_ops) + loss_val = sess.run(loss) + ``` + """ + + # TODO(shizhiw): Instead of `feature_to_table_dict` which maps to table + # name, consider `feature_to_config_dict` which maps to `FeatureConfig`. + # `FeatureConfig` could have fields other than table name. For example, it + # could have a field to indicate that the feature should not be used to + # update embedding table (cr/204852758, cr/204940540). Also, this can support + # different combiners for different features within the same table. + # TODO(shizhiw, b/118512626): Remove `batch_size` from `__init__` and move it + # to `FeatureConfig`? + + # TODO(shizhiw): will it be cleaner to make `table_to_config_dict` and + # `feature_to_table_dict` lists of `TableSpec` and `FeatureSpec` respectively? + + # TODO(shizhiw): Consider adding `input_fn` as an option to remove boilerplate + # for-loops around construction of inputs. + + # `optimization_parameter` applies to all tables. If the need arises, + # we can add `optimization_parameters` to `TableConfig` to override this + # global setting. + def __init__(self, + table_to_config_dict, + feature_to_table_dict, + batch_size, + mode, + master, + optimization_parameters=None): + """API for using TPU for embedding lookups. + + Args: + table_to_config_dict: A dictionary mapping from string of table name to + `TableConfig`. Table refers to an embedding table, e.g. `params` + argument to `tf.nn.embedding_lookup_sparse()`. + feature_to_table_dict: A dictionary mapping from string of feature name + to string of table name. Feature refers to ids to lookup in embedding + table, e.g. `sp_ids` argument to `tf.nn.embedding_lookup_sparse()`. + batch_size: An `int` representing the global batch size. + mode: `TRAINING` or `INFERENCE`. + master: A `string` representing the TensorFlow master to use. + optimization_parameters: `AdagradParameters`, `AdamParameters`, + `Stochasticgradientdescentparameters`. Must be set in training and must + be `None` in inference. + + Raises: + ValueError: if any input is invalid. + """ + _validate_table_to_config_dict(table_to_config_dict) + # Avoid nondeterminism from `Dict` iteration order by using `OrderedDict`. + self._table_to_config_dict = _create_ordered_dict(table_to_config_dict) + self._combiners = _create_combiners(self._table_to_config_dict) + + _validate_feature_to_table_dict(table_to_config_dict, feature_to_table_dict) + self._feature_to_table_dict = _create_ordered_dict(feature_to_table_dict) + self._table_to_features_dict = _create_table_to_features_dict( + self._feature_to_table_dict) + + self._batch_size = batch_size + + self._master = master + self._tpu_system_metadata = ( + tpu_system_metadata_lib._query_tpu_system_metadata(self._master)) # pylint: disable=protected-access + if self._tpu_system_metadata.num_cores == 0: + raise ValueError('TPUEmbedding needs TPUs, but master {} does not have ' + 'TPUs.'.format(self._master)) + self._num_hosts = self._tpu_system_metadata.num_hosts + self._hosts = [device.name for device in self._tpu_system_metadata.devices + if 'device:CPU:' in device.name] + self._num_cores_per_host = self._tpu_system_metadata.num_of_cores_per_host + self._num_cores = self._tpu_system_metadata.num_cores + + _validate_batch_size(self._batch_size, self._num_cores) + self._batch_size_per_core = self._batch_size // self._num_cores + + # TODO(shizhiw): remove `mode`? + if mode == TRAINING: + _validate_optimization_parameters(optimization_parameters) + self._optimization_parameters = optimization_parameters + elif mode == INFERENCE: + if optimization_parameters is not None: + raise ValueError('`optimization_parameters` should be `None` ' + 'for inference mode.') + self._optimization_parameters = ( + StochasticGradientDescentParameters(1.)) + else: + raise ValueError('`mode` only supports {} and {}; got {}.' + .format(TRAINING, INFERENCE, mode)) + self._mode = mode + + # TODO(shizhiw): move `optimization_parameters` into `_optimizer_handler` + # and create special handler for inference that inherits from + # StochasticGradientDescentHandler with more user-friendly error message + # on get_slot(). + self._optimizer_handler = _get_optimization_handler( + self._optimization_parameters) + + self._config_proto = self._create_config_proto() + + @property + def hosts(self): + """A list of device names for CPU hosts. + + Returns: + A list of device names for CPU hosts. + """ + return copy.copy(self._hosts) + + # TODO(shizhiw): change to num_tensor_cores_per_host to be more explicit and + # to be consistent with `tpu_embedding_configuration.proto`. + @property + def num_cores_per_host(self): + """Number of TPU cores on a CPU host. + + Returns: + Number of TPU cores on a CPU host. + """ + return self._num_cores_per_host + + @property + def num_cores(self): + """Total number of TPU cores on all hosts. + + Returns: + Total number of TPU cores on all hosts. + """ + return self._num_cores + + @property + def batch_size_per_core(self): + """Batch size for each TPU core. + + The sparse tensors in `sparse_features_list` to `generate_enqueue_ops` + must have batch dimension equal to this. + + Returns: + Batch size for each TPU core. + """ + return self._batch_size_per_core + + @property + def config_proto(self): + """Create embedding config proto for `tpu.initialize_system()`. + + Returns: + an `TPUEmbeddingConfiguration` proto describing the desired + configuration of the hardware embedding lookup tables, which + is passed to `tpu.initialize_system()`. + """ + return self._config_proto + + @property + def table_to_config_dict(self): + return copy.copy(self._table_to_config_dict) + + @property + def feature_to_table_dict(self): + return copy.copy(self._feature_to_table_dict) + + @property + def table_to_features_dict(self): + return copy.copy(self._table_to_features_dict) + + @property + def optimization_parameters(self): + return self._optimization_parameters + + def _create_config_proto(self): + """Create `TPUEmbeddingConfiguration`.""" + config_proto = elc.TPUEmbeddingConfiguration() + for table in self._table_to_config_dict: + table_descriptor = config_proto.table_descriptor.add() + table_descriptor.name = table + + table_config = self._table_to_config_dict[table] + table_descriptor.vocabulary_size = table_config.vocabulary_size + table_descriptor.dimension = table_config.dimension + + features_for_table = self._table_to_features_dict[table] + table_descriptor.num_features = len(features_for_table) + + table_descriptor.optimization_parameters.learning_rate.constant = ( + self._optimization_parameters.learning_rate) + table_descriptor.optimization_parameters.gradient_accumulation_status = ( + optimization_parameters_pb2.GradientAccumulationStatus.ENABLED + if self._optimization_parameters.use_gradient_accumulation else + optimization_parameters_pb2.GradientAccumulationStatus.DISABLED) + # For compatibility with old TPU workers. + table_descriptor.optimization_parameters.use_gradient_accumulation = ( + self._optimization_parameters.use_gradient_accumulation) + self._optimizer_handler.set_optimization_parameters(table_descriptor) + + config_proto.mode = self._mode + config_proto.batch_size_per_tensor_core = self._batch_size_per_core + config_proto.num_hosts = self._num_hosts + config_proto.num_tensor_cores = self._num_cores + config_proto.sharding_strategy = elc.TPUEmbeddingConfiguration.DIV_DEFAULT + config_proto.pipeline_execution_with_tensor_core = ( + self._optimization_parameters.pipeline_execution_with_tensor_core) + + return config_proto + + def create_variables_and_ops(self, embedding_variable_name_by_table=None, + slot_variable_names_by_table=None): + """Create embedding and slot variables, with ops to load and retrieve them. + + Args: + embedding_variable_name_by_table: A dictionary mapping from string of + table name to string of embedding variable name. If `None`, + defaults from `get_default_slot_variable_names()` will be used. + slot_variable_names_by_table: A dictionary mapping from string of table + name to `AdamSlotVariableNames`, `AdagradSlotVariableNames` etc. If + `None`, defaults from `get_default_slot_variable_names()` will be used. + + Returns: + `tpu_embedding.VariablesAndOps` with: + A dictionary mapping from string of table name to embedding variables, + A dictionary mapping from string of table name to AdagradSlotVariable, + AdamSlotVariables etc with slot variables, + A function which returns a list of ops to load embedding and slot + variables from TPU to CPU. + A function which returns a list of ops to retrieve embedding and slot + variables from TPU to CPU. + """ + embedding_variables_by_table = {} + slot_variables_by_table = {} + load_op_fns = [] + retrieve_op_fns = [] + for table in self._table_to_config_dict: + if embedding_variable_name_by_table: + embedding_variable_name = embedding_variable_name_by_table[table] + else: + embedding_variable_name = table + if slot_variable_names_by_table: + slot_variable_names = slot_variable_names_by_table[table] + else: + slot_variable_names = ( + self._optimizer_handler.get_default_slot_variable_names(table)) + + device_fn = _create_device_fn(self._hosts) + with ops.device(device_fn): + table_variables = _create_partitioned_variables( + name=embedding_variable_name, + num_hosts=self._num_hosts, + vocabulary_size=self._table_to_config_dict[table].vocabulary_size, + embedding_dimension=self._table_to_config_dict[table].dimension, + initializer=self._table_to_config_dict[table].initializer, + collections=[ops.GraphKeys.GLOBAL_VARIABLES]) + embedding_variables_by_table[table] = table_variables + + slot_variables_for_table, load_ops_fn, retrieve_ops_fn = ( + self._optimizer_handler.create_variables_and_ops( + table, slot_variable_names, self._num_hosts, + self._table_to_config_dict[table], table_variables) + ) + slot_variables_by_table[table] = slot_variables_for_table + load_op_fns.append(load_ops_fn) + retrieve_op_fns.append(retrieve_ops_fn) + + def load_ops(): + """Calls and returns the load ops for each embedding table. + + Returns: + A list of ops to load embedding and slot variables from CPU to TPU. + """ + load_ops_list = [] + for load_op_fn in load_op_fns: + load_ops_list.extend(load_op_fn()) + return load_ops_list + + def retrieve_ops(): + """Calls and returns the retrieve ops for each embedding table. + + Returns: + A list of ops to retrieve embedding and slot variables from TPU to CPU. + """ + retrieve_ops_list = [] + for retrieve_op_fn in retrieve_op_fns: + retrieve_ops_list.extend(retrieve_op_fn()) + return retrieve_ops_list + + return VariablesAndOps(embedding_variables_by_table, + slot_variables_by_table, + load_ops, retrieve_ops) + + def generate_enqueue_ops(self, sparse_features_list): + """Generate enqueue ops. + + Args: + sparse_features_list: a list of dictionary mapping from string + of feature names to sparse tensor. Each dictionary is for one + TPU core. Dictionaries for the same core should be contiguous + on the list. + + Returns: + Ops to enqueue to TPU for embedding. + """ + self._validate_generate_enqueue_ops_sparse_features_list( + sparse_features_list) + return [ + self._generate_enqueue_op( + sparse_features, device_ordinal=i % self._num_cores_per_host) + for i, sparse_features in enumerate(sparse_features_list) + ] + + def _validate_generate_enqueue_ops_sparse_features_list( + self, sparse_features_list): + """Validate `sparse_features_list`.""" + if len(sparse_features_list) != self._num_cores: + raise ValueError('Length of `sparse_features_list` should match the ' + 'number of cores; ' + '`len(sparse_features_list)` is {}, ' + 'number of cores is {}.'.format( + len(sparse_features_list), self._num_cores)) + + feature_set = set(self._feature_to_table_dict.keys()) + contiguous_device = None + for i, sparse_features in enumerate(sparse_features_list): + used_feature_set = set(sparse_features.keys()) + + # Check features are valid. + missing_feature_set = feature_set - used_feature_set + if missing_feature_set: + raise ValueError('`sparse_features_list[{}]` misses a feature that is ' + 'in `feature_to_config_dict`: {}.'.format( + i, missing_feature_set)) + + extra_feature_set = used_feature_set - feature_set + if extra_feature_set: + raise ValueError('`sparse_features_list[{}]` has a feature that is not ' + 'in `feature_to_config_dict`: {}.'.format( + i, extra_feature_set)) + + device = None + device_feature = None + for feature, tensor in six.iteritems(sparse_features): + if not isinstance(tensor, sparse_tensor.SparseTensor): + raise ValueError('`sparse_features_list[{}]` has a feature that is ' + 'not mapped to `SparseTensor`. ' + '`feature`: {}, type: {}'.format( + i, feature, type(tensor))) + + # Check all features are on the same device. + if device is None: + device = tensor.op.device + device_feature = feature + else: + if device != tensor.op.device: + raise ValueError('Devices are different between features in ' + '`sparse_features_list[{}]`; ' + 'devices: {}, {}; features: {}, {}.'.format( + i, device, tensor.op.device, feature, + device_feature)) + + if i % self._num_cores_per_host: + if device != contiguous_device: + raise ValueError('We expect the `sparse_features` which are on the ' + 'same host to be contiguous in ' + '`sparse_features_list`, ' + '`sparse_features_list[{}]` is on device {}, ' + 'but is expected to be on device {}.'.format( + i, device, contiguous_device)) + else: + contiguous_device = device + + def _generate_enqueue_op(self, sparse_features, device_ordinal): + with ops.colocate_with(list(sparse_features.values())[0]): + sample_idcs, embedding_idcs, aggregation_weights = ( + self._format_for_tpu_embedding_sparse_batch(sparse_features)) + return tpu_ops.enqueue_tpu_embedding_sparse_batch( + sample_idcs, + embedding_idcs, + aggregation_weights, + combiners=self._combiners, + device_ordinal=device_ordinal) + + def _format_for_tpu_embedding_sparse_batch(self, sparse_features): + """Format sparse features for `enqueue_tpu_embedding_sparse_batch()`. + + Args: + sparse_features: a `Dict` of `SparseTensor`s for embedding. + + Returns: + Arguments for `enqueue_tpu_embedding_sparse_batch()`. + """ + + sample_idcs, embedding_idcs, aggregation_weights = list(), list(), list() + for table in self._table_to_features_dict: + sample_t, indices_t, weights_t = list(), list(), list() + + features = self._table_to_features_dict[table] + for i, feature in enumerate(features): + tensor = sparse_features[feature] + sample_indices = tensor.indices[:, 0] + embedding_indices = tensor.values + weights = array_ops.ones_like(embedding_indices) + sample_t.append(i * self._batch_size_per_core + sample_indices) + indices_t.append(embedding_indices) + weights_t.append(weights) + + sample_idcs.append( + math_ops.cast(array_ops.concat(sample_t, axis=0), dtype=dtypes.int32)) + embedding_idcs.append( + math_ops.cast( + array_ops.concat(indices_t, axis=0), dtype=dtypes.int32)) + aggregation_weights.append( + math_ops.cast( + array_ops.concat(weights_t, axis=0), dtype=dtypes.float32)) + + return sample_idcs, embedding_idcs, aggregation_weights + + def get_activations(self): + """Get activations for features. + + This should be called within `computation` that is passed to + `tpu.replicate` and friends. + + Returns: + A dictionary mapping from `String` of feature name to `Tensor` + of activation. + """ + recv_activations = tpu_ops.recv_tpu_embedding_activations( + num_outputs=len(self._table_to_config_dict), + config=self._config_proto.SerializeToString()) + + activations = collections.OrderedDict() + for table_id, table in enumerate(self._table_to_features_dict): + features = self._table_to_features_dict[table] + for lookup_id, feature in enumerate(features): + start_row = lookup_id * self._batch_size_per_core + end_row = start_row + self._batch_size_per_core + activations[feature] = recv_activations[table_id][start_row:end_row, :] + return activations + + def generate_send_gradients_op(self, feature_to_gradient_dict): + """Send gradient to TPU embedding. + + Args: + feature_to_gradient_dict: dict mapping feature names to gradient wrt + activations. + + Returns: + SendTPUEmbeddingGradients Op. + + Raises: + RuntimeError: If `mode` is not `TRAINING`. + """ + if self._mode != TRAINING: + raise RuntimeError('Only in training mode gradients need to ' + 'be sent to TPU embedding; got mode {}.' + .format(self._mode)) + gradients = [] + for table in self._table_to_features_dict: + features = self._table_to_features_dict[table] + table_gradients = [ + feature_to_gradient_dict[feature] for feature in features + ] + concat_table_grads = array_ops.concat(table_gradients, axis=0) + gradients.append(concat_table_grads) + return tpu_ops.send_tpu_embedding_gradients( + inputs=gradients, config=self.config_proto.SerializeToString()) + + +def _validate_table_to_config_dict(table_to_config_dict): + """Validate `table_to_config_dict`.""" + for k, v in six.iteritems(table_to_config_dict): + if not isinstance(v, TableConfig): + raise ValueError('Value of `table_to_config_dict` must be of type ' + '`TableConfig`, got {} for {}.'.format(type(v), k)) + + +def _validate_feature_to_table_dict(table_to_config_dict, + feature_to_table_dict): + """Validate `feature_to_table_dict`.""" + used_table_set = set(feature_to_table_dict.values()) + table_set = set(table_to_config_dict.keys()) + + unused_table_set = table_set - used_table_set + if unused_table_set: + raise ValueError('`table_to_config_dict` specifies table that is not ' + 'used in `feature_to_table_dict`: {}.' + .format(unused_table_set)) + + extra_table_set = used_table_set - table_set + if extra_table_set: + raise ValueError('`feature_to_table_dict` refers to a table that is not ' + 'specified in `table_to_config_dict`: {}.' + .format(extra_table_set)) + + +def _validate_batch_size(batch_size, num_cores): + if batch_size % num_cores: + raise ValueError('`batch_size` is not a multiple of number of ' + 'cores. `batch_size`={}, `_num_cores`={}.'.format( + batch_size, num_cores)) + + +def _validate_optimization_parameters(optimization_parameters): + if not isinstance(optimization_parameters, _OptimizationParameters): + raise ValueError('`optimization_parameters` must inherit from ' + '`_OptimizationPramaters`. ' + '`type(optimization_parameters)`={}'.format( + type(optimization_parameters))) + + +class _OptimizerHandler(object): + """Interface class for handling optimizer specific logic.""" + + def __init__(self, optimization_parameters): + self._optimization_parameters = optimization_parameters + + def set_optimization_parameters(self, table_descriptor): + raise NotImplementedError() + + def get_default_slot_variable_names(self, table): + raise NotImplementedError() + + def create_variables_and_ops(self, table, slot_variable_names, num_hosts, + table_config, table_variables): + raise NotImplementedError() + + +class _AdagradHandler(_OptimizerHandler): + """Handles Adagrad specific logic.""" + + def __init__(self, optimization_parameters): + super(_AdagradHandler, self).__init__(optimization_parameters) + self._table_to_accumulator_variables_dict = {} + + def set_optimization_parameters(self, table_descriptor): + table_descriptor.optimization_parameters.adagrad.SetInParent() + + def get_default_slot_variable_names(self, table): + return AdagradSlotVariableName('{}/{}'.format(table, 'Adagrad')) + + def create_variables_and_ops(self, table, slot_variable_names, num_hosts, + table_config, table_variables): + accumulator_initializer = init_ops.constant_initializer( + self._optimization_parameters.initial_accumulator) + accumulator_variables = _create_partitioned_variables( + name=slot_variable_names.accumulator, + num_hosts=num_hosts, + vocabulary_size=table_config.vocabulary_size, + embedding_dimension=table_config.dimension, + collections=[ops.GraphKeys.GLOBAL_VARIABLES], + initializer=accumulator_initializer) + slot_variables = AdagradSlotVariable(accumulator_variables) + + def load_ops_fn(): + """Returns the retrieve ops for AdaGrad embedding tables. + + Returns: + A list of ops to load embedding and slot variables from CPU to TPU. + """ + load_op_list = [] + for host_id, table_variable, accumulator_variable in (zip( + range(num_hosts), table_variables, accumulator_variables)): + with ops.colocate_with(table_variable): + load_parameters_op = ( + tpu_ops.load_tpu_embedding_adagrad_parameters( + parameters=table_variable, + accumulators=accumulator_variable, + table_name=table, + num_shards=num_hosts, + shard_id=host_id)) + load_op_list.append(load_parameters_op) + return load_op_list + + def retrieve_ops_fn(): + """Returns the retrieve ops for AdaGrad embedding tables. + + Returns: + A list of ops to retrieve embedding and slot variables from TPU to CPU. + """ + retrieve_op_list = [] + for host_id, table_variable, accumulator_variable in (zip( + range(num_hosts), table_variables, accumulator_variables)): + with ops.colocate_with(table_variable): + retrieved_table, retrieved_accumulator = ( + tpu_ops.retrieve_tpu_embedding_adagrad_parameters( + table_name=table, + num_shards=num_hosts, + shard_id=host_id)) + retrieve_parameters_op = control_flow_ops.group( + state_ops.assign(table_variable, retrieved_table), + state_ops.assign(accumulator_variable, retrieved_accumulator)) + retrieve_op_list.append(retrieve_parameters_op) + return retrieve_op_list + + return slot_variables, load_ops_fn, retrieve_ops_fn + + +class _AdamHandler(_OptimizerHandler): + """Handles Adam specific logic.""" + + def __init__(self, optimization_parameters): + super(_AdamHandler, self).__init__(optimization_parameters) + self._table_to_m_variables_dict = {} + self._table_to_v_variables_dict = {} + + def set_optimization_parameters(self, table_descriptor): + table_descriptor.optimization_parameters.adam.beta1 = ( + self._optimization_parameters.beta1) + table_descriptor.optimization_parameters.adam.beta2 = ( + self._optimization_parameters.beta2) + table_descriptor.optimization_parameters.adam.epsilon = ( + self._optimization_parameters.epsilon) + table_descriptor.optimization_parameters.adam.use_non_lazy_adam = ( + not self._optimization_parameters.lazy_adam) + table_descriptor.optimization_parameters.adam.use_sum_inside_sqrt = ( + self._optimization_parameters.sum_inside_sqrt) + + def get_default_slot_variable_names(self, table): + return AdamSlotVariableNames('{}/{}/m'.format(table, 'Adam'), + '{}/{}/v'.format(table, 'Adam')) + + def create_variables_and_ops(self, table, slot_variable_names, num_hosts, + table_config, table_variables): + m_initializer = init_ops.zeros_initializer() + m_variables = _create_partitioned_variables( + name=slot_variable_names.m, + num_hosts=num_hosts, + vocabulary_size=table_config.vocabulary_size, + embedding_dimension=table_config.dimension, + collections=[ops.GraphKeys.GLOBAL_VARIABLES], + initializer=m_initializer) + v_initializer = init_ops.zeros_initializer() + v_variables = _create_partitioned_variables( + name=slot_variable_names.v, + num_hosts=num_hosts, + vocabulary_size=table_config.vocabulary_size, + embedding_dimension=table_config.dimension, + collections=[ops.GraphKeys.GLOBAL_VARIABLES], + initializer=v_initializer) + slot_variables = AdamSlotVariables(m_variables, v_variables) + + def load_ops_fn(): + """Returns the retrieve ops for AdaGrad embedding tables. + + Returns: + A list of ops to load embedding and slot variables from CPU to TPU. + """ + load_op_list = [] + for host_id, table_variable, m_variable, v_variable in (zip( + range(num_hosts), table_variables, + m_variables, v_variables)): + with ops.colocate_with(table_variable): + load_parameters_op = ( + tpu_ops.load_tpu_embedding_adam_parameters( + parameters=table_variable, + momenta=m_variable, + velocities=v_variable, + table_name=table, + num_shards=num_hosts, + shard_id=host_id)) + + load_op_list.append(load_parameters_op) + return load_op_list + + def retrieve_ops_fn(): + """Returns the retrieve ops for Adam embedding tables. + + Returns: + A list of ops to retrieve embedding and slot variables from TPU to CPU. + """ + + retrieve_op_list = [] + for host_id, table_variable, m_variable, v_variable in (zip( + range(num_hosts), table_variables, + m_variables, v_variables)): + with ops.colocate_with(table_variable): + retrieved_table, retrieved_m, retrieved_v = ( + tpu_ops.retrieve_tpu_embedding_adam_parameters( + table_name=table, + num_shards=num_hosts, + shard_id=host_id)) + retrieve_parameters_op = control_flow_ops.group( + state_ops.assign(table_variable, retrieved_table), + state_ops.assign(m_variable, retrieved_m), + state_ops.assign(v_variable, retrieved_v)) + + retrieve_op_list.append(retrieve_parameters_op) + return retrieve_op_list + + return slot_variables, load_ops_fn, retrieve_ops_fn + + +class _StochasticGradientDescentHandler(_OptimizerHandler): + """Handles stochastic gradient descent specific logic.""" + + def set_optimization_parameters(self, table_descriptor): + (table_descriptor.optimization_parameters.stochastic_gradient_descent + .SetInParent()) + + def get_default_slot_variable_names(self, table): + return None + + def create_variables_and_ops(self, table, slot_variable_names, num_hosts, + table_config, table_variables): + del table_config + + def load_ops_fn(): + """Returns the retrieve ops for AdaGrad embedding tables. + + Returns: + A list of ops to load embedding and slot variables from CPU to TPU. + """ + load_op_list = [] + for host_id, table_variable in (zip( + range(num_hosts), table_variables)): + with ops.colocate_with(table_variable): + load_parameters_op = ( + tpu_ops + .load_tpu_embedding_stochastic_gradient_descent_parameters( + parameters=table_variable, + table_name=table, + num_shards=num_hosts, + shard_id=host_id)) + + load_op_list.append(load_parameters_op) + return load_op_list + + def retrieve_ops_fn(): + """Returns the retrieve ops for SGD embedding tables. + + Returns: + A list of ops to retrieve embedding and slot variables from TPU to CPU. + """ + + retrieve_op_list = [] + for host_id, table_variable in (zip( + range(num_hosts), table_variables)): + with ops.colocate_with(table_variable): + retrieved_table = ( + tpu_ops + .retrieve_tpu_embedding_stochastic_gradient_descent_parameters( + table_name=table, + num_shards=num_hosts, + shard_id=host_id)) + retrieve_parameters_op = control_flow_ops.group( + state_ops.assign(table_variable, retrieved_table)) + + retrieve_op_list.append(retrieve_parameters_op) + return retrieve_op_list + + return None, load_ops_fn, retrieve_ops_fn + + +def _get_optimization_handler(optimization_parameters): + if isinstance(optimization_parameters, AdagradParameters): + return _AdagradHandler(optimization_parameters) + elif isinstance(optimization_parameters, AdamParameters): + return _AdamHandler(optimization_parameters) + elif isinstance(optimization_parameters, StochasticGradientDescentParameters): + return _StochasticGradientDescentHandler(optimization_parameters) + else: + return NotImplementedError() + + +def _create_ordered_dict(d): + """Create an OrderedDict from Dict.""" + return collections.OrderedDict((k, d[k]) for k in sorted(d)) + + +def _create_combiners(table_to_config_dict): + return [table_to_config_dict[t].combiner for t in table_to_config_dict] + + +def _create_table_to_features_dict(feature_to_table_dict): + """Create mapping from table to a list of its features.""" + table_to_features_dict_tmp = {} + for feature, table in six.iteritems(feature_to_table_dict): + if table in table_to_features_dict_tmp: + table_to_features_dict_tmp[table].append(feature) + else: + table_to_features_dict_tmp[table] = [feature] + + table_to_features_dict = collections.OrderedDict() + for table in sorted(table_to_features_dict_tmp): + table_to_features_dict[table] = sorted(table_to_features_dict_tmp[table]) + return table_to_features_dict + + +def _create_device_fn(hosts): + """Create device_fn() to use with _create_partitioned_variables().""" + + def device_fn(op): + """Returns the `device` for `op`.""" + part_match = re.match(r'.*/part_(\d+)(/|$)', op.name) + + if part_match: + idx = int(part_match.group(1)) + else: + raise RuntimeError('Internal Error: ' + 'Expected %s to contain /part_*.' % op.name) + + device = hosts[idx] + return device + + return device_fn + + +def _create_partitioned_variables(name, + num_hosts, + vocabulary_size, + embedding_dimension, + initializer, + collections=None): # pylint: disable=redefined-outer-name + """Creates ParitionedVariables based on `num_hosts` for `table`.""" + # TODO(shizhiw): automatically place embedding lookup elsewhere? + if vocabulary_size < num_hosts: + raise ValueError('`vocabulary_size`({}) is smaller than `num_hosts`({}). ' + 'As TPU embedding is not optimized for small tables, ' + 'please consider other ways for this embedding lookup.') + + return list(variable_scope.get_variable( + name, + shape=(vocabulary_size, embedding_dimension), + partitioner=partitioned_variables.fixed_size_partitioner(num_hosts), + dtype=dtypes.float32, + initializer=initializer, + collections=collections, + trainable=False)) diff --git a/tensorflow/python/tpu/tpu_embedding_gradient.py b/tensorflow/python/tpu/tpu_embedding_gradient.py new file mode 100644 index 0000000000..d7de661cc3 --- /dev/null +++ b/tensorflow/python/tpu/tpu_embedding_gradient.py @@ -0,0 +1,153 @@ +# 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. +# =================================================================== +"""Optional helper for gradient handling.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import collections + +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import ops +from tensorflow.python.ops import variable_scope +from tensorflow.python.ops import variables +from tensorflow.python.tpu.ops import tpu_ops + + +def get_gradients_through_compute_gradients(optimizer, loss, activations): + """Compute gradients to send to TPU embedding. + + Args: + optimizer: a subclass of optimizer.Optimizer, usually CrossShardOptimizer. + Used to call compute_gradients(). + loss: a Tensor to call optimizer.compute_gradients() on. + activations: an OrderedDict mapping feature_name to Tensors of activations. + + Returns: + An OrderedDict mapping from feature name Strings to Tensors of gradients of + the loss wrt the activations of the features. + """ + activation_list = activations.values() + grads_and_vars = optimizer.compute_gradients(loss, activation_list) + grads = [grad for grad, _ in grads_and_vars] + feature_to_gradient_dict = collections.OrderedDict( + zip(activations.keys(), grads)) + return feature_to_gradient_dict + + +def create_dummy_table_variables(tpu_embedding): + """Create dummy embedding table variables. + + The sole purpose of these dummy variables are to trigger gradient + calcuation wrt them so that the gradients wrt activation can be captured + and later sent to TPU embedding. + + Args: + tpu_embedding: TPUEmbedding, dummy table variables will be created for use + with tpu_embedding. + + Returns: + A tuple of dummy variables and their initializer. + + Raises: + RuntimeError: if collection to store gradients already exists and is not + empty. + """ + dummy_table_variables = collections.OrderedDict() + for table_id, table in enumerate(tpu_embedding.table_to_features_dict): + dummy_table_variables[table] = ( + # Explicitly specifying collections prevents this variable from + # being added to the GLOBAL_VARIABLES collection, so that Saver() + # ignores it. + # But Tensorflow optimizer creates slot variable for these dummy + # variable, e.g. tpu_embedding_dummy_table_variable_mlp_user/Adam{_1}, + # which will be in GLOBAL_VARIABLES collection, + variable_scope.get_variable( + 'tpu_embedding_dummy_table_variable_{}'.format(table), + dtype=dtypes.float32, + shape=[1], + use_resource=True, + trainable=True, + collections=['tpu_embedding_dummy_table_variables'])) + + g = ops.get_default_graph() + table_gradients = g.get_collection_ref( + 'tpu_embedding_gradients_table_{}'.format(table_id)) + if table_gradients: + raise RuntimeError( + 'tpu_embedding_gradients_table_{} is not empty.'.format(table_id)) + table_gradients.extend( + [None] * len(tpu_embedding.table_to_features_dict[table])) + + return (dummy_table_variables, + variables.variables_initializer( + dummy_table_variables.values(), + name='tpu_embedding_dummy_table_variables_init')) + + +def hook_dummy_table_variables_to_activations(tpu_embedding, activations, + dummy_table_variables): + """Have activations depend on dummy table variables for gradient intercept. + + Args: + tpu_embedding: TPUEmbedding, activations and dummy_table_variables are from + tpu_embedding. + activations: An OrderedDict of feature name String to activation tensors. + dummy_table_variables: An OrderedDict of table name String to dummy table + variables. + + Returns: + An OrderedDict of feature name String to activation tensors, which can be + used just as the activations input. + """ + new_activations = collections.OrderedDict() + for feature in activations: + table = tpu_embedding.feature_to_table_dict[feature] + new_activations[feature] = tpu_ops.tpu_embedding_activations( + dummy_table_variables[table], + activations[feature], + table_id=tpu_embedding.table_to_config_dict.keys().index(table), + lookup_id=tpu_embedding.table_to_features_dict[table].index(feature)) + return new_activations + + +def get_gradients_through_dummy_table_variables(tpu_embedding): + """Get gradients wrt the activations of each feature. + + Args: + tpu_embedding: TPUEmbedding, create dummy table variable to be used with + tpu_embedding. + + Returns: + An OrderedDict mapping feature name to gradient. + + Raises: + ValueError: if some gradients are not defined. + """ + g = ops.get_default_graph() + feature_to_gradient_dict = collections.OrderedDict() + for table_id, table in enumerate(tpu_embedding.table_to_config_dict): + table_gradients = g.get_collection( + 'tpu_embedding_gradients_table_{}'.format(table_id)) + if any(gradient is None for gradient in table_gradients): + raise ValueError( + 'Table {} with id {} has undefined gradients: this is probably ' + 'because the model asked TPUEmbedding to compute activations that ' + 'were not used.'.format(table, table_id)) + for feature, gradient in zip(tpu_embedding.table_to_features_dict[table], + table_gradients): + feature_to_gradient_dict[feature] = gradient + return feature_to_gradient_dict diff --git a/tensorflow/python/tpu/tpu_estimator.py b/tensorflow/python/tpu/tpu_estimator.py new file mode 100644 index 0000000000..ea6b297ac3 --- /dev/null +++ b/tensorflow/python/tpu/tpu_estimator.py @@ -0,0 +1,3760 @@ +# 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. +# =================================================================== +"""TPUEstimator class.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import collections +import copy +import os +import signal +import sys +import threading +import time + +import numpy as np +import six +from six.moves import queue as Queue # pylint: disable=redefined-builtin +from six.moves import xrange # pylint: disable=redefined-builtin + +from tensorflow.core.framework import variable_pb2 +from tensorflow.core.framework.summary_pb2 import Summary +from tensorflow.core.protobuf import config_pb2 +from tensorflow.core.protobuf.tpu import compilation_result_pb2 as tpu_compilation_result +from tensorflow.python.client import session as tf_session +from tensorflow.python.data.ops import dataset_ops +from tensorflow.python.data.util import nest as data_nest +from tensorflow.python.estimator import estimator as estimator_lib +from tensorflow.python.estimator import model_fn as model_fn_lib +from tensorflow.python.estimator.export import export_output as export_output_lib +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 +from tensorflow.python.framework import ops +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import check_ops +from tensorflow.python.ops import control_flow_ops +from tensorflow.python.ops import init_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.ops import summary_ops_v2 as contrib_summary +from tensorflow.python.ops import variable_scope +from tensorflow.python.ops import variables +from tensorflow.python.platform import tf_logging as logging +from tensorflow.python.saved_model import tag_constants +from tensorflow.python.summary import summary +from tensorflow.python.tpu import _tpu_estimator_embedding +from tensorflow.python.tpu import error_handling +from tensorflow.python.tpu import functional as tpu_functional +from tensorflow.python.tpu import session_support +from tensorflow.python.tpu import tensor_tracer +from tensorflow.python.tpu import tpu +from tensorflow.python.tpu import tpu_config +from tensorflow.python.tpu import tpu_context +from tensorflow.python.tpu import tpu_embedding_gradient +from tensorflow.python.tpu import tpu_feed +from tensorflow.python.tpu import tpu_function +from tensorflow.python.tpu import training_loop +from tensorflow.python.tpu import util as util_lib +from tensorflow.python.tpu._tpu_estimator_embedding import AdamParameters # pylint: disable=unused-import +from tensorflow.python.tpu._tpu_estimator_embedding import EmbeddingConfigSpec # pylint: disable=unused-import +from tensorflow.python.tpu.ops import tpu_ops +from tensorflow.python.training import basic_session_run_hooks +from tensorflow.python.training import evaluation +from tensorflow.python.training import session_run_hook +from tensorflow.python.training import training +from tensorflow.python.training import training_util +from tensorflow.python.util import function_utils +from tensorflow.python.util import nest +from tensorflow.python.util import tf_inspect + +_INITIAL_LOSS = 1e7 +_ZERO_LOSS = 0. +_TPU_ESTIMATOR = 'tpu_estimator' +_ITERATIONS_PER_LOOP_VAR = 'iterations_per_loop' +_BATCH_SIZE_KEY = 'batch_size' +_CTX_KEY = 'context' +_USE_TPU_KEY = 'use_tpu' +_CROSS_REPLICA_SUM_OP = 'CrossReplicaSum' +_ONE_GIGABYTE = 1024 * 1024 * 1024 +_TPU_ENQUEUE_OPS = '_tpu_enqueue_ops' +_TPU_TRAIN_OP = '_tpu_train_op' +_REWRITE_FOR_INFERENCE_MODE = '_rewrite_for_inference' +_KEY_WHEN_PREDICTIONS_IS_A_TENSOR = '_key_when_predictions_is_a_tensor' + +# Ideally _USE_TPU_KEY should be reserved as well. However there are already +# models that make use of this key, thus it can not be reserved now to prevent +# breakage. In the long run, we would like to mitigate this by migrating models +# off of using _USE_TPU_KEY. +_RESERVED_PARAMS_KEYS = [_BATCH_SIZE_KEY, _CTX_KEY] + +# TODO(b/65703635): Flip the value and remove all dead code. Currently, this is +# only used for per-core based deployments. For per-host based pipelines, if a +# user returns a Dataset instance it will be automatically wrapped in a +# tf.while_loop (This can be disabled by returning features and labels +# explicitly). +_WRAP_INPUT_FN_INTO_WHILE_LOOP = False + +ops.register_proto_function( + '{}_{}'.format(_TPU_ESTIMATOR, _ITERATIONS_PER_LOOP_VAR), + proto_type=variable_pb2.VariableDef, + to_proto=resource_variable_ops._to_proto_fn, # pylint: disable=protected-access + from_proto=resource_variable_ops._from_proto_fn) # pylint: disable=protected-access + + +def _is_iterable(obj): + """A Python 2 and 3 compatible util to check whether `obj` is iterable.""" + try: + iter(obj) + return True + except TypeError: + return False + + +class CatchInvalidHostcallFunctions(control_flow_ops.XLAControlFlowContext): + + def AddOp(self, op): + if op.type in [ + 'AudioSummary', 'AudioSummaryV2', 'HistogramSummary', 'ImageSummary', + 'MergeSummary', 'ScalarSummary', 'TensorSummary', 'TensorSummaryV2' + ]: + raise ValueError('Use tf.contrib.summary inside of host_calls.') + + +def _create_global_step(graph): + graph = graph or ops.get_default_graph() + if training.get_global_step(graph) is not None: + raise ValueError('"global_step" already exists.') + # Create in proper graph and base name_scope. + with graph.as_default() as g, g.name_scope(None): + return variable_scope.get_variable( + ops.GraphKeys.GLOBAL_STEP, + shape=[], + dtype=dtypes.int64, + initializer=init_ops.zeros_initializer(), + trainable=False, + use_resource=True, + collections=[ops.GraphKeys.GLOBAL_VARIABLES, ops.GraphKeys.GLOBAL_STEP]) + + +def _create_or_get_iterations_per_loop(): + """Creates or gets the iterations_per_loop variable. + + In TPUEstimator, the user provided computation, the model_fn, is wrapped + inside a tf.while_loop for peak performance. The iterations of the loop are + specified by this variable, which adjusts its value on the CPU after each TPU + program execution and before the next TPU execution. + + The purpose of using a variable, rather then a constant, is to allow + TPUEstimator adapt the TPU training iterations according to the final steps + specified by users. For example, if the user sets the iterations_per_loop as 4 + in TPUConfig and steps as 10 in TPUEstimator.train(), the iterations_per_loop + variable will have the following value before each TPU training. + + - 1-th TPU execution: iterations_per_loop = 4 + - 2-th TPU execution: iterations_per_loop = 4 + - 3-th TPU execution: iterations_per_loop = 2 + + As model_fn increases the global step once per train_op invocation, the global + step is 10 after all TPU executions, matching the steps=10 inputs passed in by + users. + + Returns: + A TF non-trainable resource variable. + + Raises: + RuntimeError: If multi iterations_per_loop variables were found. + """ + graph = ops.get_default_graph() + collection_name = '{}_{}'.format(_TPU_ESTIMATOR, _ITERATIONS_PER_LOOP_VAR) + iter_vars = graph.get_collection(collection_name) + if len(iter_vars) == 1: + return iter_vars[0] + elif len(iter_vars) > 1: + raise RuntimeError('Multiple iterations_per_loop_var in collection.') + + with ops.colocate_with(training_util.get_global_step()): + with variable_scope.variable_scope( + _TPU_ESTIMATOR, reuse=variable_scope.AUTO_REUSE): + return variable_scope.get_variable( + _ITERATIONS_PER_LOOP_VAR, + initializer=init_ops.zeros_initializer(), + shape=[], + dtype=dtypes.int32, + trainable=False, + collections=[collection_name, ops.GraphKeys.LOCAL_VARIABLES], + use_resource=True) + + +def _sync_variables_ops(ctx): + """Create varriables synchronization ops. + + Gets the variables back from TPU nodes. This means the variables updated + by TPU will now be *synced* to host memory. + In BROADCAST mode, we skip this sync since the variables are ususally too + big to transmit via RPC. + + Args: + ctx: A `_InternalTPUContext` instance with mode. + + Returns: + A list of sync ops. + """ + + if not ctx.is_input_broadcast_with_iterators(): + return [ + array_ops.check_numerics(v.read_value(), + 'Gradient for %s is NaN' % v.name).op + for v in variables.trainable_variables() + ] + else: + return [control_flow_ops.no_op()] + + +def _increase_eval_step_op(iterations_per_loop): + """Returns an op to increase the eval step for TPU evaluation. + + Args: + iterations_per_loop: Tensor. The number of eval steps running in TPU system + before returning to CPU host for each `Session.run`. + + Returns: + An operation + """ + eval_step = evaluation._get_or_create_eval_step() # pylint: disable=protected-access + # Estimator evaluate increases 1 by default. So, we increase the difference. + return state_ops.assign_add( + eval_step, + math_ops.cast(iterations_per_loop - 1, dtype=eval_step.dtype), + use_locking=True) + + +def _extract_key_names(tensor_or_dict): + if isinstance(tensor_or_dict, dict): + return sorted(tensor_or_dict.keys()) + return [] + + +class _SIGNAL(object): + """Signal used to control the thread of infeed/outfeed. + + All preserved signals must be negative numbers. Positive numbers are used to + indicate the number of iterations for next training/evaluation loop. + """ + NEXT_BATCH = -1 + STOP = -2 + + +class TPUEstimatorSpec(model_fn_lib._TPUEstimatorSpec): # pylint: disable=protected-access + """Ops and objects returned from a `model_fn` and passed to `TPUEstimator`. + + See `EstimatorSpec` for `mode`, `predictions`, `loss`, `train_op`, and + `export_outputs`. + + For evaluation, `eval_metrics `is a tuple of `metric_fn` and `tensors`, where + `metric_fn` runs on CPU to generate metrics and `tensors` represents the + `Tensor`s transferred from TPU system to CPU host and passed to `metric_fn`. + To be precise, TPU evaluation expects a slightly different signature from the + `tf.estimator.Estimator`. While `EstimatorSpec.eval_metric_ops` expects a + dict, `TPUEstimatorSpec.eval_metrics` is a tuple of `metric_fn` and `tensors`. + The `tensors` could be a list of `Tensor`s or dict of names to `Tensor`s. The + `tensors` usually specify the model logits, which are transferred back from + TPU system to CPU host. All tensors must have be batch-major, i.e., the batch + size is the first dimension. Once all tensors are available at CPU host from + all shards, they are concatenated (on CPU) and passed as positional arguments + to the `metric_fn` if `tensors` is list or keyword arguments if `tensors` is + a dict. `metric_fn` takes the `tensors` and returns a dict from metric string + name to the result of calling a metric function, namely a `(metric_tensor, + update_op)` tuple. See `TPUEstimator` for MNIST example how to specify the + `eval_metrics`. + + `scaffold_fn` is a function running on CPU to generate the `Scaffold`. This + function should not capture any Tensors in `model_fn`. + + `host_call` is a tuple of a `function` and a list or dictionary of `tensors` + to pass to that function and returns a list of Tensors. `host_call` currently + works for train() and evaluate(). The Tensors returned by the function is + executed on the CPU on every step, so there is communication overhead when + sending tensors from TPU to CPU. To reduce the overhead, try reducing the + size of the tensors. The `tensors` are concatenated along their major (batch) + dimension, and so must be >= rank 1. The `host_call` is useful for writing + summaries with `tf.contrib.summary.create_file_writer`. + """ + + def __new__(cls, + mode, + predictions=None, + loss=None, + train_op=None, + eval_metrics=None, + export_outputs=None, + scaffold_fn=None, + host_call=None, + training_hooks=None, + evaluation_hooks=None, + prediction_hooks=None): + """Creates a validated `TPUEstimatorSpec` instance.""" + host_calls = {} + if eval_metrics is not None: + host_calls['eval_metrics'] = eval_metrics + if host_call is not None: + host_calls['host_call'] = host_call + _OutfeedHostCall.validate(host_calls) + + training_hooks = tuple(training_hooks or []) + evaluation_hooks = tuple(evaluation_hooks or []) + prediction_hooks = tuple(prediction_hooks or []) + + for hook in training_hooks + evaluation_hooks + prediction_hooks: + if not isinstance(hook, session_run_hook.SessionRunHook): + raise TypeError('All hooks must be SessionRunHook instances, given: {}' + .format(hook)) + + return super(TPUEstimatorSpec, cls).__new__( + cls, + mode=mode, + predictions=predictions, + loss=loss, + train_op=train_op, + eval_metrics=eval_metrics, + export_outputs=export_outputs, + scaffold_fn=scaffold_fn, + host_call=host_call, + training_hooks=training_hooks, + evaluation_hooks=evaluation_hooks, + prediction_hooks=prediction_hooks) + + def as_estimator_spec(self): + """Creates an equivalent `EstimatorSpec` used by CPU train/eval.""" + host_calls = {} + if self.eval_metrics is not None: + host_calls['eval_metrics'] = self.eval_metrics + if self.host_call is not None: + host_calls['host_call'] = self.host_call + host_call_ret = _OutfeedHostCall.create_cpu_hostcall(host_calls) + eval_metric_ops = None + if self.eval_metrics is not None: + eval_metric_ops = host_call_ret['eval_metrics'] + hooks = None + if self.host_call is not None: + hooks = [_OutfeedHostCallHook(host_call_ret['host_call'])] + loss = self.loss + if tensor_tracer.TensorTracer.is_enabled() \ + and self.train_op is not None: + tt = tensor_tracer.TensorTracer() + loss = tt.trace_cpu(ops.get_default_graph(), loss, self.train_op) + + hooks = tuple(hooks or []) + scaffold = self.scaffold_fn() if self.scaffold_fn else None + return model_fn_lib.EstimatorSpec( + mode=self.mode, + predictions=self.predictions, + loss=loss, + train_op=self.train_op, + eval_metric_ops=eval_metric_ops, + export_outputs=self.export_outputs, + scaffold=scaffold, + training_hooks=self.training_hooks + hooks, + evaluation_hooks=self.evaluation_hooks + hooks, + prediction_hooks=self.prediction_hooks + hooks) + + +class _OpQueueContext(object): + """Manages work queue and thread for a infeed/outfeed thread.""" + + def __init__(self, name, target, args): + self._name = name + self._queue = Queue.Queue() + args = (self,) + args + self._thread = threading.Thread(name=name, target=target, args=args) + self._thread.daemon = True + self._thread.start() + + def stop(self): + self._queue.put(_SIGNAL.STOP) + + def send_next_batch_signal(self, iterations): + self._queue.put(iterations) + + def read_iteration_counts(self): + while True: + iterations = self._queue.get(block=True) + logging.debug('%s read iterations %s', self._name, iterations) + if iterations == _SIGNAL.STOP: + logging.info('%s received shutdown signal, stopping.', self._name) + return + yield iterations + + def join(self): + logging.info('Shutting down %s thread.', self._name) + self.stop() + self._thread.join() + + +class _OpSignalOnceQueueContext(_OpQueueContext): + """Manages work queue and thread for a infeed/outfeed thread. + + This subclass only signals once. + """ + + def __init__(self, name, target, args): + super(_OpSignalOnceQueueContext, self).__init__(name, target, args) + self._has_signaled = False + + def send_next_batch_signal(self, iterations): + if not self._has_signaled: + self._queue.put(iterations) + self._has_signaled = True + + +class TPUInfeedOutfeedSessionHook(session_run_hook.SessionRunHook): + """A Session hook setting up the TPU initialization, infeed, and outfeed. + + This hook does two major things: + 1. initialize and shutdown TPU system. + 2. launch and join the threads for infeed enqueue and (optional) outfeed + dequeue. + """ + + def __init__(self, + ctx, + enqueue_ops, + dequeue_ops, + tpu_compile_op, + run_infeed_loop_on_coordinator=True, + rendezvous=None, + master=None, + session_config=None, + tpu_init_ops=None): + self._master_job = ctx.master_job + self._enqueue_ops = enqueue_ops + self._dequeue_ops = dequeue_ops + self._rendezvous = rendezvous + self._master = master + self._session_config = session_config + self._init_ops = list(tpu_init_ops or []) + if ctx.embedding_config is None: + self._embedding_layer_config = None + else: + self._embedding_layer_config = ( + ctx.embedding_config.tpu_embedding.config_proto) + self._run_infeed_loop_on_coordinator = run_infeed_loop_on_coordinator + self._initial_infeed_sleep_secs = ( + ctx.config.tpu_config.initial_infeed_sleep_secs) + + self._feed_error = None + self._finished = False + # When using model parallelism, the TPU is pre-initialized at startup to + # fetch mesh information. We skip re-initializing it here to avoid + # suspected issues due to the mesh layout changing on the second + # initialization. + self._should_initialize_tpu = not ctx.model_parallelism_enabled + self._tpu_compile_op = tpu_compile_op + + def begin(self): + logging.info('TPU job name %s', self._master_job) + self._iterations_per_loop_var = _create_or_get_iterations_per_loop() + if self._should_initialize_tpu: + self._finalize_ops = [tpu.shutdown_system(job=self._master_job)] + else: + self._finalize_ops = [] + + summary_writer_init_ops = contrib_summary.summary_writer_initializer_op() + self._init_ops.extend(summary_writer_init_ops) + # Get all the writer resources from the initializer, so we know what to + # flush. + for op in summary_writer_init_ops: + self._finalize_ops.append(contrib_summary.flush(writer=op.inputs[0])) + + def _run_infeed(self, queue_ctx, session): + logging.info('Starting infeed thread controller.') + if self._initial_infeed_sleep_secs: + logging.info('Infeed thread sleeping for %d seconds.', + self._initial_infeed_sleep_secs) + time.sleep(self._initial_infeed_sleep_secs) + logging.info('Infeed thread starting after sleep') + + with self._rendezvous.catch_errors(source='infeed', session=session): + if self._run_infeed_loop_on_coordinator: + for count, steps in enumerate(queue_ctx.read_iteration_counts()): + for i in xrange(steps): + logging.debug('Infeed enqueue for iteration (%d, %d)', count, i) + session.run(self._enqueue_ops) + else: + for _ in queue_ctx.read_iteration_counts(): + session.run(self._enqueue_ops) + logging.info('Infeed thread finished, shutting down.') + + def _run_outfeed(self, queue_ctx, session): + logging.info('Starting outfeed thread controller.') + with self._rendezvous.catch_errors(source='outfeed', session=session): + for count, steps in enumerate(queue_ctx.read_iteration_counts()): + for i in xrange(steps): + logging.debug('Outfeed dequeue for iteration (%d, %d)', count, i) + session.run(self._dequeue_ops) + logging.info('Outfeed thread finished, shutting down.') + + def _create_infeed_controller(self, name, target, args): + return _OpQueueContext(name=name, target=target, args=args) + + def _assertCompilationSucceeded(self, result, coord): + proto = tpu_compilation_result.CompilationResultProto() + proto.ParseFromString(result) + if proto.status_error_message: + logging.error('Compilation failed: {}'.format(proto.status_error_message)) + coord.request_stop() + else: + logging.info('Compilation succeeded') + + def after_create_session(self, session, coord): + if self._should_initialize_tpu: + logging.info('Init TPU system') + start = time.time() + with ops.Graph().as_default(): + with tf_session.Session( + self._master, config=self._session_config) as sess: + sess.run( + tpu.initialize_system( + job=self._master_job, + embedding_config=self._embedding_layer_config)) + logging.info('Initialized TPU in %d seconds', time.time() - start) + + session.run(self._init_ops, + options=config_pb2.RunOptions(timeout_in_ms=5 * 60 * 1000)) + + if os.environ.get('TPU_SPLIT_COMPILE_AND_EXECUTE', '') == '1': + logging.info('Compiling user program: this may take a while...') + self._assertCompilationSucceeded(session.run(self._tpu_compile_op), coord) + + self._infeed_controller = self._create_infeed_controller( + name='InfeedController', target=self._run_infeed, args=(session,)) + + self._outfeed_controller = _OpQueueContext( + name='OutfeedController', target=self._run_outfeed, args=(session,)) + + # Enable the worker watchdog to terminate workers on coordinator exit. + watchdog_timeout = int(os.environ.get('TF_TPU_WATCHDOG_TIMEOUT', '0')) + if watchdog_timeout > 0: + session_support.start_worker_watchdog(session, + shutdown_timeout=watchdog_timeout) + + def before_run(self, run_context): + self._feed_error = None + + iterations = run_context.session.run(self._iterations_per_loop_var) + + logging.info('Enqueue next (%d) batch(es) of data to infeed.', iterations) + self._infeed_controller.send_next_batch_signal(iterations) + + logging.info('Dequeue next (%d) batch(es) of data from outfeed.', + iterations) + self._outfeed_controller.send_next_batch_signal(iterations) + + def end(self, session): + self._finished = True + logging.info('Stop infeed thread controller') + self._infeed_controller.join() + self._rendezvous.record_done('infeed') + + logging.info('Stop output thread controller') + self._outfeed_controller.join() + self._rendezvous.record_done('outfeed') + + logging.info('Shutdown TPU system.') + session.run(self._finalize_ops) + + +class TPUInfeedOutfeedSessionHookForPrediction(TPUInfeedOutfeedSessionHook): + + def __init__(self, ctx, enqueue_ops, dequeue_ops, tpu_compile_op, + rendezvous=None, master=None, session_config=None): + super(TPUInfeedOutfeedSessionHookForPrediction, self).__init__( + ctx, + enqueue_ops, + dequeue_ops, + tpu_compile_op=tpu_compile_op, + run_infeed_loop_on_coordinator=False, + rendezvous=rendezvous, + master=master, + session_config=session_config) + + def _create_infeed_controller(self, name, target, args): + return _OpSignalOnceQueueContext(name=name, target=target, args=args) + + +class _TPUStopAtStepHook(session_run_hook.SessionRunHook): + """Hook that requests stop at a specified step. + + This hook is similar to the `session_run_hook._StopAfterNEvalsHook` with + following differences for TPU training: + + 1. This hook sets the variable for iterations_per_loop, which is used by + `TPUInfeedOutfeedSessionHook` to control the iterations for infeed/outfeed. + As the hook execution order is not guaranteed, the variable update is + handled in `after_create_session` and `after_run` as + `TPUInfeedOutfeedSessionHook` reads the variable value in `before_run`. + + 2. For each training loop (session.run), the global step could be increased + multiple times on TPU. The global step tensor value will be explicitly read + again in `after_run` to ensure the latest value is retrieved to avoid race + condition. + """ + + def __init__(self, iterations, num_steps=None, last_step=None): + """Initializes a `StopAtStepHook`. + + Args: + iterations: The number of iterations to run optimizer per training loop. + num_steps: Number of steps to execute. + last_step: Step after which to stop. + + Raises: + ValueError: If one of the arguments is invalid. + """ + if num_steps is None and last_step is None: + raise ValueError('One of num_steps or last_step must be specified.') + if num_steps is not None and last_step is not None: + raise ValueError('Only one of num_steps or last_step can be specified.') + self._num_steps = num_steps + self._last_step = last_step + self._iterations = iterations + + def _next_iterations(self, global_step, last_step): + gap = last_step - global_step + return min(gap, self._iterations) + + def begin(self): + self._global_step_tensor = training_util.get_global_step() + if self._global_step_tensor is None: + raise RuntimeError('Global step should be created.') + + self._iterations_per_loop_var = _create_or_get_iterations_per_loop() + + def after_create_session(self, session, coord): + global_step = session.run(self._global_step_tensor) + if self._last_step is None: + self._last_step = global_step + self._num_steps + + iterations = self._next_iterations(global_step, self._last_step) + + self._iterations_per_loop_var.load(iterations, session=session) + + def after_run(self, run_context, run_values): + # Global step cannot be retrieved via SessionRunArgs and before_run due to + # race condition. + global_step = run_context.session.run(self._global_step_tensor) + if global_step >= self._last_step: + run_context.request_stop() + else: + iterations = self._next_iterations(global_step, self._last_step) + self._iterations_per_loop_var.load( + iterations, session=run_context.session) + + +class _SetEvalIterationsHook(session_run_hook.SessionRunHook): + """Hook that requests stop at a specified step.""" + + def __init__(self, num_steps): + """Initializes a `_SetEvalIterationsHook`. + + Args: + num_steps: Number of steps to execute. + """ + self._num_steps = num_steps + + def begin(self): + self._iterations_per_loop_var = _create_or_get_iterations_per_loop() + + def after_create_session(self, session, coord): + self._iterations_per_loop_var.load(self._num_steps, session=session) + + +class _StoppingPredictHook(session_run_hook.SessionRunHook): + """Hook that requests stop according to the stopping signal in prediction.""" + + def __init__(self, scalar_stopping_signal): + self._scalar_stopping_signal = scalar_stopping_signal + + def begin(self): + self._iterations_per_loop_var = _create_or_get_iterations_per_loop() + + def after_create_session(self, session, coord): + # This is not necessary as we do not run infeed enqueue and outfeed dequeue + # in side threads for prediction model. But it makes the + # TPUInfeedOutfeedSessionHook prints nice message. + self._iterations_per_loop_var.load(1, session=session) + + def before_run(self, run_context): + return session_run_hook.SessionRunArgs(self._scalar_stopping_signal) + + def after_run(self, run_context, run_values): + _ = run_context + scalar_stopping_signal = run_values.results + if _StopSignals.should_stop(scalar_stopping_signal): + # NOTE(xiejw): In prediction, stopping signals are inserted for each + # batch. And we append one more batch to signal the system it should stop. + # The data flow might look like + # + # batch 0: images, labels, stop = 0 (user provided) + # batch 1: images, labels, stop = 0 (user provided) + # ... + # batch 99: images, labels, stop = 0 (user provided) + # batch 100: images, labels, stop = 1 (TPUEstimator appended) + # + # where the final batch (id = 100) is appended by TPUEstimator, so we + # should drop it before returning the predictions to user. + # To achieve that, we throw the OutOfRangeError in after_run. Once + # Monitored Session sees this error in SessionRunHook.after_run, the + # "current" prediction, i.e., batch with id=100, will be discarded + # immediately + raise errors.OutOfRangeError(None, None, 'Stopped by stopping signal.') + + +def generate_per_core_enqueue_ops_fn_for_host( + ctx, input_fn, inputs_structure_recorder, host_device, host_id): + """Generates infeed enqueue ops for per-core input_fn on a single host.""" + captured_infeed_queue = _CapturedObject() + tpu_ordinal_function_impl = ctx.tpu_ordinal_function(host_id) + + def enqueue_ops_fn(): + """A fn returns enqueue_ops.""" + num_cores_per_host = ctx.num_of_cores_per_host + per_host_sharded_inputs = [] + for core_ordinal in range(num_cores_per_host): + with ops.name_scope('ordinal_%d' % (core_ordinal)): + user_context = tpu_context.TPUContext( + internal_ctx=ctx, + input_device=host_device, + invocation_index=host_id * ctx.num_of_cores_per_host + core_ordinal) + inputs = _Inputs.from_input_fn(input_fn(user_context)) + if inputs.is_dataset: + raise TypeError( + '`input_fn` returning `Dataset` is not yet supported in ' + 'per-Core input pipeline deployment yet. Please set ' + 'TPUConfig.per_host_input_for_training to True or return ' + '`features` and `labels` from `input_fn`') + features, labels = inputs.features_and_labels() + + inputs_structure_recorder.validate_and_record_structure( + features, labels) + flattened_inputs = ( + inputs_structure_recorder.flatten_features_and_labels( + features, labels)) + 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) + + per_host_enqueue_ops = infeed_queue.generate_enqueue_ops( + per_host_sharded_inputs, tpu_ordinal_function=tpu_ordinal_function_impl) + return per_host_enqueue_ops + + return enqueue_ops_fn, captured_infeed_queue + + +def generate_per_host_enqueue_ops_fn_for_host( + ctx, input_fn, inputs_structure_recorder, batch_axis, device, host_id): + """Generates infeed enqueue ops for per-host input_fn on a single host.""" + captured_infeed_queue = _CapturedObject() + + dataset_initializer = None + + with ops.device(device): + user_context = tpu_context.TPUContext( + internal_ctx=ctx, input_device=device, invocation_index=host_id) + inputs = _Inputs.from_input_fn(input_fn(user_context)) + + is_dataset = inputs.is_dataset + if ctx.mode == model_fn_lib.ModeKeys.PREDICT: + if not is_dataset: + raise TypeError( + 'For mode PREDICT, `input_fn` must return `Dataset` instead of ' + '`features` and `labels`.') + if batch_axis is not None: + raise TypeError('For mode PREDICT, batch_axis is not supported yet.') + inputs = _InputsWithStoppingSignals( + dataset=inputs.dataset, + batch_size=ctx.batch_size_for_input_fn, + add_padding=True) + + if is_dataset: + dataset_initializer = inputs.dataset_initializer() + + tpu_ordinal_function_impl = ctx.tpu_ordinal_function(host_id) + + def enqueue_ops_fn(): + """A Fn returning the TPU infeed enqueue ops. + + By providing as a Fn, it can be invoked inside the tf.while_loop such that + the input pipeline for multiple iterations can be executed by one + Session.run call. + + Returns: + list of dict of ops. + """ + with ops.device(device): + num_of_replicas_per_host = ctx.num_of_replicas_per_host + # Convert user input to features and labels. If the user returns a + # dataset, it is initialized and the features and labels extracted via + # `dataset.iterator.get_next()` + features, labels = inputs.features_and_labels() + signals = inputs.signals() + + inputs_structure_recorder.validate_and_record_structure(features, labels) + unsharded_tensor_list = ( + inputs_structure_recorder.flatten_features_and_labels( + features, labels, signals)) + + infeed_queue = tpu_feed.InfeedQueue( + tuple_types=[t.dtype for t in unsharded_tensor_list], + tuple_shapes=[t.shape for t in unsharded_tensor_list], + shard_dimensions=batch_axis) + captured_infeed_queue.capture(infeed_queue) + infeed_queue.set_number_of_shards(num_of_replicas_per_host) + per_host_enqueue_ops = ( + infeed_queue.split_inputs_and_generate_enqueue_ops( + unsharded_tensor_list, + placement_function=lambda x: device, + tpu_ordinal_function=tpu_ordinal_function_impl)) + if signals is None: + return per_host_enqueue_ops + else: + return { + 'ops': per_host_enqueue_ops, + 'signals': signals, + } + + return enqueue_ops_fn, captured_infeed_queue, dataset_initializer + + +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.""" + captured_infeed_queue = _CapturedObject() + dataset_initializer = None + + with ops.device(device): + user_context = tpu_context.TPUContext( + internal_ctx=ctx, input_device=device, invocation_index=host_id) + inputs = _Inputs.from_input_fn(input_fn(user_context)) + + 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: + inputs = _InputsWithStoppingSignals( + dataset=inputs.dataset, + batch_size=ctx.batch_size_for_input_fn, + add_padding=True, + num_invocations_per_step=ctx.num_of_replicas_per_host) + + dataset_initializer = inputs.dataset_initializer() + tpu_ordinal_function_impl = ctx.tpu_ordinal_function(host_id) + + def enqueue_ops_fn(): + """Generates the per_host enqueue ops.""" + control_deps = [] + per_host_sharded_inputs = [] + sparse_features_list = [] + num_replicas_per_host = ctx.num_of_replicas_per_host + cached_signals = None + 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() + signals = inputs.signals() + + # All the replicas share the replica 0's stopping singal. + # This avoids inconsistent state among different model replcias. + if cached_signals: + signals['stopping'] = cached_signals['stopping'] + else: + cached_signals = signals + + features, labels, sparse_features = ( + _tpu_estimator_embedding.split_inputs(ctx, features, labels)) + sparse_features_list.append(sparse_features) + + inputs_structure_recorder.validate_and_record_structure( + features, labels) + flattened_inputs = ( + inputs_structure_recorder.flatten_features_and_labels( + features, labels, signals)) + control_deps.extend(flattened_inputs) + per_host_sharded_inputs.append(flattened_inputs) + + if inputs_structure_recorder.flattened_input_dims: + input_partition_dims = inputs_structure_recorder.flattened_input_dims + if signals: + input_partition_dims += [None] * len(signals) + # pylint: disable=protected-access + infeed_queue = tpu_feed._PartitionedInfeedQueue( + number_of_tuple_elements=len(per_host_sharded_inputs[0]), + host_id=host_id, + input_partition_dims=input_partition_dims, + device_assignment=ctx.device_assignment) + per_host_enqueue_ops = infeed_queue.generate_enqueue_ops( + per_host_sharded_inputs) + else: + infeed_queue = tpu_feed.InfeedQueue( + number_of_tuple_elements=len(per_host_sharded_inputs[0])) + per_host_enqueue_ops = infeed_queue.generate_enqueue_ops( + per_host_sharded_inputs, + tpu_ordinal_function=tpu_ordinal_function_impl) + captured_infeed_queue.capture(infeed_queue) + + if ctx.embedding_config: + per_host_enqueue_ops.extend( + ctx.embedding_config.tpu_embedding.generate_enqueue_ops( + sparse_features_list)) + + if signals is None: + return per_host_enqueue_ops + else: + return { + 'ops': per_host_enqueue_ops, + 'signals': signals, + } + + return enqueue_ops_fn, captured_infeed_queue, dataset_initializer + + +def generate_broadcast_enqueue_ops_fn(ctx, input_fn, inputs_structure_recorder, + num_hosts): + """Generates infeed enqueue ops for one input_fn on all the hosts.""" + captured_infeed_queue = _CapturedObject() + dataset_initializer = None + device_0 = ctx.tpu_host_placement_function(host_id=0) + with ops.device(device_0): + user_context = tpu_context.TPUContext( + internal_ctx=ctx, input_device=device_0, invocation_index=0) + inputs = _Inputs.from_input_fn(input_fn(user_context)) + + is_dataset = inputs.is_dataset + if ctx.mode == model_fn_lib.ModeKeys.PREDICT: + if not is_dataset: + raise TypeError( + 'For mode PREDICT, `input_fn` must return `Dataset` instead of ' + '`features` and `labels`.') + + inputs = _InputsWithStoppingSignals( + dataset=inputs.dataset, + batch_size=ctx.batch_size_for_input_fn, + add_padding=True) + + if is_dataset: + dataset_initializer = inputs.dataset_initializer() + num_replicas_per_host = ctx.num_of_replicas_per_host + + def tpu_ordinal_function_impl(replica_id): + if ctx.device_assignment: + return ctx.device_assignment.tpu_ordinal(replica=replica_id) + else: + return replica_id % num_replicas_per_host + + def device_function_impl(replica_id): + return ctx.tpu_host_placement_function(replica_id=replica_id) + + def enqueue_ops_fn(): + """Generates enqueue ops for all the hosts.""" + broadcasted_inputs = [] + flattened_inputs = None # Cache result from input_fn. + signals = None + for host_id in xrange(num_hosts): + with ops.device(ctx.tpu_host_placement_function(host_id=host_id)): + for _ in xrange(ctx.num_of_replicas_per_host): + # Note: input_fn is only called once at host 0 for the first replica. + # The features and labels returned from that invocation are + # broadcasted to other replicas(including the replicas on other + # hosts). + if flattened_inputs is None: + features, labels = inputs.features_and_labels() # Calls get_next() + signals = inputs.signals() + + inputs_structure_recorder.validate_and_record_structure( + features, labels) + flattened_inputs = ( + inputs_structure_recorder.flatten_features_and_labels( + features, labels, signals)) + broadcasted_inputs.append(flattened_inputs) + + infeed_queue = tpu_feed.InfeedQueue( + number_of_tuple_elements=len(broadcasted_inputs[0])) + captured_infeed_queue.capture(infeed_queue) + enqueue_ops = infeed_queue.generate_enqueue_ops( + broadcasted_inputs, + tpu_ordinal_function=tpu_ordinal_function_impl, + placement_function=device_function_impl) + + if signals is None: + return enqueue_ops + else: + return { + 'ops': enqueue_ops, + 'signals': signals, + } + + return enqueue_ops_fn, captured_infeed_queue, dataset_initializer + + +class _InputPipeline(object): + """`_InputPipeline` handles invoking `input_fn` and piping to infeed queue. + + `_InputPipeline` abstracts the per-core/per-host `input_fn` invocation from + call site. To be precise, based on the configuration in + `_InternalTPUContext`, it invokes `input_fn` for all cores (usually + multi-host TPU training) or for one host (usually for single-host TPU + evaluation), and sends all `features` and `labels` returned by `input_fn` to + TPU infeed. For per-core invocation, `features` and `labels` are piped to + infeed directly, one tuple for each core. For per-host invocation, `features` + and `labels` are split at host (with respect to `batch_axis`) and piped to all + cores accordingly. + + In addition, flatten/unflatten are handled by `_InputPipeline` also. Model + inputs returned by the `input_fn` can have one of the following forms: + 1. features + 2. (features, labels) + 3. ((arbitrarily nested structure of features), labels) + + Internally, form 1 is reformed to `(features, None)` as features and labels + are passed separately to underlying methods. For TPU training, TPUEstimator + may expect multiple `features` and `labels` tuples one for each core. + + TPUEstimator allows various different structures for inputs (namely `features` + and `labels`). Both `features` and `labels` can be any nested sturcture + supported by TF nest (namely, dict, tuples, namedtuples or any nested + structure of such of Tensors). `labels` could be `None` as well. + + These are flattened before they are passed to the infeed/outfeed library + as that expectes flattend lists. + """ + + class InputsStructureRecorder(object): + """The recorder to record inputs structure.""" + + def __init__(self, input_partition_dims=None): + # Holds the structure of inputs + self._feature_structure = {} + self._flattened_input_dims = None + + if input_partition_dims: + # This should have been validated in TPUConfig. + assert len(input_partition_dims) <= 2, 'must have 1 or 2 elements.' + if len(input_partition_dims) == 2: + self._feature_dims, self._label_dims = input_partition_dims + else: + self._feature_dims = input_partition_dims[0] + self._label_dims = None + + assert self._feature_dims is not None, ('input_partition_dims[0] must ' + 'not be None') + else: + self._feature_dims = None + self._label_dims = None + + # Internal state. + self._initialized = False + + @property + def flattened_input_dims(self): + assert self._initialized, 'InputsStructureRecorder is not initialized.' + return self._flattened_input_dims + + def has_labels(self): + return 'labels' in self._feature_structure + + def _flatten_input_dims(self, feature_dims, feature_dims_names, label_dims, + label_dims_names, label_names, has_labels): + """Flatten input dims with the same order as flattened input tensors.""" + flattened_input_dims = [] + if feature_dims_names: + # We need a fixed ordering for matching the tensors in features. + flattened_input_dims.extend( + [feature_dims[name] for name in feature_dims_names]) + else: + flattened_input_dims.append(feature_dims) + + if label_dims_names: + # We need a fixed ordering for matching the tensors in labels. + flattened_input_dims.extend( + [label_dims[name] for name in label_dims_names]) + else: + if label_names: + num_tensors_in_label = len(label_names) + else: + num_tensors_in_label = int(has_labels) + # Setting `None` in input_partition_dims[1] will apply `None` to + # all the tensors in labels, regardless of internal structure. + flattened_input_dims.extend([label_dims] * num_tensors_in_label) + + return flattened_input_dims + + def validate_and_record_structure(self, features, labels): + """Validates and records the structure of `features` and `labels`.""" + # Extract structure. + has_labels = labels is not None + feature_names = _extract_key_names(features) + label_names = _extract_key_names(labels) + + if not self._initialized: + # Record structure. + self._initialized = True + if self._feature_dims is not None: + feature_dims_names = _extract_key_names(self._feature_dims) + if feature_dims_names != feature_names: + raise ValueError( + 'TPUConfig.input_partition_dims[0] mismatched feature' + ' keys. Expected {}, got {}'.format(feature_names, + feature_dims_names)) + + label_dims_names = _extract_key_names(self._label_dims) + if self._label_dims is not None and label_dims_names != label_names: + raise ValueError( + 'TPUConfig.input_partition_dims[1] mismatched label' + ' keys. Expected {}, got {}'.format(label_names, + label_dims_names)) + + self._flattened_input_dims = self._flatten_input_dims( + self._feature_dims, feature_dims_names, self._label_dims, + label_dims_names, label_names, has_labels) + + def flatten_features_and_labels(self, features, labels, signals=None): + """Flattens the `features` and `labels` to a single tensor list.""" + self._feature_structure['features'] = features + if labels is not None: + self._feature_structure['labels'] = labels + if signals is not None: + self._feature_structure['signals'] = signals + return data_nest.flatten(self._feature_structure) + + def unflatten_features_and_labels(self, flattened_inputs): + """Restores the flattened inputs to original features and labels form. + + Args: + flattened_inputs: Flattened inputs for each shard. + + Returns: + A tuple of (`features`, `labels`), where `labels` could be None. + Each one, if present, should have identical structure (single tensor vs + dict) as the one returned by input_fn. + + Raises: + ValueError: If the number of expected tensors from `flattened_inputs` + mismatches the recorded structure. + """ + + unflattened_inputs = data_nest.pack_sequence_as(self._feature_structure, + flattened_inputs) + return _Inputs( + unflattened_inputs['features'], + unflattened_inputs.get('labels'), + signals=unflattened_inputs.get('signals')) + + def __init__(self, input_fn, batch_axis, ctx): + """Constructor. + + Args: + input_fn: input fn for train or eval. + batch_axis: A python tuple of int values describing how each tensor + produced by the Estimator `input_fn` should be split across the TPU + compute shards. + ctx: A `_InternalTPUContext` instance with mode. + + Raises: + ValueError: If both `sharded_features` and `num_cores` are `None`. + """ + self._inputs_structure_recorder = _InputPipeline.InputsStructureRecorder( + ctx.input_partition_dims) + + self._sharded_per_core = ctx.is_input_sharded_per_core() + self._input_fn = input_fn + self._infeed_queue = None + self._ctx = ctx + self._batch_axis = batch_axis + + def generate_infeed_enqueue_ops_and_dequeue_fn(self): + """Generates infeed enqueue ops and dequeue_fn.""" + # While tf.while_loop is called, the body function, which invokes + # `enqueue_fn` passed in, is called to construct the graph. So, input_fn + # structure is recorded. + enqueue_ops, all_hooks, run_infeed_loop_on_coordinator = ( + self._invoke_input_fn_and_record_structure()) + + self._validate_input_pipeline() + + def dequeue_fn(): + """dequeue_fn is used by TPU to retrieve the tensors.""" + # 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. + 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) + + return (enqueue_ops, dequeue_fn, all_hooks, run_infeed_loop_on_coordinator) + + def _invoke_input_fn_and_record_structure(self): + """Deploys the input pipeline and record input structure.""" + enqueue_ops = [] + infeed_queues = [] + all_dataset_initializers = [] + num_hosts = self._ctx.num_hosts + tpu_host_placement_fn = self._ctx.tpu_host_placement_function + + run_infeed_loop_on_coordinator = True + + if self._sharded_per_core: + # Per-Core input pipeline deployment. + # Invoke input pipeline for each core and placed on the corresponding + # host. + for host_id in range(num_hosts): + 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 = ( + generate_per_core_enqueue_ops_fn_for_host( + self._ctx, self._input_fn, self._inputs_structure_recorder, + host_device, host_id)) + + if _WRAP_INPUT_FN_INTO_WHILE_LOOP: + run_infeed_loop_on_coordinator = False + enqueue_ops.append( + _wrap_computation_in_while_loop( + device=host_device, op_fn=enqueue_ops_fn)) + else: + enqueue_ops.append(enqueue_ops_fn()) + # Infeed_queue_getter must be called after enqueue_ops_fn is called. + infeed_queues.append(captured_infeed_queue.get()) + + elif self._ctx.is_input_broadcast_with_iterators(): + # Only calls input_fn in host 0. + host_device = tpu_host_placement_fn(host_id=0) + enqueue_ops_fn, captured_infeed_queue, dataset_initializer = ( + generate_broadcast_enqueue_ops_fn(self._ctx, self._input_fn, + self._inputs_structure_recorder, + num_hosts)) + if dataset_initializer: + all_dataset_initializers.append(dataset_initializer) + run_infeed_loop_on_coordinator = False + wrap_fn = ( + _wrap_computation_in_while_loop + if self._ctx.mode != model_fn_lib.ModeKeys.PREDICT else + _wrap_computation_in_while_loop_with_stopping_signals) + enqueue_ops.append(wrap_fn(device=host_device, op_fn=enqueue_ops_fn)) + else: + enqueue_ops.append(enqueue_ops_fn()) + infeed_queues.append(captured_infeed_queue.get()) + else: + for host_id in range(num_hosts): + 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)): + if self._ctx.is_input_per_host_with_iterators(): + enqueue_ops_fn, captured_infeed_queue, dataset_initializer = ( + 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, dataset_initializer = ( + generate_per_host_enqueue_ops_fn_for_host( + self._ctx, self._input_fn, + self._inputs_structure_recorder, self._batch_axis, + host_device, host_id)) + + # NOTE(xiejw): We dispatch here based on the return type of the + # users `input_fn`. + # + # 1. If input_fn returns a Dataset instance, we initialize the + # iterator outside of tf.while_loop, and call the iterator.get_next + # inside tf.while_loop. This should be always safe. + # + # 2. If input_fn returns (features, labels), it is too late to wrap + # them inside tf.while_loop, as resource initialization cannot be + # handled in TF control flow properly. In this case, we will use + # python loop to enqueue the data into TPU system. This may be + # slow compared to the previous case. + if dataset_initializer: + all_dataset_initializers.append(dataset_initializer) + run_infeed_loop_on_coordinator = False + wrap_fn = ( + _wrap_computation_in_while_loop + if self._ctx.mode != model_fn_lib.ModeKeys.PREDICT else + _wrap_computation_in_while_loop_with_stopping_signals) + enqueue_ops.append( + wrap_fn(device=host_device, op_fn=enqueue_ops_fn)) + else: + enqueue_ops.append(enqueue_ops_fn()) + infeed_queues.append(captured_infeed_queue.get()) + # infeed_queue is used to generate dequeue ops. The only thing it uses for + # dequeue is dtypes and types. So, any one can be used. Here, grab the + # first one. + self._infeed_queue = infeed_queues[0] + return enqueue_ops, [ + util_lib.MultiHostDatasetInitializerHook(all_dataset_initializers) + ], run_infeed_loop_on_coordinator + + def _validate_input_pipeline(self): + """Validates the input pipeline. + + Perform some sanity checks to log user friendly information. We should + error out to give users better error message. But, if + _WRAP_INPUT_FN_INTO_WHILE_LOOP is False (legacy behavior), we cannot break + user code, so, log a warning. + + Raises: + RuntimeError: If the validation failed. + """ + if ops.get_default_graph().get_collection(ops.GraphKeys.QUEUE_RUNNERS): + err_msg = ('Input pipeline contains one or more QueueRunners. ' + 'It could be slow and not scalable. Please consider ' + 'converting your input pipeline to use `tf.data` instead (see ' + 'https://www.tensorflow.org/guide/datasets for ' + 'instructions.') + if _WRAP_INPUT_FN_INTO_WHILE_LOOP: + raise RuntimeError(err_msg) + else: + logging.warn(err_msg) + + +def call_computation(computation, + experimental_exported_model_uses_all_cores=True): + """Call computation. + + computation uses a single-core for TPU inference. If + `experimental_exported_model_uses_all_cores` is `True`, this function will + round-robin + computation among all TPU cores visible to the host; otherwise, it will use + a single core. + + Args: + computation: A Python function that takes no inputs and builds computation + graph. If `computation` returns m outputs, this function will return a + list of m Tensors. + experimental_exported_model_uses_all_cores: Whether to round-robin among all + cores visible to the host, or to use a single core. + + Returns: + A list of output tensors. + """ + if experimental_exported_model_uses_all_cores: + # Using `TPUPartitionedCall` makes it possible to target a different + # TPU core with every `Session.run()` call. Note that the entire inference + # graph executes on a single core, and that invocations of this graph + # will round-robin among the cores attached to a host. + @function.Defun(capture_resource_var_by_value=False) + def tpu_subgraph(): + return computation() + + return tpu_functional.TPUPartitionedCall( + args=tpu_subgraph.captured_inputs, + device_ordinal=tpu_ops.tpu_ordinal_selector(), + Tout=[o.type for o in tpu_subgraph.definition.signature.output_arg], + f=tpu_subgraph) + else: + return computation() + + +class _ModelFnWrapper(object): + """A `model_fn` wrapper. + + This makes calling model_fn on CPU and TPU easier and more consistent and + performs necessary check and mutation required by TPU training and evaluation. + + In addition, this wrapper manages converting the `model_fn` to a single TPU + train and eval step. + """ + + def __init__(self, model_fn, config, params, ctx): + self._model_fn = model_fn + self._config = config + self._params = params + self._ctx = ctx + + def call_without_tpu(self, features, labels, is_export_mode): + return self._call_model_fn(features, labels, is_export_mode=is_export_mode) + + def _add_embedding_features(self, features, hook_dummy_table_variables): + """Add embedding features, optionally add hook to intercept gradient.""" + if self._ctx.embedding_config: + tpu_embedding_ = self._ctx.embedding_config.tpu_embedding + embedding_activations = tpu_embedding_.get_activations() + if hook_dummy_table_variables: + new_embedding_activations = ( + tpu_embedding_gradient.hook_dummy_table_variables_to_activations( + tpu_embedding_, embedding_activations, + self._ctx.embedding_config.dummy_table_variables)) + features.update(new_embedding_activations) + else: + features.update(embedding_activations) + + def convert_to_single_tpu_train_step(self, dequeue_fn): + """Converts user provided model_fn` as a single train step on TPU. + + The user provided `model_fn` takes input tuple + (features, labels) and produces the EstimatorSpec with train_op and loss for + train `mode`. This usually represents a single train computation on CPU. + + For TPU training, a train (computation) step is first wrapped in a + tf.while_loop control flow to repeat for many times and then replicated to + all TPU shards. Besides the input should be taken from TPU infeed rather + than input pipeline (input_fn) directly. To fit TPU loop and replicate + pattern, the original train computation should be reformed, which is the + returned `train_step`. + + Args: + dequeue_fn: The function to retrieve inputs, features and labels, from TPU + infeed dequeue channel. + + Returns: + A tuple of train_fn, host_calls, and captured scaffold_fn. The train_fn + representing the train step for TPU. + """ + + host_call = _OutfeedHostCall(self._ctx) + captured_scaffold_fn = _CapturedObject() + captured_training_hooks = _CapturedObject() + + def train_step(loss): + """Training step function for use inside a while loop.""" + del loss # unused; required in function signature. + inputs = dequeue_fn() + features, labels = inputs.features_and_labels() + self._add_embedding_features(features, True) + + estimator_spec = self._verify_estimator_spec( + self._call_model_fn(features, labels)) + loss, train_op = estimator_spec.loss, estimator_spec.train_op + + if isinstance(estimator_spec, model_fn_lib._TPUEstimatorSpec): # pylint: disable=protected-access + captured_scaffold_fn.capture(estimator_spec.scaffold_fn) + else: + captured_scaffold_fn.capture(None) + + captured_training_hooks.capture(estimator_spec.training_hooks) + + if self._ctx.embedding_config is None: + apply_sparse_grads = [] + else: + tpu_embedding_ = self._ctx.embedding_config.tpu_embedding + gradients = ( + tpu_embedding_gradient.get_gradients_through_dummy_table_variables( + tpu_embedding_) + ) + apply_sparse_grads = [ + tpu_embedding_.generate_send_gradients_op(gradients) + ] + + # We must run train_op to update the variables prior to running the + # outfeed. + with ops.control_dependencies([train_op] + apply_sparse_grads): + host_call_outfeed_ops = [] + if (isinstance(estimator_spec, model_fn_lib._TPUEstimatorSpec) # pylint: disable=protected-access + and estimator_spec.host_call is not None): + host_call.record({'host_call': estimator_spec.host_call}) + host_call_outfeed_ops = host_call.create_enqueue_op() + with ops.control_dependencies(host_call_outfeed_ops): + return array_ops.identity(loss) + + return (train_step, host_call, captured_scaffold_fn, + captured_training_hooks) + + def convert_to_single_tpu_eval_step(self, dequeue_fn): + """Converts user provided model_fn` as a single eval step on TPU. + + Similar to training, the user provided `model_fn` takes input tuple + (features, labels) and produces the TPUEstimatorSpec with eval_metrics for + eval `mode`. This usually represents a single evaluation computation on CPU. + + For TPU evaluation, a eval (computation) step is first wrapped in a + tf.while_loop control flow to repeat for many times and then replicated to + all TPU shards. Besides the input and output are slightly different. Input, + features and labels, should be taken from TPU infeed rather than input + pipeline (input_fn) directly. Output is managed in two stages. First, the + model outputs as the result of evaluation computation, usually model logits, + should be transferred from TPU system to CPU. Then, all model outputs are + concatenated first on CPU and sent to the metric_fn for metrics computation. + To fit TPU evaluation pattern, the original eval computation should be + reformed, which is the returned `eval_step`. + + Args: + dequeue_fn: The function to retrieve inputs, features and labels, from TPU + infeed dequeue channel. + + Returns: + A tuple of eval_fn, host_calls, and captured scaffold_fn. The eval_fn + representing the eval step for TPU. + """ + host_calls = _OutfeedHostCall(self._ctx) + captured_scaffold_fn = _CapturedObject() + captured_eval_hooks = _CapturedObject() + + def eval_step(total_loss): + """Evaluation step function for use inside a while loop.""" + inputs = dequeue_fn() + features, labels = inputs.features_and_labels() + self._add_embedding_features(features, False) + + tpu_estimator_spec = self._call_model_fn(features, labels) + if not isinstance(tpu_estimator_spec, model_fn_lib._TPUEstimatorSpec): # pylint: disable=protected-access + raise RuntimeError( + 'estimator_spec used by TPU evaluation must have type' + '`TPUEstimatorSpec`. Got {}'.format(type(tpu_estimator_spec))) + + loss = tpu_estimator_spec.loss + captured_scaffold_fn.capture(tpu_estimator_spec.scaffold_fn) + captured_eval_hooks.capture(tpu_estimator_spec.evaluation_hooks) + + to_record = {} + if tpu_estimator_spec.eval_metrics: + to_record['eval_metrics'] = tpu_estimator_spec.eval_metrics + if tpu_estimator_spec.host_call is not None: + # We assume that evaluate won't update global step, so we don't wrap + # this host_call. + to_record['host_call'] = tpu_estimator_spec.host_call + host_calls.record(to_record) + + with ops.control_dependencies(host_calls.create_enqueue_op()): + return math_ops.add(total_loss, loss) + + return eval_step, host_calls, captured_scaffold_fn, captured_eval_hooks + + def convert_to_single_tpu_predict_step(self, dequeue_fn): + """Converts user provided model_fn` as a single predict step on TPU. + + Args: + dequeue_fn: The function to retrieve inputs, features and labels, from TPU + infeed dequeue channel. + + Returns: + A tuple of predict_fn, host_calls, and captured scaffold_fn. The + predict_fn representing the predict step for TPU. + """ + host_calls = _OutfeedHostCall(self._ctx) + captured_scaffold_fn = _CapturedObject() + captured_predict_hooks = _CapturedObject() + + def predict_step(unused_scalar_stopping_signal): + """Evaluation step function for use inside a while loop.""" + inputs = dequeue_fn() + features, labels = inputs.features_and_labels() + stopping_signals = inputs.signals() + + assert stopping_signals is not None, ( + 'Internal Error: `signals` is missing.') + + tpu_estimator_spec = self._call_model_fn( + features, labels, is_export_mode=False) + if not isinstance(tpu_estimator_spec, model_fn_lib._TPUEstimatorSpec): # pylint: disable=protected-access + raise RuntimeError( + 'estimator_spec used by TPU prediction must have type' + '`TPUEstimatorSpec`. Got {}'.format(type(tpu_estimator_spec))) + + self._verify_tpu_spec_predictions(tpu_estimator_spec.predictions) + + captured_scaffold_fn.capture(tpu_estimator_spec.scaffold_fn) + captured_predict_hooks.capture(tpu_estimator_spec.prediction_hooks) + to_record = {} + identity_fn = lambda **kwargs: kwargs + to_record['predictions'] = [identity_fn, tpu_estimator_spec.predictions] + to_record['signals'] = [identity_fn, stopping_signals] + if tpu_estimator_spec.host_call is not None: + to_record['host_call'] = tpu_estimator_spec.host_call + host_calls.record(to_record) + + with ops.control_dependencies(host_calls.create_enqueue_op()): + return _StopSignals.as_scalar_stopping_signal(stopping_signals) + + return (predict_step, host_calls, captured_scaffold_fn, + captured_predict_hooks) + + def _verify_tpu_spec_predictions(self, predictions): + """Validates TPUEstimatorSpec.predictions dict.""" + # TODO(xiejw): Adds validation for prediction dictionrary. + # TODO(xiejw): Adds support for single tensor as predictions. + if not isinstance(predictions, dict): + raise TypeError('TPUEstimatorSpec.predictions must be dict of Tensors.') + + for (key, tensor) in predictions.items(): + if tensor.shape.dims[0].value is None: + raise ValueError( + 'The tensor with key ({}) in TPUEstimatorSpec.predictions has ' + 'dynamic shape (should be static). Tensor: {}'.format(key, tensor)) + return predictions + + def _validate_model_features_and_labels(self, features, labels, + is_export_mode): + """Validates that the features and labels for the model function are valid. + + A valid features/labels object is the one with: + - Type: A tensor or any nested structure of tensors supported by TF nest, + namely nested dictionary, tuple, namedtuple, or sequence of tensors. + - Static shape if is_export_mode is False. + + Args: + features: the features that would be input to the model function. + labels: the labels that would be input to the model function. + is_export_mode: boolean value specifying if in export mode. + + Raises: + TypeError: If features/labels are not of the correct type. + ValueError: If features/labels have dynamic shape. + """ + + def validate(obj, obj_name): + """Helper validate function.""" + if is_export_mode or self._ctx.is_running_on_cpu(is_export_mode): + return + if isinstance(obj, ops.Tensor): + if not obj.get_shape().is_fully_defined(): + raise ValueError( + 'The {} to the model returned by input_fn must have static shape.' + ' Tensor: {}'.format(obj_name, obj)) + else: + for tensor in data_nest.flatten(obj): + if not tensor.get_shape().is_fully_defined(): + raise ValueError( + ('The {} to the model returned by input_fn must have static ' + 'shape. Tensor: {}').format(obj_name, tensor)) + + validate(features, 'features') + if labels is not None: + validate(labels, 'labels') + + def _call_model_fn(self, features, labels, is_export_mode=False): + """Calls the model_fn with required parameters.""" + self._validate_model_features_and_labels(features, labels, is_export_mode) + model_fn_args = function_utils.fn_args(self._model_fn) + kwargs = {} + + # Makes deep copy with `config` and params` in case user mutates them. + config = copy.deepcopy(self._config) + params = copy.deepcopy(self._params) + + if 'labels' in model_fn_args: + kwargs['labels'] = labels + elif labels is not None: + raise ValueError( + 'model_fn does not take labels, but input_fn returns labels.') + if 'mode' in model_fn_args: + kwargs['mode'] = self._ctx.mode + if 'config' in model_fn_args: + kwargs['config'] = config + if 'params' in model_fn_args: + kwargs['params'] = params + + if 'params' not in model_fn_args: + raise ValueError('model_fn ({}) does not include params argument, ' + 'required by TPUEstimator to pass batch size as ' + 'params[\'batch_size\']'.format(self._model_fn)) + + if is_export_mode: + batch_size_for_model_fn = None + else: + batch_size_for_model_fn = self._ctx.batch_size_for_model_fn + + if batch_size_for_model_fn is not None: + _add_item_to_params(params, _BATCH_SIZE_KEY, batch_size_for_model_fn) + + running_on_cpu = self._ctx.is_running_on_cpu(is_export_mode) + _add_item_to_params(params, _USE_TPU_KEY, not running_on_cpu) + + if not running_on_cpu: + user_context = tpu_context.TPUContext( + internal_ctx=self._ctx, call_from_input_fn=False) + _add_item_to_params(params, _CTX_KEY, user_context) + + estimator_spec = self._model_fn(features=features, **kwargs) + if (running_on_cpu and + isinstance(estimator_spec, model_fn_lib._TPUEstimatorSpec)): # pylint: disable=protected-access + # The estimator_spec will be passed to `Estimator` directly, which expects + # type `EstimatorSpec`. + return estimator_spec.as_estimator_spec() + else: + return estimator_spec + + def _verify_estimator_spec(self, estimator_spec): + """Validates the estimator_spec.""" + if isinstance(estimator_spec, model_fn_lib._TPUEstimatorSpec): # pylint: disable=protected-access + return estimator_spec + + err_msg = '{} returned by EstimatorSpec is not supported in TPUEstimator.' + if estimator_spec.training_chief_hooks: + raise ValueError( + err_msg.format('training_chief_hooks') + 'If you want' + + ' to pass training hooks, please pass via training_hooks.') + + if estimator_spec.scaffold: + logging.warning('EstimatorSpec.Scaffold is ignored by TPU train/eval. ' + 'Please use TPUEstimatorSpec.') + return estimator_spec + + +class _OutfeedHostCall(object): + """Support for `eval_metrics` and `host_call` in TPUEstimatorSpec.""" + + def __init__(self, ctx): + self._ctx = ctx + self._names = [] + # All of these are dictionaries of lists keyed on the name. + self._host_fns = {} + self._tensor_keys = collections.defaultdict(list) + self._tensors = collections.defaultdict(list) + self._tensor_dtypes = collections.defaultdict(list) + self._tensor_shapes = collections.defaultdict(list) + + @staticmethod + def validate(host_calls): + """Validates the `eval_metrics` and `host_call` in `TPUEstimatorSpec`.""" + + for name, host_call in host_calls.items(): + if not isinstance(host_call, (tuple, list)): + raise ValueError('{} should be tuple or list'.format(name)) + if len(host_call) != 2: + raise ValueError('{} should have two elements.'.format(name)) + if not callable(host_call[0]): + raise TypeError('{}[0] should be callable.'.format(name)) + if not isinstance(host_call[1], (tuple, list, dict)): + raise ValueError('{}[1] should be tuple or list, or dict.'.format(name)) + + if isinstance(host_call[1], (tuple, list)): + fullargspec = tf_inspect.getfullargspec(host_call[0]) + fn_args = function_utils.fn_args(host_call[0]) + # wrapped_hostcall_with_global_step uses varargs, so we allow that. + if fullargspec.varargs is None and len(host_call[1]) != len(fn_args): + raise RuntimeError( + 'In TPUEstimatorSpec.{}, length of tensors {} does not match ' + 'method args of the function, which takes {}.'.format( + name, len(host_call[1]), len(fn_args))) + + @staticmethod + def create_cpu_hostcall(host_calls): + """Runs on the host_call on CPU instead of TPU when use_tpu=False.""" + + _OutfeedHostCall.validate(host_calls) + ret = {} + for name, host_call in host_calls.items(): + host_fn, tensors = host_call + if isinstance(tensors, (tuple, list)): + ret[name] = host_fn(*tensors) + else: + # Must be dict. + try: + ret[name] = host_fn(**tensors) + except TypeError as e: + logging.warning( + 'Exception while calling %s: %s. It is likely the tensors ' + '(%s[1]) do not match the ' + 'function\'s arguments', name, e, name) + raise + return ret + + def record(self, host_calls): + """Records the host_call structure.""" + + for name, host_call in host_calls.items(): + host_fn, tensor_list_or_dict = host_call + self._names.append(name) + self._host_fns[name] = host_fn + + if isinstance(tensor_list_or_dict, dict): + for (key, tensor) in six.iteritems(tensor_list_or_dict): + self._tensor_keys[name].append(key) + self._tensors[name].append(tensor) + self._tensor_dtypes[name].append(tensor.dtype) + self._tensor_shapes[name].append(tensor.shape) + else: + # List or tuple. + self._tensor_keys[name] = None + for tensor in tensor_list_or_dict: + self._tensors[name].append(tensor) + self._tensor_dtypes[name].append(tensor.dtype) + self._tensor_shapes[name].append(tensor.shape) + + def create_enqueue_op(self): + """Create the op to enqueue the recorded host_calls. + + Returns: + A list of enqueue ops, which is empty if there are no host calls. + """ + if not self._names: + return [] + + tensors = [] + # TODO(jhseu): Consider deduping tensors. + for name in self._names: + tensors.extend(self._tensors[name]) + + with ops.device(tpu.core(0)): + return [tpu_ops.outfeed_enqueue_tuple(tensors)] + + def create_tpu_hostcall(self): + """Sends the tensors through outfeed and runs the host_fn on CPU. + + The tensors are concatenated along dimension 0 to form a global tensor + across all shards. The concatenated function is passed to the host_fn and + executed on the first host. + + Returns: + A dictionary mapping name to the return type of the host_call by that + name. + + Raises: + RuntimeError: If outfeed tensor is scalar. + """ + if not self._names: + return {} + + ret = {} + # For each i, dequeue_ops[i] is a list containing the tensors from all + # shards. This list is concatenated later. + dequeue_ops = [] + tensor_dtypes = [] + tensor_shapes = [] + for name in self._names: + for _ in self._tensors[name]: + dequeue_ops.append([]) + for dtype in self._tensor_dtypes[name]: + tensor_dtypes.append(dtype) + for shape in self._tensor_shapes[name]: + tensor_shapes.append(shape) + + # Outfeed ops execute on each replica's first logical core. Note: we must + # constraint it such that we have at most one outfeed dequeue and enqueue + # per replica. + for i in xrange(self._ctx.num_replicas): + host_device, ordinal_id = self._ctx.device_for_replica(i) + with ops.device(host_device): + outfeed_tensors = tpu_ops.outfeed_dequeue_tuple( + dtypes=tensor_dtypes, + shapes=tensor_shapes, + device_ordinal=ordinal_id) + for j, item in enumerate(outfeed_tensors): + dequeue_ops[j].append(item) + + # Deconstruct dequeue ops. + flat_dequeue_ops = [] + for l in dequeue_ops: + flat_dequeue_ops.extend(l) + + dequeue_ops_by_name = {} + pos = 0 + for name in self._names: + dequeue_ops_by_name[name] = dequeue_ops[pos:pos + + len(self._tensors[name])] + pos += len(self._tensors[name]) + + def _call_host_fn(fn, *args, **kw): + context = CatchInvalidHostcallFunctions() + context.Enter() + result = fn(*args, **kw) + context.Exit() + context.ExitResult(result) + return result + + # It is assumed evaluation always happens on single host TPU system. So, + # place all ops on tpu host if possible. + # + # TODO(jhseu): Evaluate whether this is right for summaries. + with ops.device(self._ctx.tpu_host_placement_function(replica_id=0)): + for name in self._names: + dequeue_ops = dequeue_ops_by_name[name] + for i, item in enumerate(dequeue_ops): + if dequeue_ops[i][0].shape.ndims == 0: + raise RuntimeError( + 'All tensors outfed from TPU should preserve batch size ' + 'dimension, but got scalar {}'.format(dequeue_ops[i][0])) + # TODO(xiejw): Make the specification of the outfeed combinaton + # function more explicit and well-documented. We may want to give the + # user the option of concatenating along any axis. + if (self._ctx.config.tpu_config.per_host_input_for_training is + tpu_config.InputPipelineConfig.BROADCAST): + # If the infeed is in BROADCAST mode (each core recieving the same + # input), then we assume that the cores also produce identical + # copies of the same output, and we simply take the output from + # the first core. This mode is used by Mesh-TensorFlow. + with ops.control_dependencies(dequeue_ops[i]): + dequeue_ops[i] = array_ops.identity(dequeue_ops[i][0]) + else: + # Assume that the input has been batch-split and that axis 0 of the + # output tensors represents the batch size. Concatenate along + # the axis 0 to re-combine the batch. + dequeue_ops[i] = array_ops.concat(dequeue_ops[i], axis=0) + + if self._tensor_keys[name] is not None: + # The user-provided eval_metrics[1] is a dict. + dequeue_ops = dict(zip(self._tensor_keys[name], dequeue_ops)) + try: + ret[name] = _call_host_fn(self._host_fns[name], **dequeue_ops) + except TypeError as e: + logging.warning( + 'Exception while calling %s: %s. It is likely the tensors ' + '(%s[1]) do not match the ' + 'function\'s arguments', name, e, name) + raise + else: + ret[name] = _call_host_fn(self._host_fns[name], *dequeue_ops) + + # force all dequeue operations to be run if not consumed by the host calls + ret['__force_dequeue'] = control_flow_ops.group(*flat_dequeue_ops) + return ret + + +class _OutfeedHostCallHook(session_run_hook.SessionRunHook): + """Hook to run host calls when use_tpu=False.""" + + def __init__(self, tensors): + self._tensors = tensors + + def begin(self): + # We duplicate this code from the TPUInfeedOutfeedSessionHook rather than + # create a separate hook to guarantee execution order, because summaries + # need to be initialized before the outfeed thread starts. + # TODO(jhseu): Make a wrapper hook instead? + self._init_ops = contrib_summary.summary_writer_initializer_op() + # Get all the writer resources from the initializer, so we know what to + # flush. + self._finalize_ops = [] + for op in self._init_ops: + self._finalize_ops.append(contrib_summary.flush(writer=op.inputs[0])) + + def after_create_session(self, session, coord): + session.run(self._init_ops) + + def before_run(self, run_context): + return basic_session_run_hooks.SessionRunArgs(self._tensors) + + def end(self, session): + session.run(self._finalize_ops) + + +class ExamplesPerSecondHook(basic_session_run_hooks.StepCounterHook): + """Calculate and report global_step/sec and examples/sec during runtime.""" + + def __init__(self, + batch_size, + every_n_steps=100, + every_n_secs=None, + output_dir=None, + summary_writer=None): + self._batch_size = batch_size + super(ExamplesPerSecondHook, self).__init__( + every_n_steps=every_n_steps, + every_n_secs=every_n_secs, + output_dir=output_dir, + summary_writer=summary_writer) + + def _log_and_record(self, elapsed_steps, elapsed_time, global_step): + global_step_per_sec = elapsed_steps / elapsed_time + examples_per_sec = self._batch_size * global_step_per_sec + if self._summary_writer is not None: + global_step_summary = Summary(value=[ + Summary.Value(tag='global_step/sec', simple_value=global_step_per_sec) + ]) + example_summary = Summary(value=[ + Summary.Value(tag='examples/sec', simple_value=examples_per_sec) + ]) + self._summary_writer.add_summary(global_step_summary, global_step) + self._summary_writer.add_summary(example_summary, global_step) + logging.info('global_step/sec: %g', global_step_per_sec) + logging.info('examples/sec: %g', examples_per_sec) + + +class InstallSignalHandlerHook(session_run_hook.SessionRunHook): + """Change SIGINT (CTRL^C) handler to force quit the process. + + The default behavior often results in hanging processes. + The original handler is restored after training/evaluation. + """ + + def __init__(self): + self._signal_fn = signal.getsignal(signal.SIGINT) + + def before_run(self, run_context): + signal.signal(signal.SIGINT, signal.SIG_DFL) + + def end(self, session): + signal.signal(signal.SIGINT, self._signal_fn) + + +class TPUEstimator(estimator_lib.Estimator): + """Estimator with TPU support. + + TPUEstimator also supports training on CPU and GPU. You don't need to define + a separate `tf.estimator.Estimator`. + + TPUEstimator handles many of the details of running on TPU devices, such as + replicating inputs and models for each core, and returning to host + periodically to run hooks. + + TPUEstimator transforms a global batch size in params to a per-shard batch + size when calling the `input_fn` and `model_fn`. Users should specify + global batch size in constructor, and then get the batch size for each shard + in `input_fn` and `model_fn` by `params['batch_size']`. + + - For training, `model_fn` gets per-core batch size; `input_fn` may get + per-core or per-host batch size depending on `per_host_input_for_training` + in `TPUConfig` (See docstring for TPUConfig for details). + + - For evaluation and prediction, `model_fn` gets per-core batch size and + `input_fn` get per-host batch size. + + Evaluation + ========== + + `model_fn` should return `TPUEstimatorSpec`, which expects the `eval_metrics` + for TPU evaluation. If eval_on_tpu is False, the evaluation will execute on + CPU or GPU; in this case the following discussion on TPU evaluation does not + apply. + + `TPUEstimatorSpec.eval_metrics` is a tuple of `metric_fn` and `tensors`, where + `tensors` could be a list of any nested structure of `Tensor`s (See + `TPUEstimatorSpec` for details). `metric_fn` takes the `tensors` and returns + a dict from metric string name to the result of calling a metric function, + namely a `(metric_tensor, update_op)` tuple. + + One can set `use_tpu` to `False` for testing. All training, evaluation, and + predict will be executed on CPU. `input_fn` and `model_fn` will receive + `train_batch_size` or `eval_batch_size` unmodified as `params['batch_size']`. + + Current limitations: + -------------------- + + 1. TPU evaluation only works on a single host (one TPU worker) except + BROADCAST mode. + + 2. `input_fn` for evaluation should **NOT** raise an end-of-input exception + (`OutOfRangeError` or `StopIteration`). And all evaluation steps and all + batches should have the same size. + + Example (MNIST): + ---------------- + + ``` + # The metric Fn which runs on CPU. + def metric_fn(labels, logits): + predictions = tf.argmax(logits, 1) + return { + 'accuracy': tf.metrics.precision( + labels=labels, predictions=predictions), + } + + # Your model Fn which runs on TPU (eval_metrics is list in this example) + def model_fn(features, labels, mode, config, params): + ... + logits = ... + + if mode = tf.estimator.ModeKeys.EVAL: + return tpu_estimator.TPUEstimatorSpec( + mode=mode, + loss=loss, + eval_metrics=(metric_fn, [labels, logits])) + + # or specify the eval_metrics tensors as dict. + def model_fn(features, labels, mode, config, params): + ... + final_layer_output = ... + + if mode = tf.estimator.ModeKeys.EVAL: + return tpu_estimator.TPUEstimatorSpec( + mode=mode, + loss=loss, + eval_metrics=(metric_fn, { + 'labels': labels, + 'logits': final_layer_output, + })) + ``` + + Prediction + ========== + + Prediction on TPU is an experimental feature to support large batch inference. + It is not designed for latency-critical system. In addition, due to some + usability issues, for prediction with small dataset, CPU `.predict`, i.e., + creating a new `TPUEstimator` instance with `use_tpu=False`, might be more + convenient. + + Note: In contrast to TPU training/evaluation, the `input_fn` for prediction + *should* raise an end-of-input exception (`OutOfRangeError` or + `StopIteration`), which serves as the stopping signal to `TPUEstimator`. To be + precise, the ops created by `input_fn` produce one batch of the data. + The `predict()` API processes one batch at a time. When reaching the end of + the data source, an end-of-input exception should be raised by one of these + operations. The user usually does not need to do this manually. As long as the + dataset is not repeated forever, the `tf.data` API will raise an end-of-input + exception automatically after the last batch has been produced. + + Note: Estimator.predict returns a Python generator. Please consume all the + data from the generator so that TPUEstimator can shutdown the TPU system + properly for user. + + Current limitations: + -------------------- + 1. TPU prediction only works on a single host (one TPU worker). + + 2. `input_fn` must return a `Dataset` instance rather than `features`. In + fact, .train() and .evaluate() also support Dataset as return value. + + Example (MNIST): + ---------------- + ``` + height = 32 + width = 32 + total_examples = 100 + + def predict_input_fn(params): + batch_size = params['batch_size'] + + images = tf.random_uniform( + [total_examples, height, width, 3], minval=-1, maxval=1) + + dataset = tf.data.Dataset.from_tensor_slices(images) + dataset = dataset.map(lambda images: {'image': images}) + + dataset = dataset.batch(batch_size) + return dataset + + def model_fn(features, labels, params, mode): + # Generate predictions, called 'output', from features['image'] + + if mode == tf.estimator.ModeKeys.PREDICT: + return tf.contrib.tpu.TPUEstimatorSpec( + mode=mode, + predictions={ + 'predictions': output, + 'is_padding': features['is_padding'] + }) + + tpu_est = TPUEstimator( + model_fn=model_fn, + ..., + predict_batch_size=16) + + # Fully consume the generator so that TPUEstimator can shutdown the TPU + # system. + for item in tpu_est.predict(input_fn=input_fn): + # Filter out item if the `is_padding` is 1. + # Process the 'predictions' + ``` + + Exporting + ========= + + `export_savedmodel` exports 2 metagraphs, one with `tag_constants.SERVING`, + and another with `tag_constants.SERVING` and `tag_constants.TPU`. + At serving time, these tags are used to select metagraph to load. + + Before running the graph on TPU, TPU system needs to be initialized. If + TensorFlow Serving model-server is used, this is done automatically. If + not, please call `session.run(tpu.initialize_system())`. + + `tpu.outside_compilation` can be used to wrap TPU incompatible ops in + `model_fn`. + + Example: + ---------------- + + ``` + def model_fn(features, labels, mode, config, params): + ... + logits = ... + export_outputs = { + 'logits': export_output_lib.PredictOutput( + {'logits': logits}) + } + + def host_call(logits): + class_ids = math_ops.argmax(logits) + classes = string_ops.as_string(class_ids) + export_outputs['classes'] = + export_output_lib.ClassificationOutput(classes=classes) + + tpu.outside_compilation(host_call, logits) + + ... + ``` + + """ + + def __init__(self, + model_fn=None, + model_dir=None, + config=None, + params=None, + use_tpu=True, + train_batch_size=None, + eval_batch_size=None, + predict_batch_size=None, + batch_axis=None, + eval_on_tpu=True, + export_to_tpu=True, + export_to_cpu=True, + warm_start_from=None, + experimental_exported_model_uses_all_cores=False, + experimental_export_device_assignment=False, + experimental_embedding_config_spec=None): + """Constructs an `TPUEstimator` instance. + + Args: + model_fn: Model function as required by `Estimator` which returns + EstimatorSpec or TPUEstimatorSpec. `training_hooks`, 'evaluation_hooks', + and `prediction_hooks` must not capure any TPU Tensor inside the + model_fn. + 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. If `None`, the model_dir in + `config` will be used if set. If both are set, they must be same. If + both are `None`, a temporary directory will be used. + config: An `tpu_config.RunConfig` configuration object. Cannot be `None`. + params: An optional `dict` of hyper parameters that will be passed into + `input_fn` and `model_fn`. Keys are names of parameters, values are + basic python types. There are reserved keys for `TPUEstimator`, + including 'batch_size'. + use_tpu: A bool indicating whether TPU support is enabled. Currently, - + TPU training and evaluation respect this bit, but eval_on_tpu can + override execution of eval. See below. - Predict still happens on CPU. + train_batch_size: An int representing the global training batch size. + TPUEstimator transforms this global batch size to a per-shard batch + size, as params['batch_size'], when calling `input_fn` and `model_fn`. + Cannot be `None` if `use_tpu` is `True`. Must be divisible by total + number of replicas. + eval_batch_size: An int representing evaluation batch size. Must be + divisible by total number of replicas. + predict_batch_size: An int representing the prediction batch size. Must be + divisible by total number of replicas. + batch_axis: A python tuple of int values describing how each tensor + produced by the Estimator `input_fn` should be split across the TPU + compute shards. For example, if your input_fn produced (images, labels) + where the images tensor is in `HWCN` format, your shard dimensions would + be [3, 0], where 3 corresponds to the `N` dimension of your images + Tensor, and 0 corresponds to the dimension along which to split the + 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 or `PER_HOST_V2`, batch_axis is ignored. + eval_on_tpu: If False, evaluation runs on CPU or GPU. In this case, the + model_fn must return `EstimatorSpec` when called with `mode` as `EVAL`. + export_to_tpu: If True, `export_savedmodel()` exports a metagraph for + serving on TPU. Note that unsupported export modes such as EVAL will be + ignored. For those modes, only a CPU model will be exported. + Currently, export_to_tpu only supports PREDICT. + export_to_cpu: If True, `export_savedmodel()` exports a metagraph for + serving on CPU. + warm_start_from: Optional string filepath to a checkpoint or SavedModel to + warm-start from, or a `tf.estimator.WarmStartSettings` object to fully + configure warm-starting. If the string filepath is provided instead of + a `WarmStartSettings`, then all variables are warm-started, and it is + assumed that vocabularies and Tensor names are unchanged. + experimental_exported_model_uses_all_cores: Whether to round-robin among + all cores visible to the host which is serving the saved model, or to + use a single core. This is a temporary flag to enable using all TPU + cores for inference with TPUPartitionedCall(). Once outside compilation + is supported in TPUPartitionedCall(), this flag will be enabled by + default. + experimental_export_device_assignment: Whether to include the device + assignment in the exported model. Doing so is useful in case of model + parallel inference but will tie the exported model to the TPU topology + used to export the model. + experimental_embedding_config_spec: Optional EmbeddingConfigSpec instance + to support using TPU embedding. IT IS STILL WORK IN PROGRESS, SO PLEASE + DO NOT USE. + + Raises: + ValueError: `params` has reserved keys already. + """ + if config is None or not isinstance(config, tpu_config.RunConfig): + raise ValueError( + '`config` must be provided with type `tpu_config.RunConfig`') + + if params is not None and any(k in params for k in _RESERVED_PARAMS_KEYS): + raise ValueError('{} are reserved keys but existed in params {}.'.format( + _RESERVED_PARAMS_KEYS, params)) + + if use_tpu: + # Perform some very basic validations. More validations will be found in + # _InternalTPUContext. + if train_batch_size is None: + raise ValueError('`train_batch_size` cannot be `None`') + util_lib.check_positive_integer(train_batch_size, 'train_batch_size') + + if (config.tpu_config.per_host_input_for_training is + tpu_config.InputPipelineConfig.PER_SHARD_V1 and + config.tpu_config.num_cores_per_replica): + raise ValueError( + 'Model parallelism only supports per host input for training. ' + 'Please adjust TPURunconfig.per_host_input_for_training.') + + if eval_batch_size is not None: + util_lib.check_positive_integer(eval_batch_size, 'eval_batch_size') + + if predict_batch_size is not None: + util_lib.check_positive_integer(predict_batch_size, + 'predict_batch_size') + + # Verifies the model_fn signature according to Estimator framework. + estimator_lib._verify_model_fn_args(model_fn, params) # pylint: disable=protected-access + # We cannot store config and params in this constructor as parent + # constructor might change them, such as assigning a temp dir for + # config.model_dir. + model_function = self._augment_model_fn(model_fn, batch_axis) + + # Overwrite log_step_count_steps to disable TensorLoggingHook and + # StepCounterHook from being created in Estimator. TPUEstimator already + # added equivalent hooks in _augment_model_fn above. + self._log_every_n_steps = config.log_step_count_steps + config = config.replace(log_step_count_steps=None) + + # Passing non-None params as wrapped model_fn has it. + params = params or {} + super(TPUEstimator, self).__init__( + model_fn=model_function, + model_dir=model_dir, + config=config, + params=params, + warm_start_from=warm_start_from) + self._iterations_per_training_loop = ( + self._config.tpu_config.iterations_per_loop) + + # All properties passed to _InternalTPUContext are immutable. + # pylint: disable=protected-access + self._ctx = tpu_context._get_tpu_context( + self._config, train_batch_size, eval_batch_size, predict_batch_size, + use_tpu, eval_on_tpu, experimental_embedding_config_spec) + + self._export_to_cpu = export_to_cpu + self._export_to_tpu = export_to_tpu + self._experimental_exported_model_uses_all_cores = ( + experimental_exported_model_uses_all_cores) + self._experimental_export_device_assignment = ( + experimental_export_device_assignment) + if (experimental_exported_model_uses_all_cores and + experimental_export_device_assignment): + raise ValueError('experimental_exported_model_uses_all_cores and ' + 'experimental_export_device_assignment is not supported ' + 'at the same time.') + + self._is_input_fn_invoked = None + self._rendezvous = {} + + def _add_meta_graph_for_mode(self, + builder, + input_receiver_fn_map, + checkpoint_path, + save_variables=True, + mode=model_fn_lib.ModeKeys.PREDICT, + export_tags=None, + check_variables=True): + if self._export_to_tpu and mode != model_fn_lib.ModeKeys.PREDICT: + logging.warning('TPUEstimator only handles mode PREDICT for exporting ' + 'when `export_to_tpu` is `True`; Mode {} will be ignored ' + 'for TPU.'.format(mode)) + + if not self._export_to_cpu and not self._export_to_tpu: + raise ValueError('One of export_to_cpu and export_to_tpu must be true.') + + if self._export_to_cpu: + (super(TPUEstimator, self)._add_meta_graph_for_mode( + builder, + input_receiver_fn_map, + checkpoint_path, + save_variables, + mode=mode, + export_tags=export_tags, + check_variables=check_variables)) + + if self._export_to_tpu and mode == model_fn_lib.ModeKeys.PREDICT: + input_receiver_fn_map = { + _REWRITE_FOR_INFERENCE_MODE: input_receiver_fn_map[mode] + } + export_tags = [tag_constants.SERVING, tag_constants.TPU] + mode = _REWRITE_FOR_INFERENCE_MODE + + # See b/110052256 for why `check_variables` is `False`. + if not self._export_to_cpu: + check_variables = save_variables = True + else: + check_variables = save_variables = False + (super(TPUEstimator, self)._add_meta_graph_for_mode( + builder, + input_receiver_fn_map, + checkpoint_path, + save_variables=save_variables, + mode=mode, + export_tags=export_tags, + check_variables=check_variables)) + + def _call_model_fn(self, features, labels, mode, config): + if mode == _REWRITE_FOR_INFERENCE_MODE: + return self._call_model_fn_for_inference(features, labels, mode, config) + else: + return super(TPUEstimator, self)._call_model_fn(features, labels, mode, + config) + + def _call_model_fn_for_inference(self, features, labels, mode, config): + """Wraps `_call_model_fn` for `export_savedmodel`.""" + if mode != _REWRITE_FOR_INFERENCE_MODE: + raise ValueError('mode must be {}; ' + 'got {}.'.format(_REWRITE_FOR_INFERENCE_MODE, mode)) + + computation, capture = self._build_computation_for_inference( + features, labels, mode, config) + tensors = call_computation( + computation, + experimental_exported_model_uses_all_cores=self + ._experimental_exported_model_uses_all_cores) + estimator_spec, export_outputs_dict, predictions_dict, none_indices = ( + capture.get()) + predictions_list = tensors[:len(predictions_dict)] + export_outputs_list_without_none = tensors[len(predictions_dict):] + + # Reinsert `None`s which we've taken out in + # `_build_computation_for_inference()`. + export_outputs_list = [] + while none_indices or export_outputs_list_without_none: + if none_indices and none_indices[0] == len(export_outputs_list): + export_outputs_list.append(None) + none_indices.pop(0) + else: + export_outputs_list.append(export_outputs_list_without_none.pop(0)) + + # Reconstruct `export_outputs` with updated tensors. + new_export_outputs_dict = nest.pack_sequence_as(export_outputs_dict, + export_outputs_list) + export_outputs = estimator_spec.export_outputs + new_export_outputs = collections.OrderedDict( + (k, _clone_export_output_with_tensors(export_outputs[k], v)) + for k, v in six.iteritems(new_export_outputs_dict)) + # Reconstruct `predictions` with updated tensors. + new_predictions = nest.pack_sequence_as(predictions_dict, predictions_list) + if (len(new_predictions) == 1 and + _KEY_WHEN_PREDICTIONS_IS_A_TENSOR in new_predictions): + new_predictions = new_predictions[_KEY_WHEN_PREDICTIONS_IS_A_TENSOR] + + return estimator_spec._replace( + export_outputs=new_export_outputs, predictions=new_predictions) + + def _build_computation_for_inference(self, features, labels, mode, config): + capture = _CapturedObject() + + def computation(): + """Computation to be passed to `TPUPartitionedCall()`.""" + tpu_computation, tpu_capture = self._build_tpu_computation_for_inference( + features, labels, mode, config) + + if self._experimental_export_device_assignment: + # Export the device assignment as part of the model. This is useful for + # model parallel usecases where the model relies on the mapping between + # logical and physical devices. + with self._ctx.with_mode(mode) as ctx: + device_assignment = ctx.device_assignment + else: + device_assignment = None + + if self._experimental_exported_model_uses_all_cores: + tensors_on_cpu = tpu.rewrite( + tpu_computation, device_assignment=device_assignment) + else: + tensors_on_cpu = tpu.rewrite_for_inference( + tpu_computation, device_assignment=device_assignment) + + (estimator_spec, export_outputs_dict, export_outputs_list, + predictions_dict) = ( + tpu_capture.get()) + predictions_list = tensors_on_cpu[:len(predictions_dict)] + export_outputs_tpu_on_cpu_list = tensors_on_cpu[len(predictions_dict):] + + # Reconstruct tensors used in export_outputs, with TPU tensors replaced + # with their CPU counterpart returned from `rewrite_for_inference()`. + # `function.Defun()` does not like `None`s in return values, so we leave + # `None`s out but record their positions for later reconstruction. + export_outputs_list_without_none = [] + none_indices = [] + for i, t in enumerate(export_outputs_list): + if t is None: + none_indices.append(i) + else: + export_outputs_list_without_none.append( + export_outputs_tpu_on_cpu_list.pop(0)) + + capture.capture((estimator_spec, export_outputs_dict, predictions_dict, + none_indices)) + return predictions_list + export_outputs_list_without_none + + return computation, capture + + def _build_tpu_computation_for_inference(self, features, labels, mode, + config): + capture = _CapturedObject() + + def computation(): + """Compute tpu tensors used in export_outputs. + + Passed to rewrite_for_inference so that model_fn will be called under + the rewriting contexts. Only tpu tensors are returned, but export_outputs + and scaffold are captured. + + Returns: + A list of Tensors used in export_outputs and not marked for + outside_compilation. + """ + # We should only call model fn once and it should be inside `computation` + # so that building the graph will happen under `rewrite_for_inference`. + mode = model_fn_lib.ModeKeys.PREDICT + estimator_spec = self._call_model_fn(features, labels, mode, config) + + # We pick the TPU tensors out from `export_output` and later return them + # from `computation` for rewriting. + export_outputs_dict = collections.OrderedDict( + (k, _export_output_to_tensors(v)) + for k, v in six.iteritems(estimator_spec.export_outputs)) + export_outputs_list = nest.flatten(export_outputs_dict) + export_outputs_tpu_list = [ + t for t in export_outputs_list if t is not None + ] + + if isinstance(estimator_spec.predictions, dict): + predictions_dict = collections.OrderedDict( + (k, v) for k, v in six.iteritems(estimator_spec.predictions)) + else: + predictions_dict = { + _KEY_WHEN_PREDICTIONS_IS_A_TENSOR: estimator_spec.predictions + } + predictions_list = nest.flatten(predictions_dict) + + # We cannot return everything we want through the return values, so + # capture the rest here for later use. + capture.capture((estimator_spec, export_outputs_dict, export_outputs_list, + predictions_dict)) + return predictions_list + export_outputs_tpu_list + + return computation, capture + + def _create_global_step(self, graph): + """Creates a global step suitable for TPUs. + + Args: + graph: The graph in which to create the global step. + + Returns: + A global step `Tensor`. + + Raises: + ValueError: if the global step tensor is already defined. + """ + return _create_global_step(graph) + + def _convert_train_steps_to_hooks(self, steps, max_steps): + with self._ctx.with_mode(model_fn_lib.ModeKeys.TRAIN) as ctx: + if ctx.is_running_on_cpu(): + return super(TPUEstimator, self)._convert_train_steps_to_hooks( + steps, max_steps) + + # On TPU. + if steps is None and max_steps is None: + raise ValueError( + 'For TPU training, one of `steps` or `max_steps` must be set. ' + 'Cannot be both `None`.') + + # Estimator.train has explicit positiveness check. + if steps is not None: + util_lib.check_positive_integer(steps, 'Train steps') + if max_steps is not None: + util_lib.check_positive_integer(max_steps, 'Train max_steps') + + return [ + _TPUStopAtStepHook(self._iterations_per_training_loop, steps, max_steps) + ] + + def _convert_eval_steps_to_hooks(self, steps): + with self._ctx.with_mode(model_fn_lib.ModeKeys.EVAL) as ctx: + if ctx.is_running_on_cpu(): + return super(TPUEstimator, self)._convert_eval_steps_to_hooks(steps) + + if steps is None: + raise ValueError('Evaluate `steps` must be set on TPU. Cannot be `None`.') + + util_lib.check_positive_integer(steps, 'Eval steps') + + return [ + evaluation._StopAfterNEvalsHook( # pylint: disable=protected-access + num_evals=steps), + _SetEvalIterationsHook(steps) + ] + + def _call_input_fn(self, input_fn, mode): + """Calls the input function. + + Args: + input_fn: The input function. + mode: ModeKeys + + Returns: + In TPU mode, returns an input_fn to be called later in model_fn. + Otherwise, calls the input_fn and returns either fatures or + (features, labels). + + Raises: + ValueError: if input_fn takes invalid arguments or does not have `params`. + """ + input_fn_args = function_utils.fn_args(input_fn) + config = self.config # a deep copy. + kwargs = {} + if 'params' in input_fn_args: + kwargs['params'] = self.params # a deep copy. + else: + raise ValueError('input_fn ({}) does not include params argument, ' + 'required by TPUEstimator to pass batch size as ' + 'params["batch_size"]'.format(input_fn)) + if 'config' in input_fn_args: + kwargs['config'] = config + + if 'mode' in input_fn_args: + kwargs['mode'] = mode + + # Records the fact input_fn has been invoked. + self._is_input_fn_invoked = True + + with self._ctx.with_mode(mode) as ctx: + # Setting the batch size in params first. This helps user to have same + # input_fn for use_tpu=True/False. + batch_size_for_input_fn = ctx.batch_size_for_input_fn + if batch_size_for_input_fn is not None: + _add_item_to_params(kwargs['params'], _BATCH_SIZE_KEY, + batch_size_for_input_fn) + + # For export_savedmodel, input_fn is never passed to Estimator. So, + # `is_export_mode` must be False. + if ctx.is_running_on_cpu(is_export_mode=False): + with ops.device('/device:CPU:0'): + return input_fn(**kwargs) + + # For TPU computation, input_fn should be invoked in a tf.while_loop for + # performance. While constructing the tf.while_loop, the structure of + # inputs returned by the `input_fn` needs to be recorded. The structure + # includes whether features or labels is dict or single Tensor, dict keys, + # tensor shapes, and dtypes. The recorded structure is used to create the + # infeed dequeue ops, which must be wrapped and passed as a Fn, called + # inside the TPU computation, as the TPU computation is wrapped inside a + # tf.while_loop also. So, we either pass input_fn to model_fn or pass + # dequeue_fn to model_fn. Here, `input_fn` is passed directly as + # `features` in `model_fn` signature. + def _input_fn(ctx): + _add_item_to_params(kwargs['params'], _CTX_KEY, ctx) + return input_fn(**kwargs) + + return _input_fn + + def _validate_features_in_predict_input(self, result): + """Skip the validation. + + For TPUEstimator, we do not need to check the result type. `_InputPipeline` + has stronger check. Parent class's check generates confusing warning msg. + + Args: + result: `features` returned by input_fn. + """ + pass + + def train(self, + input_fn, + hooks=None, + steps=None, + max_steps=None, + saving_listeners=None): + rendezvous = error_handling.ErrorRendezvous(num_sources=3) + self._rendezvous[model_fn_lib.ModeKeys.TRAIN] = rendezvous + try: + return super(TPUEstimator, self).train( + input_fn=input_fn, + hooks=hooks, + steps=steps, + max_steps=max_steps, + saving_listeners=saving_listeners) + except Exception: # pylint: disable=broad-except + rendezvous.record_error('training_loop', sys.exc_info()) + finally: + rendezvous.record_done('training_loop') + rendezvous.raise_errors() + + def evaluate(self, + input_fn, + steps=None, + hooks=None, + checkpoint_path=None, + name=None): + rendezvous = error_handling.ErrorRendezvous(num_sources=3) + self._rendezvous[model_fn_lib.ModeKeys.EVAL] = rendezvous + try: + return super(TPUEstimator, self).evaluate( + input_fn, + steps=steps, + hooks=hooks, + checkpoint_path=checkpoint_path, + name=name) + except Exception: # pylint: disable=broad-except + rendezvous.record_error('evaluation_loop', sys.exc_info()) + finally: + rendezvous.record_done('evaluation_loop') + rendezvous.raise_errors() + + def predict(self, + input_fn, + predict_keys=None, + hooks=None, + checkpoint_path=None, + yield_single_examples=True): + rendezvous = error_handling.ErrorRendezvous(num_sources=3) + self._rendezvous[model_fn_lib.ModeKeys.PREDICT] = rendezvous + try: + for result in super(TPUEstimator, self).predict( + input_fn=input_fn, + predict_keys=predict_keys, + hooks=hooks, + checkpoint_path=checkpoint_path, + yield_single_examples=yield_single_examples): + yield result + except Exception: # pylint: disable=broad-except + rendezvous.record_error('prediction_loop', sys.exc_info()) + finally: + rendezvous.record_done('prediction_loop') + rendezvous.raise_errors() + + rendezvous.record_done('prediction_loop') + rendezvous.raise_errors() + + def _augment_model_fn(self, model_fn, batch_axis): + """Returns a new model_fn, which wraps the TPU support.""" + + def _model_fn(features, labels, mode, config, params): + """A Estimator `model_fn` for TPUEstimator.""" + with self._ctx.with_mode(mode) as ctx: + model_fn_wrapper = _ModelFnWrapper(model_fn, config, params, ctx) + + # `input_fn` is called in `train()`, `evaluate()`, and `predict()`, + # but not in `export_savedmodel()`. + if self._is_input_fn_invoked: + is_export_mode = False + else: + is_export_mode = True + + # Clear the bit. + self._is_input_fn_invoked = None + + # examples_hook is added to training_hooks for both CPU and TPU + # execution. + if self._log_every_n_steps is not None: + examples_hook = ExamplesPerSecondHook( + ctx.global_batch_size, + # pylint:disable=g-long-ternary + output_dir=(self.model_dir + if not config or config.save_summary_steps + else None), + # pylint:enable=g-long-ternary + every_n_steps=self._log_every_n_steps) + + if ctx.is_running_on_cpu(is_export_mode=is_export_mode): + logging.info('Running %s on CPU', mode) + estimator_spec = model_fn_wrapper.call_without_tpu( + features, labels, is_export_mode=is_export_mode) + if self._log_every_n_steps is not None: + estimator_spec = estimator_spec._replace( + training_hooks=estimator_spec.training_hooks + (examples_hook,)) + return estimator_spec + + assert labels is None, '`labels` passed to `model_fn` must be `None`.' + # TPUEstimator._call_input_fn passes `input_fn` as features to here. + assert callable(features), '`input_fn` is not callable.' + input_fn = features + + tpu_init_ops = [] + if ctx.embedding_config and mode == model_fn_lib.ModeKeys.TRAIN: + dummy_table_variables, dummy_table_variables_init = ( + tpu_embedding_gradient.create_dummy_table_variables( + ctx.embedding_config.tpu_embedding)) + ctx.embedding_config.dummy_table_variables = dummy_table_variables + tpu_init_ops.append(dummy_table_variables_init) + + input_holders = _InputPipeline(input_fn, batch_axis, ctx) + enqueue_ops, dequeue_fn, input_hooks, run_infeed_loop_on_coordinator = ( + input_holders.generate_infeed_enqueue_ops_and_dequeue_fn()) + + graph = ops.get_default_graph() + for enqueue_op in enqueue_ops: + if isinstance(enqueue_op, list): + graph.get_collection_ref(_TPU_ENQUEUE_OPS).extend(enqueue_op) + else: + graph.add_to_collection(_TPU_ENQUEUE_OPS, enqueue_op) + + if mode == model_fn_lib.ModeKeys.TRAIN: + compile_op, loss, host_call, scaffold, training_hooks = ( + _train_on_tpu_system(ctx, model_fn_wrapper, dequeue_fn)) + if ctx.embedding_config: + g = ops.get_default_graph() + table_to_config_dict = ( + ctx.embedding_config.tpu_embedding.table_to_config_dict) + optimization_parameters = ( + ctx.embedding_config.tpu_embedding.optimization_parameters) + embedding_variable_name_by_table, slot_variable_names_by_table = ( + _tpu_estimator_embedding.get_full_variable_names( + g, table_to_config_dict, optimization_parameters + ) + ) + embedding_variables_and_ops = ( + ctx.embedding_config.tpu_embedding.create_variables_and_ops( + embedding_variable_name_by_table, + slot_variable_names_by_table + )) + tpu_init_ops.extend(embedding_variables_and_ops.load_ops()) + + host_ops = host_call.create_tpu_hostcall() + if host_ops is None: + host_ops = [] + + shutdown_hooks = [] + shutdown_mode = os.environ.get('TF_TPU_GRACEFUL_SHUTDOWN_MODE', + 'shutdown_worker') + if shutdown_mode: + if shutdown_mode == 'shutdown_worker': + finalizer_hooks = [ + session_support.ShutdownLameWorkers(timeout_ms=60 * 1000), + ] + elif shutdown_mode == 'shutdown_computation': + finalizer_hooks = [ + session_support.RestartComputation(timeout_ms=60 * 1000), + ] + else: + raise ValueError( + 'Unknown TF_TPU_GRACEFUL_SHUTDOWN_MODE "%s"' % shutdown_mode) + + shutdown_hooks.append( + session_support.GracefulShutdownHook( + checkpoint_prefix=self.model_dir + '/model.ckpt', + on_shutdown_hooks=finalizer_hooks)) + + with ops.control_dependencies([loss]): + global_step = array_ops.identity(training.get_global_step()) + hooks = input_hooks + shutdown_hooks + hooks.extend([ + TPUInfeedOutfeedSessionHook( + ctx, + enqueue_ops, + host_ops, + tpu_compile_op=compile_op, + run_infeed_loop_on_coordinator=( + run_infeed_loop_on_coordinator), + rendezvous=self._rendezvous[mode], + master=self._config.master, + session_config=self._session_config, + tpu_init_ops=tpu_init_ops), + InstallSignalHandlerHook() + ]) + if self._log_every_n_steps is not None: + logging_hook_frequency = ( # Divide and round up + (self._log_every_n_steps + + self._config.tpu_config.iterations_per_loop - 1) // + self._config.tpu_config.iterations_per_loop) + hooks.append( + training.LoggingTensorHook({ + 'loss': array_ops.identity(loss), + 'step': global_step, + }, + every_n_iter=logging_hook_frequency)) + examples_hook._set_steps_per_run( # pylint: disable=protected-access + self._config.tpu_config.iterations_per_loop) + hooks.append(examples_hook) + + if training_hooks: + hooks.extend(training_hooks) + + chief_hooks = [] + if (self._config.save_checkpoints_secs or + self._config.save_checkpoints_steps): + checkpoint_hook = training.CheckpointSaverHook( + self.model_dir, + save_secs=self._config.save_checkpoints_secs, + save_steps=self._config.save_checkpoints_steps, + scaffold=scaffold) + checkpoint_hook._set_steps_per_run( # pylint: disable=protected-access + self._config.tpu_config.iterations_per_loop) + chief_hooks.append(checkpoint_hook) + + summary.scalar(model_fn_lib.LOSS_METRIC_KEY, loss) + with ops.control_dependencies([loss]): + update_ops = _sync_variables_ops(ctx) + if ctx.embedding_config: + update_ops.extend(embedding_variables_and_ops.retrieve_ops()) + + # Validate the TPU training graph to catch basic errors + _validate_tpu_training_graph() + + train_op = control_flow_ops.group(*update_ops) + graph.add_to_collection(_TPU_TRAIN_OP, train_op) + + return model_fn_lib.EstimatorSpec( + mode, + loss=loss, + training_chief_hooks=chief_hooks, + training_hooks=hooks, + train_op=train_op, + scaffold=scaffold) + + if mode == model_fn_lib.ModeKeys.EVAL: + compile_op, total_loss, host_calls, scaffold, eval_hooks = ( + _eval_on_tpu_system(ctx, model_fn_wrapper, dequeue_fn)) + iterations_per_loop_var = _create_or_get_iterations_per_loop() + mean_loss = math_ops.div( + total_loss, + math_ops.cast(iterations_per_loop_var, dtype=total_loss.dtype)) + + with ops.control_dependencies([mean_loss]): + # After TPU evaluation computation is done (the mean_loss tensor), + # reads all variables back from TPU and updates the eval step + # counter properly + internal_ops_to_run = _sync_variables_ops(ctx) + internal_ops_to_run.append( + _increase_eval_step_op(iterations_per_loop_var)) + + host_call_ret = host_calls.create_tpu_hostcall() + eval_metric_ops = {} + eval_update_ops = [] + + eval_metrics = host_call_ret.get('eval_metrics', {}) + if eval_metrics: + # Creates a dummy metric update_op for all metrics. Estimator + # expects all metrics in `eval_metric_ops` have update_op and calls + # them one by one. The real metric update_ops are invoked in a + # separated thread. So, here give Estimator the dummy op for all + # metrics. + with ops.control_dependencies(internal_ops_to_run): + dummy_update_op = control_flow_ops.no_op() + + for k, v in eval_metrics.items(): + eval_metric_ops[k] = (v[0], dummy_update_op) + eval_update_ops.append(v[1]) + else: + # If no eval metrics are passed, create an identity node for the + # loss and add `internal_ops_to_run` to its dependencies. So + # `internal_ops_to_run` can be executed. + with ops.control_dependencies(internal_ops_to_run): + mean_loss = array_ops.identity(mean_loss) + + if 'host_call' not in host_call_ret: + host_ops = [] + else: + host_ops = host_call_ret['host_call'] + hooks = [ + TPUInfeedOutfeedSessionHook( + ctx, + enqueue_ops, + eval_update_ops + host_ops, + tpu_compile_op=compile_op, + run_infeed_loop_on_coordinator=( + run_infeed_loop_on_coordinator), + rendezvous=self._rendezvous[mode], + master=self._config.evaluation_master, + session_config=self._session_config, + tpu_init_ops=tpu_init_ops) + ] + input_hooks + + if eval_hooks: + hooks.extend(eval_hooks) + + return model_fn_lib.EstimatorSpec( + mode, + loss=mean_loss, + evaluation_hooks=hooks, + eval_metric_ops=eval_metric_ops, + scaffold=scaffold) + + # Predict + assert mode == model_fn_lib.ModeKeys.PREDICT + + (compile_op, dummy_predict_op, host_calls, + scaffold, prediction_hooks) = _predict_on_tpu_system( + ctx, model_fn_wrapper, dequeue_fn) + with ops.control_dependencies([dummy_predict_op]): + internal_ops_to_run = _sync_variables_ops(ctx) + with ops.control_dependencies(internal_ops_to_run): + dummy_predict_op = control_flow_ops.no_op() + + # In train and evaluation, the main TPU program is passed to monitored + # training session to run. Infeed enqueue and outfeed dequeue are + # executed in side threads. This is not the configuration for + # prediction mode. + # + # For prediction, the Estimator executes the EstimatorSpec.predictions + # directly and yield the element (via generator) to call site. So, the + # outfeed based prediction must be passed to MonitoredSession directly. + # Other parts of the TPU execution are organized as follows. + # + # 1. All outfeed based Tensors must be grouped with predictions Tensors + # to form a single invocation. This avoid the issue we might trigger + # multiple outfeeds incorrectly. To achieve this, `host_call` is + # placed in control_dependencies of `stopping_signals`, and + # `stopping_signals` is passed into _StoppingPredictHook, which sets + # the `stopping_signals` as SessionRunArgs. MonitoredSession merges + # all SessionRunArgs with the fetch in session.run together. + # + # 2. The TPU program (dummy_predict_op) and enqueue_ops (infeed Enqueue) + # are grouped together. They will be launched once and only once in + # side threads and they quit naturally according to the SAME stopping + # condition. + enqueue_ops.append(dummy_predict_op) + + host_call_ret = host_calls.create_tpu_hostcall() + if 'host_call' not in host_call_ret: + host_ops = [] + else: + host_ops = host_call_ret['host_call'] + + predictions = host_call_ret['predictions'] + _verify_cross_hosts_transfer_size( + predictions, + message=( + 'The estimated size for TPUEstimatorSpec.predictions is too ' + 'large.')) + signals = host_call_ret['signals'] + + with ops.control_dependencies(host_ops): + host_ops = [] # Empty, we do do not need it anymore. + scalar_stopping_signal = _StopSignals.as_scalar_stopping_signal( + signals) + predictions = _PaddingSignals.slice_tensor_or_dict( + predictions, signals) + + hooks = [ + _StoppingPredictHook(scalar_stopping_signal), + TPUInfeedOutfeedSessionHookForPrediction( + ctx, enqueue_ops, host_ops, rendezvous=self._rendezvous[mode], + tpu_compile_op=compile_op, + master=self._config.master, + session_config=self._session_config), + ] + input_hooks + + if prediction_hooks: + hooks.extend(prediction_hooks) + + return model_fn_lib.EstimatorSpec( + mode, + prediction_hooks=hooks, + predictions=predictions, + scaffold=scaffold) + + return _model_fn + + +def _export_output_to_tensors(export_output): + """Get a list of `Tensors` used in `export_output`. + + Args: + export_output: an `ExportOutput` object such as `ClassificationOutput`, + `RegressionOutput`, or `PredictOutput`. + + Returns: + a list of tensors used in export_output. + + Raises: + ValueError: if `export_output` is not one of `ClassificationOutput`, + `RegressionOutput`, or `PredictOutput`. + """ + if isinstance(export_output, export_output_lib.ClassificationOutput): + return [export_output.scores, export_output.classes] + elif isinstance(export_output, export_output_lib.RegressionOutput): + return [export_output.value] + elif isinstance(export_output, export_output_lib.PredictOutput): + return list(export_output.outputs.values()) + else: + raise ValueError( + '`export_output` must be have type `ClassificationOutput`, ' + '`RegressionOutput`, or `PredictOutput`; got {}.'.format(export_output)) + + +def _clone_export_output_with_tensors(export_output, tensors): + """Clones `export_output` but with new `tensors`. + + Args: + export_output: an `ExportOutput` object such as `ClassificationOutput`, + `RegressionOutput`, or `PredictOutput`. + tensors: a list of `Tensors` used to construct a new `export_output`. + + Returns: + A dict similar to `export_output` but with `tensors`. + + Raises: + ValueError: if `export_output` is not one of `ClassificationOutput`, + `RegressionOutput`, or `PredictOutput`. + """ + if isinstance(export_output, export_output_lib.ClassificationOutput): + if len(tensors) != 2: + raise ValueError('tensors must be of length 2; ' + 'got {}.'.format(len(tensors))) + return export_output_lib.ClassificationOutput(*tensors) + elif isinstance(export_output, export_output_lib.RegressionOutput): + if len(tensors) != 1: + raise ValueError('tensors must be of length 1; ' + 'got {}'.format(len(tensors))) + return export_output_lib.RegressionOutput(*tensors) + elif isinstance(export_output, export_output_lib.PredictOutput): + return export_output_lib.PredictOutput( + dict(zip(export_output.outputs.keys(), tensors))) + else: + raise ValueError( + '`export_output` must be have type `ClassificationOutput`, ' + '`RegressionOutput`, or `PredictOutput`; got {}.'.format(export_output)) + + +def _eval_on_tpu_system(ctx, model_fn_wrapper, dequeue_fn): + """Executes `model_fn_wrapper` multiple times on all TPU shards.""" + iterations_per_loop_var = _create_or_get_iterations_per_loop() + + (single_tpu_eval_step, host_calls, captured_scaffold_fn, captured_eval_hooks + ) = model_fn_wrapper.convert_to_single_tpu_eval_step(dequeue_fn) + + def multi_tpu_eval_steps_on_single_shard(): + return training_loop.repeat(iterations_per_loop_var, single_tpu_eval_step, + [_ZERO_LOSS]) + + (compile_op, loss,) = tpu.split_compile_and_shard( + multi_tpu_eval_steps_on_single_shard, + inputs=[], + num_shards=ctx.num_replicas, + outputs_from_all_shards=False, + device_assignment=ctx.device_assignment) + + loss = loss[0] + scaffold = _get_scaffold(captured_scaffold_fn) + return compile_op, loss, host_calls, scaffold, captured_eval_hooks.get() + + +def _train_on_tpu_system(ctx, model_fn_wrapper, dequeue_fn): + """Executes `model_fn_wrapper` multiple times on all TPU shards.""" + iterations_per_loop_var = _create_or_get_iterations_per_loop() + + (single_tpu_train_step, host_call, captured_scaffold_fn, + captured_training_hooks) = ( + model_fn_wrapper.convert_to_single_tpu_train_step(dequeue_fn)) + + @tpu_function.on_device_training_loop + def multi_tpu_train_steps_on_single_shard(): + return training_loop.repeat(iterations_per_loop_var, single_tpu_train_step, + [_INITIAL_LOSS]) + + (compile_op, loss,) = tpu.split_compile_and_shard( + multi_tpu_train_steps_on_single_shard, + inputs=[], + num_shards=ctx.num_replicas, + outputs_from_all_shards=False, + device_assignment=ctx.device_assignment) + + loss = loss[0] + scaffold = _get_scaffold(captured_scaffold_fn) + return compile_op, loss, host_call, scaffold, captured_training_hooks.get() + + +def _predict_on_tpu_system(ctx, model_fn_wrapper, dequeue_fn): + """Executes `model_fn_wrapper` multiple times on all TPU shards.""" + (single_tpu_predict_step, host_calls, captured_scaffold_fn, + captured_predict_hooks + ) = model_fn_wrapper.convert_to_single_tpu_predict_step(dequeue_fn) + + @tpu_function.on_device_training_loop + def multi_tpu_predict_steps_on_single_shard(): + + def cond(scalar_stopping_signal): + return math_ops.logical_not( + _StopSignals.should_stop(scalar_stopping_signal)) + + inputs = [_StopSignals.NON_STOPPING_SIGNAL] + outputs = training_loop.while_loop( + cond, single_tpu_predict_step, inputs=inputs, name=b'loop') + return outputs + + (compile_op, dummy_predict_op,) = tpu.split_compile_and_shard( + multi_tpu_predict_steps_on_single_shard, + inputs=[], + num_shards=ctx.num_replicas, + outputs_from_all_shards=False, + device_assignment=ctx.device_assignment) + + dummy_predict_op = dummy_predict_op[0] + scaffold = _get_scaffold(captured_scaffold_fn) + return (compile_op, dummy_predict_op, host_calls, scaffold, + captured_predict_hooks.get()) + + +def _wrap_computation_in_while_loop(device, op_fn): + """Wraps the ops generated by `op_fn` in tf.while_loop.""" + + def computation(i): + with ops.control_dependencies(op_fn()): + return i + 1 + + iterations_per_loop_var = _create_or_get_iterations_per_loop() + # By setting parallel_iterations=1, the parallel execution in while_loop is + # basically turned off. + with ops.device(device): + iterations = array_ops.identity(iterations_per_loop_var) + return control_flow_ops.while_loop( + lambda i: i < iterations, + computation, [constant_op.constant(0)], + parallel_iterations=1) + + +def _wrap_computation_in_while_loop_with_stopping_signals(device, op_fn): + """Wraps the ops generated by `op_fn` in tf.while_loop.""" + + def cond(scalar_stopping_signal): + return math_ops.logical_not( + _StopSignals.should_stop(scalar_stopping_signal)) + + def computation(unused_scalar_stopping_signal): + return_value = op_fn() + execute_ops = return_value['ops'] + signals = return_value['signals'] + with ops.control_dependencies(execute_ops): + return _StopSignals.as_scalar_stopping_signal(signals) + + # By setting parallel_iterations=1, the parallel execution in while_loop is + # basically turned off. + with ops.device(device): + return control_flow_ops.while_loop( + cond, + computation, [_StopSignals.NON_STOPPING_SIGNAL], + parallel_iterations=1) + + +def _validate_tpu_training_graph(): + """Validate graph before running distributed training. + + Raises: + ValueError: If the graph seems invalid for running on device + """ + operations = ops.get_default_graph().get_operations() + + # Check if there is atleast one CrossReplicaSum operation in the graph + # This should be introduced by using the CrossShardOptimizer wrapper + cross_replica_sum_ops = [ + o for o in operations if o.type == _CROSS_REPLICA_SUM_OP + ] + if not cross_replica_sum_ops: + raise ValueError( + 'CrossShardOptimizer must be used for model training on TPUs.') + + +class _CapturedObject(object): + """A placeholder to capture an object. + + This is useful when we need to capture a Python object in the Tensorflow + control flow body function and use it outside the control flow. + """ + + def __init__(self): + self._object = None + self._captured = False + + def capture(self, o): + if self._captured: + raise RuntimeError( + 'InternalError: Object can capture only once. Please file bug.') + + self._captured = True + self._object = o + + def get(self): + if not self._captured: + raise RuntimeError( + 'InternalError: Object is not captured properly before `get`. ' + 'Please file bug.') + return self._object + + +def _get_scaffold(captured_scaffold_fn): + """Retrieves the Scaffold from `captured_scaffold_fn`.""" + with _CapturingContext(message='Inside scaffold_fn'): + scaffold_fn = captured_scaffold_fn.get() + if scaffold_fn: + scaffold = scaffold_fn() + if scaffold is None: + raise ValueError( + 'TPUEstimatorSpec.scaffold_fn returns None, which is not allowed') + else: + scaffold = None + + if scaffold: + wrapped_finalize = scaffold.finalize + + def _finalize(): + with _CapturingContext('Inside Scaffold.finalize'): + wrapped_finalize() + + scaffold.finalize = _finalize + return scaffold + + +class _CapturingContext(control_flow_ops.ControlFlowContext): + """Tracks references to Tensors defined in TPU replication.""" + + def __init__(self, message): + control_flow_ops.ControlFlowContext.__init__(self) + self._message = message + + def to_control_flow_context_def(self, context_def, export_scope=None): + # pylint: disable=useless-super-delegation + # NOTE(slebedev): the method is required by `ControlFlowContext`. + super(_CapturingContext, self).to_control_flow_context_def( + context_def, export_scope) + + def AddOp(self, op): # pylint: disable=invalid-name + for c in op.inputs: + if tpu._TPU_REPLICATE_ATTR in c.op.node_def.attr: # pylint: disable=protected-access + raise ValueError('{}: Op {} depends on TPU computation {}, ' + 'which is not allowed.'.format(self._message, op, c)) + + def __enter__(self): + # pylint: disable=protected-access + self._g = ops.get_default_graph() + self._old = self._g._get_control_flow_context() + self._g._set_control_flow_context(self) + # pylint: enable=protected-access + + def __exit__(self, _, __, ___): # pylint: disable=invalid-name + self._g._set_control_flow_context(self._old) # pylint: disable=protected-access + + +class _Inputs(object): + """A data structure representing the input_fn returned values. + + This also supports the returned value from input_fn as `Dataset`. + """ + + def __init__(self, features=None, labels=None, dataset=None, signals=None): + if dataset is not None and (features is not None or labels is not None or + signals is not None): + raise RuntimeError('Internal Error: Either (features and labels) or ' + 'dataset should be provided, not both. Please file ' + 'bug') + + self._features = features + self._labels = labels + self._signals = signals + + self._dataset = dataset + self._iterator = None + + @staticmethod + def from_input_fn(return_values): + """Returns an `_Inputs` instance according to `input_fn` return value.""" + if isinstance(return_values, dataset_ops.DatasetV2): + dataset = return_values + return _Inputs(dataset=dataset) + + features, labels = _Inputs._parse_inputs(return_values) + return _Inputs(features, labels) + + @staticmethod + def _parse_inputs(return_values): + if isinstance(return_values, tuple): + features, labels = return_values + else: + features, labels = return_values, None + return features, labels + + @property + def is_dataset(self): + """Returns True if the return value from input_fn is Dataset.""" + return self._dataset is not None + + def dataset_initializer(self): + """Returns the dataset's initializer. + + The initializer must be run before calling `features_and_labels`. + """ + self._iterator = dataset_ops.make_initializable_iterator(self._dataset) + return self._iterator.initializer + + def features_and_labels(self): + """Gets `features` and `labels`.""" + if self.is_dataset: + if self._iterator is None: + raise RuntimeError('Internal error: Must run dataset_initializer ' + 'before calling features_and_labels(). Please file ' + 'a bug!') + return _Inputs._parse_inputs(self._iterator.get_next()) + + return (self._features, self._labels) + + def signals(self): + return self._signals + + @property + def dataset(self): + return self._dataset + + +class _InputsWithStoppingSignals(_Inputs): + """Inputs with `_StopSignals` inserted into the dataset.""" + + def __init__(self, + dataset, + batch_size, + add_padding=False, + num_invocations_per_step=1): + + assert dataset is not None + user_provided_dataset = dataset.map( + _InputsWithStoppingSignals.insert_stopping_signal( + stop=False, batch_size=batch_size, add_padding=add_padding)) + if num_invocations_per_step == 1: + final_batch_dataset = dataset.take(1).map( + _InputsWithStoppingSignals.insert_stopping_signal( + stop=True, batch_size=batch_size, add_padding=add_padding)) + else: + # We append (2 * num_invocations_per_step - 1) batches for exhausting the + # user_provided_dataset and stop properly. + # For example, if num_invocations_per_step is 2, we append 3 additional + # padding batches: b1, b2, b3. + # If user_provided_dataset contains two batches: a1, a2 + # Step 1: [a1, a2] + # Step 2: [b1, b2] -> STOP + # If user_provided_dataset contains three batches: a1, a2, a3. + # The training loops: + # Step 1: [a1, a2] + # Step 2: [a3, b1] + # Step 3: [b2, b3] -> STOP. + final_batch_dataset = dataset.take(1).map( + _InputsWithStoppingSignals.insert_stopping_signal( + stop=True, batch_size=batch_size, add_padding=add_padding)) + final_batch_dataset = final_batch_dataset.repeat( + 2 * num_invocations_per_step - 1) + + def _set_mask(data_dict): + signals = data_dict['signals'] + signals['padding_mask'] = array_ops.ones_like(signals['padding_mask']) + data_dict['signals'] = signals + return data_dict + + # Mask out the extra batch. + final_batch_dataset = final_batch_dataset.map(_set_mask) + + dataset = user_provided_dataset.concatenate(final_batch_dataset).prefetch(2) + + super(_InputsWithStoppingSignals, self).__init__(dataset=dataset) + self._current_inputs = None + + def features_and_labels(self): + if self._current_inputs is not None: + raise RuntimeError( + 'Internal Error: The previous inputs have not been properly ' + 'consumed. First call features_and_labels, then call signals.') + + inputs_with_signals = self._iterator.get_next() + features = inputs_with_signals['features'] + labels = inputs_with_signals.get('labels') + + self._current_inputs = inputs_with_signals + return features, labels + + def signals(self): + """Returns the `Signals` from `_Inputs`.""" + if self._current_inputs is None: + raise RuntimeError( + 'Internal Error: The current inputs have not been properly ' + 'generated. First call features_and_labels, then call signals.') + signals = self._current_inputs['signals'] + self._current_inputs = None + return signals + + @staticmethod + def insert_stopping_signal(stop, batch_size, add_padding=False): + """Inserts stopping_signal into dataset via _map_fn. + + Here we change the data structure in the dataset, such that the return value + is a dictionary now and `features`, `labels`, and `signals` are three + distinguished keys in that dict. This provides a better structure, which + eases the process to decompose the inputs (see `features_and_labels`). + + Args: + stop: bool, state of current stopping signals. + batch_size: int, batch size. + add_padding: bool, whether to pad the tensor to full batch size. + + Returns: + A map_fn passed to dataset.map API. + """ + + def _map_fn(*args): + """The map fn to insert signals.""" + if len(args) == 1: + # Unpack the single Tensor/dict argument as features. This is required + # for the input_fn returns no labels. + args = args[0] + features, labels = _Inputs._parse_inputs(args) + new_input_dict = {} + + if add_padding: + padding_mask, features, labels = ( + _PaddingSignals.pad_features_and_labels(features, labels, + batch_size)) + + new_input_dict['features'] = features + if labels is not None: + new_input_dict['labels'] = labels + + else: + new_input_dict['features'] = features + if labels is not None: + new_input_dict['labels'] = labels + padding_mask = None + + new_input_dict['signals'] = _StopSignals( + stop=stop, batch_size=batch_size, + padding_mask=padding_mask).as_dict() + + return new_input_dict + + return _map_fn + + +class _StopSignals(object): + """Signals class holding all logic to handle TPU stopping condition.""" + + NON_STOPPING_SIGNAL = False + STOPPING_SIGNAL = True + + def __init__(self, stop, batch_size, padding_mask=None): + self._stop = stop + self._batch_size = batch_size + self._padding_mask = padding_mask + + def as_dict(self): + """Returns the signals as Python dict.""" + shape = [self._batch_size, 1] + dtype = dtypes.bool + + if self._stop: + stopping = array_ops.ones(shape=shape, dtype=dtype) + else: + stopping = array_ops.zeros(shape=shape, dtype=dtype) + + signals = {'stopping': stopping} + if self._padding_mask is not None: + signals['padding_mask'] = self._padding_mask + return signals + + @staticmethod + def as_scalar_stopping_signal(signals): + return array_ops.identity(signals['stopping'][0][0]) + + @staticmethod + def should_stop(scalar_stopping_signal): + """Detects whether scalar_stopping_signal indicates stopping.""" + if isinstance(scalar_stopping_signal, ops.Tensor): + # STOPPING_SIGNAL is a constant True. Here, the logical_and is just the TF + # way to express the bool check whether scalar_stopping_signal is True. + return math_ops.logical_and(scalar_stopping_signal, + _StopSignals.STOPPING_SIGNAL) + else: + # For non Tensor case, it is used in SessionRunHook. So, we cannot modify + # the graph anymore. Here, we use pure Python. + return bool(scalar_stopping_signal) + + +class _PaddingSignals(object): + """Signals class holding all logic to handle padding.""" + + @staticmethod + def pad_features_and_labels(features, labels, batch_size): + """Pads out the batch dimension of features and labels.""" + real_batch_size = array_ops.shape( + _PaddingSignals._find_any_tensor(features))[0] + + batch_size_tensor = constant_op.constant(batch_size, dtypes.int32) + + check_greater = check_ops.assert_greater_equal( + batch_size_tensor, + real_batch_size, + data=(batch_size_tensor, real_batch_size), + message='The real batch size should not be greater than batch_size.') + + with ops.control_dependencies([check_greater]): + missing_count = batch_size_tensor - real_batch_size + + def pad_single_tensor(tensor): + """Pads out the batch dimension of a tensor to the complete batch_size.""" + rank = len(tensor.shape) + assert rank > 0 + padding = array_ops.stack([[0, missing_count]] + [[0, 0]] * (rank - 1)) + padded_shape = (batch_size,) + tuple(tensor.shape[1:]) + padded_tensor = array_ops.pad(tensor, padding) + padded_tensor.set_shape(padded_shape) + return padded_tensor + + def nest_pad(tensor_or_dict): + return nest.map_structure(pad_single_tensor, tensor_or_dict) + + features = nest_pad(features) + if labels is not None: + labels = nest_pad(labels) + + padding_mask = _PaddingSignals._padding_mask(real_batch_size, missing_count, + batch_size) + + return padding_mask, features, labels + + @staticmethod + def slice_tensor_or_dict(tensor_or_dict, signals): + """Slice the real Tensors according to padding mask in signals.""" + + padding_mask = signals['padding_mask'] + batch_size = array_ops.shape(padding_mask)[0] + + def verify_batch_size(tensor): + check_batch_size = math_ops.equal(batch_size, tensor.shape[0]) + with ops.control_dependencies([check_batch_size]): + return array_ops.identity(tensor) + + def slice_single_tensor(tensor): + rank = len(tensor.shape) + assert rank > 0 + real_batch_size = batch_size - math_ops.reduce_sum(padding_mask) + return verify_batch_size(tensor)[0:real_batch_size] + + # As we split the Tensors to all TPU cores and concat them back, it is + # important to ensure the real data is placed before padded ones, i.e., + # order is preserved. By that, the sliced padding mask should have all 0's. + # If this assertion failed, # the slice logic here would not hold. + sliced_padding_mask = slice_single_tensor(padding_mask) + assert_padding_mask = math_ops.equal( + math_ops.reduce_sum(sliced_padding_mask), 0) + + with ops.control_dependencies([assert_padding_mask]): + should_stop = _StopSignals.should_stop( + _StopSignals.as_scalar_stopping_signal(signals)) + + is_full_batch = math_ops.equal(math_ops.reduce_sum(padding_mask), 0) + + def slice_fn(tensor): + # If the current batch is full batch or part of stopping signals, we do + # not need to slice to save performance. + return control_flow_ops.cond( + math_ops.logical_or(should_stop, is_full_batch), + (lambda: verify_batch_size(tensor)), + (lambda: slice_single_tensor(tensor))) + + return nest.map_structure(slice_fn, tensor_or_dict) + + @staticmethod + def _find_any_tensor(batch_features): + tensors = [ + x for x in nest.flatten(batch_features) if isinstance(x, ops.Tensor) + ] + if not tensors: + raise ValueError('Cannot find any Tensor in features dict.') + return tensors[0] + + @staticmethod + def _padding_mask(real_batch_size, missing_count, batch_size): + padding_mask = array_ops.concat([ + array_ops.zeros((real_batch_size,), dtype=dtypes.int32), + array_ops.ones((missing_count,), dtype=dtypes.int32) + ], + axis=0) + padding_mask.set_shape((batch_size,)) + return padding_mask + + +def _verify_cross_hosts_transfer_size(tensor_dict, message): + total_size = 0 + tensor_structure = {} + for key, tensor in tensor_dict.items(): + shape = tensor.shape + size = np.product(shape) * tensor.dtype.size + tensor_structure[key] = shape + total_size += size + if total_size >= _ONE_GIGABYTE: + raise ValueError( + '{} The transfer size is larger than the protobuf limit. Please ' + 'consider to use Tensors with smaller shapes or reduce batch ' + 'size. Given:\n' + '{}'.format( + message, '\n'.join([ + ' -- Key: {}, Shape: {}'.format(k, v) + for k, v in tensor_structure.items() + ]))) + + +def _add_item_to_params(params, key, value): + """Adds a new item into `params`.""" + if hasattr(params, 'set_hparam'): + # For HParams, we need to use special API. + if key in params: + params.set_hparam(key, value) + else: + params.add_hparam(key, value) + else: + # Now params is Python dict. + params[key] = value + + +def export_estimator_savedmodel(estimator, + export_dir_base, + serving_input_receiver_fn, + assets_extra=None, + as_text=False, + checkpoint_path=None, + strip_default_attrs=False): + """Export `Estimator` trained model for TPU inference. + + Args: + estimator: `Estimator` with which model has been trained. + export_dir_base: A string containing a directory in which to create + timestamped subdirectories containing exported SavedModels. + serving_input_receiver_fn: A function that takes no argument and returns a + `ServingInputReceiver` or `TensorServingInputReceiver`. + assets_extra: A dict specifying how to populate the assets.extra directory + within the exported SavedModel, or `None` if no extra assets are needed. + as_text: whether to write the SavedModel proto in text format. + checkpoint_path: The checkpoint path to export. If `None` (the default), + the most recent checkpoint found within the model directory is chosen. + strip_default_attrs: Boolean. If `True`, default-valued attributes will be + removed from the NodeDefs. + + Returns: + The string path to the exported directory. + """ + # `TPUEstimator` requires `tpu_config.RunConfig`, so we cannot use + # `estimator.config`. + config = tpu_config.RunConfig(model_dir=estimator.model_dir) + est = TPUEstimator( + estimator._model_fn, # pylint: disable=protected-access + config=config, + params=estimator.params, + use_tpu=True, + train_batch_size=2048, # Does not matter. + eval_batch_size=2048, # Does not matter. + ) + return est.export_savedmodel(export_dir_base, serving_input_receiver_fn, + assets_extra, as_text, checkpoint_path, + strip_default_attrs) diff --git a/tensorflow/contrib/tpu/python/tpu/tpu_estimator_signals_test.py b/tensorflow/python/tpu/tpu_estimator_signals_test.py similarity index 99% rename from tensorflow/contrib/tpu/python/tpu/tpu_estimator_signals_test.py rename to tensorflow/python/tpu/tpu_estimator_signals_test.py index e3ea983abf..ca3eeaa9c9 100644 --- a/tensorflow/contrib/tpu/python/tpu/tpu_estimator_signals_test.py +++ b/tensorflow/python/tpu/tpu_estimator_signals_test.py @@ -20,12 +20,12 @@ from __future__ import print_function import numpy as np -from tensorflow.contrib.tpu.python.tpu import tpu_estimator from tensorflow.python.client import session from tensorflow.python.data.ops import dataset_ops from tensorflow.python.framework import errors from tensorflow.python.framework import ops from tensorflow.python.platform import test +from tensorflow.python.tpu import tpu_estimator def make_input_fn(num_samples): diff --git a/tensorflow/python/tpu/tpu_feed.py b/tensorflow/python/tpu/tpu_feed.py new file mode 100644 index 0000000000..de1adc80e6 --- /dev/null +++ b/tensorflow/python/tpu/tpu_feed.py @@ -0,0 +1,919 @@ +# 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. +# =================================================================== + +"""Helper library for handling infeed between hosts and TPUs. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import itertools + +import numpy as np +from six.moves import xrange # pylint: disable=redefined-builtin + +from tensorflow.compiler.xla.experimental.xla_sharding import xla_sharding +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import ops +from tensorflow.python.framework import tensor_shape +from tensorflow.python.ops import array_ops +from tensorflow.python.tpu import tpu +from tensorflow.python.tpu import tpu_sharding +from tensorflow.python.tpu.ops import tpu_ops + +from tensorflow.python.util import nest + + +def partition_or_replicate_on_host(tensor, dims): + """Partitions or replicates the input tensor. + + The ops inside this function are placed on the host side. + + Args: + tensor: The input tensor which will be partioned or replicated. + dims: A list of integer describes how to partition the input tensor. + + Returns: + An iterator of `Tensor`s or a list of partioned tensors. + """ + if dims is None: + return itertools.repeat(tensor) + dims = np.array(dims) + output = [tensor] + shape_list = np.array(tensor.shape.as_list()) + quotients, remainders = np.divmod(shape_list, dims) + for axis, (quotient, remainder, dim, original_size) in enumerate( + zip(quotients, remainders, dims, shape_list)): + if dim <= 1: + continue + if remainder > 0: + # For each dimension, when it cannot be evenly partitioned, XLA assumes + # tensors are partitioned in a greedy manner by using + # ceil_ratio(size/dim) first. E.g. 2D tensor with shape (5, 14) and dims + # are (2, 4). Since 5 % 2 = 1 and 14 % 4 = 2, [5, 14] => + # [[(3, 4), (3, 4), (2, 4), (2, 2)], + # [(2, 4), (2, 4), (2, 4), (2, 2)]] + ceil_ratio = quotient + 1 + num_full_slots, left_over = np.divmod(original_size, ceil_ratio) + num_or_size_splits = [ceil_ratio] * num_full_slots + [left_over] + if len(num_or_size_splits) < dim: + num_or_size_splits += [0] * (dim - len(num_or_size_splits)) + new_output = [] + for x in output: + new_output.append( + array_ops.split( + x, num_or_size_splits=num_or_size_splits, axis=axis)) + output = new_output + else: + output = [array_ops.split(x, dim, axis=axis) for x in output] + output = nest.flatten(output) + return output + + +def _tag_sharding_attribute_for_dequeued_tensor(tensor, dims): + """Tags appropriate XLA sharding attribute to the dequeued tensor. + + Args: + tensor: The dequeued tensor on TPU. + dims: A list of integer describes how the tensor is partitioned. + + Returns: + The same tensor with the xla_sharding attribute. + """ + if dims is None: + return xla_sharding.replicate(tensor) + elif np.prod(dims) == 1: + return xla_sharding.assign_device(tensor, 0) + else: + tile_assignment = np.arange(np.prod(dims)).reshape(dims) + return xla_sharding.tile(tensor=tensor, tile_assignment=tile_assignment) + + +def tag_sharding_attribute_for_dequeued_tensors(dequeues, dims): + """Tags appropriate XLA sharding attribute to the dequeued tensors. + + Args: + dequeues: A list of dequeued tensors on TPU. + dims: A list of integer describes how the tensor is partitioned. + + Returns: + The same dequeues with appropriate xla_sharding attribute. + """ + nest.assert_shallow_structure(dequeues, dims) + return nest.map_structure_up_to( + dequeues, _tag_sharding_attribute_for_dequeued_tensor, dequeues, dims) + + +class InfeedQueue(object): + """A helper object to build a device infeed queue. + + The InfeedQueue builds the host-side and device-side Ops to enqueue and + dequeue elements, respectively, and ensures that their types and + shapes match. + """ + + def __init__(self, + number_of_tuple_elements=None, + tuple_types=None, + tuple_shapes=None, + shard_dimensions=None, + name=None): + """Creates a new InfeedQueue with the given configuration. + + The configuration need not be fully specified at creation since it + can be modified subsequently by methods that set the values + explicitly or infer them from the shapes of inputs. + + Args: + number_of_tuple_elements: the number of Tensors fed atomically through the + queue, must be present unless it can be inferred from other arguments. + tuple_types: if not None, a list of types of the elements of the queue. + tuple_shapes: if not None, a list of shapes of the elements of the queue. + shard_dimensions: if not None, a list of dimensions on which the + elements of the queue should be sharded during automatic + parallelization. + name: the name of the queue. + + Raises: + ValueError: if number_of_tuple_elements <= 0; or + number_of_tuple_arguments, tuple_types, tuple_shapes, and + shard_dimensions are all None; or the length of tuple_types, + tuple_shapes, or shard_dimensions is not equal to + number_of_tuple_elements; or any element of shard_dimensions + can't be converted to a Dimension. + TypeError: if any element of tuple_types or tuple_shapes can't + be converted to a dtype or TensorShape, respectively. + """ + self._frozen = False + self._generated_enqueue_ops = False + self._generated_dequeue_op = False + self._name = "InfeedQueue" if name is None else name + if number_of_tuple_elements is None: + if tuple_types is not None: + number_of_tuple_elements = len(tuple_types) + elif tuple_shapes is not None: + number_of_tuple_elements = len(tuple_shapes) + elif shard_dimensions is not None: + number_of_tuple_elements = len(shard_dimensions) + else: + raise ValueError( + "number of tuple elements cannot be inferred from InfeedQueue " + "constructor") + if number_of_tuple_elements <= 0: + raise ValueError("number_of_tuple_elements %d must be > 0" % + number_of_tuple_elements) + # Make an empty sharding policy for each tuple element. + self._sharding_policies = [ + tpu_sharding.ShardingPolicy() + for _ in xrange(number_of_tuple_elements) + ] + if tuple_types is not None: + self.set_tuple_types(tuple_types) + else: + self._tuple_types = None + if tuple_shapes is not None: + self.set_tuple_shapes(tuple_shapes) + else: + self._tuple_shapes = None + if shard_dimensions is not None: + self.set_shard_dimensions(shard_dimensions) + self._validate() + + def _validate(self): + """Checks that the configuration is self-consistent. + + Raises: + ValueError: if the shapes and sharding policies don't match. + """ + if self.tuple_shapes is not None: + for (policy, shape) in zip(self._sharding_policies, self._tuple_shapes): + # Raise an error if the policy is incompatible with the shape. + _ = policy.get_sharded_shape(shape) + + @property + def number_of_tuple_elements(self): + """Returns the number of InfeedQueue tuple elements.""" + return len(self._sharding_policies) + + @property + def tuple_types(self): + """Returns the types of the InfeedQueue tuple elements.""" + return self._tuple_types + + def set_tuple_types(self, tuple_types): + """Sets the type of each element of the queue. + + tuple_types must be a list of length + self.number_of_tuple_elements, and each element must be + convertible to a dtype. + + Args: + tuple_types: the types of each queue element. + + Raises: + ValueError: if tuple_types is not of length + self.number_of_tuple_elements. + TypeError: if an element of tuple_types cannot be converted to a + dtype. + """ + if len(tuple_types) != self.number_of_tuple_elements: + raise ValueError("tuple_types is %s, but must be a list of length %d" % + (str(tuple_types), self.number_of_tuple_elements)) + if self._frozen: + for (frozen, updated) in zip(self._tuple_types, tuple_types): + if frozen != updated: + raise ValueError( + "Trying to update InfeedQueue with frozen configuration with an " + "incompatible type. Frozen types are %s, updated types are %s" % ( + str(self._tuple_types), str(tuple_types))) + else: + try: + self._tuple_types = [dtypes.as_dtype(t) for t in tuple_types] + except (TypeError) as e: + raise TypeError( + "tuple_types is %s, but must be a list of elements each " + "convertible to dtype: got error %s" % (str(tuple_types), str(e))) + + @property + def tuple_shapes(self): + """Returns the shapes of the InfeedQueue tuple elements.""" + return self._tuple_shapes + + def set_tuple_shapes(self, tuple_shapes): + """Sets the shape of each element of the queue. + + tuple_shapes must be a list of length + self.number_of_tuple_elements, and each element must be + convertible to a TensorShape. + + Args: + tuple_shapes: the shapes of each queue element. + + Raises: + ValueError: if tuple_shapes is not of length + self.number_of_tuple_elements. + TypeError: if an element of tuple_shapes cannot be converted to + a TensorShape. + """ + if len(tuple_shapes) != self.number_of_tuple_elements: + raise ValueError("tuple_shapes is %s, but must be a list of length %d" % + (str(tuple_shapes), self.number_of_tuple_elements)) + try: + tuple_shapes = [tensor_shape.as_shape(shape) for shape in tuple_shapes] + except (ValueError, TypeError) as e: + raise TypeError( + "tuple_shapes is %s, but must be a list of elements each " + "convertible to TensorShape: got error %s" % (str(tuple_shapes), + str(e))) + if self._frozen: + for (frozen, updated) in zip(self._tuple_shapes, tuple_shapes): + if frozen != updated: + raise ValueError( + "Trying to update InfeedQueue with frozen configuration with an " + "incompatible shape. Frozen shapes are %s, updated shapes are %s" + % (str(self._tuple_shapes), str(tuple_shapes))) + else: + self._tuple_shapes = tuple_shapes + self._validate() + + @property + def sharding_policies(self): + """Returns the sharding policies of the InfeedQueue tuple elements.""" + return self._sharding_policies + + @property + def shard_dimensions(self): + """Gets the shard dimension of each tuple element. + + Returns: + A list of length number_of_tuple_elements, where each list entry + is the shard dimension of that tuple element or None if the + shard dimension has not been set. + """ + # The number of shards is always the same for all the policies. + return [policy.shard_dimension for policy in self._sharding_policies] + + def set_shard_dimensions(self, shard_dimensions): + """Sets the shard_dimension of each element of the queue. + + shard_dimensions must be a list of length + self.number_of_tuple_elements, and each element must be + convertible to a Dimension compatible with self.tuple_shapes. + + Args: + shard_dimensions: the dimensions of each queue element. + + Raises: + ValueError: if shard_dimensions is not of length + self.number_of_tuple_elements; or an element of + shard_dimensions cannot be converted to a Dimension; or an + element of shard_dimensions is a Dimension that is out of + range for the corresponding tuple element shape. + """ + if len(shard_dimensions) != self.number_of_tuple_elements: + raise ValueError("shard_dimensions is %s, but must be a list of length %d" + % (str(shard_dimensions), + self.number_of_tuple_elements)) + for (policy, dimension) in zip(self._sharding_policies, shard_dimensions): + policy.set_shard_dimension(dimension) + self._validate() + + @property + def number_of_shards(self): + """Gets the number of shards to use for the InfeedQueue. + + Returns: + Number of shards or None if the number of shards has not been set. + """ + # The number of shards is always the same for all the policies. + return self._sharding_policies[0].number_of_shards + + def set_number_of_shards(self, number_of_shards): + """Sets the number of shards to use for the InfeedQueue. + + Args: + number_of_shards: number of ways to shard the InfeedQueue. + + Raises: + ValueError: if number_of_shards is not > 0; or the policies have + been frozen and number_of_shards was already set to something + else. + """ + for policy in self._sharding_policies: + policy.set_number_of_shards(number_of_shards) + self._validate() + + def set_configuration_from_input_tensors(self, input_tensors): + """Sets the shapes and types of the queue tuple elements. + + input_tensors is a list of Tensors whose types and shapes are used + to set the queue configuration. + + Args: + input_tensors: list of Tensors of the same types and shapes as + the desired queue Tuple. + + Raises: + ValueError: if input_tensors is not a list of length + self.number_of_tuple_elements + """ + if len(input_tensors) != self.number_of_tuple_elements: + raise ValueError("input_tensors is %s, but should be a list of %d Tensors" + % (str(input_tensors), self.number_of_tuple_elements)) + self.set_tuple_shapes([t.shape for t in input_tensors]) + self.set_tuple_types([t.dtype for t in input_tensors]) + + def set_configuration_from_sharded_input_tensors(self, input_tensors): + """Sets the shapes and types of the queue tuple elements. + + input_tensors is a list of lists of Tensors whose types and shapes are used + to set the queue configuration. The length of the outer list is the number + of shards required, and each inner list is the tuple of Tensors to use to + determine the types and shapes of the corresponding shard. This method + depends on the shard dimension, and calling it freezes the shard policy. + + Args: + input_tensors: list of lists of Tensors. The outer list length corresponds + to the desired number of shards, and each inner list is the size + and shape of the desired configuration of the corresponding shard. + + Raises: + ValueError: if any inner list is not a list of length + self.number_of_tuple_elements; or the inner lists do not combine to + form a consistent unsharded shape. + TypeError: if the types of the Tensors in the inner lists do not match. + """ + if not self._frozen: + # Unset the tuple shapes in case the configuration becomes + # transiently inconsistent. + self._tuple_shapes = None + number_of_shards = len(input_tensors) + self.set_number_of_shards(number_of_shards) + for t in input_tensors: + if len(t) != self.number_of_tuple_elements: + raise ValueError( + "input_tensors is %s but must be a list of lists, where each inner" + " list has length number_of_tuple_elements=%d" % ( + str(input_tensors), self.number_of_tuple_elements)) + # Transpose the inputs to make a list of shard shapes for each tuple + # element. + sharded_shapes = [[t[i].shape for t in input_tensors] + for i in xrange(self.number_of_tuple_elements)] + # For each tuple, get the unsharded shape using that tuple's policy. + unsharded_shapes = [ + policy.get_unsharded_shape(s) + for (policy, s) in zip(self._sharding_policies, sharded_shapes) + ] + self.set_tuple_shapes(unsharded_shapes) + for i in xrange(1, self.number_of_shards): + for (t1, t2) in zip(input_tensors[0], input_tensors[i]): + if t1.dtype != t2.dtype: + raise TypeError( + "types of the tuple elements of input_tensors %s are not " + "consistent" % str(input_tensors)) + self.set_tuple_types([t.dtype for t in input_tensors[0]]) + + def freeze(self): + """Freezes the InfeedQueue so it can no longer be modified. + + The configuration is implicitly frozen before any host-side or + device-side Ops are generated. The configuration cannot be frozen + until the types and shapes of the tuple elements have been set. + + Raises: + ValueError: if the types or shapes of the tuple elements have not been + set. + """ + self._frozen = True + if self._tuple_types is None: + raise ValueError( + "Can't freeze an InfeedQueue without setting all tuple types.") + if self._tuple_shapes is None: + raise ValueError( + "Can't freeze an InfeedQueue without setting all tuple shapes.") + for shape in self._tuple_shapes: + if shape.dims is None: + raise ValueError( + "Can't freeze an InfeedQueue without setting all tuple shapes.") + for policy in self._sharding_policies: + policy.freeze() + self._validate() + + 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. + + Raises: + ValueError: if the types or shapes of the tuple elements have not been + set; or if a dequeue op has already been generated. + """ + self.freeze() + if self._generated_dequeue_op: + raise ValueError("Can't generate two dequeue Ops from the same queue") + self._generated_dequeue_op = True + full_name = "%s/dequeue" % self._name + sharded_shapes = [ + policy.get_sharded_shape(shape) + for (shape, policy) in zip(self._tuple_shapes, self._sharding_policies) + ] + 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, + name_prefix, + index, + device=None, + tpu_ordinal=-1): + """Generate a host-side Op to enqueue a tuple to the queue. + + If device is None the inputs are all required to have the same + device specification, and the enqueue Op is colocated with + inputs[0]. Otherwise the enqueue Op is placed on 'device'. + + Args: + inputs: a list of Tensors with the types and shapes of the tuple elements. + name_prefix: the base name for the Op. + index: the shard index, used to uniquify the Op name. + device: device to place the Op on, or None if it should be + colocated with the inputs. + tpu_ordinal: ordinal of the TPU device on the host to use for + infeed if device is a CPU device. Should be set to -1 if device + is a TPU device. + + Returns: + An Op corresponding to a shard of infeed enqueued at the host, + suitable for use within a replicated block. + + Raises: + ValueError: if device is None and inputs do not all have the + same device specification. + """ + full_name = "%s/%d" % (name_prefix, index) + shapes = [t.shape for t in inputs] + if device is None: + devices = [t.device for t in inputs] + for i in xrange(1, self.number_of_tuple_elements): + if devices[0] != devices[i]: + raise ValueError( + "input devices for shard %d are %s, but should all be the same" % + (index, str(devices))) + with ops.colocate_with(inputs[0]): + return tpu_ops.infeed_enqueue_tuple( + inputs=inputs, + shapes=shapes, + name=full_name, + device_ordinal=tpu_ordinal) + else: + with ops.device(device): + return tpu_ops.infeed_enqueue_tuple( + inputs=inputs, + shapes=shapes, + name=full_name, + device_ordinal=tpu_ordinal) + + def generate_enqueue_ops(self, + sharded_inputs, + tpu_ordinal_function=None, + placement_function=None): + """Generates the host-side Ops to enqueue the shards of a tuple. + + sharded_inputs is a list, one for each shard, of lists of + Tensors. sharded_inputs[0] is the tuple of Tensors to use to feed + shard 0 if the queue. Returns the host-side Ops that must be run to + enqueue the sharded tuple. The Op for shard i is colocated with the inputs + for shard i. + + Implicitly freezes the queue configuration if it is not already + frozen. If the configuration has already been frozen, and is not + compatible with the types and shapes of sharded_inputs, an error + will be raised. + + Args: + sharded_inputs: a list of lists of Tensors. The length of the outer list + determines the number of shards. Each inner list indicates the types + and shapes of the tuples in the corresponding shard. + tpu_ordinal_function: if not None, a function that takes the + shard index as input and returns the ordinal of the TPU device + the shard's infeed should be placed on. tpu_ordinal_function must be + set if the inputs are placed on CPU devices. + placement_function: if not None, a function that takes the shard index as + input and returns the host device where the enqueue op should be placed + on. + + Returns: + A list of host-side Ops, one for each shard, that when executed together + will enqueue a full-size element of infeed. + + Raises: + ValueError: if the queue configuration has previously been frozen and the + shapes of the elements of sharded_inputs are not compatible with the + frozen configuration; or if the shapes of the elements of sharded_inputs + don't form a consistent unsharded tuple; or if the elements of a tuple + have different device constraints. + TypeError: if the queue configuration has previously been frozen and the + types of the elements of sharded_inputs are not compatible with the + frozen configuration; or if the types of the elements of sharded_inputs + don't form a consistent unsharded tuple. + """ + self.set_configuration_from_sharded_input_tensors(sharded_inputs) + self.freeze() + if self._generated_enqueue_ops: + raise ValueError("Can't generate two enqueue Ops from the same queue") + self._generated_enqueue_ops = True + if tpu_ordinal_function is None: + tpu_ordinal_function = lambda index: -1 + name_prefix = "%s/enqueue" % self._name + return [ + self._generate_enqueue_op( + shard, + name_prefix, + index, + tpu_ordinal=tpu_ordinal_function(index), + device=placement_function(index) if placement_function else None) + for (shard, index) in zip(sharded_inputs, xrange(self.number_of_shards)) + ] + + # TODO(misard) Generalize this to the case of systems that don't + # have 8 devices per host, and figure out what to do with + # model-parallelism. + def _default_placement_function(self, index): + return "/task:%d/device:CPU:0" % (index / 8) + + def _default_ordinal_function(self, index): + return index % 8 + + # TODO(b/36470756) remove this from tutorials once we have a better story + # for automatic placement of input pipelines. + def split_inputs_and_generate_enqueue_ops(self, + inputs, + device_assignment=None, + placement_function=None, + tpu_ordinal_function=None): + """POORLY-PERFORMING ON MULTI-HOST SYSTEMS. + + Generates the host-side Ops to enqueue a tuple. + + This method performs poorly because it takes an entire input on a single + host, splits it, and distributes it to all of the cores. It is present only + to simplify tutorial examples. + + inputs is a list of Tensors to use to feed the queue. Each input is split + into self.number_of_shards shards. Returns an Op for each shard to enqueue + the shard. The Op for shard i is placed on device placement_function(i). + + Implicitly freezes the queue configuration if it is not already + frozen. If the configuration has already been frozen, and is not + compatible with the types and shapes of inputs, an error + will be raised. + + Args: + inputs: a list of Tensors which indicates the types and shapes of the + queue tuple. + device_assignment: if not `None`, a TPU `DeviceAssignment`. If + device_assignment is not `None`, but `placement_function` and + `ordinal_function` are None, then `device_assignment` will be used to + place infeeds on the first k TPU shards, where k is the number of shards + in the queue. If all three are `None`, then default placement and + ordinal functions are used. + placement_function: if not None, a function that takes the shard + index as input and returns a device string indicating which + device the shard's infeed should be placed on. If placement_function + and tpu_ordinal_function are None, inputs are sharded round-robin + across the devices in the system. + tpu_ordinal_function: if not None, a function that takes the + shard index as input and returns the ordinal of the TPU device + the shard's infeed should be placed on. If placement_function + and tpu_ordinal_function are None, inputs are sharded round-robin + across the devices in the system. + + Returns: + A list of host-side Ops, one for each shard, that when executed together + will enqueue a full-size element of infeed. + + Raises: + ValueError: if the queue configuration has previously been frozen and the + shapes of the elements of inputs are not compatible with the frozen + configuration. + TypeError: if the queue configuration has previously been frozen and the + types of the elements of inputs are not compatible with the frozen + configuration. + """ + if device_assignment is None: + if placement_function is None: + placement_function = self._default_placement_function + if tpu_ordinal_function is None: + tpu_ordinal_function = self._default_ordinal_function + else: + + def _placement_function_from_map(index): + return device_assignment.host_device(replica=index) + + def _ordinal_function_from_map(index): + return device_assignment.tpu_ordinal(replica=index) + + if placement_function is None: + placement_function = _placement_function_from_map + if tpu_ordinal_function is None: + tpu_ordinal_function = _ordinal_function_from_map + self.set_configuration_from_input_tensors(inputs) + self.freeze() + if self._generated_enqueue_ops: + raise ValueError("Can't generate two enqueue Ops from the same queue") + self._generated_enqueue_ops = True + split_name_prefix = "%s/split" % self._name + if self.number_of_shards == 1: + transposed_sharded_inputs = [[inp] for inp in inputs] + else: + + def split_fn(inp, num_shards, axis, name): + with ops.colocate_with(inp): + return array_ops.split(inp, num_shards, axis=axis, name=name) + + transposed_sharded_inputs = [ + split_fn( + inp, + self.number_of_shards, + axis=policy.shard_dimension, + name="%s/%d" % (split_name_prefix, index)) + for (inp, policy, index) in zip(inputs, self._sharding_policies, + xrange(self.number_of_tuple_elements)) + ] + sharded_inputs = [[shard[i] for shard in transposed_sharded_inputs] + for i in xrange(self.number_of_shards)] + name_prefix = "%s/enqueue" % self._name + return [ + self._generate_enqueue_op( + shard, + name_prefix, + index, + device=placement_function(index), + tpu_ordinal=tpu_ordinal_function(index)) + for (shard, index) in zip(sharded_inputs, xrange(self.number_of_shards)) + ] + + +class _PartitionedInfeedQueue(InfeedQueue): + """A helper object to build a device infeed queue with input partition. + + Args: + number_of_tuple_elements: the number of Tensors fed atomically through the + queue, must be present unless it can be inferred from other arguments. + device_assignment: A TPU `DeviceAssignment` which is used to place all the + partitions to different TPU infeed queues. + host_id: The id of the host machine. + input_partition_dims: A nested list/tuple of integers. Each inner + list/tuple describes how to partition the corresponding input tensor. + tuple_types: If not None, a list of types of the elements of the queue. + tuple_shapes: If not None, a list of shapes of the elements of the queue. + name: The name of the queue. + """ + + def __init__(self, + number_of_tuple_elements, + device_assignment, + host_id, + input_partition_dims=None, + tuple_types=None, + tuple_shapes=None, + name=None): + super(_PartitionedInfeedQueue, self).__init__( + number_of_tuple_elements=number_of_tuple_elements, + tuple_types=tuple_types, + tuple_shapes=None, + shard_dimensions=None, + name="PartitionedInfeedQueue" if name is None else name) + self._input_partition_dims = input_partition_dims + self._host_id = host_id + self._device_assignment = device_assignment + + def generate_dequeue_op(self, tpu_device=0): + """Generate TPU dequeue ops. + + Args: + tpu_device: The TPU device ordinal where the infeed instruction should be + placed. + + Returns: + A list of Outputs corresponding to a partition of infeed dequeued + into XLA, suitable for use within a replicated block. + + Raises: + ValueError: if the types or shapes of the tuple elements have not been + set; or if a dequeue op has already been generated. + """ + self.freeze() + if self._generated_dequeue_op: + raise ValueError("Can't generate two dequeue Ops from the same queue") + self._generated_dequeue_op = True + full_name = "%s/dequeue" % self._name + sharded_shapes = [ + policy.get_sharded_shape(shape) + for (shape, policy) in zip(self._tuple_shapes, self._sharding_policies) + ] + with ops.device(tpu.core(tpu_device)): + values = tpu_ops.infeed_dequeue_tuple( + dtypes=self._tuple_types, shapes=sharded_shapes, name=full_name) + return tag_sharding_attribute_for_dequeued_tensors( + values, self._input_partition_dims) + + def generate_enqueue_ops(self, per_host_sharded_inputs): + """Generates the host-side Ops to enqueue the partitioned inputs. + + per_host_sharded_inputs is a list, one for each replica, of lists of + Tensors. sharded_inputs[i] is the tuple of Tensors to use to feed + replica i. + sharded_inputs[i][j] is partitioned by self._input_partition_dims[j]. + + For example, if sharded_inputs[i][j] is a 2-D Tensor: + [[A, B, C, D], + [E ,F, G, H]] + self._input_partition_dims[j] is [2, 4]. + + sharded_inputs[i][j] will be partitioned and flattened into: + [A, B, C, D, E, F, G, H] and fed into the logical core ids: + [0, 1, 2, 3, 4, 5, 6, 7] respectively. + + Args: + per_host_sharded_inputs: a list of lists of Tensors. The length of the + outer list determines the number of shards. Each inner list indicates + the types and shapes of the tuples in the corresponding shard. + + Returns: + A list of host-side Ops, one for each shard, that when executed together + will enqueue a full-size element of infeed. + + Raises: + ValueError: if the queue configuration has previously been frozen and the + shapes of the elements of sharded_inputs are not compatible with the + frozen configuration; or if the shapes of the elements of sharded_inputs + don't form a consistent unsharded tuple; or if the elements of a tuple + have different device constraints; or if the partition dims are invalid. + TypeError: if the queue configuration has previously been frozen and the + types of the elements of sharded_inputs are not compatible with the + frozen configuration; or if the types of the elements of sharded_inputs + don't form a consistent unsharded tuple. + """ + self.set_configuration_from_sharded_input_tensors(per_host_sharded_inputs) + number_of_replicas_per_host = len(per_host_sharded_inputs) + number_of_tuple_elements = len(per_host_sharded_inputs[0]) + + assert len(self._input_partition_dims) == number_of_tuple_elements + per_host_enqueue_ops = [] + + for replica_index in range(number_of_replicas_per_host): + flattened_inputs = per_host_sharded_inputs[replica_index] + inputs_part_dims_flat = nest.flatten_up_to(flattened_inputs, + self._input_partition_dims) + inputs_parted_iters = [ + iter(self._check_dims_and_partition_or_replicate_on_host(x, dims)) + for x, dims in zip(per_host_sharded_inputs[replica_index], + inputs_part_dims_flat) + ] + + for logical_core in xrange(self._device_assignment.num_cores_per_replica): + # Places different partitions to different logic cores. + replica_id = self._device_assignment.lookup_replicas( + self._host_id, logical_core)[replica_index] + ordinal = self._device_assignment.tpu_ordinal( + replica=replica_id, logical_core=logical_core) + infeed_inputs = [] + for it in inputs_parted_iters: + input_for_device = next(it, None) + if input_for_device is not None: + infeed_inputs.append(input_for_device) + + if infeed_inputs: + per_host_enqueue_ops.append( + tpu_ops.infeed_enqueue_tuple( + inputs=infeed_inputs, + shapes=[x.shape for x in infeed_inputs], + name="enqueue/replica_{0}/input_{1}".format( + replica_index, logical_core), + device_ordinal=ordinal)) + return per_host_enqueue_ops + + def _check_input_partition_dims(self, tensor, dims): + """Checks that input partition dims are valid for the `Tensor`. + + Args: + tensor: Input tensor for partitioning. + dims: A list of integer describes how to partition the input tensor. + + Raises: + ValueError: If the tensor can't be partitioned by dims or the + num_cores_per_replica doesn't match the number of + partitions(dims.prod()). + """ + # No partitioning specified, so don't perform further checks. + if dims is None: + return + + dims = np.array(dims) + + if (dims < 1).any(): + raise ValueError("All input partition dims must be >= 1.") + + # No partitioning, so don't perform further checks. + if dims.prod() == 1: + return + + if dims.prod() != self._device_assignment.num_cores_per_replica: + raise ValueError( + "The product of each input parition dim should equal to " + "num_cores_per_replica. (dim = {}, num_cores_per_replica " + "= {})".format(dims, self._device_assignment.num_cores_per_replica)) + if dims.shape[0] != tensor.shape.ndims: + raise ValueError( + "Input partition dims must have the same number of dimensions " + "as the `Tensor` to be partitioned. (tensor shape = {}, input " + "partition dims = {}).".format(tensor.shape.as_list(), dims)) + + tensor.shape.assert_is_fully_defined() + + def _check_dims_and_partition_or_replicate_on_host(self, tensor, dims): + """Checks dims and partitions or replicates the input tensor. + + The ops inside this function are placed on the host side. + + Args: + tensor: The input tensor which will be partioned or replicated. + dims: A list of integer describes how to partition the input tensor. + + Returns: + An iterator of `Tensor`s or a list of partioned tensors. + """ + self._check_input_partition_dims(tensor, dims) + return partition_or_replicate_on_host(tensor, dims) diff --git a/tensorflow/python/tpu/tpu_function.py b/tensorflow/python/tpu/tpu_function.py new file mode 100644 index 0000000000..422c7d3b26 --- /dev/null +++ b/tensorflow/python/tpu/tpu_function.py @@ -0,0 +1,66 @@ +# 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. +# ============================================================================= + +"""Helper library for functions used during TPU compilation.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import contextlib + + +class TpuContext(object): + """A context object holding state about the TPU computation being built.""" + + def __init__(self): + """Creates a new TpuContext.""" + self._number_of_shards = None + + @property + def number_of_shards(self): + return self._number_of_shards + + def set_number_of_shards(self, number_of_shards): + self._number_of_shards = number_of_shards + + +# The Tpu context holds the number of shards when a sharded computation is +# being built, or None if no computation is being built. +_current_tpu_context = TpuContext() + + +@contextlib.contextmanager +def tpu_shard_context(number_of_shards): + if _current_tpu_context.number_of_shards is not None: + raise NotImplementedError("tpu_shard_context cannot be nested.") + try: + _current_tpu_context.set_number_of_shards(number_of_shards) + yield + finally: + _current_tpu_context.set_number_of_shards(None) + + +def get_tpu_context(): + return _current_tpu_context + + +# Decorator function for tpu computation func that was passed to tpu.rewrite() +# if there is an embedded training loop in this func, trace tools will generate +# step markers for each iteration. +def on_device_training_loop(func): + # Value for this attribute is from xla.DebugOptions.StepMarkerLocation. + setattr(func, "step_marker_location", "STEP_MARK_AT_TOP_LEVEL_WHILE_LOOP") + return func diff --git a/tensorflow/contrib/tpu/python/tpu/tpu_infeed_test.py b/tensorflow/python/tpu/tpu_infeed_test.py similarity index 99% rename from tensorflow/contrib/tpu/python/tpu/tpu_infeed_test.py rename to tensorflow/python/tpu/tpu_infeed_test.py index a41ff60d0a..3e90979157 100644 --- a/tensorflow/contrib/tpu/python/tpu/tpu_infeed_test.py +++ b/tensorflow/python/tpu/tpu_infeed_test.py @@ -19,11 +19,11 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib.tpu.python.tpu import tpu_feed from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes from tensorflow.python.platform import test +from tensorflow.python.tpu import tpu_feed class InfeedTest(test.TestCase): diff --git a/tensorflow/python/tpu/tpu_optimizer.py b/tensorflow/python/tpu/tpu_optimizer.py new file mode 100644 index 0000000000..22c409eaa1 --- /dev/null +++ b/tensorflow/python/tpu/tpu_optimizer.py @@ -0,0 +1,203 @@ +# 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. +# ============================================================================= + +"""Optimizer that implements cross-shard gradient reduction for TPU.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + + +from tensorflow.python.framework import ops +from tensorflow.python.ops.losses import losses +from tensorflow.python.platform import tf_logging as logging +from tensorflow.python.tpu import tpu_function +from tensorflow.python.tpu.ops import tpu_ops +from tensorflow.python.training import optimizer + + +class CrossShardOptimizer(optimizer.Optimizer): + """An optimizer that averages gradients across TPU shards.""" + + def __init__(self, + opt, + reduction=losses.Reduction.MEAN, + name="CrossShardOptimizer", + group_assignment=None): + """Construct a new cross-shard optimizer. + + Args: + opt: An existing `Optimizer` to encapsulate. + reduction: The reduction to apply to the shard losses. + name: Optional name prefix for the operations created when applying + gradients. Defaults to "CrossShardOptimizer". + group_assignment: Optional 2d int32 lists with shape + [num_groups, num_replicas_per_group] which describles how to apply + optimizer to subgroups. + + Raises: + ValueError: If reduction is not a valid cross-shard reduction. + """ + if reduction not in (losses.Reduction.SUM, losses.Reduction.MEAN): + raise ValueError("Unsupported reduction: %s." % reduction) + + super(CrossShardOptimizer, self).__init__(False, name) + self._opt = opt + self._reduction = reduction + self._group_assignment = group_assignment + + def _verify_and_get_subgroup_size(self, group_assignment, num_shards): + """Verify group_assignment and get the subgroup size". + + Args: + group_assignment: list of group ids for applying the optimizer + to subgroups. + num_shards: The number of TPU shards. + + Returns: + The size of one subgroup in group_assignment. + + Raises: + ValueError: If group_assignment is invalid. + """ + if not group_assignment: + return None + if not (isinstance(group_assignment, list) and + all(isinstance(i, list) for i in group_assignment)): + raise ValueError("group_assignment must be a list of list. Got {}".format( + group_assignment)) + + replica_ids = set() + for g in group_assignment: + for i in g: + replica_ids.add(i) + + if set(range(num_shards)) != replica_ids: + raise ValueError("group_assignment must be a permutation of range({0})." + " Got group_assignment={1}".format( + num_shards, group_assignment)) + + subgroup_size_list = [len(group) for group in group_assignment] + if all(subgroup_size_list[0] == size for size in subgroup_size_list): + return subgroup_size_list[0] + else: + raise ValueError("The size of each subgroup in group_assignment must " + "be equal. Got group_assignment={}".format( + self._group_assignment)) + + def compute_gradients(self, loss, var_list=None, **kwargs): + """Compute gradients of "loss" for the variables in "var_list". + + This simply wraps the compute_gradients() from the real optimizer. The + gradients will be aggregated in the apply_gradients() so that user can + modify the gradients like clipping with per replica global norm if needed. + The global norm with aggregated gradients can be bad as one replica's huge + gradients can hurt the gradients from other replicas. + + Args: + loss: A Tensor containing the value to minimize. + 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 `GraphKey.TRAINABLE_VARIABLES`. + **kwargs: Keyword arguments for compute_gradients(). + + Returns: + A list of (gradient, variable) pairs. + + Raises: + ValueError: If not within a tpu_shard_context or group_assignment is + invalid. + """ + num_shards = tpu_function.get_tpu_context().number_of_shards + if num_shards is None: + logging.warning( + "CrossShardOptimizer should be used within a tpu_shard_context, but " + "got unset number_of_shards. Assuming 1.") + num_shards = 1 + + subgroup_size = self._verify_and_get_subgroup_size(self._group_assignment, + num_shards) + + if num_shards > 1 and self._reduction == losses.Reduction.MEAN: + if self._group_assignment: + scale = 1.0 / subgroup_size + else: + scale = 1.0 / num_shards + loss *= scale + + return self._opt.compute_gradients(loss, var_list=var_list, **kwargs) + + def apply_gradients(self, grads_and_vars, global_step=None, name=None): + """Apply gradients to variables. + + Calls tpu_ops.cross_replica_sum() to sum gradient contributions across + replicas, and then applies the real optimizer. + + 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 gradients. If `global_step` was not None, + that operation also increments `global_step`. + + Raises: + ValueError: If the grads_and_vars is malformed. + """ + summed_grads_and_vars = [] + for (grad, var) in grads_and_vars: + if grad is None: + summed_grads_and_vars.append((grad, var)) + else: + with ops.colocate_with(grad): + summed_grads_and_vars.append((tpu_ops.cross_replica_sum( + grad, self._group_assignment), var)) + return self._opt.apply_gradients(summed_grads_and_vars, global_step, name) + + def get_slot(self, *args, **kwargs): + """Return a slot named "name" created for "var" by the Optimizer. + + This simply wraps the get_slot() from the actual optimizer. + + Args: + *args: Arguments for get_slot(). + **kwargs: Keyword arguments for get_slot(). + + Returns: + The `Variable` for the slot if it was created, `None` otherwise. + """ + return self._opt.get_slot(*args, **kwargs) + + def get_slot_names(self, *args, **kwargs): + """Return a list of the names of slots created by the `Optimizer`. + + This simply wraps the get_slot_names() from the actual optimizer. + + Args: + *args: Arguments for get_slot(). + **kwargs: Keyword arguments for get_slot(). + + Returns: + A list of strings. + """ + return self._opt.get_slot_names(*args, **kwargs) + + def variables(self): + """Forwarding the variables from the underlying optimizer.""" + return self._opt.variables() diff --git a/tensorflow/python/tpu/tpu_sharding.py b/tensorflow/python/tpu/tpu_sharding.py new file mode 100644 index 0000000000..f5af03f33c --- /dev/null +++ b/tensorflow/python/tpu/tpu_sharding.py @@ -0,0 +1,253 @@ +# 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. +# ============================================================================= + +"""Helper library for sharding during TPU compilation.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from six.moves import xrange # pylint: disable=redefined-builtin + +from tensorflow.python.framework import tensor_shape + +_DEFAULT_NUMBER_OF_SHARDS = 1 +_DEFAULT_SHARD_DIMENSION = 0 + + +# TODO(b/36777903) change other parts of tpu.py to use this class. +class ShardingPolicy(object): + """An object use to hold the sharding policy for a Tensor. + """ + + def __init__(self): + self._number_of_shards = None + self._shard_dimension = None + self._frozen = False + + def __str__(self): + if self.number_of_shards is None or self.shard_dimension is None: + return "ShardingPolicy(unset)" + else: + return ("ShardingPolicy(%d shards dimension %d)" % + (self.number_of_shards, self.shard_dimension)) + + def _fill_default_values(self): + if self._number_of_shards is None: + self._number_of_shards = _DEFAULT_NUMBER_OF_SHARDS + if self._shard_dimension is None: + self._shard_dimension = tensor_shape.as_dimension( + _DEFAULT_SHARD_DIMENSION) + + def freeze(self): + """Prevents further modification to the sharding policy. + + Any values that have not been set when freeze is called are set to + defaults. If the ShardingPolicy is already frozen, this is a NoOp. + """ + if not self._frozen: + self._fill_default_values() + self._frozen = True + + @property + def number_of_shards(self): + """Returns the number of shards in the policy or None if unspecified.""" + return self._number_of_shards + + def set_number_of_shards(self, number_of_shards): + """Sets the number of shards for the current policy. + + If the policy has been frozen then number_of_shards must match the + existing setting. + + Args: + number_of_shards: The number of shards to use in the policy. + + Raises: + ValueError: If the policy has been frozen and number_of_shards + differs from the frozen value; or number_of_shards <= 0. + """ + if self._frozen: + if self._number_of_shards != number_of_shards: + raise ValueError( + "Can't set sharding policy to use %d shards since it has been " + "frozen to use %d." % (number_of_shards, self._number_of_shards)) + else: + if number_of_shards > 0: + self._number_of_shards = number_of_shards + else: + raise ValueError( + "Can't set sharding policy to use %s shards; value must be >0", + str(number_of_shards)) + + @property + def shard_dimension(self): + """Returns the shard dimension of the policy or None if unspecified.""" + return self._shard_dimension + + def set_shard_dimension(self, shard_dimension): + """Sets the shard dimension for the current policy. + + If the policy has been frozen then shard_dimension must match the + existing setting. + + Args: + shard_dimension: The shard dimension to use in the policy. + + Raises: + ValueError: If the policy has been frozen and shard_dimension + differs from the frozen value, or shard_dimension can't be + interpreted as a Dimension. + """ + if self._frozen: + if self._shard_dimension != shard_dimension: + raise ValueError( + "Can't set shard dimension to %d since it has been frozen to " + "use %d." % (shard_dimension, self._shard_dimension)) + else: + self._shard_dimension = tensor_shape.as_dimension(shard_dimension) + + def merge(self, other): + """Merges the policy of another policy into the current policy. + + Args: + other: The policy to merge into this one. + + Raises: + ValueError: If this policy has been frozen and the merge conflicts with + the frozen policy. + """ + if other.number_of_shards is not None: + self.set_number_of_shards(other.number_of_shards) + if other.shard_dimension is not None: + self.set_shard_dimension(other.shard_dimension) + + def get_sharded_shape(self, shape, shard_index=None): + """Returns the shape of a shard of a full Tensor. + + When given the shape of a 'full-size' Tensor, returns the shape of + the sub-Tensor after it has been sharded. Freezes the policy if it + has not yet been frozen. + + Args: + shape: The shape of the full-size Tensor to be sharded. + shard_index: The index of the shard whose shape should be returned. + shard_index can be None for sharding policies that use the same + shape for every shard. + freeze_config: + + Returns: + The shape of the sharded version of the Tensor. + + Raises: + ValueError: If shard_index is None when shards are of different + shapes; or shard_index is not None and + !(0<=shard_index= self.number_of_shards: + raise ValueError("shard_index %d, but must be in [0,%d)." % + (shard_index, self._number_of_shards)) + shape = tensor_shape.as_shape(shape) + if self._number_of_shards == 1: + # Don't do anything when there's only one shard. + return shape + ndims = shape.ndims + if ndims is None: + raise ValueError("shape must be a specified shape not Unknown") + if ndims <= self._shard_dimension: + raise ValueError("shape %s does not contain shard_dimension %d" % + (shape.as_list(), self._shard_dimension)) + dims = shape.as_list() + if dims[self._shard_dimension] is None: + raise ValueError("shape %s must have a fixed size for dimension %d " + "that is known at graph construction time." % + (shape.as_list(), self._shard_dimension)) + if (dims[self._shard_dimension] % self._number_of_shards) != 0: + raise ValueError("shape %s cannot be sharded %d ways along dimension %d" % + (shape.as_list(), self._number_of_shards, + self._shard_dimension)) + dims[self._shard_dimension] /= self._number_of_shards + return tensor_shape.as_shape(dims) + + def _unshard_shape(self, shape): + """Return the unsharded shape that would generate a given sharded shape. + + Args: + shape: the sharded shape to unshard + + Returns: + The unsharded shape. + + Raises: + ValueError: if shape is unknown or does not contain + self.shard_dimension + TypeError: if shape is not convertible to a TensorShape + """ + shape = tensor_shape.as_shape(shape) + if self._number_of_shards == 1: + # Don't do anything when there's only one shard. + return shape + ndims = shape.ndims + if ndims is None: + raise ValueError("shape must be a specified shape not Unknown") + if ndims <= self._shard_dimension: + raise ValueError("shape %s does not contain shard_dimension %d" % + (shape.as_list(), self._shard_dimension)) + dims = shape.as_list() + dims[self._shard_dimension] *= self._number_of_shards + return tensor_shape.as_shape(dims) + + def get_unsharded_shape(self, shapes): + """Returns the shape of an unsharded Tensor given a list of shards. + + When given a list of shapes of shards, returns the shape of the + unsharded Tensor that would generate the shards. Sets defaults for the + policy if number_of_shards or shard_dimension is None. + + Args: + shapes: The shapes of the Tensor shards to be combined. + + Returns: + The shape of the unsharded version of the Tensor. + + Raises: + ValueError: if shapes is not a list of length + self.number_of_shards; or any element of shapes is not a valid + shape consistent with the sharding policy; or the list of + shapes is not a valid sharding of a full shape. + TypeError: if an element of shapes is not convertible to a + TensorShape + """ + self._fill_default_values() + if len(shapes) != self.number_of_shards: + raise ValueError( + "shapes is %s but must be a list of length number_of_shards=%d" % ( + str(shapes), self.number_of_shards)) + unsharded_shapes = [self._unshard_shape(s) for s in shapes] + for i in xrange(self.number_of_shards - 1): + if not unsharded_shapes[i].is_compatible_with( + unsharded_shapes[self.number_of_shards - 1]): + raise ValueError( + "sharded shapes %s are not consistent shards of a full shape " + "sharded %d ways along dimension %d" % ( + str(shapes), self.number_of_shards, self.shard_dimension)) + return unsharded_shapes[0] diff --git a/tensorflow/contrib/tpu/python/tpu/tpu_sharding_test.py b/tensorflow/python/tpu/tpu_sharding_test.py similarity index 98% rename from tensorflow/contrib/tpu/python/tpu/tpu_sharding_test.py rename to tensorflow/python/tpu/tpu_sharding_test.py index b0a5511d2d..21d2a0897a 100644 --- a/tensorflow/contrib/tpu/python/tpu/tpu_sharding_test.py +++ b/tensorflow/python/tpu/tpu_sharding_test.py @@ -19,10 +19,10 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib.tpu.python.tpu import tpu_sharding from tensorflow.python.framework import tensor_shape from tensorflow.python.platform import test +from tensorflow.python.tpu import tpu_sharding class ShardingTest(test.TestCase): diff --git a/tensorflow/python/tpu/tpu_system_metadata.py b/tensorflow/python/tpu/tpu_system_metadata.py new file mode 100644 index 0000000000..866895922a --- /dev/null +++ b/tensorflow/python/tpu/tpu_system_metadata.py @@ -0,0 +1,156 @@ +# 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. +# =================================================================== +"""TPU system metadata and associated tooling.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import collections +import re + +from tensorflow.core.protobuf import config_pb2 +from tensorflow.python.client import session as session_lib +from tensorflow.python.framework import errors +from tensorflow.python.framework import ops +from tensorflow.python.platform import tf_logging as logging +from tensorflow.python.tpu import tpu + +_PINGING_MASTER_TIMEOUT_IN_MS = 60 * 1000 # 1 min +_RETRY_TIMES = 120 +_INITIAL_TPU_SYSTEM_TIMEOUT_IN_MS = 300 * 1000 # 5 mins + +_TPU_DEVICE_REG = re.compile(r'.*task:(\d+)/.*device:TPU:(\d+)$') + +# _TPUSystemMetadata is used by TPUEstimator to hold TPU configuration, +# including num_cores and num_hosts. +_TPUSystemMetadata = collections.namedtuple('_TPUSystemMetadata', [ + 'num_cores', + 'num_hosts', + 'num_of_cores_per_host', + 'topology', + 'devices', +]) + + +def _query_tpu_system_metadata(master_address, cluster_def=None, + query_topology=False): + """Automatically detects the TPU system metadata in the system.""" + tpu_core_count = 0 + devices = [] + device_dict = collections.defaultdict(list) + + # TODO(b/120564445): Replace with standard library for retries. + retry_count = 1 + while True: + logging.info('Querying Tensorflow master (%s) for TPU system metadata.', + master_address) + try: + with ops.Graph().as_default(): + with session_lib.Session( + master_address, + config=get_session_config_with_timeout( + _PINGING_MASTER_TIMEOUT_IN_MS, + cluster_def)) as sess: + devices = sess.list_devices() + for device in devices: + match = _TPU_DEVICE_REG.match(device.name) + if match: + host_id = match.group(1) + core_id = match.group(2) + device_dict[host_id].append(core_id) + tpu_core_count += 1 + break + except errors.DeadlineExceededError: + 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 + # here. + if retry_count <= _RETRY_TIMES: + logging.warning('%s', msg) + logging.warning('Retrying (%d/%d).', retry_count, _RETRY_TIMES) + retry_count += 1 + else: + raise ValueError(msg) + + num_of_cores_per_host = 0 + if tpu_core_count: + num_cores_per_host_set = set( + [len(core_ids) for core_ids in device_dict.values()]) + if len(num_cores_per_host_set) != 1: + raise RuntimeError( + 'TPU cores on each host is not same. This should not happen!. ' + 'devices: {}'.format(devices)) + num_of_cores_per_host = num_cores_per_host_set.pop() + + topology = None + if query_topology: + if not tpu_core_count: + raise RuntimeError( + 'Cannot find any TPU cores in the system (master address {}). ' + 'This usually means the master address is incorrect or the ' + 'TPU worker has some problems. Available devices: {}'.format( + master_address, devices)) + + topology = _obtain_topology(master_address, cluster_def) + + metadata = _TPUSystemMetadata( + num_cores=tpu_core_count, + num_hosts=len(device_dict), + num_of_cores_per_host=num_of_cores_per_host, + topology=topology, + devices=devices) + + if tpu_core_count: + logging.info('Found TPU system:') + logging.info('*** Num TPU Cores: %d', metadata.num_cores) + logging.info('*** Num TPU Workers: %d', metadata.num_hosts) + logging.info('*** Num TPU Cores Per Worker: %d', + metadata.num_of_cores_per_host) + for device in metadata.devices: + logging.info('*** Available Device: %s', device) + else: + logging.info('Failed to find TPU: %s', metadata) + return metadata + + +def _obtain_topology(master_address, cluster_def): + """Obtains TPU fabric topology.""" + try: + logging.info('Initializing TPU system (master: %s) to fetch topology ' + 'for model parallelism. This might take a while.', + master_address) + with ops.Graph().as_default(): + session_config = get_session_config_with_timeout( + _INITIAL_TPU_SYSTEM_TIMEOUT_IN_MS, cluster_def) + with session_lib.Session( + master_address, config=session_config) as sess: + topology = sess.run(tpu.initialize_system()) + return topology + except errors.DeadlineExceededError: + raise ValueError( + 'Fail to initialize TPU system with master (%s). ' + 'Please double check the TPU system is functional.' % ( + master_address)) + + +def get_session_config_with_timeout(timeout_in_secs, cluster_def): + """Returns a session given a timeout and a cluster configuration.""" + config = config_pb2.ConfigProto( + operation_timeout_in_ms=timeout_in_secs, cluster_def=cluster_def) + return config diff --git a/tensorflow/contrib/tpu/python/tpu/tpu_test.py b/tensorflow/python/tpu/tpu_test.py similarity index 94% rename from tensorflow/contrib/tpu/python/tpu/tpu_test.py rename to tensorflow/python/tpu/tpu_test.py index 6bdaa528f9..69b03811da 100644 --- a/tensorflow/contrib/tpu/python/tpu/tpu_test.py +++ b/tensorflow/python/tpu/tpu_test.py @@ -19,18 +19,16 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib.tpu.python.tpu import tpu -from tensorflow.contrib.tpu.python.tpu import tpu_feed -from tensorflow.contrib.tpu.python.tpu import training_loop - from tensorflow.python.framework import dtypes from tensorflow.python.layers import convolutional 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 math_ops - from tensorflow.python.platform import test +from tensorflow.python.tpu import tpu +from tensorflow.python.tpu import tpu_feed +from tensorflow.python.tpu import training_loop class TPUContextTest(test.TestCase): diff --git a/tensorflow/python/tpu/training_loop.py b/tensorflow/python/tpu/training_loop.py new file mode 100644 index 0000000000..cffeb7e915 --- /dev/null +++ b/tensorflow/python/tpu/training_loop.py @@ -0,0 +1,222 @@ +# 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. +# ============================================================================= + +"""Library for constructing a training loop, suitable for TPUs.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.python.framework import ops +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import control_flow_ops +from tensorflow.python.tpu import tensor_tracer +from tensorflow.python.tpu import tpu_function +from tensorflow.python.tpu import xla + + +def while_loop(condition, body, inputs=None, infeed_queue=None, name=None): + """Builds a training loop for TPUs. + + The set of loop-carried tensors corresponds to `inputs`. Both + `condition` and `body` take the current value of the loop-carried + tensors. 'body' additionally takes a tuple of infeed from + infeed_queue if infeed_queue is not None. `condition` must return a + single boolean value that determines whether iteration + continues. `body` must return an updated list of values for the + loop-carried tensors. + + Args: + condition: a Python function that builds the loop condition. + body: a Python function that builds the loop body. + inputs: a list of initial values passed into the training loop, or + None (equivalent to an empty list). + infeed_queue: if not None, the infeed queue from which to append a tuple + of arguments as inputs to condition. + name: (Deprecated) Does nothing. + + Returns: + The final values of the loop-carried tensors. + + Raises: + TypeError: if body or condition has the wrong signature. + """ + del name + # Converts inputs to Tensors. + inputs = [] if inputs is None else [ops.convert_to_tensor(x) for + x in inputs] + input_types = [x.dtype for x in inputs] + input_arity = len(inputs) + + body_arg_error = xla.check_function_argument_count( + body, input_arity, infeed_queue) + if body_arg_error is not None: + if infeed_queue is None: + raise TypeError( + "Supplied loop body function cannot be called with the specified " + "inputs. You specified %d inputs: %s, but the loop body needs %s" % ( + input_arity, str([i.name for i in inputs]), body_arg_error)) + else: + raise TypeError( + "Supplied loop body function cannot be called with the specified " + "inputs. You specified %d inputs: %s and %d additional inputs from " + "infeed, but the computation needs %s" % (input_arity, str( + [i.name for i in inputs]), infeed_queue.number_of_tuple_elements, + body_arg_error)) + condition_arg_error = xla.check_function_argument_count( + condition, input_arity, None) + if condition_arg_error is not None: + if infeed_queue is None: + raise TypeError( + "Supplied loop condition function cannot be called with the " + "specified inputs. You specified %d inputs: %s, but the loop " + "condition needs %s" % (input_arity, str([i.name for i in inputs]), + condition_arg_error)) + else: + raise TypeError( + "Supplied loop condition function cannot be called with the " + "specified inputs. You specified %d inputs: %s, but the loop " + "condition needs %s. Note that infeed is not passed to the loop " + "condition." % (input_arity, str([i.name for i in inputs]), + condition_arg_error)) + + def condition_wrapper(*inputs): + # Discards the dummy output added for arity-0 loops. + if input_arity == 0: + inputs = [] + return condition(*inputs) + + def body_wrapper(*inputs): + """Wrapper around `body` that handles infeed queues and control deps.""" + inputs = list(inputs) + + # Discards the dummy output added for arity-0 loops. + if input_arity == 0: + inputs = [] + + # Runs `body` with the dequeue_ops appended. + if infeed_queue: + number_of_shards = tpu_function.get_tpu_context().number_of_shards + if number_of_shards is None: + raise ValueError("Can't build training loop with infeed when there is " + "no tpu_shard_context. Are you building a loop or " + "graph directly rather than from inside tpu.rewrite, " + "tpu.batch_parallel, tpu.shard, or tpu.replicate?") + infeed_queue.set_number_of_shards(number_of_shards) + dequeue_ops = [d for d in infeed_queue.generate_dequeue_op()] + else: + dequeue_ops = [] + outputs = body(*(inputs + dequeue_ops)) + + # If the computation only returned one value, make it a tuple. + if not isinstance(outputs, (list, tuple)): + outputs = (outputs,) + + outputs = [ + o if isinstance(o, ops.Operation) else ops.convert_to_tensor(o) + for o in outputs + ] + + # Separates the returned Operations and Tensors. + output_operations = [o for o in outputs if isinstance(o, ops.Operation)] + output_tensors = [o for o in outputs + if not isinstance(o, ops.Operation)] + + if outputs != output_tensors + output_operations: + raise ValueError( + "TPU training loop body must return zero or more Tensor values " + "followed by zero or more Operations.") + + output_types = [op.dtype for op in output_tensors] + if input_types != output_types: + raise TypeError( + "Mismatch between input types and output types for training loop " + "body: {} vs {}".format(input_types, output_types)) + + # Add the dequeue operations to output_operations to ensure they are run + # by the loop, even if the programmer's loop body does not use them. + output_operations += dequeue_ops + + # Add a dummy output, if needed. + if not output_tensors: + output_tensors = array_ops.constant(0) + + if output_operations: + # TODO(phawkins): in principle this is too restrictive since it serializes + # the training loop steps. In practice it does not matter since this loop + # will be compiled by XLA. + output_tensors = control_flow_ops.tuple(output_tensors, + control_inputs=output_operations) + + if tensor_tracer.TensorTracer.is_enabled(): + num_replicas = tpu_function.get_tpu_context().number_of_shards + if num_replicas is None: + num_replicas = 1 + tt = tensor_tracer.TensorTracer() + output_tensors = tt.trace_tpu(ops.get_default_graph(), + output_tensors, None, + num_replicas) + return output_tensors + + # If the body has arity 0, add a dummy loop-carried value to which we can add + # control dependencies from any side-effecting operations. + if input_arity == 0: + inputs = [array_ops.constant(0)] + return control_flow_ops.while_loop( + condition_wrapper, body_wrapper, inputs, name="", parallel_iterations=1) + + +def repeat(n, body, inputs=None, infeed_queue=None, name=None): + """Builds a training loop that executes a fixed number of iterations. + + The set of loop-carried tensors correspond to `inputs`. + `body` must be a function that takes and returns the values of the + loop-carried tensors. + + Args: + n: the number of loop iterations + body: a Python function that builds the loop body. + inputs: a list of initial values passed into the training loop or + None (equivalent to an empty list). + infeed_queue: if not None, the infeed queue from which to append a tuple + of arguments as inputs to condition. + name: (Deprecated) Does nothing. + Returns: + The final values of the loop-carried tensors. + Raises: + ValueError: if there is a type error. + """ + def _convert_to_list(xs): + if not isinstance(xs, (list, tuple)): + return [xs] + else: + return list(xs) + + def cond(i, *args): + del args + return i < n + + def body_wrapper(i, *args): + return [i + 1] + _convert_to_list(body(*args)) + + inputs = [0] if inputs is None else [0] + _convert_to_list(inputs) + outputs = while_loop( + cond, body_wrapper, inputs=inputs, infeed_queue=infeed_queue, name=name) + outputs = _convert_to_list(outputs) + if len(outputs) == 1: + # Returns the Op rather than an empty list. + return outputs[0].op + else: + return outputs[1:] diff --git a/tensorflow/python/tpu/util.py b/tensorflow/python/tpu/util.py new file mode 100644 index 0000000000..dfb8ce1d18 --- /dev/null +++ b/tensorflow/python/tpu/util.py @@ -0,0 +1,51 @@ +# 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. +# =================================================================== + +"""Utilities for the functionalities.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import time +import six + +from tensorflow.python.platform import tf_logging as logging +from tensorflow.python.training import training + +def check_positive_integer(value, name): + """Checks whether `value` is a positive integer.""" + if not isinstance(value, six.integer_types): + raise TypeError('{} must be int, got {}'.format(name, type(value))) + + if value <= 0: + raise ValueError('{} must be positive, got {}'.format(name, value)) + + +# TODO(b/118302029) Remove this copy of MultiHostDatasetInitializerHook after we +# release a tensorflow_estimator with MultiHostDatasetInitializerHook in +# python/estimator/util.py. +class MultiHostDatasetInitializerHook(training.SessionRunHook): + """Creates a SessionRunHook that initializes all passed iterators.""" + + def __init__(self, dataset_initializers): + self._initializers = dataset_initializers + + def after_create_session(self, session, coord): + del coord + start = time.time() + session.run(self._initializers) + logging.info('Initialized dataset iterators in %d seconds', + time.time() - start) diff --git a/tensorflow/python/tpu/xla.py b/tensorflow/python/tpu/xla.py new file mode 100644 index 0000000000..58476fae3d --- /dev/null +++ b/tensorflow/python/tpu/xla.py @@ -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. +# ============================================================================= +"""XLA utility functions.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import collections + +from tensorflow.python.util import tf_inspect + + +def is_flat(outputs): + """Checks if outputs is a flat structure. + + Following structures and values are considered flat: + 1) None + 2) A single object + 3) A list or tuple of Tensors/Operations + + The only structures that this function understands are sequences and + dictionaries. E.g. this means that if outputs contains a single + user-defined Object, it is considered to be flat. Errors are raised later on + if that Object cannot be converted to a Tensor. + + Args: + outputs: Output from `computation` inside `xla.compile`. + + Returns: + A boolean indicates whether outputs is flat. + """ + # If outputs is a list or tuple, check if it has any nested structure. If + # there is, then outputs is non-flat. + if isinstance(outputs, collections.Sequence): + for o in outputs: + if isinstance(o, collections.Sequence) or isinstance(o, dict): + return False + + # If outputs is a dict, it is non-flat. + if isinstance(outputs, dict): + return False + + # Getting here means either outputs itself is a single non-structured value + # or it is a flat list of single non-structured values. + return True + + +def check_function_argument_count(func, input_arity, infeed_queue): + """Validate the number of input arguments to an XLA function. + + Args: + func: the Python function that will be called to generate the body of an XLA + computation graph. + input_arity: the number of explicit arguments supplied by the caller. + infeed_queue: if not None, the infeed queue that will supply + additional arguments to the function. + + Returns: + None if function can be called with the supplied number of + arguments, or an error string if it cannot. + """ + def format_error(complaint, quantity): + return '%s %d argument%s' % (complaint, quantity, '' + if quantity == 1 else 's') + + num_args_supplied = input_arity + if infeed_queue is not None: + num_args_supplied += infeed_queue.number_of_tuple_elements + arg_spec = tf_inspect.getargspec(func) + num_func_args = len(arg_spec.args) + if arg_spec.defaults is None: + num_func_defaults = 0 + else: + num_func_defaults = len(arg_spec.defaults) + min_func_args = num_func_args - num_func_defaults + if num_args_supplied < min_func_args: + # The required number of arguments is not enough to call the function. + if num_func_defaults == 0 and arg_spec.varargs is None: + return format_error('exactly', num_func_args) + else: + return format_error('at least', min_func_args) + if arg_spec.varargs is None and num_args_supplied > num_func_args: + # The required number of arguments is too many to call the function. + if num_func_defaults == 0: + return format_error('exactly', num_func_args) + else: + return format_error('at most', num_func_args) + # Reaching here means either + # 1) There are varargs, func can accept any number of arguments greater than + # the minimum. + # 2) Number of supplied arguments falls in range of acceptable argument count + # of func. + return None -- GitLab From dcbd855277d8af27520aaff0641971c068c9c41f Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 20 Feb 2019 13:10:46 -0800 Subject: [PATCH 195/766] Don't remove training identity nodes that are used as control edge inputs PiperOrigin-RevId: 234854047 --- tensorflow/python/framework/graph_util_impl.py | 18 +++++++++++++----- tensorflow/python/framework/graph_util_test.py | 17 +++++++++++++++-- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/tensorflow/python/framework/graph_util_impl.py b/tensorflow/python/framework/graph_util_impl.py index 661c531cdb..f0cd1647d0 100644 --- a/tensorflow/python/framework/graph_util_impl.py +++ b/tensorflow/python/framework/graph_util_impl.py @@ -353,19 +353,27 @@ def remove_training_nodes(input_graph, protected_nodes=None): nodes_after_removal.append(new_node) types_to_splice = {"Identity": True} + control_input_names = set() + node_names_with_control_input = set() + for node in nodes_after_removal: + for node_input in node.input: + if "^" in node_input: + control_input_names.add(node_input.replace("^", "")) + node_names_with_control_input.add(node.name) + names_to_splice = {} for node in nodes_after_removal: if node.op in types_to_splice and node.name not in protected_nodes: # We don't want to remove nodes that have control edge inputs, because # they might be involved in subtle dependency issues that removing them # will jeopardize. - has_control_edge = False - for input_name in node.input: - if re.match(r"^\^", input_name): - has_control_edge = True - if not has_control_edge: + if node.name not in node_names_with_control_input: names_to_splice[node.name] = node.input[0] + # We also don't want to remove nodes which are used as control edge inputs. + names_to_splice = {name: value for name, value in names_to_splice.items() + if name not in control_input_names} + nodes_after_splicing = [] for node in nodes_after_removal: if node.name in names_to_splice: diff --git a/tensorflow/python/framework/graph_util_test.py b/tensorflow/python/framework/graph_util_test.py index dd26b8a78e..78777dc877 100644 --- a/tensorflow/python/framework/graph_util_test.py +++ b/tensorflow/python/framework/graph_util_test.py @@ -308,8 +308,9 @@ class DeviceFunctionsTest(test.TestCase): new_node.input.extend([input_name]) return new_node - def create_constant_node_def(self, name, value, dtype, shape=None): - node = self.create_node_def("Const", name, []) + def create_constant_node_def(self, name, value, dtype, + shape=None, inputs=None): + node = self.create_node_def("Const", name, inputs or []) self.set_attr_dtype(node, "dtype", dtype) self.set_attr_tensor(node, "value", value, dtype, shape) return node @@ -393,6 +394,18 @@ class DeviceFunctionsTest(test.TestCase): self.assertProtoEquals(expected_graph_def, graph_util.remove_training_nodes(graph_def)) + def testRemoveIdentityUsedAsControlInputInConst(self): + """Check that Identity nodes used as control inputs are not removed.""" + graph_def = graph_pb2.GraphDef() + graph_def.node.extend([ + self.create_constant_node_def("C", 1, dtypes.float32, inputs=["^I"]), + self.create_node_def("Identity", "I", ["Base"]), + self.create_node_def("BaseOp", "Base", []) + ]) + + self.assertProtoEquals(graph_def, + graph_util.remove_training_nodes(graph_def)) + if __name__ == "__main__": test.main() -- GitLab From 9af4f4b9f649f7022a1e65844d455d6fe4448f37 Mon Sep 17 00:00:00 2001 From: Melissa Grueter Date: Fri, 1 Feb 2019 15:09:24 -0800 Subject: [PATCH 196/766] adding whileLoop method for the Java API Co-authored-by: Irene Dea Co-authored-by: Samantha Andow --- .../src/main/java/org/tensorflow/Graph.java | 118 ++++++++++++- tensorflow/java/src/main/native/graph_jni.cc | 160 +++++++++++++++++- tensorflow/java/src/main/native/graph_jni.h | 14 +- .../test/java/org/tensorflow/GraphTest.java | 101 +++++++++++ 4 files changed, 382 insertions(+), 11 deletions(-) diff --git a/tensorflow/java/src/main/java/org/tensorflow/Graph.java b/tensorflow/java/src/main/java/org/tensorflow/Graph.java index 752b49af04..a408004344 100644 --- a/tensorflow/java/src/main/java/org/tensorflow/Graph.java +++ b/tensorflow/java/src/main/java/org/tensorflow/Graph.java @@ -235,7 +235,115 @@ public final class Graph implements AutoCloseable { public Output[] addGradients(Output y, Output[] x) { return addGradients(null, new Output[] {y}, x, null); } - + + /** + * Used to instantiate an abstract class which overrides the buildSubgraph method to build + * a conditional or body subgraph for a while loop. After Java 8, this can alternatively be + * used to create a lambda for the same purpose. + * + * To be used when calling + * {@link #whileLoop(Output[], org.tensorflow.Graph.WhileSubgraphBuilder, org.tensorflow.Graph.WhileSubgraphBuilder, String)} + * + *

Example usage (prior to Java 8): + *

+ * {@code WhileSubgraphBuilder bodyGraphBuilder = new WhileSubgraphBuilder() { + * @Override + * public void buildSubgraph(Graph bodyGraph, Output[] bodyInputs, Output[] bodyOutputs) { + * // build body subgraph + * } + * }; + * } + *

Example usage (after Java 8): + *

+ * {@code WhileSubgraphBuilder bodyGraphBuilder = (bodyGraph, bodyInputs, bodyOutputs) -> { // build body subgraph };} + */ + public interface WhileSubgraphBuilder { + /** + * To be overridden by user with code to build conditional or body subgraph for a while loop + * + * @param g the subgraph + * @param inputs subgraph inputs + * @param outputs subgraph outputs + */ + public void buildSubgraph(Graph g, Output[] inputs, Output[] outputs); + } + + // called by while loop code in graph_jni.cc to construct conditional/body subgraphs + private static long[] buildSubgraph(WhileSubgraphBuilder subgraphBuilder, + long subgraphHandle, + long[] inputHandles, + int[] inputIndices, + long[] outputHandles, + int[] outputIndices) { + Graph subgraph = new Graph(subgraphHandle); + + int ninputs = inputHandles.length; + int noutputs = outputHandles.length; + Output[] inputs = new Output[ninputs]; + Output[] outputs = new Output[noutputs]; + long[] outputHandlesAndIndices = new long[noutputs * 2]; + + synchronized (subgraph.nativeHandleLock) { + try (Reference ref = subgraph.ref()) { + + for (int i = 0; i < ninputs; i++) { + Operation op = new Operation(subgraph, inputHandles[i]); + inputs[i] = new Output<>(op, inputIndices[i]); + } + + for (int i = 0; i < noutputs; i++) { + Operation op = new Operation(subgraph, outputHandles[i]); + outputs[i] = new Output<>(op, outputIndices[i]); + } + + subgraphBuilder.buildSubgraph(subgraph, inputs, outputs); + + for (int i = 0, j = noutputs; i < noutputs; i++, j++) { + outputHandlesAndIndices[i] = outputs[i].op().getUnsafeNativeHandle(); + outputHandlesAndIndices[j] = (long) outputs[i].index(); + } + } + return outputHandlesAndIndices; + } + } + + /** + * Builds a while loop. + * + * @param inputs the loop inputs + * @param cgBuilder WhileSubgraphBuilder to build the conditional subgraph + * @param bgBuilder WhileSubgraphBuilder to build the body subgraph + * @param name name for the loop + * @return list of loop outputs, of the same length as {@code inputs} + */ + public Output[] whileLoop(Output[] inputs, + WhileSubgraphBuilder cgBuilder, + WhileSubgraphBuilder bgBuilder, + String name) { + int ninputs = inputs.length; + long[] inputHandles = new long[ninputs]; + int[] inputIndices = new int[ninputs]; + Output[] outputs = new Output[ninputs]; + + synchronized (nativeHandleLock) { + try (Reference ref = ref()) { + + for (int i = 0; i < ninputs; i++) { + inputHandles[i] = inputs[i].op().getUnsafeNativeHandle(); + inputIndices[i] = inputs[i].index(); + } + + long[] outputHandlesAndIndices = whileLoop(nativeHandle, inputHandles, inputIndices, name, cgBuilder, bgBuilder); + + for (int i = 0, j = ninputs; i < ninputs; ++i, ++j) { + Operation op = new Operation(this, outputHandlesAndIndices[i]); + outputs[i] = new Output<>(op, (int) outputHandlesAndIndices[j]); + } + } + return outputs; + } + } + private final Object nativeHandleLock = new Object(); private long nativeHandle; private int refcount = 0; @@ -357,6 +465,14 @@ public final class Graph implements AutoCloseable { long[] gradInputHandles, int[] gradInputIndices); + private static native long[] whileLoop( + long handle, + long[] inputHandles, + int[] inputIndices, + String name, + WhileSubgraphBuilder condGraphBuilder, + WhileSubgraphBuilder bodyGraphBuilder); + static { TensorFlow.init(); } diff --git a/tensorflow/java/src/main/native/graph_jni.cc b/tensorflow/java/src/main/native/graph_jni.cc index f1744d8769..570ba8ac10 100644 --- a/tensorflow/java/src/main/native/graph_jni.cc +++ b/tensorflow/java/src/main/native/graph_jni.cc @@ -18,19 +18,28 @@ limitations under the License. #include #include #include "tensorflow/c/c_api.h" -#include "tensorflow/java/src/main/native/utils_jni.h" #include "tensorflow/java/src/main/native/exception_jni.h" +#include "tensorflow/java/src/main/native/utils_jni.h" namespace { -TF_Graph* requireHandle(JNIEnv* env, jlong handle) { - static_assert(sizeof(jlong) >= sizeof(TF_Graph*), +template +T* requireHandleImpl(JNIEnv* env, jlong handle) { + static_assert(sizeof(jlong) >= sizeof(T*), "Cannot package C object pointers as a Java long"); if (handle == 0) { throwException(env, kIllegalStateException, "close() has been called on the Graph"); return nullptr; } - return reinterpret_cast(handle); + return reinterpret_cast(handle); +} + +TF_Graph* requireHandle(JNIEnv* env, jlong handle) { + return requireHandleImpl(env, handle); +} + +TF_Operation* requireOperationHandle(JNIEnv* env, jlong handle) { + return requireHandleImpl(env, handle); } } // namespace @@ -56,10 +65,8 @@ JNIEXPORT jlong JNICALL Java_org_tensorflow_Graph_operation(JNIEnv* env, return reinterpret_cast(op); } -JNIEXPORT jlongArray JNICALL Java_org_tensorflow_Graph_nextOperation(JNIEnv* env, - jclass clazz, - jlong handle, - jint position) { +JNIEXPORT jlongArray JNICALL Java_org_tensorflow_Graph_nextOperation( + JNIEnv* env, jclass clazz, jlong handle, jint position) { TF_Graph* g = requireHandle(env, handle); if (g == nullptr) return nullptr; @@ -189,3 +196,140 @@ JNIEXPORT jlongArray JNICALL Java_org_tensorflow_Graph_addGradients( return dy_handles_and_indices; } + +// helper function for while loop -- constructs conditional or body subgraph +jlongArray buildSubgraph(JNIEnv* env, jclass clazz, jobject subgraph_builder, + TF_Graph* const subgraph, + const TF_Output* const inputs, + const TF_Output* const outputs, const int ninputs, + const int noutputs) { + jmethodID build_subgraph_method_id = env->GetStaticMethodID( + clazz, "buildSubgraph", + "(Lorg/tensorflow/Graph$WhileSubgraphBuilder;J[J[I[J[I)[J"); + if (build_subgraph_method_id == 0) return nullptr; + + jlong subgraph_handle = reinterpret_cast(subgraph); + + jlongArray input_handles = env->NewLongArray(ninputs); + jintArray input_indices = env->NewIntArray(ninputs); + jlongArray output_handles = env->NewLongArray(noutputs); + jintArray output_indices = env->NewIntArray(noutputs); + + jlong* input_handles_elems = + env->GetLongArrayElements(input_handles, nullptr); + jint* input_indices_elems = env->GetIntArrayElements(input_indices, nullptr); + jlong* output_handles_elems = + env->GetLongArrayElements(output_handles, nullptr); + jint* output_indices_elems = + env->GetIntArrayElements(output_indices, nullptr); + + for (int i = 0; i < ninputs; ++i) { + input_handles_elems[i] = reinterpret_cast((inputs[i]).oper); + input_indices_elems[i] = static_cast((inputs[i]).index); + } + + for (int i = 0; i < noutputs; ++i) { + output_handles_elems[i] = reinterpret_cast((outputs[i]).oper); + output_indices_elems[i] = static_cast((outputs[i]).index); + } + + env->ReleaseLongArrayElements(input_handles, input_handles_elems, 0); + env->ReleaseIntArrayElements(input_indices, input_indices_elems, 0); + env->ReleaseLongArrayElements(output_handles, output_handles_elems, 0); + env->ReleaseIntArrayElements(output_indices, output_indices_elems, 0); + + // call Java code to construct the subgraph + jlongArray output_handles_and_indices = + (jlongArray)env->CallStaticObjectMethod( + clazz, build_subgraph_method_id, subgraph_builder, subgraph_handle, + input_handles, input_indices, output_handles, output_indices); + + if (env->ExceptionOccurred()) { + env->ExceptionDescribe(); + return nullptr; + } + + // returned array contains both op handles and output indices, in pair + return output_handles_and_indices; +} + +JNIEXPORT jlongArray JNICALL Java_org_tensorflow_Graph_whileLoop( + JNIEnv* env, jclass clazz, jlong handle, jlongArray input_handles, + jintArray input_indices, jstring name, jobject cond_graph_builder, + jobject body_graph_builder) { + TF_Graph* g = requireHandle(env, handle); + TF_Status* status = TF_NewStatus(); + if (g == nullptr) return nullptr; + + int ninputs = env->GetArrayLength(input_handles); + + std::unique_ptr inputs(new TF_Output[ninputs]); + resolveOutputs(env, "inputs", input_handles, input_indices, inputs.get(), + ninputs); + if (env->ExceptionCheck()) return nullptr; + + // initialize while params + TF_WhileParams params = TF_NewWhile(g, inputs.get(), ninputs, status); + throwExceptionIfNotOK(env, status); + + // build conditional subgraph + jlongArray cond_output_handles_and_indices = + buildSubgraph(env, clazz, cond_graph_builder, params.cond_graph, + params.cond_inputs, ¶ms.cond_output, params.ninputs, 1); + + // build body subgraph + jlongArray body_output_handles_and_indices = buildSubgraph( + env, clazz, body_graph_builder, params.body_graph, params.body_inputs, + params.body_outputs, params.ninputs, params.ninputs); + + if (cond_output_handles_and_indices == nullptr || + body_output_handles_and_indices == nullptr) + return nullptr; + + // set cond_output param to output of the conditional subgraph + jlong* cond_output_elems = + env->GetLongArrayElements(cond_output_handles_and_indices, nullptr); + TF_Operation* cond_output_op = + requireOperationHandle(env, cond_output_elems[0]); + params.cond_output = {cond_output_op, + static_cast(cond_output_elems[1])}; + env->ReleaseLongArrayElements(cond_output_handles_and_indices, + cond_output_elems, 0); + + // set body_outputs param to outputs of the body subgraph + jlong* body_output_elems = + env->GetLongArrayElements(body_output_handles_and_indices, nullptr); + for (int i = 0, j = ninputs; i < ninputs; ++i, ++j) { + TF_Operation* body_output_op = + requireOperationHandle(env, body_output_elems[i]); + params.body_outputs[i] = {body_output_op, + static_cast(body_output_elems[j])}; + } + env->ReleaseLongArrayElements(body_output_handles_and_indices, + body_output_elems, 0); + + // set loop name param + params.name = env->GetStringUTFChars(name, 0); + + // build the while loop, storing loop outputs in `outputs` + std::unique_ptr outputs(new TF_Output[ninputs]); + TF_FinishWhile(¶ms, status, outputs.get()); + + throwExceptionIfNotOK(env, status); + TF_DeleteStatus(status); + + env->ReleaseStringUTFChars(name, params.name); + + // returned array contains both op handles and output indices, in pair + jlongArray output_handles_and_indices = env->NewLongArray(ninputs * 2); + jlong* output_elems = + env->GetLongArrayElements(output_handles_and_indices, nullptr); + for (int i = 0, j = ninputs; i < ninputs; ++i, ++j) { + TF_Output output = outputs.get()[i]; + output_elems[i] = reinterpret_cast(output.oper); + output_elems[j] = static_cast(output.index); + } + env->ReleaseLongArrayElements(output_handles_and_indices, output_elems, 0); + + return output_handles_and_indices; +} diff --git a/tensorflow/java/src/main/native/graph_jni.h b/tensorflow/java/src/main/native/graph_jni.h index efed23f83b..4281297dca 100644 --- a/tensorflow/java/src/main/native/graph_jni.h +++ b/tensorflow/java/src/main/native/graph_jni.h @@ -51,8 +51,9 @@ JNIEXPORT jlong JNICALL Java_org_tensorflow_Graph_operation(JNIEnv *, jclass, * Signature: (JI)[J */ JNIEXPORT jlongArray JNICALL Java_org_tensorflow_Graph_nextOperation(JNIEnv *, - jclass, jlong, - jint); + jclass, + jlong, + jint); /* * Class: org_tensorflow_Graph @@ -82,6 +83,15 @@ JNIEXPORT jlongArray JNICALL Java_org_tensorflow_Graph_addGradients( JNIEnv *, jclass, jlong, jstring, jlongArray, jintArray, jlongArray, jintArray, jlongArray, jintArray); +/* + * Class: org_tensorflow_Graph + * Method: whileLoop + * Signature: + * (J[J[IILjava/lang/String;Lorg/tensorflow/Graph/WhileSubgraphBuilder;Lorg/tensorflow/Graph/WhileSubgraphBuilder;)[J + */ +JNIEXPORT jlongArray JNICALL Java_org_tensorflow_Graph_whileLoop( + JNIEnv *, jclass, jlong, jlongArray, jintArray, jstring, jobject, jobject); + #ifdef __cplusplus } // extern "C" #endif // __cplusplus diff --git a/tensorflow/java/src/test/java/org/tensorflow/GraphTest.java b/tensorflow/java/src/test/java/org/tensorflow/GraphTest.java index 7c05c1deaf..9c09240529 100644 --- a/tensorflow/java/src/test/java/org/tensorflow/GraphTest.java +++ b/tensorflow/java/src/test/java/org/tensorflow/GraphTest.java @@ -254,6 +254,107 @@ public class GraphTest { } } } + + @Test + public void buildWhileLoopSingleInput() { + try(Graph g = new Graph(); + Session s = new Session(g)) { + + Output input = TestUtil.placeholder(g, "input1", Integer.class); + + // could write this using lambda after Java 8 + Graph.WhileSubgraphBuilder condGraphBuilder = new Graph.WhileSubgraphBuilder() { + @Override + public void buildSubgraph(Graph condGraph, + Output[] condInputs, + Output[] condOutputs) { + Output sixteen = TestUtil.constant(condGraph, "sixteen", 16); + // condInputs[0] < 16 + Output condOutput = condGraph.opBuilder("Less", "cond") + .addInput(condInputs[0]).addInput(sixteen).build().output(0); + + condOutputs[0] = condOutput; + } + }; + + // could write this using lambda after Java 8 + Graph.WhileSubgraphBuilder bodyGraphBuilder = new Graph.WhileSubgraphBuilder() { + @Override + public void buildSubgraph(Graph bodyGraph, + Output[] bodyInputs, + Output[] bodyOutputs) { + bodyOutputs[0] = TestUtil.square(bodyGraph, "square", bodyInputs[0]); + } + }; + + Output[] loopOutputs = + g.whileLoop(toArray(input), condGraphBuilder, bodyGraphBuilder, "test_loop"); + + try(Tensor c = Tensors.create(2); + Tensor output = s.runner() + .feed(input, c) + .fetch(loopOutputs[0]) + .run() + .get(0)) { + + assertEquals(16, output.intValue()); // ((2^2)^2) + } + } + } + + @Test + public void buildWhileLoopMultipleInputs() { + try(Graph g = new Graph(); + Session s = new Session(g)) { + + Output input1 = TestUtil.placeholder(g, "input1", Integer.class); + Output input2 = TestUtil.placeholder(g, "input2", Integer.class); + Output[] inputs = toArray(input1, input2); + + // could write this using lambda after Java 8 + Graph.WhileSubgraphBuilder condGraphBuilder = new Graph.WhileSubgraphBuilder() { + @Override + public void buildSubgraph(Graph condGraph, + Output[] condInputs, + Output[] condOutputs) { + Output sixteen = TestUtil.constant(condGraph, "sixteen", 16); + Output condOutput = condGraph.opBuilder("Less", "cond") + .addInput(condInputs[0]).addInput(sixteen).build().output(0); // condInputs[0] < 16 + + condOutputs[0] = condOutput; + } + }; + + // could write this using lambda after Java 8 + Graph.WhileSubgraphBuilder bodyGraphBuilder = new Graph.WhileSubgraphBuilder() { + @Override + public void buildSubgraph(Graph bodyGraph, + Output[] bodyInputs, + Output[] bodyOutputs) { + bodyOutputs[0] = TestUtil.square(bodyGraph, "square1", bodyInputs[0]); + bodyOutputs[1] = TestUtil.square(bodyGraph, "square2", bodyInputs[1]); + } + }; + + Output[] loopOutputs = + g.whileLoop(inputs, condGraphBuilder, bodyGraphBuilder, "test_loop"); + + try(Tensor c1 = Tensors.create(2); + Tensor c2 = Tensors.create(5); + TestUtil.AutoCloseableList> outputs = new TestUtil.AutoCloseableList<>( + s.runner() + .feed(input1, c1) + .feed(input2, c2) + .fetch(loopOutputs[0]) + .fetch(loopOutputs[1]) + .run())) { + + assertEquals(2, outputs.size()); + assertEquals(16, outputs.get(0).intValue()); // ((2^2)^2) + assertEquals(625, outputs.get(1).intValue()); // ((5^2)^2) + } + } + } private static Output[] toArray(Output... outputs) { return outputs; -- GitLab From 5c019aacdc6d0768ec8cd40fac29931a37192ab9 Mon Sep 17 00:00:00 2001 From: Rick Chao Date: Wed, 20 Feb 2019 13:20:34 -0800 Subject: [PATCH 197/766] Add several api upgrade mapping for tf.contrib.estimator.*. PiperOrigin-RevId: 234855809 --- tensorflow/tools/compatibility/tf_upgrade_v2.py | 10 ++++++++++ tensorflow/tools/compatibility/tf_upgrade_v2_test.py | 11 +++++++++++ 2 files changed, 21 insertions(+) diff --git a/tensorflow/tools/compatibility/tf_upgrade_v2.py b/tensorflow/tools/compatibility/tf_upgrade_v2.py index c195daa47b..a2d846fac1 100644 --- a/tensorflow/tools/compatibility/tf_upgrade_v2.py +++ b/tensorflow/tools/compatibility/tf_upgrade_v2.py @@ -568,6 +568,16 @@ class TFAPIChangeSpec(ast_edits.APIChangeSpec): "tf.data.experimental.unbatch", "tf.contrib.data.unique": "tf.data.experimental.unique", + "tf.contrib.estimator.make_early_stopping_hook": + "tf.estimator.experimental.make_early_stopping_hook", + "tf.contrib.estimator.stop_if_higher_hook": + "tf.estimator.experimental.stop_if_higher_hook", + "tf.contrib.estimator.stop_if_lower_hook": + "tf.estimator.experimental.stop_if_lower_hook", + "tf.contrib.estimator.stop_if_no_decrease_hook": + "tf.estimator.experimental.stop_if_no_decrease_hook", + "tf.contrib.estimator.stop_if_no_increase_hook": + "tf.estimator.experimental.stop_if_no_increase_hook", "tf.contrib.framework.CriticalSection": "tf.CriticalSection", "tf.contrib.framework.is_tensor": diff --git a/tensorflow/tools/compatibility/tf_upgrade_v2_test.py b/tensorflow/tools/compatibility/tf_upgrade_v2_test.py index 200f112287..65622aa0fa 100644 --- a/tensorflow/tools/compatibility/tf_upgrade_v2_test.py +++ b/tensorflow/tools/compatibility/tf_upgrade_v2_test.py @@ -1231,6 +1231,17 @@ def _log_prob(self, x): _, _, _, new_text = self._upgrade(text) self.assertEqual(expected_text, new_text) + def test_contrib_estimator_early_stopping(self): + api_symbols = [ + "make_early_stopping_hook", "stop_if_higher_hook", "stop_if_lower_hook", + "stop_if_no_decrease_hook", "stop_if_no_increase_hook" + ] + for symbol in api_symbols: + text = "tf.contrib.estimator." + symbol + expected_text = "tf.estimator.experimental." + symbol + _, _, _, new_text = self._upgrade(text) + self.assertEqual(expected_text, new_text) + def test_contrib_rnn(self): api_symbols = ["BasicLSTMCell", "BasicRNNCell", "GRUCell", "LSTMCell", "MultiRNNCell"] -- GitLab From ab8e47136a4b964c0038d2fa7cc65986dde2caf4 Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Wed, 20 Feb 2019 13:47:34 -0800 Subject: [PATCH 198/766] Cast task_type to string and task_id to int in TFConfigClusterResolver PiperOrigin-RevId: 234861210 --- .../tfconfig_cluster_resolver.py | 8 ++--- .../tfconfig_cluster_resolver_test.py | 34 +++++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/tensorflow/python/distribute/cluster_resolver/tfconfig_cluster_resolver.py b/tensorflow/python/distribute/cluster_resolver/tfconfig_cluster_resolver.py index 48bfc60651..0cd8239160 100644 --- a/tensorflow/python/distribute/cluster_resolver/tfconfig_cluster_resolver.py +++ b/tensorflow/python/distribute/cluster_resolver/tfconfig_cluster_resolver.py @@ -77,17 +77,17 @@ class TFConfigClusterResolver(ClusterResolver): def task_type(self): if self._task_type is None: task_info = _get_value_in_tfconfig(_TASK_KEY, {}) - return task_info['type'] if 'type' in task_info else None + return str(task_info['type']) if 'type' in task_info else None else: - return self._task_type + return str(self._task_type) @property def task_id(self): if self._task_type is None: task_info = _get_value_in_tfconfig(_TASK_KEY, {}) - return task_info['index'] if 'index' in task_info else None + return int(task_info['index']) if 'index' in task_info else None else: - return self._task_id + return int(self._task_id) @task_type.setter def task_type(self, task_type): diff --git a/tensorflow/python/distribute/cluster_resolver/tfconfig_cluster_resolver_test.py b/tensorflow/python/distribute/cluster_resolver/tfconfig_cluster_resolver_test.py index 97a5eb685f..65e75d4dd3 100644 --- a/tensorflow/python/distribute/cluster_resolver/tfconfig_cluster_resolver_test.py +++ b/tensorflow/python/distribute/cluster_resolver/tfconfig_cluster_resolver_test.py @@ -183,6 +183,40 @@ class TFConfigClusterResolverTest(test.TestCase): self.assertEqual(1, cluster_resolver.task_id) self.assertEqual('test', cluster_resolver.rpc_layer) + def testTaskTypeCastToString(self): + os.environ['TF_CONFIG'] = """ + { + "cluster": { + "123456": ["ps0:2222", "ps1:2222"], + "worker": ["worker0:2222", "worker1:2222", "worker2:2222"] + }, + "rpc_layer": "grpc", + "task": { + "type": 123456, + "index": 0 + } + } + """ + cluster_resolver = TFConfigClusterResolver() + self.assertEqual('123456', cluster_resolver.task_type) + + def testTaskIndexCastToInteger(self): + os.environ['TF_CONFIG'] = """ + { + "cluster": { + "ps": ["ps0:2222", "ps1:2222"], + "worker": ["worker0:2222", "worker1:2222", "worker2:2222"] + }, + "rpc_layer": "grpc", + "task": { + "type": "ps", + "index": "1" + } + } + """ + cluster_resolver = TFConfigClusterResolver() + self.assertEqual(1, cluster_resolver.task_id) + def testZeroItemsInClusterSpecMasterRead(self): os.environ['TF_CONFIG'] = """ {} -- GitLab From 95e1847a2e58f8e6174772a8b52f284ea58d01cd Mon Sep 17 00:00:00 2001 From: Shashi Shekhar Date: Wed, 20 Feb 2019 14:07:04 -0800 Subject: [PATCH 199/766] Update post training quantization tutorial to use converter optimize API. PiperOrigin-RevId: 234865245 --- .../lite/tutorials/post_training_quant.ipynb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tensorflow/lite/tutorials/post_training_quant.ipynb b/tensorflow/lite/tutorials/post_training_quant.ipynb index 394ab0760b..8bc02eedf6 100644 --- a/tensorflow/lite/tutorials/post_training_quant.ipynb +++ b/tensorflow/lite/tutorials/post_training_quant.ipynb @@ -301,7 +301,7 @@ "id": "7BONhYtYocQY" }, "source": [ - "To quantize the model on export, set the `post_training_quantize` flag:" + "To quantize the model on export, set the `optimizations` flag to optimize for size:" ] }, { @@ -313,11 +313,11 @@ "id": "g8PUvLWDlmmz" }, "outputs": [], - "source": [ + "source": [ "# Note: If you don't have a recent tf-nightly installed, the\n", - "# \"post_training_quantize\" line will have no effect.\n", + "# \"optimizations\" line will have no effect.\n", "tf.logging.set_verbosity(tf.logging.INFO)\n", - "converter.post_training_quantize = True\n", + "converter.optimizations = [tf.lite.Optimize.OPTIMIZE_FOR_SIZE]\n", "tflite_quant_model = converter.convert()\n", "tflite_model_quant_file = tflite_models_dir/\"mnist_model_quant.tflite\"\n", "tflite_model_quant_file.write_bytes(tflite_quant_model)" @@ -329,8 +329,8 @@ "colab_type": "text", "id": "PhMmUTl4sbkz" }, - "source": [ - "Note how the resulting file, with `post_training_quantize` set, is approximately `1/4` the size." + "source": [ + "Note how the resulting file, is approximately `1/4` the size." ] }, { @@ -383,7 +383,7 @@ "source": [ "import numpy as np\n", "mnist_train, mnist_test = tf.keras.datasets.mnist.load_data()\n", - "images, labels = tf.to_float(mnist_test[0])/255.0, mnist_test[1]\n", + "images, labels = tf.cast(mnist_test[0], tf.float32)/255.0, mnist_test[1]\n", "\n", "# Note: If you change the batch size, then use \n", "# `tf.lite.Interpreter.resize_tensor_input` to also change it for\n", @@ -489,7 +489,7 @@ "plt.imshow(img[0])\n", "template = \"True:{true}, predicted:{predict}\"\n", "_ = plt.title(template.format(true= str(label[0].numpy()),\n", - " predict=str(predictions[0,0])))\n", + " predict=str(predictions[0])))\n", "plt.grid(False)" ] }, @@ -650,7 +650,7 @@ "output_arrays = [\"output\"]\n", "converter = tf.lite.TFLiteConverter.from_frozen_graph(\n", " str(graph_def_file), input_arrays, output_arrays, input_shapes={\"input\":[1,299,299,3]})\n", - "converter.post_training_quantize = True\n", + "converter.optimizations = [tf.lite.Optimize.OPTIMIZE_FOR_SIZE]\n", "resnet_tflite_file = graph_def_file.parent/\"resnet_v2_101_quantized.tflite\"\n", "resnet_tflite_file.write_bytes(converter.convert())\n" ] -- GitLab From fff6696b9fcedded21a5f48e5f1cdc372865fe02 Mon Sep 17 00:00:00 2001 From: Gunhan Gulsoy Date: Wed, 20 Feb 2019 15:28:43 -0800 Subject: [PATCH 200/766] Let configure script set TF test options in bazelrc PiperOrigin-RevId: 234881690 --- configure.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/configure.py b/configure.py index df7341357b..3eb09a1ae9 100644 --- a/configure.py +++ b/configure.py @@ -1495,6 +1495,34 @@ def set_other_mpi_vars(environ_cp): 'Cannot find the MPI library file in %s/lib or %s/lib64 or %s/lib32' % (mpi_home, mpi_home, mpi_home)) +def system_specific_test_config(env): + """Add default test flags required for TF tests to bazelrc.""" + write_to_bazelrc('test --flaky_test_attempts=3') + write_to_bazelrc('test --test_size_filters=small,medium') + write_to_bazelrc( + 'test --test_tag_filters=-benchmark-test,-no_oss,-oss_serial') + write_to_bazelrc('test --build_tag_filters=-benchmark-test,-no_oss') + if is_windows(): + if env.get('TF_NEED_CUDA', None) == 1: + write_to_bazelrc( + 'test --test_tag_filters=-no_windows,-no_windows_gpu,-no_gpu') + write_to_bazelrc( + 'test --build_tag_filters=-no_windows,-no_windows_gpu,-no_gpu') + else: + write_to_bazelrc('test --test_tag_filters=-no_windows,-gpu') + write_to_bazelrc('test --build_tag_filters=-no_windows,-gpu') + elif is_macos(): + write_to_bazelrc('test --test_tag_filters=-gpu,-nomac,-no_mac') + write_to_bazelrc('test --build_tag_filters=-gpu,-nomac,-no_mac') + elif is_linux(): + if env.get('TF_NEED_CUDA', None) == 1: + write_to_bazelrc('test --test_tag_filters=-no_gpu') + write_to_bazelrc('test --build_tag_filters=-no_gpu') + write_to_bazelrc('test --test_env=LD_LIBRARY_PATH') + else: + write_to_bazelrc('test --test_tag_filters=-gpu') + write_to_bazelrc('test --build_tag_filters=-gpu') + def set_system_libs_flag(environ_cp): syslibs = environ_cp.get('TF_SYSTEM_LIBS', '') @@ -1705,6 +1733,8 @@ def main(): create_android_ndk_rule(environ_cp) create_android_sdk_rule(environ_cp) + system_specific_test_config(os.environ) + if get_var( environ_cp, 'TF_CONFIGURE_APPLE_BAZEL_RULES', 'Configure Bazel rules for Apple platforms', False, -- GitLab From 0cdb5ed7807d1822b024649f55a02d4edfcd24e0 Mon Sep 17 00:00:00 2001 From: Nick Felt Date: Wed, 20 Feb 2019 15:31:59 -0800 Subject: [PATCH 201/766] Internal change PiperOrigin-RevId: 234882348 --- tensorflow/python/kernel_tests/BUILD | 4 + .../python/kernel_tests/summary_ops_test.py | 241 ++++++++++++++++-- 2 files changed, 221 insertions(+), 24 deletions(-) diff --git a/tensorflow/python/kernel_tests/BUILD b/tensorflow/python/kernel_tests/BUILD index a883b40c12..51599dcf1b 100644 --- a/tensorflow/python/kernel_tests/BUILD +++ b/tensorflow/python/kernel_tests/BUILD @@ -1092,12 +1092,16 @@ tf_py_test( "//tensorflow/core:protos_all_py", "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", + "//tensorflow/python:dtypes", "//tensorflow/python:framework_ops", "//tensorflow/python:framework_test_lib", "//tensorflow/python:lib", + "//tensorflow/python:math_ops", "//tensorflow/python:platform", "//tensorflow/python:summary_ops_v2", + "//tensorflow/python:tensor_spec", "//tensorflow/python:tensor_util", + "//tensorflow/python:variables", "//tensorflow/python/eager:function", "//tensorflow/python/eager:context", ], diff --git a/tensorflow/python/kernel_tests/summary_ops_test.py b/tensorflow/python/kernel_tests/summary_ops_test.py index cd446eb40e..bb09921df0 100644 --- a/tensorflow/python/kernel_tests/summary_ops_test.py +++ b/tensorflow/python/kernel_tests/summary_ops_test.py @@ -19,17 +19,22 @@ from __future__ import division from __future__ import print_function import os +import time from tensorflow.core.framework import summary_pb2 from tensorflow.core.util import event_pb2 from tensorflow.python.eager import context from tensorflow.python.eager import def_function 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_spec from tensorflow.python.framework import tensor_util from tensorflow.python.framework import test_util from tensorflow.python.lib.io import tf_record +from tensorflow.python.ops import math_ops from tensorflow.python.ops import summary_ops_v2 as summary_ops +from tensorflow.python.ops import variables from tensorflow.python.platform import gfile from tensorflow.python.platform import test @@ -136,44 +141,101 @@ class SummaryOpsTest(test_util.TensorFlowTestCase): with context.eager_mode(): self.assertFalse(summary_ops.write('tag', 42, step=0)) - def testWrite_shouldRecordSummaries(self): + def testWrite_recordIf_constant(self): logdir = self.get_temp_dir() with context.eager_mode(): with summary_ops.create_file_writer(logdir).as_default(): - self.assertTrue(summary_ops.write('default_on', 1, step=0)) - with summary_ops.always_record_summaries(): + self.assertTrue(summary_ops.write('default', 1, step=0)) + with summary_ops.record_if(True): self.assertTrue(summary_ops.write('set_on', 1, step=0)) - with summary_ops.never_record_summaries(): + with summary_ops.record_if(False): self.assertFalse(summary_ops.write('set_off', 1, step=0)) events = events_from_logdir(logdir) self.assertEqual(3, len(events)) - self.assertEqual('default_on', events[1].summary.value[0].tag) + self.assertEqual('default', events[1].summary.value[0].tag) self.assertEqual('set_on', events[2].summary.value[0].tag) - def testWrite_shouldRecordSummaries_fromFunction(self): + def testWrite_recordIf_constant_fromFunction(self): logdir = self.get_temp_dir() @def_function.function - def f(tag_prefix): + def f(): + with summary_ops.create_file_writer(logdir).as_default(): + # Use assertAllEqual instead of assertTrue since it works in a defun. + self.assertAllEqual(summary_ops.write('default', 1, step=0), True) + with summary_ops.record_if(True): + self.assertAllEqual(summary_ops.write('set_on', 1, step=0), True) + with summary_ops.record_if(False): + self.assertAllEqual(summary_ops.write('set_off', 1, step=0), False) + with context.eager_mode(): + f() + events = events_from_logdir(logdir) + self.assertEqual(3, len(events)) + self.assertEqual('default', events[1].summary.value[0].tag) + self.assertEqual('set_on', events[2].summary.value[0].tag) + + def testWrite_recordIf_callable(self): + logdir = self.get_temp_dir() + with context.eager_mode(): + step = variables.Variable(-1, dtype=dtypes.int64) + def record_fn(): + step.assign_add(1) + return int(step % 2) == 0 + with summary_ops.create_file_writer(logdir).as_default(): + with summary_ops.record_if(record_fn): + self.assertTrue(summary_ops.write('tag', 1, step=step)) + self.assertFalse(summary_ops.write('tag', 1, step=step)) + self.assertTrue(summary_ops.write('tag', 1, step=step)) + self.assertFalse(summary_ops.write('tag', 1, step=step)) + self.assertTrue(summary_ops.write('tag', 1, step=step)) + events = events_from_logdir(logdir) + self.assertEqual(4, len(events)) + self.assertEqual(0, events[1].step) + self.assertEqual(2, events[2].step) + self.assertEqual(4, events[3].step) + + def testWrite_recordIf_callable_fromFunction(self): + logdir = self.get_temp_dir() + with context.eager_mode(): + step = variables.Variable(-1, dtype=dtypes.int64) + @def_function.function + def record_fn(): + step.assign_add(1) + return math_ops.equal(step % 2, 0) + @def_function.function + def f(): + with summary_ops.create_file_writer(logdir).as_default(): + with summary_ops.record_if(record_fn): + return [ + summary_ops.write('tag', 1, step=step), + summary_ops.write('tag', 1, step=step), + summary_ops.write('tag', 1, step=step)] + self.assertAllEqual(f(), [True, False, True]) + self.assertAllEqual(f(), [False, True, False]) + events = events_from_logdir(logdir) + self.assertEqual(4, len(events)) + self.assertEqual(0, events[1].step) + self.assertEqual(2, events[2].step) + self.assertEqual(4, events[3].step) + + def testWrite_recordIf_tensorInput_fromFunction(self): + logdir = self.get_temp_dir() + @def_function.function(input_signature=[ + tensor_spec.TensorSpec(shape=[], dtype=dtypes.int64)]) + def f(step): with summary_ops.create_file_writer(logdir).as_default(): - default_output = summary_ops.write(tag_prefix + '_default', 1, step=0) - with summary_ops.always_record_summaries(): - on_output = summary_ops.write(tag_prefix + '_on', 1, step=0) - with summary_ops.never_record_summaries(): - off_output = summary_ops.write(tag_prefix + '_off', 1, step=0) - return [default_output, on_output, off_output] + with summary_ops.record_if(math_ops.equal(step % 2, 0)): + return summary_ops.write('tag', 1, step=step) with context.eager_mode(): - self.assertAllEqual([True, True, False], f('default')) - with summary_ops.always_record_summaries(): - self.assertAllEqual([True, True, False], f('on')) - with summary_ops.never_record_summaries(): - self.assertAllEqual([False, True, False], f('off')) + self.assertTrue(f(0)) + self.assertFalse(f(1)) + self.assertTrue(f(2)) + self.assertFalse(f(3)) + self.assertTrue(f(4)) events = events_from_logdir(logdir) - self.assertEqual(6, len(events)) - self.assertEqual('default_default', events[1].summary.value[0].tag) - self.assertEqual('default_on', events[2].summary.value[0].tag) - self.assertEqual('on_default', events[3].summary.value[0].tag) - self.assertEqual('on_on', events[4].summary.value[0].tag) - self.assertEqual('off_on', events[5].summary.value[0].tag) + self.assertEqual(4, len(events)) + self.assertEqual(0, events[1].step) + self.assertEqual(2, events[2].step) + self.assertEqual(4, events[3].step) @test_util.also_run_as_tf_function def testSummaryScope(self): @@ -223,6 +285,137 @@ class SummaryOpsTest(test_util.TensorFlowTestCase): self.assertEqual('with/slash', tag) +class SummaryWriterTest(test_util.TensorFlowTestCase): + + def testWriterInitAndClose(self): + logdir = self.get_temp_dir() + with context.eager_mode(): + writer = summary_ops.create_file_writer( + logdir, max_queue=1000, flush_millis=1000000) + files = gfile.Glob(os.path.join(logdir, '*')) + self.assertEqual(1, len(files)) + file1 = files[0] + self.assertEqual(1, len(events_from_file(file1))) # file_version Event + # Calling init() again while writer is open has no effect + writer.init() + self.assertEqual(1, len(events_from_file(file1))) + with writer.as_default(): + summary_ops.write('tag', 1, step=0) + self.assertEqual(1, len(events_from_file(file1))) + # Calling .close() should do an implicit flush + writer.close() + self.assertEqual(2, len(events_from_file(file1))) + # Calling init() on a closed writer should start a new file + time.sleep(1.1) # Ensure filename has a different timestamp + writer.init() + files = gfile.Glob(os.path.join(logdir, '*')) + self.assertEqual(2, len(files)) + files.remove(file1) + file2 = files[0] + self.assertEqual(1, len(events_from_file(file2))) # file_version + self.assertEqual(2, len(events_from_file(file1))) # should be unchanged + + def testSharedName(self): + logdir = self.get_temp_dir() + with context.eager_mode(): + # Create with default shared name (should match logdir) + writer1 = summary_ops.create_file_writer(logdir) + with writer1.as_default(): + summary_ops.write('tag', 1, step=1) + summary_ops.flush() + # Create with explicit logdir shared name (should be same resource/file) + shared_name = 'logdir:' + logdir + writer2 = summary_ops.create_file_writer(logdir, name=shared_name) + with writer2.as_default(): + summary_ops.write('tag', 1, step=2) + summary_ops.flush() + # Create with different shared name (should be separate resource/file) + time.sleep(1.1) # Ensure filename has a different timestamp + writer3 = summary_ops.create_file_writer(logdir, name='other') + with writer3.as_default(): + summary_ops.write('tag', 1, step=3) + summary_ops.flush() + + event_files = iter(sorted(gfile.Glob(os.path.join(logdir, '*')))) + + # First file has tags "one" and "two" + events = iter(events_from_file(next(event_files))) + self.assertEqual('brain.Event:2', next(events).file_version) + self.assertEqual(1, next(events).step) + self.assertEqual(2, next(events).step) + self.assertRaises(StopIteration, lambda: next(events)) + + # Second file has tag "three" + events = iter(events_from_file(next(event_files))) + self.assertEqual('brain.Event:2', next(events).file_version) + self.assertEqual(3, next(events).step) + self.assertRaises(StopIteration, lambda: next(events)) + + # No more files + self.assertRaises(StopIteration, lambda: next(event_files)) + + def testMaxQueue(self): + logdir = self.get_temp_dir() + with context.eager_mode(): + with summary_ops.create_file_writer( + logdir, max_queue=1, flush_millis=999999).as_default(): + get_total = lambda: len(events_from_logdir(logdir)) + # Note: First tf.Event is always file_version. + self.assertEqual(1, get_total()) + summary_ops.write('tag', 1, step=0) + self.assertEqual(1, get_total()) + # Should flush after second summary since max_queue = 1 + summary_ops.write('tag', 1, step=0) + self.assertEqual(3, get_total()) + + def testWriterFlush(self): + logdir = self.get_temp_dir() + get_total = lambda: len(events_from_logdir(logdir)) + with context.eager_mode(): + writer = summary_ops.create_file_writer( + logdir, max_queue=1000, flush_millis=1000000) + self.assertEqual(1, get_total()) # file_version Event + with writer.as_default(): + summary_ops.write('tag', 1, step=0) + self.assertEqual(1, get_total()) + writer.flush() + self.assertEqual(2, get_total()) + summary_ops.write('tag', 1, step=0) + self.assertEqual(2, get_total()) + # Exiting the "as_default()" should do an implicit flush + self.assertEqual(3, get_total()) + + def testFlushFunction(self): + logdir = self.get_temp_dir() + with context.eager_mode(): + writer = summary_ops.create_file_writer( + logdir, max_queue=999999, flush_millis=999999) + with writer.as_default(), summary_ops.always_record_summaries(): + get_total = lambda: len(events_from_logdir(logdir)) + # Note: First tf.Event is always file_version. + self.assertEqual(1, get_total()) + summary_ops.write('tag', 1, step=0) + summary_ops.write('tag', 1, step=0) + self.assertEqual(1, get_total()) + summary_ops.flush() + self.assertEqual(3, get_total()) + # Test "writer" parameter + summary_ops.write('tag', 1, step=0) + self.assertEqual(3, get_total()) + summary_ops.flush(writer=writer) + self.assertEqual(4, get_total()) + summary_ops.write('tag', 1, step=0) + self.assertEqual(4, get_total()) + summary_ops.flush(writer=writer._resource) # pylint:disable=protected-access + self.assertEqual(5, get_total()) + + @test_util.assert_no_new_pyobjects_executing_eagerly + def testEagerMemory(self): + logdir = self.get_temp_dir() + with summary_ops.create_file_writer(logdir).as_default(): + summary_ops.write('tag', 1, step=0) + + def events_from_file(filepath): """Returns all events in a single event file. -- GitLab From b44b443436bd8f9f036fb1dbf3130eca270203ed Mon Sep 17 00:00:00 2001 From: Gunhan Gulsoy Date: Wed, 20 Feb 2019 15:39:00 -0800 Subject: [PATCH 202/766] Rewrite tf kernel dso dependencies. Now we have a single shared object for multiple kernels. PiperOrigin-RevId: 234883753 --- tensorflow/tensorflow.bzl | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/tensorflow/tensorflow.bzl b/tensorflow/tensorflow.bzl index a7205a42ee..6163236791 100644 --- a/tensorflow/tensorflow.bzl +++ b/tensorflow/tensorflow.bzl @@ -386,16 +386,13 @@ def tf_binary_additional_srcs(): ], ) -def _linux_kernel_dso_name(kernel_build_target): - """Given a build target, construct the dso name for linux.""" - parts = kernel_build_target.split(":") - return "%s:libtfkernel_%s.so" % (parts[0], parts[1]) - # Helper functions to add kernel dependencies to tf binaries when using dynamic # kernel linking. -def tf_binary_dynamic_kernel_dsos(kernels): +def tf_binary_dynamic_kernel_dsos(): return if_dynamic_kernels( - extra_deps = [_linux_kernel_dso_name(k) for k in kernels], + extra_deps = [ + "//tensorflow/core/kernels:libtfkernel_all_kernels.so", + ], otherwise = [], ) @@ -419,9 +416,9 @@ def tf_cc_shared_object( native.cc_binary( name = name, srcs = srcs + framework_so, - deps = deps + tf_binary_dynamic_kernel_deps(kernels), + deps = deps, linkshared = 1, - data = data + tf_binary_dynamic_kernel_dsos(kernels), + data = data + tf_binary_dynamic_kernel_dsos(), linkopts = linkopts + _rpath_linkopts(name) + select({ clean_dep("//tensorflow:darwin"): [ "-Wl,-install_name,@rpath/" + name.split("/")[-1], @@ -452,6 +449,11 @@ def tf_cc_binary( copts = tf_copts(), kernels = [], **kwargs): + if kernels: + added_data_deps = tf_binary_dynamic_kernel_dsos() + else: + added_data_deps = [] + native.cc_binary( name = name, copts = copts, @@ -461,7 +463,7 @@ def tf_cc_binary( clean_dep("//third_party/mkl:intel_binary_blob"), ], ), - data = data + tf_binary_dynamic_kernel_dsos(kernels), + data = depset(data + added_data_deps), linkopts = linkopts + _rpath_linkopts(name), **kwargs ) @@ -809,7 +811,7 @@ def tf_cc_test( clean_dep("//third_party/mkl:intel_binary_blob"), ], ), - data = data + tf_binary_dynamic_kernel_dsos(kernels), + data = data + tf_binary_dynamic_kernel_dsos(), exec_compatible_with = tf_exec_compatible_with(kwargs), # Nested select() statements seem not to be supported when passed to # linkstatic, and we already have a cuda select() passed in to this @@ -931,7 +933,7 @@ def tf_cuda_only_cc_test( args = args, copts = _cuda_copts() + rocm_copts() + tf_copts(), features = if_cuda(["-use_header_modules"]), - data = data + tf_binary_dynamic_kernel_dsos(kernels), + data = data + tf_binary_dynamic_kernel_dsos(), deps = deps + tf_binary_dynamic_kernel_deps(kernels) + if_cuda_is_configured([ clean_dep("//tensorflow/core:cuda"), clean_dep("//tensorflow/core:gpu_lib"), @@ -1010,7 +1012,7 @@ def tf_cc_test_mkl( ], }) + _rpath_linkopts(src_to_test_name(src)), deps = deps + tf_binary_dynamic_kernel_deps(kernels) + mkl_deps(), - data = data + tf_binary_dynamic_kernel_dsos(kernels), + data = data + tf_binary_dynamic_kernel_dsos(), exec_compatible_with = tf_exec_compatible_with({"tags": tags}), linkstatic = linkstatic, tags = tags, @@ -1064,7 +1066,7 @@ def tf_java_test( native.java_test( name = name, srcs = srcs, - deps = deps + tf_binary_additional_srcs() + tf_binary_dynamic_kernel_dsos(kernels) + tf_binary_dynamic_kernel_deps(kernels), + deps = deps + tf_binary_additional_srcs() + tf_binary_dynamic_kernel_dsos() + tf_binary_dynamic_kernel_deps(kernels), *args, **kwargs ) @@ -1784,7 +1786,7 @@ def py_test(deps = [], data = [], kernels = [], **kwargs): data = data + select({ "//conditions:default": [], clean_dep("//tensorflow:no_tensorflow_py_deps"): ["//tensorflow/tools/pip_package:win_pip_package_marker"], - }) + tf_binary_dynamic_kernel_dsos(kernels), + }) + tf_binary_dynamic_kernel_dsos(), exec_compatible_with = tf_exec_compatible_with(kwargs), **kwargs ) -- GitLab From bb071775c75015614fb98bea6b2eaafc5a43809f Mon Sep 17 00:00:00 2001 From: Gunhan Gulsoy Date: Wed, 20 Feb 2019 15:40:18 -0800 Subject: [PATCH 203/766] Remove dependencies from kernel libraries to ops. PiperOrigin-RevId: 234883994 --- tensorflow/core/kernels/BUILD | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tensorflow/core/kernels/BUILD b/tensorflow/core/kernels/BUILD index 1ac147c65c..e630e962a4 100644 --- a/tensorflow/core/kernels/BUILD +++ b/tensorflow/core/kernels/BUILD @@ -4340,7 +4340,6 @@ tf_kernel_library( "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", - "//tensorflow/core:stateful_random_ops_op_lib", "@com_google_absl//absl/strings", "@com_google_absl//absl/types:variant", ], @@ -6801,8 +6800,6 @@ tf_mkl_kernel_library( "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", - "//tensorflow/core:mkl_nn_ops_op_lib", - "//tensorflow/core:nn_ops_op_lib", ] + mkl_deps(), ) @@ -6944,12 +6941,9 @@ tf_mkl_kernel_library( "//third_party/eigen3", "@gemmlowp", "@mkl_dnn", - "//tensorflow/core:array_ops_op_lib", "//tensorflow/core:core_cpu", "//tensorflow/core:framework", "//tensorflow/core:lib", - "//tensorflow/core:math_ops_op_lib", - "//tensorflow/core:nn_ops_op_lib", "//third_party/mkl:intel_binary_blob", ], ), -- GitLab From 8e0399153413f64769c6a1cf6884dde7923c6e96 Mon Sep 17 00:00:00 2001 From: Yuefeng Zhou Date: Wed, 20 Feb 2019 15:46:42 -0800 Subject: [PATCH 204/766] Fix the issue that CollectiveAllReduceStrategy inherits from MirroredStrategy whose reduction method bypasses collective ops when input is a tensor PiperOrigin-RevId: 234885084 --- tensorflow/contrib/distribute/python/BUILD | 1 + .../python/cross_device_ops_test.py | 152 +++++++++++++----- .../collective_all_reduce_strategy.py | 24 +++ 3 files changed, 141 insertions(+), 36 deletions(-) diff --git a/tensorflow/contrib/distribute/python/BUILD b/tensorflow/contrib/distribute/python/BUILD index 63879968bf..2ab94d0056 100644 --- a/tensorflow/contrib/distribute/python/BUILD +++ b/tensorflow/contrib/distribute/python/BUILD @@ -515,6 +515,7 @@ cuda_py_test( name = "cross_device_ops_test", srcs = ["cross_device_ops_test.py"], additional_deps = [ + ":collective_all_reduce_strategy", ":combinations", ":multi_worker_test_base", ":mirrored_strategy", diff --git a/tensorflow/contrib/distribute/python/cross_device_ops_test.py b/tensorflow/contrib/distribute/python/cross_device_ops_test.py index 2d56c25f10..2b8e019796 100644 --- a/tensorflow/contrib/distribute/python/cross_device_ops_test.py +++ b/tensorflow/contrib/distribute/python/cross_device_ops_test.py @@ -23,6 +23,7 @@ import itertools from absl.testing import parameterized import numpy as np +from tensorflow.contrib.distribute.python import collective_all_reduce_strategy from tensorflow.contrib.distribute.python import combinations from tensorflow.contrib.distribute.python import mirrored_strategy from tensorflow.contrib.distribute.python import multi_worker_test_base @@ -429,6 +430,9 @@ class MultiWorkerCrossDeviceOpsTest(multi_worker_test_base.MultiWorkerTestBase, self._testReductionAndBroadcast(cross_device_ops, distribution) +NUM_WORKERS = 3 + + class MultiWorkerCollectiveAllReduceTest( multi_worker_test_base.MultiWorkerTestBase, parameterized.TestCase): @@ -436,9 +440,9 @@ class MultiWorkerCollectiveAllReduceTest( @classmethod def setUpClass(cls): - """Create a local cluster with 2 workers.""" + """Create a local cluster with 3 workers.""" cls._cluster_spec = multi_worker_test_base.create_in_process_cluster( - num_workers=3, num_ps=0) + num_workers=NUM_WORKERS, num_ps=0) def setUp(self): super(MultiWorkerCollectiveAllReduceTest, self).setUp() @@ -446,7 +450,12 @@ class MultiWorkerCollectiveAllReduceTest( # collective key base for different tests. MultiWorkerCollectiveAllReduceTest.collective_key_base += 100000 - def _get_test_objects(self, task_type, task_id, num_gpus=0, local_mode=False): + def _get_test_objects(self, + task_type, + task_id, + num_gpus=0, + use_strategy_object=False, + local_mode=False): collective_keys = cross_device_utils.CollectiveKeys( group_key_start=10 * num_gpus + MultiWorkerCollectiveAllReduceTest.collective_key_base, @@ -455,16 +464,24 @@ class MultiWorkerCollectiveAllReduceTest( instance_key_with_id_start=num_gpus * 10000 + MultiWorkerCollectiveAllReduceTest.collective_key_base) if local_mode: - collective_all_reduce_ops = cross_device_ops_lib.CollectiveAllReduce( - 1, num_gpus, collective_keys=collective_keys) if num_gpus: devices = ["/device:GPU:%d" % i for i in range(num_gpus)] else: devices = ["/device:CPU:0"] - return collective_all_reduce_ops, devices, "" + + if use_strategy_object: + # Still using contrib CollectiveAllReduceStrategy because we can specify + # num_gpus in its constructor. + strategy = collective_all_reduce_strategy.CollectiveAllReduceStrategy( + num_gpus_per_worker=num_gpus) + strategy.extended._collective_keys = collective_keys + strategy.extended._cross_device_ops._collective_keys = collective_keys + return strategy, devices, "" + else: + collective_all_reduce_ops = cross_device_ops_lib.CollectiveAllReduce( + 1, num_gpus, collective_keys=collective_keys) + return collective_all_reduce_ops, devices, "" else: - collective_all_reduce_ops = cross_device_ops_lib.CollectiveAllReduce( - 3, num_gpus, collective_keys=collective_keys) if num_gpus: devices = [ "/job:%s/task:%d/device:GPU:%d" % (task_type, task_id, i) @@ -472,8 +489,23 @@ class MultiWorkerCollectiveAllReduceTest( ] else: devices = ["/job:%s/task:%d" % (task_type, task_id)] - return (collective_all_reduce_ops, devices, - "grpc://" + self._cluster_spec[task_type][task_id]) + + if use_strategy_object: + strategy = collective_all_reduce_strategy.CollectiveAllReduceStrategy( + num_gpus_per_worker=num_gpus) + strategy.configure( + cluster_spec=self._cluster_spec, + task_type=task_type, + task_id=task_id) + strategy.extended._collective_keys = collective_keys + strategy.extended._cross_device_ops._collective_keys = collective_keys + return (strategy, devices, + "grpc://" + self._cluster_spec[task_type][task_id]) + else: + collective_all_reduce_ops = cross_device_ops_lib.CollectiveAllReduce( + NUM_WORKERS, num_gpus, collective_keys=collective_keys) + return (collective_all_reduce_ops, devices, + "grpc://" + self._cluster_spec[task_type][task_id]) def _assert_values_equal(self, left, right, sess): if isinstance(left, list): @@ -493,9 +525,18 @@ class MultiWorkerCollectiveAllReduceTest( for l, r in zip(left_values, right_values): self.assertEqual(l, r) - def _test_reduction(self, task_type, task_id, num_gpus, local_mode=False): + def _test_reduction(self, + task_type, + task_id, + num_gpus, + use_strategy_object=False, + local_mode=False): collective_all_reduce, devices, master_target = self._get_test_objects( - task_type, task_id, num_gpus, local_mode=local_mode) + task_type, + task_id, + num_gpus, + use_strategy_object=use_strategy_object, + local_mode=local_mode) if local_mode: num_workers = 1 worker_device = None @@ -503,6 +544,27 @@ class MultiWorkerCollectiveAllReduceTest( num_workers = len(self._cluster_spec.get("chief", [])) + len( self._cluster_spec.get("worker", [])) worker_device = "/job:%s/task:%d" % (task_type, task_id) + + def _reduce(test_object, reduce_op, per_replica, destinations): + if use_strategy_object: + with test_object.scope(): + # Mimic the behavior that distribution strategy usually strips the + # wrapper if there is only one value. + if len(per_replica.values) == 1: + per_replica = per_replica.values[0] + return test_object.extended.reduce_to(reduce_op, per_replica, + destinations) + else: + return test_object.reduce(reduce_op, per_replica, destinations) + + def _batch_reduce(test_object, reduce_op, value_destination_pairs): + if use_strategy_object: + with test_object.scope(): + return test_object.extended.batch_reduce_to(reduce_op, + value_destination_pairs) + else: + return test_object.batch_reduce(reduce_op, value_destination_pairs) + with ops.Graph().as_default(), \ ops.device(worker_device), \ self.cached_session(target=master_target) as sess: @@ -527,26 +589,30 @@ class MultiWorkerCollectiveAllReduceTest( # test reduce() for destinations in all_destinations: self._assert_values_equal( - collective_all_reduce.reduce( + _reduce( + collective_all_reduce, reduce_util.ReduceOp.MEAN, per_replica, - destinations=destinations), - _fake_mirrored(mean, destinations), sess) + destinations=destinations), _fake_mirrored(mean, destinations), + sess) self._assert_values_equal( - collective_all_reduce.reduce( + _reduce( + collective_all_reduce, reduce_util.ReduceOp.MEAN, per_replica_2, - destinations=destinations), - _fake_mirrored(mean_2, destinations), sess) + destinations=destinations), _fake_mirrored( + mean_2, destinations), sess) self._assert_values_equal( - collective_all_reduce.reduce( + _reduce( + collective_all_reduce, reduce_util.ReduceOp.SUM, per_replica, destinations=destinations), _fake_mirrored(mean * len(devices) * num_workers, destinations), sess) self._assert_values_equal( - collective_all_reduce.reduce( + _reduce( + collective_all_reduce, reduce_util.ReduceOp.SUM, per_replica_2, destinations=destinations), @@ -556,17 +622,13 @@ class MultiWorkerCollectiveAllReduceTest( # test batch_reduce() for d1, d2 in itertools.product(all_destinations, all_destinations): self._assert_values_equal( - collective_all_reduce.batch_reduce(reduce_util.ReduceOp.MEAN, - [(per_replica, d1), - (per_replica_2, d2)]), - [ - _fake_mirrored(mean, d1), - _fake_mirrored(mean_2, d2) - ], sess) + _batch_reduce(collective_all_reduce, reduce_util.ReduceOp.MEAN, + [(per_replica, d1), (per_replica_2, d2)]), + [_fake_mirrored(mean, d1), + _fake_mirrored(mean_2, d2)], sess) self._assert_values_equal( - collective_all_reduce.batch_reduce(reduce_util.ReduceOp.SUM, - [(per_replica, d1), - (per_replica_2, d2)]), + _batch_reduce(collective_all_reduce, reduce_util.ReduceOp.SUM, + [(per_replica, d1), (per_replica_2, d2)]), [ _fake_mirrored(mean * len(devices) * num_workers, d1), _fake_mirrored(mean_2 * len(devices) * num_workers, d2) @@ -575,18 +637,36 @@ class MultiWorkerCollectiveAllReduceTest( return True @combinations.generate( - combinations.combine(mode=["graph"], num_gpus=[0, 1, 2], required_gpus=1)) - def testReductionDistributed(self, num_gpus): + combinations.combine( + mode=["graph"], + num_gpus=[0, 1, 2], + required_gpus=1, + use_strategy_object=[True, False])) + def testReductionDistributed(self, num_gpus, use_strategy_object): if context.num_gpus() < num_gpus: return - self._run_between_graph_clients(self._test_reduction, self._cluster_spec, - num_gpus) + self._run_between_graph_clients( + self._test_reduction, + self._cluster_spec, + num_gpus, + use_strategy_object=use_strategy_object) # Collective ops doesn't support strategy with one device. - def testReductionLocal(self, num_gpus=2): + @combinations.generate( + combinations.combine( + mode=["graph"], + num_gpus=[2], + required_gpus=2, + use_strategy_object=[True, False])) + def testReductionLocal(self, num_gpus, use_strategy_object): if context.num_gpus() < num_gpus: return - self._test_reduction(None, None, num_gpus, local_mode=True) + self._test_reduction( + None, + None, + num_gpus, + use_strategy_object=use_strategy_object, + local_mode=True) if __name__ == "__main__": diff --git a/tensorflow/python/distribute/collective_all_reduce_strategy.py b/tensorflow/python/distribute/collective_all_reduce_strategy.py index 74d3030270..25bffe4015 100644 --- a/tensorflow/python/distribute/collective_all_reduce_strategy.py +++ b/tensorflow/python/distribute/collective_all_reduce_strategy.py @@ -29,6 +29,7 @@ from tensorflow.python.distribute import input_lib from tensorflow.python.distribute import mirrored_strategy from tensorflow.python.distribute import multi_worker_util from tensorflow.python.distribute import numpy_dataset +from tensorflow.python.distribute import reduce_util from tensorflow.python.distribute import values from tensorflow.python.distribute.cluster_resolver import SimpleClusterResolver from tensorflow.python.distribute.cluster_resolver import TFConfigClusterResolver @@ -354,6 +355,29 @@ class CollectiveAllReduceExtended(mirrored_strategy.MirroredExtended): return updated_config + def _reduce_to(self, reduce_op, value, destinations): + if (isinstance(value, values.Mirrored) and + reduce_op == reduce_util.ReduceOp.MEAN): + return value + assert not isinstance(value, values.Mirrored) + + if (isinstance(value, values.DistributedValues) and + len(self.worker_devices) == 1): + value = value.values[0] + + # When there are multiple workers, we need to reduce across workers using + # collective ops. + if (not isinstance(value, values.DistributedValues) and + self._num_workers == 1): + # This function handles reducing values that are not PerReplica or + # Mirrored values. For example, the same value could be present on all + # replicas in which case `value` would be a single value or value could + # be 0. + return cross_device_ops_lib.reduce_non_distributed_value( + reduce_op, self._device_map, value, destinations) + return self._get_cross_device_ops().reduce( + reduce_op, value, destinations=destinations) + @property def experimental_between_graph(self): return True -- GitLab From 6513629c353a72eadbce31e53e08ad68ad573a1f Mon Sep 17 00:00:00 2001 From: Pavithra Vijay Date: Wed, 20 Feb 2019 15:47:23 -0800 Subject: [PATCH 205/766] Updating cosine similarity loss. - Changed CosineProximity to CosineSimilarity in v2 - Added `tf.keras.losses.cosine_similarity` to the namespaces. Keeping the existing v1 namespaces. - Removed the negate sign from cosine similarity. PiperOrigin-RevId: 234885212 --- .../keras/api/keras/losses/__init__.py | 2 +- .../keras/api/keras/metrics/__init__.py | 2 +- tensorflow/python/keras/losses.py | 63 ++++++++++--------- tensorflow/python/keras/losses_test.py | 23 ++++--- tensorflow/python/keras/metrics.py | 40 +++++++----- tensorflow/python/keras/metrics_test.py | 14 ++--- ...low.keras.losses.-cosine-similarity.pbtxt} | 7 ++- .../golden/v1/tensorflow.keras.losses.pbtxt | 6 +- ...ow.keras.metrics.-cosine-similarity.pbtxt} | 6 +- .../golden/v1/tensorflow.keras.metrics.pbtxt | 2 +- ...low.keras.losses.-cosine-similarity.pbtxt} | 7 ++- .../golden/v2/tensorflow.keras.losses.pbtxt | 8 +-- ...ow.keras.metrics.-cosine-similarity.pbtxt} | 6 +- .../golden/v2/tensorflow.keras.metrics.pbtxt | 10 +-- ...ensorflow.losses.-cosine-similarity.pbtxt} | 7 ++- .../api/golden/v2/tensorflow.losses.pbtxt | 8 +-- ...nsorflow.metrics.-cosine-similarity.pbtxt} | 6 +- .../api/golden/v2/tensorflow.metrics.pbtxt | 10 +-- 18 files changed, 110 insertions(+), 117 deletions(-) rename tensorflow/tools/api/golden/{v2/tensorflow.losses.-cosine-proximity.pbtxt => v1/tensorflow.keras.losses.-cosine-similarity.pbtxt} (73%) rename tensorflow/tools/api/golden/v1/{tensorflow.keras.metrics.-cosine-proximity.pbtxt => tensorflow.keras.metrics.-cosine-similarity.pbtxt} (96%) rename tensorflow/tools/api/golden/v2/{tensorflow.keras.losses.-cosine-proximity.pbtxt => tensorflow.keras.losses.-cosine-similarity.pbtxt} (73%) rename tensorflow/tools/api/golden/v2/{tensorflow.keras.metrics.-cosine-proximity.pbtxt => tensorflow.keras.metrics.-cosine-similarity.pbtxt} (96%) rename tensorflow/tools/api/golden/{v1/tensorflow.keras.losses.-cosine-proximity.pbtxt => v2/tensorflow.losses.-cosine-similarity.pbtxt} (74%) rename tensorflow/tools/api/golden/v2/{tensorflow.metrics.-cosine-proximity.pbtxt => tensorflow.metrics.-cosine-similarity.pbtxt} (96%) diff --git a/tensorflow/contrib/keras/api/keras/losses/__init__.py b/tensorflow/contrib/keras/api/keras/losses/__init__.py index c4476a7bbd..b12832d2e2 100644 --- a/tensorflow/contrib/keras/api/keras/losses/__init__.py +++ b/tensorflow/contrib/keras/api/keras/losses/__init__.py @@ -22,7 +22,7 @@ from __future__ import print_function from tensorflow.python.keras.losses import binary_crossentropy from tensorflow.python.keras.losses import categorical_crossentropy from tensorflow.python.keras.losses import categorical_hinge -from tensorflow.python.keras.losses import cosine_proximity +from tensorflow.python.keras.losses import cosine_similarity from tensorflow.python.keras.losses import hinge from tensorflow.python.keras.losses import kullback_leibler_divergence from tensorflow.python.keras.losses import logcosh diff --git a/tensorflow/contrib/keras/api/keras/metrics/__init__.py b/tensorflow/contrib/keras/api/keras/metrics/__init__.py index 7317fdb52c..095b5d798d 100644 --- a/tensorflow/contrib/keras/api/keras/metrics/__init__.py +++ b/tensorflow/contrib/keras/api/keras/metrics/__init__.py @@ -23,7 +23,7 @@ from tensorflow.python.keras.metrics import binary_accuracy from tensorflow.python.keras.metrics import binary_crossentropy from tensorflow.python.keras.metrics import categorical_accuracy from tensorflow.python.keras.metrics import categorical_crossentropy -from tensorflow.python.keras.metrics import cosine_proximity +from tensorflow.python.keras.metrics import cosine_similarity from tensorflow.python.keras.metrics import hinge from tensorflow.python.keras.metrics import kullback_leibler_divergence from tensorflow.python.keras.metrics import mean_absolute_error diff --git a/tensorflow/python/keras/losses.py b/tensorflow/python/keras/losses.py index 1bffcab5c7..2d8358cab4 100644 --- a/tensorflow/python/keras/losses.py +++ b/tensorflow/python/keras/losses.py @@ -943,38 +943,53 @@ def poisson(y_true, y_pred): return K.mean(y_pred - y_true * math_ops.log(y_pred + K.epsilon()), axis=-1) -@keras_export('keras.metrics.cosine_proximity', - 'keras.metrics.cosine', - 'keras.losses.cosine_proximity', - 'keras.losses.cosine') +# Retaining the legacy namespaces: 'cosine_proximity' and 'cosine'. +# TODO(psv): Change name of this function to `cosine_similarity` after fixing +# estimator test. +@keras_export( + 'keras.losses.cosine_similarity', + v1=[ + 'keras.metrics.cosine_proximity', + 'keras.metrics.cosine', + 'keras.losses.cosine_proximity', + 'keras.losses.cosine', + 'keras.losses.cosine_similarity', + ]) def cosine_proximity(y_true, y_pred, axis=-1): + """Computes the cosine similarity between labels and predictions.""" y_true = nn.l2_normalize(y_true, axis=axis) y_pred = nn.l2_normalize(y_pred, axis=axis) - return -math_ops.reduce_sum(y_true * y_pred, axis=axis) + return math_ops.reduce_sum(y_true * y_pred, axis=axis) -@keras_export('keras.losses.CosineProximity') -class CosineProximity(Loss): - """Computes the cosine proximity between `y_true` and `y_pred`. +@keras_export('keras.losses.CosineSimilarity') +class CosineSimilarity(LossFunctionWrapper): + """Computes the cosine similarity between `y_true` and `y_pred`. Usage: ```python - cosine_loss = tf.keras.losses.CosineProximity() - loss = cosine_loss([0., 1., 1.], [1., 0., 1.]) - print('Loss: ', loss.numpy()) # Loss: -0.5 + cosine_loss = tf.keras.losses.CosineSimilarity(axis=1) + loss = cosine_loss([[0., 1.], [1., 1.]], [[1., 0.], [1., 1.]]) + # l2_norm(y_true) = [[0., 1.], [1./1.414], 1./1.414]]] + # l2_norm(y_pred) = [[1., 0.], [1./1.414], 1./1.414]]] + # l2_norm(y_true) . l2_norm(y_pred) = [[0., 0.], [0.5, 0.5]] + # loss = mean(sum(l2_norm(y_true) . l2_norm(y_pred), axis=1)) + = ((0. + 0.) + (0.5 + 0.5)) / 2 + + print('Loss: ', loss.numpy()) # Loss: 0.5 ``` Usage with tf.keras API: ```python model = tf.keras.Model(inputs, outputs) - model.compile('sgd', loss=tf.keras.losses.CosineProximity()) + model.compile('sgd', loss=tf.keras.losses.CosineSimilarity(axis=1)) ``` Args: axis: (Optional) Defaults to -1. The dimension along which the cosine - proximity is computed. + similarity is computed. reduction: (Optional) Type of `tf.keras.losses.Reduction` to apply to loss. Default value is `SUM_OVER_BATCH_SIZE`. name: Optional name for the op. @@ -983,23 +998,9 @@ class CosineProximity(Loss): def __init__(self, axis=-1, reduction=losses_utils.ReductionV2.SUM_OVER_BATCH_SIZE, - name=None): - super(CosineProximity, self).__init__(reduction=reduction, name=name) - self.axis = axis - - def call(self, y_true, y_pred): - """Calculates the cosine proximity loss. - - Args: - y_true: Ground truth values. - y_pred: The predicted values. - - Returns: - Cosine distance loss. - """ - y_pred = ops.convert_to_tensor(y_pred) - y_true = math_ops.cast(y_true, y_pred.dtype) - return cosine_proximity(y_true, y_pred, axis=self.axis) + name='cosine_similarity'): + super(CosineSimilarity, self).__init__( + cosine_similarity, reduction=reduction, name=name, axis=axis) # Aliases. @@ -1009,7 +1010,7 @@ mae = MAE = mean_absolute_error mape = MAPE = mean_absolute_percentage_error msle = MSLE = mean_squared_logarithmic_error kld = KLD = kullback_leibler_divergence -cosine = cosine_proximity +cosine_similarity = cosine_proximity def is_categorical_crossentropy(loss): diff --git a/tensorflow/python/keras/losses_test.py b/tensorflow/python/keras/losses_test.py index 48e4b68fc9..6fe68335e8 100644 --- a/tensorflow/python/keras/losses_test.py +++ b/tensorflow/python/keras/losses_test.py @@ -45,7 +45,7 @@ ALL_LOSSES = [keras.losses.mean_squared_error, keras.losses.binary_crossentropy, keras.losses.kullback_leibler_divergence, keras.losses.poisson, - keras.losses.cosine_proximity, + keras.losses.cosine_similarity, keras.losses.logcosh, keras.losses.categorical_hinge] @@ -501,7 +501,7 @@ class MeanSquaredLogarithmicErrorTest(test.TestCase): @test_util.run_all_in_graph_and_eager_modes -class CosineProximityTest(test.TestCase): +class CosineSimilarityTest(test.TestCase): def l2_norm(self, x, axis): epsilon = 1e-12 @@ -515,28 +515,27 @@ class CosineProximityTest(test.TestCase): y_true = self.l2_norm(self.np_y_true, axis) y_pred = self.l2_norm(self.np_y_pred, axis) - self.expected_loss = -np.sum(np.multiply(y_true, y_pred), axis=(axis,)) + self.expected_loss = np.sum(np.multiply(y_true, y_pred), axis=(axis,)) self.y_true = constant_op.constant(self.np_y_true) self.y_pred = constant_op.constant(self.np_y_pred) def test_config(self): - cosine_obj = keras.losses.CosineProximity( + cosine_obj = keras.losses.CosineSimilarity( axis=2, reduction=losses_utils.ReductionV2.SUM, name='cosine_loss') self.assertEqual(cosine_obj.name, 'cosine_loss') self.assertEqual(cosine_obj.reduction, losses_utils.ReductionV2.SUM) - self.assertEqual(cosine_obj.axis, 2) def test_unweighted(self): self.setup() - cosine_obj = keras.losses.CosineProximity() + cosine_obj = keras.losses.CosineSimilarity() loss = cosine_obj(self.y_true, self.y_pred) expected_loss = np.mean(self.expected_loss) self.assertAlmostEqual(self.evaluate(loss), expected_loss, 3) def test_scalar_weighted(self): self.setup() - cosine_obj = keras.losses.CosineProximity() + cosine_obj = keras.losses.CosineSimilarity() sample_weight = 2.3 loss = cosine_obj(self.y_true, self.y_pred, sample_weight=sample_weight) expected_loss = np.mean(self.expected_loss * sample_weight) @@ -544,7 +543,7 @@ class CosineProximityTest(test.TestCase): def test_sample_weighted(self): self.setup() - cosine_obj = keras.losses.CosineProximity() + cosine_obj = keras.losses.CosineSimilarity() sample_weight = np.asarray([1.2, 3.4]) loss = cosine_obj( self.y_true, @@ -555,14 +554,14 @@ class CosineProximityTest(test.TestCase): def test_timestep_weighted(self): self.setup() - cosine_obj = keras.losses.CosineProximity() + cosine_obj = keras.losses.CosineSimilarity() np_y_true = self.np_y_true.reshape((2, 3, 1)) np_y_pred = self.np_y_pred.reshape((2, 3, 1)) sample_weight = np.asarray([3, 6, 5, 0, 4, 2]).reshape((2, 3)) y_true = self.l2_norm(np_y_true, 2) y_pred = self.l2_norm(np_y_pred, 2) - expected_loss = -np.sum(np.multiply(y_true, y_pred), axis=(2,)) + expected_loss = np.sum(np.multiply(y_true, y_pred), axis=(2,)) y_true = constant_op.constant(np_y_true) y_pred = constant_op.constant(np_y_pred) @@ -574,13 +573,13 @@ class CosineProximityTest(test.TestCase): def test_zero_weighted(self): self.setup() - cosine_obj = keras.losses.CosineProximity() + cosine_obj = keras.losses.CosineSimilarity() loss = cosine_obj(self.y_true, self.y_pred, sample_weight=0) self.assertAlmostEqual(self.evaluate(loss), 0., 3) def test_axis(self): self.setup(axis=1) - cosine_obj = keras.losses.CosineProximity(axis=1) + cosine_obj = keras.losses.CosineSimilarity(axis=1) loss = cosine_obj(self.y_true, self.y_pred) expected_loss = np.mean(self.expected_loss) self.assertAlmostEqual(self.evaluate(loss), expected_loss, 3) diff --git a/tensorflow/python/keras/metrics.py b/tensorflow/python/keras/metrics.py index 9ceb03395f..b0b4a8dfb9 100644 --- a/tensorflow/python/keras/metrics.py +++ b/tensorflow/python/keras/metrics.py @@ -34,7 +34,7 @@ from tensorflow.python.keras.engine.base_layer import Layer from tensorflow.python.keras.losses import binary_crossentropy from tensorflow.python.keras.losses import categorical_crossentropy from tensorflow.python.keras.losses import categorical_hinge -from tensorflow.python.keras.losses import cosine_proximity +from tensorflow.python.keras.losses import cosine_similarity from tensorflow.python.keras.losses import hinge from tensorflow.python.keras.losses import kullback_leibler_divergence from tensorflow.python.keras.losses import logcosh @@ -1818,21 +1818,30 @@ class AUC(Metric): return dict(list(base_config.items()) + list(config.items())) -@keras_export('keras.metrics.CosineProximity') -class CosineProximity(MeanMetricWrapper): - """Computes the cosine distance between the labels and predictions. +@keras_export('keras.metrics.CosineSimilarity') +class CosineSimilarity(MeanMetricWrapper): + """Computes the cosine similarity between the labels and predictions. + + cosine similarity = (a . b) / ||a|| ||b|| + (https://en.wikipedia.org/wiki/Cosine_similarity) For example, if `y_true` is [0, 1, 1], and `y_pred` is [1, 0, 1], the cosine - proximity is -0.5. + similarity is 0.5. - This metric keeps the average cosine distance between `predictions` and + This metric keeps the average cosine similarity between `predictions` and `labels` over a stream of data. Usage: ```python - m = tf.keras.metrics.CosineProximity() - m.update_state([0, 1, 1], [1, 0, 1]) - print('Final result: ', m.result().numpy()) # Final result: -0.5 + m = tf.keras.metrics.CosineSimilarity(axis=1) + m.update_state([[0., 1.], [1., 1.]], [[1., 0.], [1., 1.]]) + # l2_norm(y_true) = [[0., 1.], [1./1.414], 1./1.414]]] + # l2_norm(y_pred) = [[1., 0.], [1./1.414], 1./1.414]]] + # l2_norm(y_true) . l2_norm(y_pred) = [[0., 0.], [0.5, 0.5]] + # result = mean(sum(l2_norm(y_true) . l2_norm(y_pred), axis=1)) + = ((0. + 0.) + (0.5 + 0.5)) / 2 + + print('Final result: ', m.result().numpy()) # Final result: 0.5 ``` Usage with tf.keras API: @@ -1842,20 +1851,21 @@ class CosineProximity(MeanMetricWrapper): model.compile( 'sgd', loss='mse', - metrics=[tf.keras.metrics.CosineProximity()]) + metrics=[tf.keras.metrics.CosineSimilarity(axis=1)]) ``` """ - def __init__(self, name='cosine_proximity', dtype=None, axis=-1): - """Creates a `CosineProximity` instance. + def __init__(self, name='cosine_similarity', dtype=None, axis=-1): + """Creates a `CosineSimilarity` instance. Args: name: (Optional) string name of the metric instance. dtype: (Optional) data type of the metric result. axis: (Optional) Defaults to -1. The dimension along which the cosine - proximity is computed. + similarity is computed. """ - super(CosineProximity, self).__init__(cosine, name, dtype=dtype, axis=axis) + super(CosineSimilarity, self).__init__( + cosine_similarity, name, dtype=dtype, axis=axis) @keras_export('keras.metrics.MeanAbsoluteError') @@ -2744,7 +2754,7 @@ mse = MSE = mean_squared_error mae = MAE = mean_absolute_error mape = MAPE = mean_absolute_percentage_error msle = MSLE = mean_squared_logarithmic_error -cosine = cosine_proximity +cosine_proximity = cosine_similarity def clone_metric(metric): diff --git a/tensorflow/python/keras/metrics_test.py b/tensorflow/python/keras/metrics_test.py index 274b0a5e10..ffe093532a 100644 --- a/tensorflow/python/keras/metrics_test.py +++ b/tensorflow/python/keras/metrics_test.py @@ -453,7 +453,7 @@ class KerasAccuracyTest(test.TestCase): @test_util.run_all_in_graph_and_eager_modes -class CosineProximityTest(test.TestCase): +class CosineSimilarityTest(test.TestCase): def l2_norm(self, x, axis): epsilon = 1e-12 @@ -467,25 +467,25 @@ class CosineProximityTest(test.TestCase): y_true = self.l2_norm(self.np_y_true, axis) y_pred = self.l2_norm(self.np_y_pred, axis) - self.expected_loss = -np.sum(np.multiply(y_true, y_pred), axis=(axis,)) + self.expected_loss = np.sum(np.multiply(y_true, y_pred), axis=(axis,)) self.y_true = constant_op.constant(self.np_y_true) self.y_pred = constant_op.constant(self.np_y_pred) def test_config(self): - cosine_obj = metrics.CosineProximity( + cosine_obj = metrics.CosineSimilarity( axis=2, name='my_cos', dtype=dtypes.int32) self.assertEqual(cosine_obj.name, 'my_cos') self.assertEqual(cosine_obj._dtype, dtypes.int32) # Check save and restore config - cosine_obj2 = metrics.CosineProximity.from_config(cosine_obj.get_config()) + cosine_obj2 = metrics.CosineSimilarity.from_config(cosine_obj.get_config()) self.assertEqual(cosine_obj2.name, 'my_cos') self.assertEqual(cosine_obj2._dtype, dtypes.int32) def test_unweighted(self): self.setup() - cosine_obj = metrics.CosineProximity() + cosine_obj = metrics.CosineSimilarity() self.evaluate(variables.variables_initializer(cosine_obj.variables)) loss = cosine_obj(self.y_true, self.y_pred) expected_loss = np.mean(self.expected_loss) @@ -493,7 +493,7 @@ class CosineProximityTest(test.TestCase): def test_weighted(self): self.setup() - cosine_obj = metrics.CosineProximity() + cosine_obj = metrics.CosineSimilarity() self.evaluate(variables.variables_initializer(cosine_obj.variables)) sample_weight = np.asarray([1.2, 3.4]) loss = cosine_obj( @@ -506,7 +506,7 @@ class CosineProximityTest(test.TestCase): def test_axis(self): self.setup(axis=1) - cosine_obj = metrics.CosineProximity(axis=1) + cosine_obj = metrics.CosineSimilarity(axis=1) self.evaluate(variables.variables_initializer(cosine_obj.variables)) loss = cosine_obj(self.y_true, self.y_pred) expected_loss = np.mean(self.expected_loss) diff --git a/tensorflow/tools/api/golden/v2/tensorflow.losses.-cosine-proximity.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.losses.-cosine-similarity.pbtxt similarity index 73% rename from tensorflow/tools/api/golden/v2/tensorflow.losses.-cosine-proximity.pbtxt rename to tensorflow/tools/api/golden/v1/tensorflow.keras.losses.-cosine-similarity.pbtxt index 3ec290213d..aa14c44fa3 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.losses.-cosine-proximity.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.losses.-cosine-similarity.pbtxt @@ -1,11 +1,12 @@ -path: "tensorflow.losses.CosineProximity" +path: "tensorflow.keras.losses.CosineSimilarity" tf_class { - is_instance: "" + is_instance: "" + is_instance: "" is_instance: "" is_instance: "" member_method { name: "__init__" - argspec: "args=[\'self\', \'axis\', \'reduction\', \'name\'], varargs=None, keywords=None, defaults=[\'-1\', \'sum_over_batch_size\', \'None\'], " + argspec: "args=[\'self\', \'axis\', \'reduction\', \'name\'], varargs=None, keywords=None, defaults=[\'-1\', \'sum_over_batch_size\', \'cosine_similarity\'], " } member_method { name: "call" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.losses.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.losses.pbtxt index 4240ff69a5..7e90b36ead 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.losses.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.losses.pbtxt @@ -13,7 +13,7 @@ tf_module { mtype: "" } member { - name: "CosineProximity" + name: "CosineSimilarity" mtype: "" } member { @@ -108,6 +108,10 @@ tf_module { name: "cosine_proximity" argspec: "args=[\'y_true\', \'y_pred\', \'axis\'], varargs=None, keywords=None, defaults=[\'-1\'], " } + member_method { + name: "cosine_similarity" + argspec: "args=[\'y_true\', \'y_pred\', \'axis\'], varargs=None, keywords=None, defaults=[\'-1\'], " + } member_method { name: "deserialize" argspec: "args=[\'name\', \'custom_objects\'], varargs=None, keywords=None, defaults=[\'None\'], " diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-cosine-proximity.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-cosine-similarity.pbtxt similarity index 96% rename from tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-cosine-proximity.pbtxt rename to tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-cosine-similarity.pbtxt index b247e5cf39..678c7b0681 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-cosine-proximity.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.-cosine-similarity.pbtxt @@ -1,6 +1,6 @@ -path: "tensorflow.keras.metrics.CosineProximity" +path: "tensorflow.keras.metrics.CosineSimilarity" tf_class { - is_instance: "" + is_instance: "" is_instance: "" is_instance: "" is_instance: "" @@ -90,7 +90,7 @@ tf_class { } member_method { name: "__init__" - argspec: "args=[\'self\', \'name\', \'dtype\', \'axis\'], varargs=None, keywords=None, defaults=[\'cosine_proximity\', \'None\', \'-1\'], " + argspec: "args=[\'self\', \'name\', \'dtype\', \'axis\'], varargs=None, keywords=None, defaults=[\'cosine_similarity\', \'None\', \'-1\'], " } member_method { name: "add_loss" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.pbtxt index 92dddacf2c..3f7fe4a2f4 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.metrics.pbtxt @@ -29,7 +29,7 @@ tf_module { mtype: "" } member { - name: "CosineProximity" + name: "CosineSimilarity" mtype: "" } member { diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.losses.-cosine-proximity.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.losses.-cosine-similarity.pbtxt similarity index 73% rename from tensorflow/tools/api/golden/v2/tensorflow.keras.losses.-cosine-proximity.pbtxt rename to tensorflow/tools/api/golden/v2/tensorflow.keras.losses.-cosine-similarity.pbtxt index 4952a76291..aa14c44fa3 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.losses.-cosine-proximity.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.losses.-cosine-similarity.pbtxt @@ -1,11 +1,12 @@ -path: "tensorflow.keras.losses.CosineProximity" +path: "tensorflow.keras.losses.CosineSimilarity" tf_class { - is_instance: "" + is_instance: "" + is_instance: "" is_instance: "" is_instance: "" member_method { name: "__init__" - argspec: "args=[\'self\', \'axis\', \'reduction\', \'name\'], varargs=None, keywords=None, defaults=[\'-1\', \'sum_over_batch_size\', \'None\'], " + argspec: "args=[\'self\', \'axis\', \'reduction\', \'name\'], varargs=None, keywords=None, defaults=[\'-1\', \'sum_over_batch_size\', \'cosine_similarity\'], " } member_method { name: "call" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.losses.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.losses.pbtxt index af9d3c12e3..4ec0e887fc 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.losses.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.losses.pbtxt @@ -13,7 +13,7 @@ tf_module { mtype: "" } member { - name: "CosineProximity" + name: "CosineSimilarity" mtype: "" } member { @@ -105,11 +105,7 @@ tf_module { argspec: "args=[\'y_true\', \'y_pred\'], varargs=None, keywords=None, defaults=None" } member_method { - name: "cosine" - argspec: "args=[\'y_true\', \'y_pred\', \'axis\'], varargs=None, keywords=None, defaults=[\'-1\'], " - } - member_method { - name: "cosine_proximity" + name: "cosine_similarity" argspec: "args=[\'y_true\', \'y_pred\', \'axis\'], varargs=None, keywords=None, defaults=[\'-1\'], " } member_method { diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-cosine-proximity.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-cosine-similarity.pbtxt similarity index 96% rename from tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-cosine-proximity.pbtxt rename to tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-cosine-similarity.pbtxt index b247e5cf39..678c7b0681 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-cosine-proximity.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.-cosine-similarity.pbtxt @@ -1,6 +1,6 @@ -path: "tensorflow.keras.metrics.CosineProximity" +path: "tensorflow.keras.metrics.CosineSimilarity" tf_class { - is_instance: "" + is_instance: "" is_instance: "" is_instance: "" is_instance: "" @@ -90,7 +90,7 @@ tf_class { } member_method { name: "__init__" - argspec: "args=[\'self\', \'name\', \'dtype\', \'axis\'], varargs=None, keywords=None, defaults=[\'cosine_proximity\', \'None\', \'-1\'], " + argspec: "args=[\'self\', \'name\', \'dtype\', \'axis\'], varargs=None, keywords=None, defaults=[\'cosine_similarity\', \'None\', \'-1\'], " } member_method { name: "add_loss" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.pbtxt index 92dddacf2c..71e89765cb 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.metrics.pbtxt @@ -29,7 +29,7 @@ tf_module { mtype: "" } member { - name: "CosineProximity" + name: "CosineSimilarity" mtype: "" } member { @@ -180,14 +180,6 @@ tf_module { name: "categorical_crossentropy" argspec: "args=[\'y_true\', \'y_pred\', \'from_logits\', \'label_smoothing\'], varargs=None, keywords=None, defaults=[\'False\', \'0\'], " } - member_method { - name: "cosine" - argspec: "args=[\'y_true\', \'y_pred\', \'axis\'], varargs=None, keywords=None, defaults=[\'-1\'], " - } - member_method { - name: "cosine_proximity" - argspec: "args=[\'y_true\', \'y_pred\', \'axis\'], varargs=None, keywords=None, defaults=[\'-1\'], " - } member_method { name: "deserialize" argspec: "args=[\'config\', \'custom_objects\'], varargs=None, keywords=None, defaults=[\'None\'], " diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.losses.-cosine-proximity.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.losses.-cosine-similarity.pbtxt similarity index 74% rename from tensorflow/tools/api/golden/v1/tensorflow.keras.losses.-cosine-proximity.pbtxt rename to tensorflow/tools/api/golden/v2/tensorflow.losses.-cosine-similarity.pbtxt index 4952a76291..7829f0f327 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.losses.-cosine-proximity.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.losses.-cosine-similarity.pbtxt @@ -1,11 +1,12 @@ -path: "tensorflow.keras.losses.CosineProximity" +path: "tensorflow.losses.CosineSimilarity" tf_class { - is_instance: "" + is_instance: "" + is_instance: "" is_instance: "" is_instance: "" member_method { name: "__init__" - argspec: "args=[\'self\', \'axis\', \'reduction\', \'name\'], varargs=None, keywords=None, defaults=[\'-1\', \'sum_over_batch_size\', \'None\'], " + argspec: "args=[\'self\', \'axis\', \'reduction\', \'name\'], varargs=None, keywords=None, defaults=[\'-1\', \'sum_over_batch_size\', \'cosine_similarity\'], " } member_method { name: "call" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.losses.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.losses.pbtxt index d906734542..f688a32db7 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.losses.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.losses.pbtxt @@ -13,7 +13,7 @@ tf_module { mtype: "" } member { - name: "CosineProximity" + name: "CosineSimilarity" mtype: "" } member { @@ -105,11 +105,7 @@ tf_module { argspec: "args=[\'y_true\', \'y_pred\'], varargs=None, keywords=None, defaults=None" } member_method { - name: "cosine" - argspec: "args=[\'y_true\', \'y_pred\', \'axis\'], varargs=None, keywords=None, defaults=[\'-1\'], " - } - member_method { - name: "cosine_proximity" + name: "cosine_similarity" argspec: "args=[\'y_true\', \'y_pred\', \'axis\'], varargs=None, keywords=None, defaults=[\'-1\'], " } member_method { diff --git a/tensorflow/tools/api/golden/v2/tensorflow.metrics.-cosine-proximity.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.metrics.-cosine-similarity.pbtxt similarity index 96% rename from tensorflow/tools/api/golden/v2/tensorflow.metrics.-cosine-proximity.pbtxt rename to tensorflow/tools/api/golden/v2/tensorflow.metrics.-cosine-similarity.pbtxt index 329d64f288..326df8fcc2 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.metrics.-cosine-proximity.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.metrics.-cosine-similarity.pbtxt @@ -1,6 +1,6 @@ -path: "tensorflow.metrics.CosineProximity" +path: "tensorflow.metrics.CosineSimilarity" tf_class { - is_instance: "" + is_instance: "" is_instance: "" is_instance: "" is_instance: "" @@ -90,7 +90,7 @@ tf_class { } member_method { name: "__init__" - argspec: "args=[\'self\', \'name\', \'dtype\', \'axis\'], varargs=None, keywords=None, defaults=[\'cosine_proximity\', \'None\', \'-1\'], " + argspec: "args=[\'self\', \'name\', \'dtype\', \'axis\'], varargs=None, keywords=None, defaults=[\'cosine_similarity\', \'None\', \'-1\'], " } member_method { name: "add_loss" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.metrics.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.metrics.pbtxt index 639186a979..4ae997c6bd 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.metrics.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.metrics.pbtxt @@ -29,7 +29,7 @@ tf_module { mtype: "" } member { - name: "CosineProximity" + name: "CosineSimilarity" mtype: "" } member { @@ -180,14 +180,6 @@ tf_module { name: "categorical_crossentropy" argspec: "args=[\'y_true\', \'y_pred\', \'from_logits\', \'label_smoothing\'], varargs=None, keywords=None, defaults=[\'False\', \'0\'], " } - member_method { - name: "cosine" - argspec: "args=[\'y_true\', \'y_pred\', \'axis\'], varargs=None, keywords=None, defaults=[\'-1\'], " - } - member_method { - name: "cosine_proximity" - argspec: "args=[\'y_true\', \'y_pred\', \'axis\'], varargs=None, keywords=None, defaults=[\'-1\'], " - } member_method { name: "deserialize" argspec: "args=[\'config\', \'custom_objects\'], varargs=None, keywords=None, defaults=[\'None\'], " -- GitLab From 69ab50a9b67cf24140f48100a1b859d127cb2411 Mon Sep 17 00:00:00 2001 From: Sachin Joglekar Date: Wed, 20 Feb 2019 15:54:22 -0800 Subject: [PATCH 206/766] Allow empty vectors in tflite::Verify PiperOrigin-RevId: 234886466 --- tensorflow/lite/tools/verifier.cc | 5 ++--- tensorflow/lite/tools/verifier_test.cc | 8 +++----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/tensorflow/lite/tools/verifier.cc b/tensorflow/lite/tools/verifier.cc index 713f152427..ffc56c1965 100644 --- a/tensorflow/lite/tools/verifier.cc +++ b/tensorflow/lite/tools/verifier.cc @@ -110,9 +110,8 @@ bool VerifyNumericTensorBuffer(const Tensor& tensor, const Buffer& buffer, ErrorReporter* error_reporter) { uint64_t bytes_required = 1; if (!tensor.shape()) { - ReportError(error_reporter, "Tensor %s shape is empty", - tensor.name()->c_str()); - return false; + // Empty tensor. Avoid further checks. + return true; } for (int dim : *tensor.shape()) { bytes_required *= dim; diff --git a/tensorflow/lite/tools/verifier_test.cc b/tensorflow/lite/tools/verifier_test.cc index a3cffefd42..7d67d9430a 100644 --- a/tensorflow/lite/tools/verifier_test.cc +++ b/tensorflow/lite/tools/verifier_test.cc @@ -148,20 +148,18 @@ TEST(VerifyModel, TestEmptyModel) { ::testing::ContainsRegex("Missing 'subgraphs' section.")); } -TEST(VerifyModel, TestEmptyShape) { +TEST(VerifyModel, TestEmptyVector) { TfLiteFlatbufferModelBuilder builder({}, {"test"}); builder.AddOperator({0, 1}, {3}, BuiltinOperator_CUSTOM, "test"); builder.AddTensor({2, 3}, TensorType_UINT8, {1, 2, 3, 4, 5, 6}, "input"); - builder.AddTensor({}, TensorType_UINT8, {1, 2, 3, 4, 5, 6}, "inputtwo"); + builder.AddTensor({}, TensorType_UINT8, {}, "empty_vector"); builder.AddTensor( {2}, TensorType_STRING, {2, 0, 0, 0, 16, 0, 0, 0, 17, 0, 0, 0, 19, 0, 0, 0, 'A', 'B', 'C'}, "data"); builder.AddTensor({2, 3}, TensorType_INT32, {}, "output"); builder.FinishModel({0, 1}, {3}); - ASSERT_FALSE(builder.Verify()); - EXPECT_THAT(builder.GetErrorString(), - ::testing::ContainsRegex("Tensor inputtwo shape is empty")); + ASSERT_TRUE(builder.Verify()); } TEST(VerifyModel, TestSimpleModel) { -- GitLab From 39be9adca116f828cabaf68ccc588c0dc67f5078 Mon Sep 17 00:00:00 2001 From: Allen Lavoie Date: Wed, 20 Feb 2019 15:58:42 -0800 Subject: [PATCH 207/766] Add the SavedObjectGraph used by tf.saved_model.save/load to SavedModel We reference a bunch of things in the MetaGraph/GraphDef, so it makes sense to add it there rather than to the SavedModel directly. This is in preparation for non-experimental tf.saved_model.save/load symbols. We don't yet have an exposed symbol for loading object-based SavedModels, so this CL won't break anyone (despite moving around the proto and not checking the old location). RFC: https://github.com/tensorflow/community/pull/34 PiperOrigin-RevId: 234887195 --- .../makefile/proto_text_pb_cc_files.txt | 3 ++ .../makefile/proto_text_pb_h_files.txt | 3 ++ .../contrib/makefile/tf_proto_files.txt | 3 ++ tensorflow/core/BUILD | 2 ++ tensorflow/core/protobuf/meta_graph.proto | 4 +++ .../protobuf}/saved_object_graph.proto | 27 ++++++++--------- .../protobuf}/struct.proto | 27 +++++++++++++++-- tensorflow/python/saved_model/BUILD | 29 ++++--------------- .../saved_model/function_serialization.py | 2 +- tensorflow/python/saved_model/load.py | 20 ++----------- .../saved_model/nested_structure_coder.py | 2 +- .../nested_structure_coder_test.py | 2 +- .../python/saved_model/revived_types.py | 2 +- .../python/saved_model/revived_types_test.py | 2 +- tensorflow/python/saved_model/save.py | 17 ++++------- .../v1/tensorflow.-meta-graph-def.pbtxt | 7 +++++ 16 files changed, 78 insertions(+), 74 deletions(-) rename tensorflow/{python/saved_model => core/protobuf}/saved_object_graph.proto (88%) rename tensorflow/{python/saved_model => core/protobuf}/struct.proto (64%) diff --git a/tensorflow/contrib/makefile/proto_text_pb_cc_files.txt b/tensorflow/contrib/makefile/proto_text_pb_cc_files.txt index 8330c45cc1..1c1460ce77 100644 --- a/tensorflow/contrib/makefile/proto_text_pb_cc_files.txt +++ b/tensorflow/contrib/makefile/proto_text_pb_cc_files.txt @@ -25,6 +25,7 @@ tensorflow/core/framework/variable.pb.cc tensorflow/core/framework/versions.pb.cc tensorflow/core/grappler/costs/op_performance_data.pb.cc tensorflow/core/lib/core/error_codes.pb.cc +tensorflow/core/protobuf/trackable_object_graph.pb.cc tensorflow/core/protobuf/cluster.pb.cc tensorflow/core/protobuf/config.pb.cc tensorflow/core/protobuf/eager_service.pb.cc @@ -34,7 +35,9 @@ tensorflow/core/protobuf/meta_graph.pb.cc tensorflow/core/protobuf/named_tensor.pb.cc tensorflow/core/protobuf/queue_runner.pb.cc tensorflow/core/protobuf/rewriter_config.pb.cc +tensorflow/core/protobuf/saved_object_graph.pb.cc tensorflow/core/protobuf/saver.pb.cc +tensorflow/core/protobuf/struct.pb.cc tensorflow/core/protobuf/tensorflow_server.pb.cc tensorflow/core/protobuf/verifier_config.pb.cc tensorflow/core/util/event.pb.cc diff --git a/tensorflow/contrib/makefile/proto_text_pb_h_files.txt b/tensorflow/contrib/makefile/proto_text_pb_h_files.txt index 7257ac8fee..5def632e8a 100644 --- a/tensorflow/contrib/makefile/proto_text_pb_h_files.txt +++ b/tensorflow/contrib/makefile/proto_text_pb_h_files.txt @@ -25,6 +25,7 @@ tensorflow/core/framework/variable.pb.h tensorflow/core/framework/versions.pb.h tensorflow/core/grappler/costs/op_performance_data.pb.h tensorflow/core/lib/core/error_codes.pb.h +tensorflow/core/protobuf/trackable_object_graph.pb.h tensorflow/core/protobuf/cluster.pb.h tensorflow/core/protobuf/config.pb.h tensorflow/core/protobuf/debug.pb.h @@ -34,7 +35,9 @@ tensorflow/core/protobuf/meta_graph.pb.h tensorflow/core/protobuf/named_tensor.pb.h tensorflow/core/protobuf/queue_runner.pb.h tensorflow/core/protobuf/rewriter_config.pb.h +tensorflow/core/protobuf/saved_object_graph.pb.h tensorflow/core/protobuf/saver.pb.h +tensorflow/core/protobuf/struct.pb.h tensorflow/core/protobuf/tensor_bundle.pb.h tensorflow/core/protobuf/tensorflow_server.pb.h tensorflow/core/protobuf/verifier_config.pb.h diff --git a/tensorflow/contrib/makefile/tf_proto_files.txt b/tensorflow/contrib/makefile/tf_proto_files.txt index 24d86d313b..deb6a5b940 100644 --- a/tensorflow/contrib/makefile/tf_proto_files.txt +++ b/tensorflow/contrib/makefile/tf_proto_files.txt @@ -31,6 +31,7 @@ tensorflow/core/framework/versions.proto tensorflow/core/grappler/costs/op_performance_data.proto tensorflow/core/kernels/boosted_trees/boosted_trees.proto tensorflow/core/lib/core/error_codes.proto +tensorflow/core/protobuf/trackable_object_graph.proto tensorflow/core/protobuf/cluster.proto tensorflow/core/protobuf/config.proto tensorflow/core/protobuf/debug.proto @@ -40,7 +41,9 @@ tensorflow/core/protobuf/meta_graph.proto tensorflow/core/protobuf/named_tensor.proto tensorflow/core/protobuf/queue_runner.proto tensorflow/core/protobuf/rewriter_config.proto +tensorflow/core/protobuf/saved_object_graph.proto tensorflow/core/protobuf/saver.proto +tensorflow/core/protobuf/struct.proto tensorflow/core/protobuf/tensor_bundle.proto tensorflow/core/protobuf/tensorflow_server.proto tensorflow/core/protobuf/verifier_config.proto diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD index 906e8695cd..06c108b38f 100644 --- a/tensorflow/core/BUILD +++ b/tensorflow/core/BUILD @@ -236,6 +236,8 @@ ADDITIONAL_CORE_PROTO_SRCS = [ "protobuf/meta_graph.proto", "protobuf/named_tensor.proto", "protobuf/saved_model.proto", + "protobuf/saved_object_graph.proto", + "protobuf/struct.proto", "protobuf/tensorflow_server.proto", "protobuf/transport_options.proto", "util/test_log.proto", diff --git a/tensorflow/core/protobuf/meta_graph.proto b/tensorflow/core/protobuf/meta_graph.proto index 75a2a88ed7..fa0192cf67 100644 --- a/tensorflow/core/protobuf/meta_graph.proto +++ b/tensorflow/core/protobuf/meta_graph.proto @@ -12,6 +12,7 @@ import "tensorflow/core/framework/graph.proto"; import "tensorflow/core/framework/op_def.proto"; import "tensorflow/core/framework/tensor_shape.proto"; import "tensorflow/core/framework/types.proto"; +import "tensorflow/core/protobuf/saved_object_graph.proto"; import "tensorflow/core/protobuf/saver.proto"; // NOTE: This protocol buffer is evolving, and will go through revisions in the @@ -84,6 +85,9 @@ message MetaGraphDef { // Asset file def to be used with the defined graph. repeated AssetFileDef asset_file_def = 6; + + // Extra information about the structure of functions and stateful objects. + SavedObjectGraph object_graph_def = 7; } // CollectionDef should cover most collections. diff --git a/tensorflow/python/saved_model/saved_object_graph.proto b/tensorflow/core/protobuf/saved_object_graph.proto similarity index 88% rename from tensorflow/python/saved_model/saved_object_graph.proto rename to tensorflow/core/protobuf/saved_object_graph.proto index bfc83e4711..48060b33dc 100644 --- a/tensorflow/python/saved_model/saved_object_graph.proto +++ b/tensorflow/core/protobuf/saved_object_graph.proto @@ -1,10 +1,10 @@ syntax = "proto3"; import "tensorflow/core/protobuf/trackable_object_graph.proto"; +import "tensorflow/core/protobuf/struct.proto"; import "tensorflow/core/framework/tensor_shape.proto"; import "tensorflow/core/framework/types.proto"; import "tensorflow/core/framework/versions.proto"; -import "tensorflow/python/saved_model/struct.proto"; option cc_enable_arenas = true; @@ -15,14 +15,12 @@ package tensorflow; // languages) that make up a model, with nodes[0] at the root. // SavedObjectGraph shares some structure with TrackableObjectGraph, but -// ObjectGraph belongs to the SavedModel and contains pointers to functions and -// type information, while TrackableObjectGraph lives in the checkpoint and -// contains pointers only to variable values. - -// NOTE: This protocol buffer format is experimental and subject to change. +// SavedObjectGraph belongs to the MetaGraph and contains pointers to functions +// and type information, while TrackableObjectGraph lives in the checkpoint +// and contains pointers only to variable values. message SavedObjectGraph { - // List of objects in the SavedModel. + // Flattened list of objects in the object graph. // // The position of the object in this list indicates its id. // Nodes[0] is considered the root node. @@ -37,10 +35,11 @@ message SavedObject { // Objects which this object depends on: named edges in the dependency // graph. // - // Note: only valid if kind == "object". - repeated TrackableObjectGraph.TrackableObject.ObjectReference children = 1; + // Note: currently only valid if kind == "user_object". + repeated TrackableObjectGraph.TrackableObject.ObjectReference + children = 1; - // Removed when forking from TrackableObjectGraph. + // Removed when forking SavedObject from TrackableObjectGraph. reserved "attributes"; reserved 2; @@ -48,7 +47,7 @@ message SavedObject { // (optimizer, variable, slot variable) relationship; none of the three // depend on the others directly. // - // Note: only valid if kind == "object". + // Note: currently only valid if kind == "user_object". repeated TrackableObjectGraph.TrackableObject.SlotVariableReference slot_variables = 3; @@ -76,7 +75,7 @@ message SavedUserObject { VersionDef version = 2; } -// A SavedAsset represents a file in a SavedModel. +// A SavedAsset points to an asset in the MetaGraph. // // When bound to a function this object evaluates to a tensor with the absolute // filename. Users should not depend on a particular part of the filename to @@ -128,13 +127,11 @@ message SavedConstant { } // Represents a Variable that is initialized by loading the contents from the -// SavedModel checkpoint. +// checkpoint. message SavedVariable { DataType dtype = 1; TensorShapeProto shape = 2; bool trainable = 3; - - // TODO(andresp): Add save_slice_info_def? } // Represents `FunctionSpec` used in `Function`. This represents a diff --git a/tensorflow/python/saved_model/struct.proto b/tensorflow/core/protobuf/struct.proto similarity index 64% rename from tensorflow/python/saved_model/struct.proto rename to tensorflow/core/protobuf/struct.proto index fd7db84e05..55b9b520a8 100644 --- a/tensorflow/python/saved_model/struct.proto +++ b/tensorflow/core/protobuf/struct.proto @@ -8,6 +8,27 @@ package tensorflow; // `StructuredValue` represents a dynamically typed value representing various // data structures that are inspired by Python data structures typically used in // TensorFlow functions as inputs and outputs. +// +// For example when saving a Layer there may be a `training` argument. If the +// user passes a boolean True/False, that switches between two concrete +// TensorFlow functions. In order to switch between them in the same way after +// loading the SavedModel, we need to represent "True" and "False". +// +// A more advanced example might be a function which takes a list of +// dictionaries mapping from strings to Tensors. In order to map from +// user-specified arguments `[{"a": tf.constant(1.)}, {"q": tf.constant(3.)}]` +// after load to the right saved TensorFlow function, we need to represent the +// nested structure and the strings, recording that we have a trace for anything +// matching `[{"a": tf.TensorSpec(None, tf.float32)}, {"q": tf.TensorSpec([], +// tf.float64)}]` as an example. +// +// Likewise functions may return nested structures of Tensors, for example +// returning a dictionary mapping from strings to Tensors. In order for the +// loaded function to return the same structure we need to serialize it. +// +// This is an ergonomic aid for working with loaded SavedModels, not a promise +// to serialize all possible function signatures. For example we do not expect +// to pickle generic Python objects, and ideally we'd stay language-agnostic. message StructuredValue { // The kind of value. oneof kind { @@ -29,11 +50,11 @@ message StructuredValue { // Represents a boolean value. bool bool_value = 14; - // Represents a tf.TensorShape. + // Represents a TensorShape. tensorflow.TensorShapeProto tensor_shape_value = 31; - // Represents an enum value for tf.DType. + // Represents an enum value for dtype. tensorflow.DataType tensor_dtype_value = 32; - // Represents a value for tf.TensorShape. + // Represents a value for tf.TensorSpec. TensorSpecProto tensor_spec_value = 33; // Represents a list of `Value`. diff --git a/tensorflow/python/saved_model/BUILD b/tensorflow/python/saved_model/BUILD index 8c6f7341b0..01d4818879 100644 --- a/tensorflow/python/saved_model/BUILD +++ b/tensorflow/python/saved_model/BUILD @@ -291,7 +291,6 @@ py_library( ":function_serialization", ":nested_structure_coder", ":revived_types", - ":saved_object_graph_py", ":signature_constants", ":signature_def_utils", ":signature_serialization", @@ -346,8 +345,8 @@ py_library( ":loader", ":nested_structure_coder", ":revived_types", - ":saved_object_graph_py", ":utils", + "//tensorflow/core:protos_all_py", "//tensorflow/python:constant_op", "//tensorflow/python:framework_ops", "//tensorflow/python:init_ops", @@ -429,7 +428,7 @@ py_library( ], srcs_version = "PY2AND3", deps = [ - ":saved_object_graph_py", + "//tensorflow/core:protos_all_py", ], ) @@ -438,7 +437,7 @@ tf_py_test( srcs = ["revived_types_test.py"], additional_deps = [ ":revived_types", - ":saved_object_graph_py", + "//tensorflow/core:protos_all_py", "//tensorflow/python:client_testlib", ], ) @@ -451,7 +450,7 @@ py_library( srcs_version = "PY2AND3", deps = [ ":nested_structure_coder", - ":saved_object_graph_py", + "//tensorflow/core:protos_all_py", "//tensorflow/python/eager:def_function", "//tensorflow/python/eager:function", ], @@ -469,27 +468,11 @@ py_library( ], ) -tf_proto_library( - name = "struct", - srcs = ["struct.proto"], - cc_api_version = 2, - protodeps = tf_additional_all_protos(), - visibility = ["//tensorflow:internal"], -) - -tf_proto_library( - name = "saved_object_graph", - srcs = ["saved_object_graph.proto"], - cc_api_version = 2, - protodeps = tf_additional_all_protos() + [":struct"], - visibility = ["//tensorflow:internal"], -) - py_library( name = "nested_structure_coder", srcs = ["nested_structure_coder.py"], deps = [ - ":struct_py", + "//tensorflow/core:protos_all_py", "//tensorflow/python:framework", "@six_archive//:six", ], @@ -500,7 +483,7 @@ tf_py_test( srcs = ["nested_structure_coder_test.py"], additional_deps = [ ":nested_structure_coder", - ":struct_py", + "//tensorflow/core:protos_all_py", "//tensorflow/python:framework", "//tensorflow/python/eager:test", ], diff --git a/tensorflow/python/saved_model/function_serialization.py b/tensorflow/python/saved_model/function_serialization.py index 19801e618b..e876eef8b3 100644 --- a/tensorflow/python/saved_model/function_serialization.py +++ b/tensorflow/python/saved_model/function_serialization.py @@ -18,9 +18,9 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +from tensorflow.core.protobuf import saved_object_graph_pb2 from tensorflow.python.framework import func_graph as func_graph_module from tensorflow.python.saved_model import nested_structure_coder -from tensorflow.python.saved_model import saved_object_graph_pb2 def _serialize_function_spec(function_spec, coder): diff --git a/tensorflow/python/saved_model/load.py b/tensorflow/python/saved_model/load.py index fd3c3db61c..ac33d26f49 100644 --- a/tensorflow/python/saved_model/load.py +++ b/tensorflow/python/saved_model/load.py @@ -24,23 +24,19 @@ import os from tensorflow.python.framework import constant_op from tensorflow.python.framework import ops from tensorflow.python.framework import tensor_util -from tensorflow.python.lib.io import file_io from tensorflow.python.ops import init_ops from tensorflow.python.ops import resource_variable_ops from tensorflow.python.ops import variables -from tensorflow.python.saved_model import constants from tensorflow.python.saved_model import function_deserialization from tensorflow.python.saved_model import load_v1_in_v2 from tensorflow.python.saved_model import loader_impl from tensorflow.python.saved_model import nested_structure_coder from tensorflow.python.saved_model import revived_types -from tensorflow.python.saved_model import saved_object_graph_pb2 from tensorflow.python.saved_model import utils_impl as saved_model_utils from tensorflow.python.training.tracking import base from tensorflow.python.training.tracking import graph_view from tensorflow.python.training.tracking import tracking from tensorflow.python.training.tracking import util -from tensorflow.python.util import compat from tensorflow.python.util import nest @@ -265,12 +261,6 @@ def _call_attribute(instance, *args, **kwargs): return instance.__call__(*args, **kwargs) -def _load_saved_object_graph_proto(filename): - with file_io.FileIO(filename, "rb") as f: - contents = f.read() - return saved_object_graph_pb2.SavedObjectGraph.FromString(contents) - - def load(export_dir, tags=None): """Load a SavedModel from `export_dir`. @@ -315,12 +305,8 @@ def load(export_dir, tags=None): # Supports e.g. tags=SERVING and tags=[SERVING] tags = nest.flatten(tags) saved_model_proto = loader_impl.parse_saved_model(export_dir) - object_graph_filename = os.path.join( - compat.as_bytes(export_dir), - compat.as_bytes(constants.EXTRA_ASSETS_DIRECTORY), - compat.as_bytes("object_graph.pb")) - if (file_io.file_exists(object_graph_filename) - and len(saved_model_proto.meta_graphs) == 1): + if (len(saved_model_proto.meta_graphs) == 1 + and saved_model_proto.meta_graphs[0].HasField("object_graph_def")): meta_graph_def = saved_model_proto.meta_graphs[0] if (tags is not None and set(tags) != set(meta_graph_def.meta_info_def.tags)): @@ -329,7 +315,7 @@ def load(export_dir, tags=None): "incompatible argument tags={} to tf.saved_model.load. You may omit " "it, pass 'None', or pass matching tags.") .format(export_dir, meta_graph_def.meta_info_def.tags, tags)) - object_graph_proto = _load_saved_object_graph_proto(object_graph_filename) + object_graph_proto = meta_graph_def.object_graph_def with ops.init_scope(): loader = _Loader(object_graph_proto, saved_model_proto, diff --git a/tensorflow/python/saved_model/nested_structure_coder.py b/tensorflow/python/saved_model/nested_structure_coder.py index 3d335de555..59a2687eda 100644 --- a/tensorflow/python/saved_model/nested_structure_coder.py +++ b/tensorflow/python/saved_model/nested_structure_coder.py @@ -34,10 +34,10 @@ import collections import functools import six +from tensorflow.core.protobuf import struct_pb2 from tensorflow.python.framework import dtypes from tensorflow.python.framework import tensor_shape from tensorflow.python.framework import tensor_spec -from tensorflow.python.saved_model import struct_pb2 from tensorflow.python.util import compat diff --git a/tensorflow/python/saved_model/nested_structure_coder_test.py b/tensorflow/python/saved_model/nested_structure_coder_test.py index 8636301719..1538fbf127 100644 --- a/tensorflow/python/saved_model/nested_structure_coder_test.py +++ b/tensorflow/python/saved_model/nested_structure_coder_test.py @@ -20,12 +20,12 @@ from __future__ import print_function import collections +from tensorflow.core.protobuf import struct_pb2 from tensorflow.python.framework import dtypes from tensorflow.python.framework import tensor_shape from tensorflow.python.framework import tensor_spec from tensorflow.python.platform import test from tensorflow.python.saved_model import nested_structure_coder -from tensorflow.python.saved_model import struct_pb2 class NestedStructureTest(test.TestCase): diff --git a/tensorflow/python/saved_model/revived_types.py b/tensorflow/python/saved_model/revived_types.py index 39b55e29bf..3140d3d617 100644 --- a/tensorflow/python/saved_model/revived_types.py +++ b/tensorflow/python/saved_model/revived_types.py @@ -19,7 +19,7 @@ from __future__ import division from __future__ import print_function from tensorflow.core.framework import versions_pb2 -from tensorflow.python.saved_model import saved_object_graph_pb2 +from tensorflow.core.protobuf import saved_object_graph_pb2 class VersionedTypeRegistration(object): diff --git a/tensorflow/python/saved_model/revived_types_test.py b/tensorflow/python/saved_model/revived_types_test.py index c58a0be16c..8bd806f315 100644 --- a/tensorflow/python/saved_model/revived_types_test.py +++ b/tensorflow/python/saved_model/revived_types_test.py @@ -19,9 +19,9 @@ from __future__ import division from __future__ import print_function from tensorflow.core.framework import versions_pb2 +from tensorflow.core.protobuf import saved_object_graph_pb2 from tensorflow.python.platform import test from tensorflow.python.saved_model import revived_types -from tensorflow.python.saved_model import saved_object_graph_pb2 from tensorflow.python.training.tracking import tracking diff --git a/tensorflow/python/saved_model/save.py b/tensorflow/python/saved_model/save.py index 617d314732..c1eb7a314d 100644 --- a/tensorflow/python/saved_model/save.py +++ b/tensorflow/python/saved_model/save.py @@ -24,6 +24,7 @@ import os from tensorflow.core.framework import versions_pb2 from tensorflow.core.protobuf import meta_graph_pb2 from tensorflow.core.protobuf import saved_model_pb2 +from tensorflow.core.protobuf import saved_object_graph_pb2 from tensorflow.python.eager import context from tensorflow.python.eager import def_function from tensorflow.python.eager import function as defun @@ -40,7 +41,6 @@ from tensorflow.python.saved_model import constants from tensorflow.python.saved_model import function_serialization from tensorflow.python.saved_model import nested_structure_coder from tensorflow.python.saved_model import revived_types -from tensorflow.python.saved_model import saved_object_graph_pb2 from tensorflow.python.saved_model import signature_constants from tensorflow.python.saved_model import signature_def_utils from tensorflow.python.saved_model import signature_serialization @@ -542,7 +542,7 @@ def _fill_meta_graph_def(meta_graph_def, saveable_view, signature_functions): return asset_info, exported_graph -def _write_object_graph(saveable_view, export_dir, asset_file_def_index): +def _serialize_object_graph(saveable_view, asset_file_def_index): """Save a SavedObjectGraph proto for `root`.""" # SavedObjectGraph is similar to the TrackableObjectGraph proto in the # checkpoint. It will eventually go into the SavedModel. @@ -559,14 +559,7 @@ def _write_object_graph(saveable_view, export_dir, asset_file_def_index): for obj, obj_proto in zip(saveable_view.nodes, proto.nodes): _write_object_proto(obj, obj_proto, asset_file_def_index) - - extra_asset_dir = os.path.join( - compat.as_bytes(export_dir), - compat.as_bytes(constants.EXTRA_ASSETS_DIRECTORY)) - file_io.recursive_create_dir(extra_asset_dir) - object_graph_filename = os.path.join( - extra_asset_dir, compat.as_bytes("object_graph.pb")) - file_io.write_string_to_file(object_graph_filename, proto.SerializeToString()) + return proto def _write_object_proto(obj, proto, asset_file_def_index): @@ -814,8 +807,10 @@ def save(obj, export_dir, signatures=None): path = os.path.join( compat.as_bytes(export_dir), compat.as_bytes(constants.SAVED_MODEL_FILENAME_PB)) + object_graph_proto = _serialize_object_graph( + saveable_view, asset_info.asset_index) + meta_graph_def.object_graph_def.CopyFrom(object_graph_proto) file_io.write_string_to_file(path, saved_model.SerializeToString()) - _write_object_graph(saveable_view, export_dir, asset_info.asset_index) # Clean reference cycles so repeated export()s don't make work for the garbage # collector. Before this point we need to keep references to captured # constants in the saved graph. diff --git a/tensorflow/tools/api/golden/v1/tensorflow.-meta-graph-def.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.-meta-graph-def.pbtxt index d71c2358c9..b453f7e990 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.-meta-graph-def.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.-meta-graph-def.pbtxt @@ -44,6 +44,13 @@ tf_proto { type: TYPE_MESSAGE type_name: ".tensorflow.AssetFileDef" } + field { + name: "object_graph_def" + number: 7 + label: LABEL_OPTIONAL + type: TYPE_MESSAGE + type_name: ".tensorflow.SavedObjectGraph" + } nested_type { name: "MetaInfoDef" field { -- GitLab From 9ddbdf1258f30d95ec46b499f2b37d5ac5c5e0b0 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 20 Feb 2019 15:58:58 -0800 Subject: [PATCH 208/766] Fix for path to tf binary. PiperOrigin-RevId: 234887249 --- tensorflow/tools/ci_build/builds/pip_new.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tensorflow/tools/ci_build/builds/pip_new.sh b/tensorflow/tools/ci_build/builds/pip_new.sh index 03fc5775fa..edbd191c9c 100755 --- a/tensorflow/tools/ci_build/builds/pip_new.sh +++ b/tensorflow/tools/ci_build/builds/pip_new.sh @@ -645,7 +645,7 @@ echo "Size of the PIP wheel file built: $(ls -l ${WHL_PATH} | awk '{print $5}')" # Run tests (if any is specified). run_all_tests -for WHL_PATH in $(ls ${PIP_TEST_ROOT}/${PROJECT_NAME}*.whl); do +for WHL_PATH in $(ls ${PIP_WHL_DIR}/${PROJECT_NAME}*.whl); do if [[ "${TF_NEED_CUDA}" -eq "1" ]]; then # Copy and rename for gpu manylinux as we do not want auditwheel to package in libcudart.so WHL_PATH=${AUDITED_WHL_NAME} @@ -660,7 +660,8 @@ for WHL_PATH in $(ls ${PIP_TEST_ROOT}/${PROJECT_NAME}*.whl); do WHL_PATH=${AUDITED_WHL_NAME} echo "Repaired manylinux1 wheel file at: ${WHL_PATH}" else - die "ERROR: Cannot find repaired wheel." + # TODO(hyey): modify below to call die upon failure. + echo "WARNING: Cannot find repaired wheel." fi fi done -- GitLab From ae2ac055729b57ca80d240004a30f0f716e72796 Mon Sep 17 00:00:00 2001 From: Jingyue Wu Date: Wed, 20 Feb 2019 16:17:57 -0800 Subject: [PATCH 209/766] Fix NCHW_VECT_C maxpooling. PiperOrigin-RevId: 234890899 --- tensorflow/core/kernels/pooling_ops_common.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/core/kernels/pooling_ops_common.cc b/tensorflow/core/kernels/pooling_ops_common.cc index 69122f467c..903cf9313a 100644 --- a/tensorflow/core/kernels/pooling_ops_common.cc +++ b/tensorflow/core/kernels/pooling_ops_common.cc @@ -215,7 +215,7 @@ void DnnPoolingOp::Compute(OpKernelContext* context, // NCHW_VECT_C is not supported by cudnnPoolingForward(), but can be // emulated via NHWC. data_layout = se::dnn::DataLayout::kBatchYXDepth; - batch_size *= depth; + batch_size *= depth / 4; depth = 4; break; default: -- GitLab From 065653e48b4c65482052eb8af75c3c7c7091621c Mon Sep 17 00:00:00 2001 From: Penporn Koanantakool Date: Wed, 20 Feb 2019 16:18:16 -0800 Subject: [PATCH 210/766] Minor API description fixes. PiperOrigin-RevId: 234890954 --- .../base_api/api_def_RequantizationRange.pbtxt | 10 ++++++---- .../core/api_def/base_api/api_def_Requantize.pbtxt | 12 +++++++----- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/tensorflow/core/api_def/base_api/api_def_RequantizationRange.pbtxt b/tensorflow/core/api_def/base_api/api_def_RequantizationRange.pbtxt index 07bbd4ac60..cd7d4e3ec2 100644 --- a/tensorflow/core/api_def/base_api/api_def_RequantizationRange.pbtxt +++ b/tensorflow/core/api_def/base_api/api_def_RequantizationRange.pbtxt @@ -30,10 +30,12 @@ END The type of the input. END } - summary: "Given a quantized tensor described by (input, input_min, input_max), outputs a" + summary: +"Computes a range that covers the actual values present in a quantized tensor." description: < Date: Wed, 20 Feb 2019 16:20:54 -0800 Subject: [PATCH 211/766] Expose the xla module in tf.contrib.compile. PiperOrigin-RevId: 234891390 --- tensorflow/contrib/compiler/BUILD | 1 + tensorflow/contrib/compiler/__init__.py | 1 + 2 files changed, 2 insertions(+) diff --git a/tensorflow/contrib/compiler/BUILD b/tensorflow/contrib/compiler/BUILD index e32097cedd..79c6158911 100644 --- a/tensorflow/contrib/compiler/BUILD +++ b/tensorflow/contrib/compiler/BUILD @@ -23,6 +23,7 @@ py_library( ], srcs_version = "PY2AND3", deps = [ + ":xla", "//tensorflow/core:protos_all_py", "//tensorflow/python:framework_for_generated_wrappers", ], diff --git a/tensorflow/contrib/compiler/__init__.py b/tensorflow/contrib/compiler/__init__.py index c4937dadfb..797e5e8164 100644 --- a/tensorflow/contrib/compiler/__init__.py +++ b/tensorflow/contrib/compiler/__init__.py @@ -19,3 +19,4 @@ from __future__ import division from __future__ import print_function from tensorflow.contrib.compiler import jit +from tensorflow.contrib.compiler import xla -- GitLab From fb671b314038f49674dc972f1d274270dd66b6e2 Mon Sep 17 00:00:00 2001 From: Jonathan Hseu Date: Wed, 20 Feb 2019 16:23:50 -0800 Subject: [PATCH 212/766] Expose more private symbols used by other tests. PiperOrigin-RevId: 234891940 --- tensorflow/contrib/tpu/python/tpu/tpu_estimator.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py b/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py index 71d1d0fdd4..893118412e 100644 --- a/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py +++ b/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py @@ -26,5 +26,8 @@ from tensorflow.python.tpu.tpu_estimator import _create_global_step from tensorflow.python.tpu.tpu_estimator import _export_output_to_tensors from tensorflow.python.tpu.tpu_estimator import _get_scaffold from tensorflow.python.tpu.tpu_estimator import _Inputs +from tensorflow.python.tpu.tpu_estimator import _ITERATIONS_PER_LOOP_VAR +from tensorflow.python.tpu.tpu_estimator import _TPU_ENQUEUE_OPS from tensorflow.python.tpu.tpu_estimator import _TPU_ESTIMATOR +from tensorflow.python.tpu.tpu_estimator import _TPU_TRAIN_OP # pylint: enable=wildcard-import,unused-import,redefined-builtin -- GitLab From 86ff976fad988857e12dd6eb219e691d19be3f38 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 20 Feb 2019 16:27:59 -0800 Subject: [PATCH 213/766] Add a MulNoNan op to be used in safe gradient code for functions with singularities. PiperOrigin-RevId: 234892656 --- .../api_def/base_api/api_def_MulNoNan.pbtxt | 11 +++++++ .../api_def/python_api/api_def_MulNoNan.pbtxt | 4 +++ .../core/kernels/cwise_op_gpu_mul.cu.cc | 1 + tensorflow/core/kernels/cwise_op_mul_1.cc | 3 ++ tensorflow/core/kernels/cwise_ops.h | 33 +++++++++++++++---- tensorflow/core/ops/math_grad.cc | 10 ++++++ tensorflow/core/ops/math_ops.cc | 8 +++++ tensorflow/python/ops/math_grad.py | 20 +++++++++++ tensorflow/python/ops/math_grad_test.py | 25 ++++++++++++++ tensorflow/python/ops/math_ops.py | 28 +++++++++++++++- tensorflow/python/ops/math_ops_test.py | 16 +++++++++ .../tools/api/golden/v1/tensorflow.math.pbtxt | 8 +++++ .../api/golden/v1/tensorflow.raw_ops.pbtxt | 4 +++ .../tools/api/golden/v2/tensorflow.math.pbtxt | 8 +++++ .../tools/api/golden/v2/tensorflow.pbtxt | 4 --- .../api/golden/v2/tensorflow.raw_ops.pbtxt | 4 +++ tensorflow/tools/compatibility/renames_v2.py | 3 +- 17 files changed, 178 insertions(+), 12 deletions(-) create mode 100644 tensorflow/core/api_def/base_api/api_def_MulNoNan.pbtxt create mode 100644 tensorflow/core/api_def/python_api/api_def_MulNoNan.pbtxt diff --git a/tensorflow/core/api_def/base_api/api_def_MulNoNan.pbtxt b/tensorflow/core/api_def/base_api/api_def_MulNoNan.pbtxt new file mode 100644 index 0000000000..51a5fa95d3 --- /dev/null +++ b/tensorflow/core/api_def/base_api/api_def_MulNoNan.pbtxt @@ -0,0 +1,11 @@ +op { + graph_op_name: "MulNoNan" + endpoint { + name: "MulNoNan" + } + summary: "Returns x * y element-wise. Returns zero if y is zero, even if x if infinite or NaN." + description: <

If models don’t cover your use cases, you can always - bring your own existing TensorFlow Lite models. Just upload your model, - and we’ll take care of hosting and serving it to your app.

- -

Models acts as an API layer to your custom model, making it easy to - run and use. In addition to deploying your models, we are releasing an - experimental model compression flow that aims to reduce model size (up - to orders of magnitudes) while maintaining similar accuracy. Sign up at - g.co/firebase/signup

+ Generate reply suggestions to input conversational chat messages. + image_path: /resources/images/tf-logo-card-16x9.png + path: /lite/models/smart_reply/overview + buttons: + - label: Use the model + path: /lite/models/smart_reply/overview -

And if you’re new to machine learning and want more information on - custom models for mobile, you can learn more about TensorFlow - Lite.

- image_path: /resources/images/tflite-card-16x9.png - image_left: true - classname: devsite-landing-row-large-headings foreground: theme items: - - heading: Just the beginning + - heading: Benefits of pre-trained models description: > - Our ultimate goal is to reduce idea–to–implementation cycles and make AI - an essential and intuitive part of a developer's toolkit. We will do so - by continuing to add new Base APIs that leverage Google’s machine - learning expertise. Base APIs will ultimately cover significantly more - use cases in the vision, speech, and text fields. We will also continue - to simplify use of custom models, adding tools to deploy, compress, and - create them. + The best way to get started is to used pre-trained models because they + allow you to save time and money, leverage learnings from research scientists, + and avoid expensive datasets. Instead of having to start from scratch, you + could use an existing model and just retrain the last few layers of the + neural network to suit your needs. This process is called transfer learning. diff --git a/tensorflow/lite/g3doc/models.md b/tensorflow/lite/g3doc/models/hosted.md similarity index 99% rename from tensorflow/lite/g3doc/models.md rename to tensorflow/lite/g3doc/models/hosted.md index 62b3f17c79..84421e1fc4 100644 --- a/tensorflow/lite/g3doc/models.md +++ b/tensorflow/lite/g3doc/models/hosted.md @@ -1,5 +1,4 @@ - -# List of Hosted Models +# Hosted models # AutoML mobile image classification models (Float Models) diff --git a/tensorflow/lite/g3doc/models/image/label/ios.md b/tensorflow/lite/g3doc/models/image/label/ios.md deleted file mode 100644 index 904c6450ac..0000000000 --- a/tensorflow/lite/g3doc/models/image/label/ios.md +++ /dev/null @@ -1,3 +0,0 @@ -# iOS - -lorem diff --git a/tensorflow/lite/g3doc/models/image/label/overview.md b/tensorflow/lite/g3doc/models/image/label/overview.md deleted file mode 100644 index b3d9133bb2..0000000000 --- a/tensorflow/lite/g3doc/models/image/label/overview.md +++ /dev/null @@ -1,8 +0,0 @@ -# Overview - -Image labeling gives you insight into the content of images. When you use the -API, you get a list of the entities that were recognized: people, things, -places, activities, and so on. Each label found comes with a score that -indicates the confidence the ML model has in its relevance. With this -information, you can perform tasks such as automatic metadata generation -and content moderation. diff --git a/tensorflow/lite/g3doc/models/image/label/android.md b/tensorflow/lite/g3doc/models/image_classification/android.md similarity index 85% rename from tensorflow/lite/g3doc/models/image/label/android.md rename to tensorflow/lite/g3doc/models/image_classification/android.md index c755328ac0..61606096f7 100644 --- a/tensorflow/lite/g3doc/models/image/label/android.md +++ b/tensorflow/lite/g3doc/models/image_classification/android.md @@ -3,21 +3,20 @@ This tutorial provides a simple Android mobile application to classify images using the Android device camera. In this tutorial, you will download the demo application from the Tensorflow repository, build it on your computer, and -install it on your Android Device. You will also learn how to customize the +install it on your Android device. You will also learn how to customize the application to suit your requirements. ### Prerequisites * Android Studio 3.2 (installed on a Linux, Mac or Windows machine) -* Android Device +* Android device * USB cable (to connect Android device to your computer) ### Step 1. Clone the TensorFlow source code -First, we clone the GitHub repository on the computer in a folder to get the -demo application. +Clone the GitHub repository to your computer to get the demo application. ``` @@ -29,21 +28,21 @@ Open the TensorFlow source code in Android Studio. To do this, open Android Studio and select `Open an existing project` setting the folder to `tensorflow/lite/examples/android` -![Step 1](images/classifydemo_img1.png) + This folder contains the demo application for image classification, object detection, and speech hotword detection. ### Step 2. Build the Android Studio project -In this step, Select `Build -> Make Project` and check that the project builds +Select `Build -> Make Project` and check that the project builds successfully. You will need Android SDK configured in the settings. You'll need at least SDK version 23. The gradle file will prompt you to download any missing libraries. -![Step 2](images/classifydemo_img4.png) + -![Step 2a](images/classifydemo_img2.png) + #### TensorFlow Lite AAR from JCenter: @@ -53,25 +52,25 @@ build. If you see a build error related to compatibility with Tensorflow Lite's Java API (example: method X is undefined for type Interpreter), there has likely been a backwards compatible change to the API. You will need to pull new app code -that's compatible with the nightly build by running git pull. +that's compatible with the nightly build by running `git pull`. -### Step 3. Install and Run the app +### Step 3. Install and run the app -Connect the Android device to the computer, and be sure to approve any ADB +Connect the Android device to the computer and be sure to approve any ADB permission prompts that appear on your phone. Select `Run -> Run app.` Select -the deployment target in the connected devices to the device on which app will +the deployment target in the connected devices to the device on which the app will be installed. This will install the app on the device. -![Step 3](images/classifydemo_img5.png) + -![Step 3a](images/classifydemo_img6.png) + -![Step 3b](images/classifydemo_img7.png) + -![Step 3c](images/classifydemo_img8.png) + -To test the app, open the app named `TFL Classify` on the device. When you run -the app first time, the app will request permission to access the camera. +To test the app, open the app called `TFL Classify` on your device. When you run +the app the first time, the app will request permission to access the camera. Re-installing the app may require you to uninstall the previous installations. ## Understanding Android App Code @@ -85,7 +84,7 @@ This file depends on `AndroidManifest.xml` in the folder `tensorflow/tensorflow/lite/examples/android/app/src/main` to set the camera orientation. -### Pre-process of bitmap image +### Pre-process bitmap image The mobile application code that pre-processes the images and runs inference is in @@ -102,7 +101,7 @@ DIM_PIXEL_SIZE); c.imgData.order(ByteOrder.nativeOrder()); ``` -While running the application, we preprocess the incoming bitmap images from the +While running the application, we pre-process the incoming bitmap images from the camera to a Bytebuffer. Since this model is quantized 8-bit, we will put a single byte for each channel. `imgData` will contain an encoded `Color` for each pixel in ARGB format, so we need to mask the least significant 8 bits to get diff --git a/tensorflow/lite/g3doc/models/image_classification/images/build_and_execute.png b/tensorflow/lite/g3doc/models/image_classification/images/build_and_execute.png new file mode 100644 index 0000000000000000000000000000000000000000..a305350cb7b413b70d2095cfd8fea6d396c63695 GIT binary patch literal 81148 zcmeAS@N?(olHy`uVBq!ia0y~yVBF2Xz_43@iGhJZ`Azjc1_mC7OlRkS%;aPS29M6E z;p`E?C&lJP2YF^Ra(Hm=?F?Gt&AHH3V1m{S4o`)JM`j4Pyg9LnYhA3%fwpyB8oPEe zZ3?@xRj+HWy+~l-wWCeEQ_VFOysmq{_x;@Wv#bBlv3zc?TKs-4qr>tx^{LMm?P7T1 z`6>0IpyC?Nm6JAK|8I0uyO~-nGmwW^pF z&f#YeVlQlSICsAJM>xX=p?x9K_!%y;zgnCA#CvLm&=EGH845>&RL=!H7E(PI@Yrjr zZ;}oBqgaLsnKtuuxEbE2twpL%kDr!0^eQUEiBI(O(w%!|{!h35xBbaeEw^tR*^K^DDc|(Pn=G`P3N8<*RB&PTWzyWO)pM3&mf!>&Zuv~l@#1@Saql2 zMX6f1WfpU|4Pz8D7Fx`oz2al6%9M8142$fP%a4-T3zzjAzV^fHL&~ZhUcV=I%~9Fb z_J5hj3E3z0!75%pfzNvaxsRMQ6v%cyGv&F8%gS7X*-C=yX&2mim4yT*J0+lX!l z38`i4WOu7fQOSID)N093#g9_=!!O;j=(Y6|edea|HB4akDQ+Q?eH96IF%z=tY}{ta zPhW9P`22}yy}iNDo5ha4ftQ_v3lRG2A``rji;2^ZQ3mOT^eJ4L@)d_iO1EO zt$%^rpZ*s$GqUw$PVKCJbt6D{?L`f?%1Eo`X-5o_Gz=$6_>~;-{ors%zQ65yg2kOe z`8(wXx7UAbIo?us$oY_|6Z^w!A%z>??5>JPka^J{c*xW=y(Zc?k3G`%qks*=C+EC5 zowFIlYfO5M9BWR<@n7*Y!zeuLzwP(?SwEJ&ma^{BW?jm&m?cpV)jpmQkKWhJA-Xs3sX@Rg0=Q$R8rs)Qa6I^FFYN&e&-@PrQ3lP$U)Uq^A}>p?p&R1pUeLC;ZmjB@*2wuR2X> z!I>pGi*uH43D67LrSUh!c9pGZ!z_=p%k&n61xc@1zM_2P`mI~Gtl3ht^;mZ2)@RwF zS#PgeE&l58n%Q^$GndB~)Gmr_S-R!zmgFtbTdtQ_-R+a}@n8J>^7KouU#P$M{__1+ z$y>@>civ)pTY2m1t=?PHx5mE+D9I_kVHI4`Riah$?RCtRmlv15xb?EAq;gkr>FQGc zSNATwy`21F=gXt7p1#z*czb1e*!z`sp7;Iiec~6yFWT?=zokx{IXS}Eg2ygdFvPb{0L?{>4tOL}Y4m6w6>F}AVAvD8G9br8;(spk65p=+45}T6JyEet~*qglD@fo zV|>ewwi8Z#EqfmSD127X*P66YaAE4g+{9mro{6D}-w&@nwE58M!_~?*%4x1L-6}3_ zuIoDX95j9WX=mx3dmHa1Kku=XbnW))Sk+Y}@m55)J5RE`Pue=~>W^ae_T{g9p8DRM@8{v~d4J}=PVv*LpMFg(FwQcn`YH1->R++rhJ>vPq9qh! zG;1bFPS|PmIWx@R+Nw2qffH6ZT~U74vHFPqX64Q1Y5Pxip1nEGc*5*SyVd*WOrKXC zut@*p#5r@)?A8`U?Yj|8kJyhkHyvnt z)%4wXQu-VHE80^;;%2-wDgJmy-SwfX{rN-bAJey<+csx!!nbD2Hy+$$a1gXE)x3__$F%4rXObyO=;CtO;i2sZB^g#W8uD#-63IBkKO;vo9i7C z?-bvv`z>Nm-pA=ijyHD}UH{be$=~~j_qo4Mz6C$^XZALpzD@UY)V4&+!ksBUzlu6) zm#w)H9l2#w@zouny{jWvpAOp}XFYt)wXT(gxP_ao-yFHU=I(N>z^hwd zO?YK%y3DjY``%jdHT(A{RyZGa+uZw9+CEPxf7edSq~heYvAIXZ9!+b~?cc7JZ~J%i z`SYc>eYQ=_|Gg)6)81tFKYf!+@0FHX`$d*g`i$YRL!|O0i_IIidtSM6an7wfbp>w@UK9I2 zjd_3XpWNSyr=731^YZIkJJ|*mr98;EcyV9yrDWUVoBPVQ`Q`2{PCC@NTK)L^9Ba|i zS7+Wo+&|yqtX=IFp93?U)Bm26vw2>;?&Y5AKaX+G6X*N$@aOSHv-R7Kb>9=8t^YGF zD^vb*-IJmVPalUnt^ZRuwfgIytuwOUUZ1?~{vN}ZZpVEOo?X7ZD1Yys*n-{v&;8lm zvOD#A$@|bd=6mCR>mRRw`|`spi$4lq4z{*)^1mGwwK3OnSG4BUi|X&zj>GJzy4cO`S8P|>-(Gj@48^UaQo|}Z=N1lt-8K9KEK}h zmv44_%>BRHEAxNfpSst)ZvW%v^X-@AgYE2I&i~l?W#y;MkDpJ|4~&1u_k@?nqta+z zmUhFz#UqTop3iuu8uCRN)k|l7yZz#tf^)+_ftB!CtuvG--UAH{WaD%)3 zZp{ha_ovoYB^fx_+O)N~J((%@E=y0!=A*zpex5yM%netV4y4~a;PC%fW8?Yg#7)~7 zEX-nS_l0l?&f_)PsdX{6`|pG5pXLiLH(ah@+ttLtz@U)q5#-CjP^HGe(9pub@QZa@a2CEqi4B`cIb_LonFfgzsdAqy(2LsNC?{^p&7&r?&B8wRq#8g3; z(KATp0|NsCdx@v7EBj+6Ms{ffksDH03=DjsRUr{2L5bxG1x5L3nK`KnC6xuK3YnlT zOhoJ2nVFNG?BH$v|5?P2Nxa}-;SRSS|HMDAdqz%;j5Yq|VzK(BS73~T4nM!w_xF}} z_y172`e;vW-#MXG>-YJ{AHEtIb~7~VOyH^`FN#mks{XR?-H~MJE18FXyQI%q*D@(< zRh-keo2S2=fByXM{aU`=yVAeD-W(JYKk4t`{@>|`oi&1rd{ysm|M^n?qUgqVo8GZI zuUl>M@W8%fRrjY~dBiR)%Nz2*b7&D4to4|!M?I-LHB>puK8NubN$=C2?;Z! zuUvXBJL`B?hPIvd`;U`OTyOgGbz;W)<@S}~X3MW_TL1fTJipmt>F&G2eS5@aNn}j@ z{Hk8;%f_9b!iBExpSI`>vfE>g<2CKl}gs{Qr8z5R2@8 zl~W?->2Ka5we)Gl{kG_niCc|MO`P~RdtvmUgHl zzyEi%@olejFSlM$Ugfvv=L7ra>Ghf~G(3OY*^&SGzj^BZvKdW>+|R`e&r@Cy@=dJv z;pOaw%A&dtcYO`Md9pmovv}g)FZZvvoL4jRh_~7$?)5Q>!}I%m?xVAc&+uP35`Ae( z)yY)p7j<(Kp4Q6lteN?LE3;J8*R#{*WS6G72ls8W|GhPQ<6_U>ih;-f%#hT!5q@{W zdvUB#>+~i0LWx`N>^u8ESN6$~_&DKr-ck3&PTiNb5z5Wa(ERv2o5x^m)nm|Z+-{Br)$C@A*wjv+?xJQVJ8ju| z#-G*Zlixi5Xe7MdCi%To@vm#9~;EkQw zuPG((7ON+{mC~`-{j%f0{a@GH4e!kV_xbI&`*Hu19iJ{L(Xt7Q3{EcoUwU13_kGR( zN@q_e>Yuzb-|zdvJ3qW1{Cl+GV#1U6B_D39e9rT}Usm6G?dzSr>wj$&Xy6Z1pP#mr zbyksm=2xlN#|(9r-~To3chv{Yo7D-|;+ekdt~g!cx#WNI%)`$E_ixac$R$|4+WX&` z@10jQ;;Se6yw+vk6J|8eNpSX4kIGfdpB9OC;g-uEPr7X06`sMZ7ns3*bsGzt!pVCP{)>{#jvLQkw5|1>;Dn0m zLte*r3*QuTbHB}E^8J);_wsXpU%cykH?_y)V#gC_txs(iUwVBmdbnk&N`cfdw~1WY zjO!xaDgJU}sXeN$wL3g0 z&~*)0NyJ;PuMQtR^SFi|Sfj@kZMJrKU?AV0?-3iUvJO3xK9%#sGy9_Xk=(hz586(a zTC@IPkCaYyijjwUu}uEeNkPt8kE_+czkYD=Nn7j`PFt4ymSOIj=JmFfEo!ohS&+Q2 zPwUsLee#=)>pyf(yz+Bmv;2Z3Q#UssOpf?b;QEo7+i%8O6;nmOeac_MmUKPm`Q}Jik$@NFezicdQ@BN;;#=Y}>xvjz*1sU!fLD{!^ zJH5ZQf0p0#hk=)Uz1@pfJkF0EpP#o(@y^bOL&tsQ>{r?(6y5wUbnaOmmrq~yPcXWN zbL6&NxOnY~K-uF0Q-N?rFTRZ)3MEc&G>>@RUa-Z#@4$2J-2so88MgHKpXU)saS}H3 zdk|>NrfKUTz2P&fMb0-C87H0#bzV~*F?CDtW zWv-mog#c9+t@Z$ElSS_v@^=RK^YY3lUSKv2$XMHc%|E02z0V$ncI8Y-N6VFbG3C0- zZ3~afS7xOje!vCo&?3~N<& zwya8=!R8|?*8Mhs>84dq$)?al#}i7pa!Xo@9#4}Le7XouU4&O)u=Kvvk0Ee%QQt_T2uPvg9q%3u0}2k_mr7> zrbtNLZ}Ed^dzWvNW?ReYHC4@hmi5xs#?EOi^VI~@0+*?HiA^`ttbmUPC1Q!DN< zKHXz>b?4ryCfRR_0iKT5L9Y+I-J^UnCDW+IVH%r_kj@9El3Sq(MmLsvr7N>2ZuH-I z@8*UN4q1=<4OaQAadT{C_MW~}BH8!mgofB{54g(Li4?J&Kex$+KjHcW#Lnk<>r2QRw;ts}0i(l{>eli%fgFwtY+URQAAYMeAfrgme#NDQ=#&PV&<1z_!y< z1C)B7w{dUVRWw^!TVt{7LF>~Mrsd1N`dM4@Z!w#)EzWKFM?wev!_STitS>ryRS;FSBsk!E=j?ZP_ig zZT>pMN`K%jmnf(!I;FSxjpjO;kh%mecAxgXi7y`NO5c9)-m$nR9mhM^9Ydb{NuDpW49Sw_M6be54#+7HAN3Q6)kCc zUn;OALD6RGMV-|F>ja#y{&>yCWZuYTaUfyIJl@lr#C`TkF3otdLhKoni;ai4Y?!#g zLbF9v{_K=mcP~ZwjnApt1ARWb1&;=p+?@F(K*%fGm9hD*$c3=b>tEiu#cZ>yR=#Rp za^u*{Trq*>eXJ$A&(;**(o36t*H0<;)wR_(n9FVImK?GEcYVqi#*A{+>CqcBBAA{W zSSjV`KIch|iT~b10-v>byaJakV&8hb{TAD#hoKv+uNoINEvtQaCE@Q2ch6?8nJnU~ zcYQF}G-1;BlJC9Sb@;4SoZZ!&cqm^yGgNEkR)+T`x>NMGcqs>S1-i}yOo*X^-MnVtl6^`z0K3DB)n#8BsYl__ZUTH7ESOx zHtT_8=VBiR#z#@l9qU%je9FE>*Y_L~uWx#w?oQS_`k#2KLY^>mq^#d_A?)00oq#!C zFC5Nl-Lm83Sti9}?`C9wHNIf`G?rTn5^SMuL=KJ!XL`(Y8Quz|@r{`Mk zr(C)r#WBX91_kAI)H%jF{=FR7vcuqvbdOkxu$MXZJ0iG9_Rb&+#CzLy6Zzz8oe9G!*58K6@ zo=gkbkmG{PUm0|lJ3n<_%U&&{!OQz9^2mv4h2Lf+JlRw(awYbgcS1$=+zD~(P2OCe z`P%m|Go}===V0^26`yd+yELAwT#1_5UmXf4%&;|Niw$s*&||TPpT$ zFpo=^|8Y-8yZJr-Yqj&^g;IB_v@KZn<@DFb%oCd$mVUo((Rk_gPf?}$WqaQ?X5L?X z{Oh)7I?1))#60v}nWfIZSA2a*v5a$lyr+HtH;Ki+^Bt#6d}IIQ*XPIonCETEJe~Th zTb_ZTfx*+oF{I+wo7i#(k>lUzTR)I`$`>$8=_pf#V`o$Bo4wM}Z(@aeb+_FRT6{5L zzgustu=kt2$Ko6oH;D!~Z)DUexT1aF?)(1jv&tpY?mj;7Wcr7H7SnFt)XYyyxBGpo z<3!x1{x|ANL%fjDQI|*icQYc3Ed7$Noc-^%m$m!UPb>CJ+cNLZ@kOBuJ2hoh>=&(H zeS6hQmg^JvH0#U1|IcmN8GA%1=4rFa>#v%#*8URwzh#?fzVtuNw?DTF&2wE^lNP9% z?zM7?*US69N>PD)8#b*BPkz2^@~yJ0+k0~FF8Jj5x5@3s{Vn#n^@k>%;tI=N_l$3g zlzRS(8{f*y_e=rlTnQt)d=%6gPCK4sUe0f4x9Md^kKn3L2j4ZHx^lYglFQTAKe;z~ zPiBwuZh5k0iOj>Tf5dMo@5wFkQ+)ctKD0>l=vDn%-r8CFHeFUtouPCw=G=erXa1Sh zUblm9&Aaq%%C_hGug&X^?BhK0CT4B%(Q~0Aker%<|e{o&3;?9@cK#qwJlxxmo#riY@#qsIh)+-8Q?6&z7X@JK}Aj z&h50QW<7hvT$32Rh~NuhcWz|yEZH2iV|hT;N1<5#)nyOgZd~&8%jRo`wc9GMnDTAC z|04ELQAw4LchWzOnU*@g-tUQbT|DVQziRDI{SU{5w4VBF)_eW@?Ejdb-O#b_=ilet z)wTb#m%o}DRq{&XN#g0q_!x!|4rPPLO@`d-kKVb*Ip6J9>aOTp3abxm*Uym4`>c^z z@x^dbj;yw!*joMnMMbB-U+!+OZ#&~SciEJS`75XWIq9;=^@;X5y<2}a&hbk6XK>o{ za`>g?-g8v^Rr}IxLYI8+uJiKnI{DH4#O$~&R}W13dVG?csD8Zx`-5APLO1UC(LDce z^>hBYmp|=2T=Xx(Prm-&jMLj6&9bl8ulM4c7iCw^;Oe2~)tox<**(#??oRie`J3Lm z+@EN{y8p?ayUikVgc*eW7OszcdU*2%-d)KbPTTJGDfd1-W!t5uk*Gs%bM{3QkSPd3NRXi~6_U zQv+sqFfRW2+2-fgzZ0I-&6wP_T*c{*Sn<_6KbzCJZR6yN|IIkF$fQ=a)@t2cRz2@` ze;3`)s4ED#^ygr-R6(1ZX4P$N%YQR=%hk>K%6`rH4EqerHFZ@_Q|>M8^8I7-Ir)E6`j5zW8Grfr)$logcf!$QsnbmAe|)~6sd`p^ zPkQ1HkL#@EXP@%x$#+an+GSdh{r1|kg8PS!`Obd`sa#f}wo$70qW1so%RIvy1wHay zHc$Q9+Y#*LSJGpioFHr_dVpK+$-$q6+-INi`z?RHG(LH+hn(0TmJXa=n}bsHCwF38{|f2lFZi95*R{eX(y^|2SId>tzp9o! z)wrO1G4i6|M@7Y_hhvkylhvKYXYspEcAKHyn|w9MfZFRF>T#YF6s0-aFo}cqbd(plW{d=ZfP4+v)!u%$GZJfyd*=xs{8TIBm zPu4zPyZ6bn{t4#X_m`%72tMb3FMZ@rapm;_g=@DRmv44!G1Z=-yWBu%_3~-nZjn2h z=KpY?dGsp3gHKp+1are@X62rHO8y_i)h2g_A7YH=TOzT>)%$4r!^msqUC%zx$-Y^d z5y5s{XUDNq!5oi296R%4({uJ&fBqPjUvfBi=ju%1pzMj!*-~}IxBqp#*<)gr{ZHWJ zm497@eA;u~Kl&3PmtfQsYrQt$!`|M(%4z?!HSb-k4j+EcePU4$v#aN$zmIAjt2le>tZk1x z^^0ZEZ;rhuej06$&XYPTe^kYl)m8u3->0vVa!#t{^Co{z-m7m?zvhwa`ja02j?SIO zul7D+*1k!0u~+)PsLtF{(#2|0_&aibtBJ~@;}i3wdGkK-Bsy|QiEVhs|9?66UZ+cQ zp3Cb?$#DG4Ii~q6YU$^CDN*tImh&l(xs4jP9C*3Llx3ZxylZiD+ou`NO}-p||M>V5 z{(JXZdvw-#m`rIg7rdsY___W6Wai};_8v|?*eeoswJ`RNgYbHL9mAFn{bFZAceG8r zoXuKiR-XB8^Bvnmg?$sY3O-+cdWrj~CwifF!bzLj#LPSPCrk8o%#zf5T0GITSNw?Y zn&VwUJ?DAvU)EW?R7z&|T2RbpF1v8wN^Q1=vzh1qoRa#F+KEO7richT`#0zu`svJX zpe?&DQB#o5&{8U=$N0vXw!(t5Q~x{6)0b`%{WyamK#WPLS=3hK-i7jqsgFu( zVm&xl)a+=})Vq~dVqWt6ndnp|6_%t=+h_jQ`)hRhr{Tx@ON$Sg>X>X;Fyn(k#6{PVs#Rk6j-Ab;;vXc<-Tg&XV1fTfyKX3km=u+1CP(e^Zmc``1ul5)y?yk{yd=7 z8_M*TQ(eX0_L`H0^Yl!kdaa29hduuZF^c#&J7X@Yt8QTczjGXFiEqjPT>OZ^K|0gi9n0&X*Ympx9`t>52LeE81x=o7z^ z-_7g(e1S>xMDjXaHD39jsWbm1St!nL3OK~>w0^UiaIC-*=kFq?pD1pcoT8+7B;^p} zjwPo}9N%4AwDi&DU450?ezpi)3^HV_6>MYAo7jo${D@7XbR%h$KOtbtzwW;l2e9$j2@q;j(}YzMS`rr|I6)O6%sF+LrHPUfg8NE@~FH z;T8iw_sq{f)|$)KHvajO@keuu?T)i*f3uzmyt=b$f|PK;?F8GD56|^$^h#_GE+cTx+Apt)|`xKi?C+LE8oX8C=mb;oo^X+GXFP zH#+mL%*}tgZnpW~eKo7r?RJ@dzj<=fy>0&wedM2f3EUuB7r&#gOjdix+Ir)Q>ot7O zKex00{U_RSX`{&J=e5tTwJfc>w)p3a&w9J9=1Pb4*9(hYYI~$qwbOjN>PFcl_hWp_ zm268kOgX0z-zYL+Q$=KW#fzDVd9&^L>VH@UZ&}_ImUgk~uVMPd`&V!EUp_u*#nbtB z?$d)%LkY?eCyGw$kzA@Mv>lPTd;(oBKw2V2GO zxz3p%x^F^Nq`q;TSpW17Vper)s~+A6KH(I$g>`Ah-e>v!0(ll@1=8`ALSbRE_8dK3 zvVF_@eMtvXlE5`U>b}t2kUt0Fjk5VT9`;Yz`0RY7iGqiH>*?i7%hwdW*f-l+J0d8e z&ThtSt+2X(*Dm?boGov!TC?WmK9OAC1lv%dKMPe~{88N;6=d@7f~Mx`pli$iocMow z)!vGqhgY@EH~w;W(cYJ?)%(xnuHCs+WG(NXt^#vQ9(1yPDVx2}@!a(_ z-yHtb&S47vw%1`^`p$= z(Tmb8iQQ%u==Ljg9m^@l_MaOSO|@jDKTX$3dD(8Zf8AG;_ikp-Ruwq7tzI3m^}-*) zL!ti)6B{`no=SRH`=tKyhutSn1$Q(>9}v@!(c7FL|35>Fcb#K=OOIWBuj<+Pi?#}^ zp7is0>$w{xL++J82B3$}+?V8>V0kaS7pHQ%squz3XLRv3VjI{q<&OLh=MV_`DKl~@n zYWKO!-3yM}{gd*|ag(gvk-akTx-J#hVS6k~oT>kXW#yQ4<<#pyRQyZr|bDw3Nac^tJ+xmBZb21)%ofx?I zTmQRx1%`@h4^@|BAKLQ!%Sns$Hk0C#TQ6s&X8UmN`tf@EZteGLw2uUw68aZm$KLY! z&YhnJ?)#nNnzr5T+AY;3?njS??l}FnJmF1ClJABO{9<|>2lHDk#FP&%D)?%p*RFZ= zNjeLkB$w~OtdbXUZl@x}FDqT*D!Tf-Z|6OK0YmNKkel14?dMobS6I2zpTUHz`4{hK4YcI0dc8o4{R7T>bdK0ep`|A z_tB9xOKroev)_u_MLnOqEi<$|{@nh|y=A95AAg+x{`!*pr#<{OZMYS zEuzz}o9s6If2}c^{Y7tAU2pfdQy;w#`9$r$y5qLMvA;r2_bR8)wwVCxk{>dhv}V13 zjPia}`G{k4xc4mCW%r`)$J#I4h8rf{c`yC4=6&9i^Mdj7=JRQO=nj6MxudP9rP!_G z(DZ6;`4gX0ow*Fd9LsD>9;Qy2Cbel=xW~tLN}L-m#^#s3%lnbPUOdgl$mQ}{Q6`qc znSY9#^O#>pUdwqRpZaj?<)x<8yB=yYoN@xI_fsZ;s-^)}~p^CIu~pVzmE|L})rb(x|}aQL~535Kzo zVrHqwYX+abt}$&_c!~YXUHo-VpQqZYt2Qdj*k;*m01Zf_HQHE7h^aYv-B50A>sR=8NHsa; z2-DUryZJAR3EML$Oez!CP-)@X#jUg9-t?chKbKVBZ~k8Xbo$Rnet$|QOLTQ5{(M=v zu5AD6^~>%SSc-4`9#+{f`&z!-u6r@3o>nTHpU!*G?;nFhW2Zvxk+6&d9B-FLng`1> zPxj9* z3!jPC%iOn)jjccZ?1SO8`1rU9o4m{HEn8-5^gemH(_fMGU0&t9xWuJ$^A62Pbd1jX zA$~GFO}{bwgSP&UyINOfZ8k~Obo-fm<-=)b|MItdXI;-$y>)tP^7hP%;6QHW*LgGd zEd4+2;_SY>?wguz>$QI!U63eHFWusx@kf+fo=uH0)xl!_*%x^)&P~#Id|hsPq{nTp zlg297HMcvjGWibAgHg}?Tf}n5d(ho(}6RTCMI_JOH_Gn_#lryK+cE8q|p1Ml?RQ9RXJMRmQFYH^rx4iP~=UQz! zoqGzWjtg?Nu8}i{j#%u_&yl_E(w`^uZ?F03_E~2C^^(~K`KFrAe;BzZdgG3>_x*}L zU#Jx{Vt--T);Uc-X@du|T}^N4wg-!w9x8KtJzD<5QMYcJK*veeLf7EQ+YUW(dYHhf z*ED-h>9bd7I9F~}=}PuL*TXEJ`A%n_>Pw#r&Z1Lm{Z91XXSu0!e8@3&%)r*Jd<;Fb?VTWGK{;cz>=7-DPynd`%r@At@J)4b^)UoJYm^3$&q zEy>dHHkEPTeujiu&Ui8Hz&6Hbl5_Uk%#!|)xY;Jn_rtC|6|-ABIoB>a+CTZx+=rXl z(;lSupU&6cza!wQ(_-$k4@W@Mi(^3_=(>8jXm=x=ojR(uO;deRx@!56UEI6kqAu~jT(g*|SUaQbMbtt4B@){t+N9T}2B&Oa8nmmX z?x*~Vh<%gm{-@2hZH^JHwYk1Ss*9_ue4W;Nv#;s(P6zI!@0@)y=+cBYisAWkT@!B34Sw&d zt)C)wF-273=eJ|)TTIk*f6rp`eR^(cW>UHJ_tSIoZqBx>KJ{wn-6C^utCiWYyVu4f z%l_K4u3~Db)=yb4V>_(^@7l(tE0 zrrfT)IeT5H@5gDibN4t@$nSi4)nneg-(SAXdLw#0Z5v1Eyf+y^-O0N)Z4HhV^5OW% zR$6e)mG`9X1mEqm6kpA7|J1Q>@zqG_&KFnXk3IbxvQ0Hbey3;s+DY?TMAB@X^`}eN z6lz{h?M{jDzihrl-+O-biie696+hmcvsXK9JYi2#OdQ+^om>{#d?Yq}Q60^93pw zWM*gnR>|{TaO=R%A0gF0Ba=3rUgc%aUS1V?d7G={?qj)DS0|{)^u?K$T;851xnEUg zW%RyJ3AZ=0On3e+KAm4PKYeBUQ@?wKsp+rhYVS@dnRy{K_vg-c4^z#S5G2aq=%Kwox?t}Iq3Dl zYemNDe?ENm+Shn*+dbc<#oT=wTF%}JuAe_`6F=cKtC!OCo%K;0KHg5AS+fUh2ix^| zoT6rxSCpNPwu0QzVc3N{`O~jXj zeEjEAWCBG)(w=Hp@B0|TQ>h@!UeFfxK_W5l!rsHWEpZNefBJuZF1@|+>ZU0ZJX4t0 z*v}Dpm6*2UA89!Zivz;ou_bedNjO~7F)PkBAW%I0JWA&wc{+?cuzxDb#874_h z?c?p;cFQ~8=NgyIBgsC={i!m(-zM=)@h^OpdHdzvTO9HRKT@P(>}>sa%rv-OyzF_w z{ipk`*A%}y_5R-O->cr-`((RH#|mg+;rc=t^fYU-|uFe)3e&P`CGa zJGC>y=l*r)x3@1WI`H+plf5*b{N#t(hqT-IXB(y+dnH+#^W3VW_#NAwhMP>AucVaD zoo%UkH6^7``o+x?Us=ywU1j+<a|eyzAy`cdHH?VFFhHD|1kU$uKL^Ykx_h7s>hG+xxddY9`nPogVVQft<8 zg=OFwK6j@RiAK!sSD&ZId3S!@y;gg>ci!PU^C~Vsy0lt(`Yfx+)t9ujjD#mWl9S|< zZu)B6azZ~dC`C=>XzBUf6&tVRwS_V6XxS~W#Y1q-i?tuNS!&MIFH}*P{$$z7^)KEe zMR$jv3_9sD?a8F(*)0Nw+s}5dic;-rk{SORHVv)U@ z|D6_TxvtRr;k`ZhHSqmN2JQ`SRa$m(8^*k-C{F!M8i0 zBlP~AGoCBeau<9SS@rMsuj$6d8lJ{?ogN;08S{2U?u5^0-~Ev}%M@w<(|BjFo#OTz zcNQ$Pbhz|Jc9y)!YfpWr4s%|;%m=R9h3jn`>ipDhwVKqFi!48Bo&CzhY~q<@^^97Tuk2fWM;}Hl{0(0%lBp9 zSDz|p5j#)8v*XvgeePFxHmbZ}_Dh(-r_H}qUP}D3wwl?o($!a|UC>>3%WYPxRPCqP zPbO;~PZrs@Z{zkW?pLl~F`wEyg?sA#AJ;yVel4B8cJkUOUzHZ06i+EXa^y@Tui z+W39le_4O!cazM9l9?g9Joo+lvn}}V`!By2mhW%a8G7g>+XS<#+#D-qF73FtYFGB_ ze>?X6`X+b#x0_t&9%)OaDyb^#FP<6whQ;n}{0|R2b-M5BJ#EhRvw>?S^mUpa>wY9} z%~w9>g@t*6OTz5~3s#5*sxMZWTr>HW+EdfBS*tS4s$NWS{QW!6;@^jmE0(oKPD+0? z-#6|26w@iaeQ}bu3cHqXUw%Fy|M$do%5Hr*oU3xHjy`bvdA`!fDJ=M(OWtkO#om|o z{#s4!TcVs;+rki=WWKtNB@4D|x?j4uv&HCcI*uO4s zS9r12#jNEIkI#v|`~90J?_-Bc%U;>9y*uY!`rH4#|F>58mFi!+TL1s^qviMY)h-_K z`qFvJxHj~cfAX)MlTyZKJ6R?_$gB8!|MKS_x4c>IW&e1iQtfh0<#6rl+alX}>yH09 z^~>^?p#39v5A~vHYkq%x*3Vogvubi`Nqya!wY~Qi+?2Veen@*!dC1-so`LbIKh-=Z z34MIS^v~ganaxeZ9C!1#8gFCY${l~$CoKECUhCD}o!^%ovz30E?egu-&CTED{QGs_ zgR1%Y?&qD~CG%8m`tMJfAZlQjP<`NDptzf^MCq~3GoSmM4@>B-Px#(Ajs4m3`k(*y z{#g6}Po4djGQN4zcXTT(bTf}-8>xTY<2vX5wI$~@9`XILahBT0uVNA?x<<)FW6Ad; z-{a(`-m^aOPQkjf`EAirMpp6pEl)dqj_+N_AMs4&sYjRRhl#!tn#FZDIbsf()@BGD zko|b#O?#xf!44A{GnUuN5zo$+dZ@2rpcTXN-=)K&K7vgXIWHD zR^#Fdmep>5rtI6ExaX?wtwNUHGnnsP-+E^Ab+_cFJ9a)0(x~qF=$clq!P~4-7hkH- zF{RV8`TeKWVUe#gbz~WDO0O=v!S>?Ihr)+b4{J9`$zG96_~3W@>4(*7nOB)}yF2eL z{P8&T(B>cBBFi6#UHo(K<+aMTguhBZSpPl9j!|B6*z|;T=8MkiuI-F#q83f{{SkT6 ztaIv<1t)g|Y!rzo+f~w_74vxYg7&-TZh=LNZU9pBWg*ybzRnP0Z<&BkS3 zZd1N2Sod-@=cQ$=*E}b$iMyWl_~D|sn#0eI-AsSc-?P76y~ScS-C@^vo%MZjW)`uJ7j<5#cqG>-qIFO` zG2ze@24~Uo`B(Jz#fq2YN=%WcP%Jp;ab57Ha%yIp->51q1 z6h_B?oQ#az&Ej>tHb_6YS)2O(%iZ|H)f27VH-{@%zWA0A;xjv{JZtY7QDej8MWhYuZ|bJ!?Rho_i_{pi68nWg+GiAIwP@@E|Gw$csLiM*fY z`})+{;scyQ+9{dL3?A%~5zXeM<<~fRYXo;H7ukL|8?*b#omD$uZ((K7V+?cRT5-bn zTFY-C^^?L?y+_|$|9$6tMqozr=KV8lQ@%y`%Jxow7Wn?;e81PCyle&r>pw&s{`zU> z>HpjJn4a;h{XOU3O1HH?Hac%HUZWlme(C$k?nz8nyRSaKdWSRDFnzK4(HFJ9y6;OY zP5#vrY4A*F&*m@dTJC4Bo4mK@=MwHb`z!xG+&Mh2KW}B3C0}6-+XeNT-j_npJ!GHp zv-uh?pZuJnniHL^%heh-^1h4BSbSmC!oLg3>w5pxfBBzxw`;5KHM{mt3snvHck{hn z^kUch(>vqq{yohU*wk=W_sNkxzv_N2x-Wk1P(I`7zkQ}|UlM0XG&@@TFTAhj*3Xg5 zFt0KD&3&icybncsL^CgBCUwueZ@s+gTIC+ghihWgQcfSO@6y}x>B4MD2H!v46(_Ui z>{*y3&085M*8a`S(<7~P4a@)Fw3BLc?w@pf_Vv}$lsVl5XN1E7z2(`0OtTrTeU47tBywR(dvgIo$@23}L$(CH(j+#ur zy+w*?|FJ)Mjvszb-0S|`xly>S=f|w#3AYyRToK7{xlo43!BAt#_S~q>w!3^U4tVY; zSIev1BeqLtb+Cs0;}4(Z-|bSjt@-ixXlFf3d~U^pLwh$*uUfbFnZfCozt?#2TgBa) zxLxOK-m#l+LTAeHpPH{Zf0KR9g@x9BUwMDtv2BPbwC&iwrYMCehEv{D-wCd`M$mHbSZw&nj zkK0pazdt%Ne-Tp0Y~YOKN96ME*0oUu37gcUt2i!6@!izkbbi zp1QD4|Ng$Ns;y5;F3p@^yzrIX%h{LJS4FS9zOsDdJ#LT6b#?E4-LZXm`^e(X$_etn zW|(MHdVczlepu-8BRz%*Z-ZBtU%kg2|9_&<*U;|fZvJ`J72D_ge&67i%UE|%k*RT~ zqc4-C0Nb@K2bNy%H{#x8scm)D?5fh$H);K|Vl&Gas|0T>y!P&Zy4_AYIlItR?^n*f zUQo9;R&lj+`iVcOF3S$jj(>3OfV1OG#jW;&?E6~Nt=GN(P<;EJK_3Hm(f-Tq5m#IO zEZD@(mo1jDKI{6txQb67IK$5*&QPylJ^Z?}Q%dA0^TPWsRbL<5sjZ9zWbl{-TkdqD3dxv{X!bUWL!ON#VIu#Vkn43g$CU$x+zPs$DROU}rnMlv~`WMXjlA0FXsoMUQ z`L}`8+gTPt2@{Tg2z+{O-cOgS7gox#P293;PJAPu>VZcdEfdaFEviX#>-|%3-mdAB zu+zPjCmu>Qq(#j$ySP~9Tr+z!UlhyNTWbZUmj7_A*lV)$+r?`I-3zisirReNefyKo zb#KOr6%)Lsq;{8Zr&!A#Vh8>1S--S)z`8O6G(-n;d=UWx6|*wOddeC?a!Bk8`!qMnL;&#s(km4Ci++u^mhpMP!I zUBS0O=f;b&BC}}j;N4O07sjr%aJ6*Z;WHup;(|Nh_9eBJ^ccn7bh>%5bmncj?TyWC z{81JeVk~nHKE2>=f9B_$lRxu$&KAy8ux|Xh?QU{)|Jz!}M@x6EeY@c8%9s^Z>#EF) zHDU$A7hhRXl9N#!_ed>YBPwIF^FOisQ}0hxKehVNl&<4zW=o_fGzQNN><#+b7#AY( ztn|Qc)-4n2xTgHLzxHR|iSolq*2gN2^@Nud?|F8GTg741{WyPLspI>9rrtSKa8{r( zesjsfH#6Qe9M(80cJjf2S&eaxRTD%l-v!78hO`x2FbKLed)KX79v68dcbCtObUBcE z@uQb^%=Gy&b_@%&zAz;yJyfs1$n@y-v=#O%ZuW%p|6-1~6Mg5fMv^FV_LQVHm0b_y z{Mcu32l>oU*W38+vhA9WHi@jy4=v`rV7a0uVE+Qa8$r{`#23$XvIzY#-Aejw#e~R9 zle{~oaK=41V2<1+cBNjs>p ztBOZ2mA>{kzG3Qw`yX8Q8UJMQYKXhge8lTxZpz)&rvj50H+n5-muFCAiDlnk6wvr^ z;nBCpU7XFM)_I=JaXH@W*jDFM#rR6}N14p$geR93f1l#hnIH9Z-UN;m9r^T})h^mQ zd@EdQy>C8y)>l&J{XC`7?AoSmrK2({^e>dNZr~`-RF|8OamlNALdK!Z3YzJyRS)+s z+yBxqVJV~PGoH66{XR?f^QkYtv*N5zNeN%MWoGdA%xiDIRtjCreLeT;rcL5vHl`&9 z_ghMT>c9QwW9ho^+xmNg{`>v5-@7_8Ue74S+3D|DAjf3-~2ix{`II z7wkQ^Q)pAI+m9Am`}#*8I%OApVvE+>V9>pK+K!h~-JN!`tY`VZpvduVb5*_7hUwd; zhlQRlT=vSY>{h^;$=|2E_KjL6oVp}l^t^Dn>77N}QxtAI)6V%)^eK1RzPk%|B?lbN zTKn!>B$onP6=R?EjMY0@?Aea}`E-C=LFB|br$hD|x&8%J`v81ds(9G^vrhbfz0G!$tx>zG*WAk;+h*q0 zGZgW=Q85AE>Gc=*Zi(t$CZYhV#PKf>~0z$AmvC zRn>km?eP6w^X~KJ^ZQ#jGIMH#N0q*<)HN4-aE0Zjo}E#R_1610FED7`I{jb3 z@z0j)s;7KiD*up~;mPGc2dDakS#G)Q6yr0w{J;0y-ZYy(MSmC#c7c~0ua1x8Q+2Aj zA*;Si_v?j2Utiu$=SlZFJf}U|qdfPC)Y~lf`RpI9g^V7nHz_h|EOylOo1XL8_V%6Q{AKWr=R>db$QipUcaT9K|;x= z8`~}FcPJ?^%u-;yTJv~9`2M|D1>ItK+9yfzuK9lDYps=edH$Kj=Eb76@!_gXoKxPE zuCoYTx9fi4D(CFw+tzr@nepk+=eo=OTh)z}Vq-2G``0jcZ>P?axgwwCESusP>PijX zx-?DgY1yRT`t-K#x!Cu4ms1iZFZE4p*IBnd&tT`FFQ4X~a{YAJBIsCG8foB zh-v!s#^PUqpTUCzOV%$7-#e>0wd+jfqiOG#i%;}TaO-`pslIXMB+A7a;S$!Zbc zXXxHk_i0)EL(Znz5t;wu)_Fd;eBiG8{o4h%Uro#PZ|_|;LGZWu<+%A3GL8Im>h%^m zFncjw;bS3ItbyOw(U>Y3Ec)o*`S#N>byA9wv^*p zde8co*HoVta+-9aV!HFL=vxbZdn=XS-}V0M=Ib98EZ$bUYS;CDAvYr}BLgkl6>i5r zN#(gT_k!EPQ0LnXR-u+LI|D4ab@UI49y5!1xaNdPEQeyOW8k_?#W~AgIdZIYDQgz- z6ldV44Vmg;d3@bon+sK9OB!Na+gA43~U&n^Z0+ovrw} zJw{gI_vOu{@$0LNSO1!Rn(d5g%O15Qk#p11XUlHO%v3M(3}(}goq9Q>tnY2e)<*9; zw@g)6lou5L-^92od(!(q`)_}_Yw+FT`@eUKWjmD%RaSAX+O}$eNpjzb8EK#99op@A z!mjAD=D}$dSKq(As=n*IdiyomH)RH&FRXBW%>P#L?8(|mcW$pfzrD84?S8e4Y`w`h zuQNAhTx@>N^DlA3;^n7|j}||#F{;1x=cl>Wot&y-m(6R>-ZHfnc2sIQJiqqq(=GnX z?w9^P`d5AXV!P=zOto9)%vs5**m7W2bGi|O)TOR9%DZ!_EvA&_&D-lGJk{vT-K(+h z?c|@g=>C7vkvxCypQm5tHG|E!7Sz1maqFYk{XKU3zeI8;uX?jGXW^CAQ7fH7x90Yg zEskCGeeSL0`SUZQ)$%{ydi4Kko4#v&SLdWXYMP4o+gsYNX&STsS(*F2^Pc3r1wVNV zp8o4N{%X0u^Sd^SwwgTa>xpZMt0Vqqoc7+s#L#|U`&8+B2G#%b4zcRqQd*^d?%d8V z;i?vIH>7SdIy`I7RDq!0XT}mUd2G$E7;HJy_eJ(_S&n7t`zJppSD#qkGGSZ7>DQL` zHaDiPKISdmpR-4OPRry1qrca_#4nd`xOC^$j-9)0C&ZU|S=gz(~z9|1UpXV^2(wC{qdokVlu37D_m;aV+TYloCzh4vUhu!y7LnNMt z#y?&7D{!kvc;s!z-Dg*}=c-@yeV~5j(F=vi&-Q*@^H%cNO|B(B+T~@t!XD&KZe`AJJvUN_Zvx_U1A9SgPy0c16_0gx80Eb)|ZWW!iB+WD_{Ud%r!-**5ND zdq>lk=fAu^hq>JRR>1f7Z}IBfpL)FKv}Ku@kKec;RJd#Ff$hh)=GB&OT(|1g>SF)@ z>RG|ot3DRIFs(OGIG33)^W8KvkCLzwml}>kuM7N=^XDp^K5rEG#T?W_V|{7ygV zwZ`BrzZLtG@6AFB^e)_PaN$~_@W8z$<-)}Lei5PP2hZR7zjnWDM2X``i!U25D_k|$ z-CpsG;mQH_iboHuCkSts$ui_T^||$R#`TR4(paV~u*l&1qIC4fv;HS@H&{LN;4AsL zBUgr9Z?EixE6#@J5?epVZkWUN&w0)X^GwgDaa)@&aVfbhUSo0obz1c7t=UoXZ(IuH z9S?DpJ`8XynO*q)z*DB<&zpB!cnF_053_owZz0jR`NRvk#ch37z1auZbq}36(m1Uk z`(@>!O>>&fVzyfTKf5ve!?nZdLi@wo?Gyx;G|W%?I`8WCTbqr~+dc|akYM6f+1K_* zG^%pnr$G6+)hWLhW<9umhHG2vPWSygAFN?Bo*8HS&^Kg%=<(H`LsCQRw4`_Gotv6l z`mE(j)GD>qZM%!xzdtfey6RNS+`i^e8e0k1YfYQWFBl@!^0l|f9A!Q3|FC>=sMK4b zcK$;TqB>ogC4y8JmN-kkr9kJ_<2=!<-BuxWiu=GI8IS)w`qHow77_I#nH{yQ+f0L>|WA-#4D&HXzD+y1qW3p zysFo#PTgF`ck5HK+THlc_8-$^T9lab3r{eI&0Zk3zA${-@^$O~`c@~!pI{5)@ccYI zQ*4R8m~LR~=abX76n+aADC({Ge5+Yt6W1YE!6e(je|pxxqL!wXez|V;x9HI+#+Wv@ z>AN46nSO9u{x7!u8sD0wD{kE_`x~EYwNY2_?Zy*Uo28=;S*bH?7vI;m3t9h+?4szK4w1mLiUNV>oT!Vk(qrbdZ+T7(sQPw#~pq$T@sI8 zv|{Cl6N)=kzQ-CY?p5jPNo!?4^md`@EE5H@X6cRyd5InCuFDpwRwl18nfYC6dDvw$ z>zk!Bz9b%8V!}DC=|Z%~vlPqx`3v{ePTt>9SEYVCZfb$vvp3!9U7Nc6W#zThHMN-M zzbt$>?Ip|gi*a8sFDjZPp}xfRsetmwH!pXd7rvDyRhqA-n9}?<=Jw1*vs-RV`u^N# zu~BLD+G$d+Z`EqwpJ6sG)xMR*miT$ zmdSG_O}QSrxvT8fgIkwN=f7AMJIm%yRda5W^NQlXTe2)|dxU)$N=%>ka7o+r-+U*0 z^?2N|9drGPW1W4sdumAb&ni`YeREOcrS?yr*TN0YD{fk>U8uV5`iTms6K+*HMbE@q z_TT=q;Yv&OH}3F9UL1jk|Cxrk-+)^0uQ{JckIl4sD)bZ*X!tBThwx21Kt ze_HcIyw|k%w25B6_4ULppWXg^yXe@RVJl=*vUB3Zx$Dlom{Hg}=bXjTEyW?4J9jC_ zuKOCl>qf?$6D-Md6qRyM#)tFgwa$ON;r!3oSbpQfzTs@=tPDWynDdG=AFkwo=^s!b zJolN_VNsuCxp%pHr%0~5;c&@A!LaqZf?@2VJzEa3-QKxvVJmC@VtdELFU8N&*kSPgVoBqnSs=Ot!X^Q-H)AV>>HGOQY(?70_>EJF@ zd@{pZT>R4lXVvLhXXozUWS?;S;c*@(4#hCur@W%K_kb49R_;9B#iwUE3A9>xgOqyy ziWw`_i!LQae{-mHe7r7iM-D|6ot9U(CtN=3 zIk&a)8!w+wj{3Fp?TH+|~VRm!*7nVGibsI&+i z&Hb$}^Z&%!$y>E*85L9Zh#Ds;=RS1bFEmH!TjIebTOKmZtE&E5`0Db)c`Md!Tz9Z^ zZg%nY4O{kF+V))Duu0mzMEgn0m#nZOEA>uapY-%<-&J$9jasWG`W|UMYA)=xt#7IX zr{Y{6@3f;`$&&mIr%$u*o^15>l>5%rr*lkKE;SPrc3RXiD@XUqECDBvZ^mbj{`q=G z{IkRAkmc1|F0GpIvgh$^CJFE&mylagy*Wy9a~fF=3yDuYxjBTz?vLv{wQFCC|Eg}N z|1d=$`%)LntZ4@h79Nv%tipbpL(#F&_ig_)%Z00^-LNm(^?mxicmHmEI&M?{$;8xS z!M0saXG8^5ma3lp!ttnxVaH}OMVsRZc0Xi4-+sPbQ0YTqZ6p7}Ul zs9C(Qxl@qasN%rh!-ctq43GXxEdG6NYG+nrYwa>ee~+nZp|jSig?jKQwoGW)pVRJL zWYiW|;Wn&iC;7koLA&nu97!Rh;I_qgSrxozqES6rUHF22Myz4YPC zhSSf#e@K1lkkoEF??c7f_i6V{?kA_RPm7OBO}uq)j@+^D8y=sxJU{8h;nH`yy<5Mv zUEbbzcM5-px}|I1!&?*oOlqE?#~dTRyV>`lN7b6fb{+Z8PM?ysQ%th9Eab~Q(|n)% z8Kb<$H-mec1_zp=BA)sDh)rIbX|iDXefM8~7hU(+^JwEUp8X-S^AvM!+Im%e!hQ%T zJ6f-td4AT7q*wkgZofzua@saKUSr?0d?odTHf~a4Wz(N7Q;>1rAD-wxWwwW;W$?}1 zNo`#-cl-^z^k?e(Z7ETIbyZk}=j#25JY%kAUwTdWsd2FX(w%$cGPj;B(0QKiv_|?w z$Dge>aqG^moouSRmr-x>Qo9Fv$}^m_ZFgRu{p3r*t$@2Lt3JPCe!J*NaU3Kl;#>Uz8PZ@UY zuTa~N&~-Ac=UDgFr57hf?b&)kJ>lY|+N#3|j8Zzni_%5dDz~he^^SWlh7HK>u%$21NF#!b8dml za}GXrwk(NPn6W1|w{pSOmJjt&i??^i@jC`uXPkN9*c_X6ZpGFuX6f=X#h3YCi(wUC z_dRS|%Y-WXtMj5)%f58mr(^O;mo?Y-PJ_UXg9YucO@CcDx_NHh_sqQkio3+GxSz50 z`@r~KP_Fz6_b*Kw?F5&M+PMP8Dly#8IOplE@lUF*h>^c*(c&@Z>z;^9^26N-FcTxvYpk*W`DWJ-4H z%u2U$*c?}(FQAdQ>d;wL4Pm~oKRU51KS=)5%R!=tb5uZ@E?fUx1 zehKmRCpf-})V;Na#Am8#~+WjbzBBqF% zxBq^9%g(RUwD_i)e$(&cZ8px0(NCxQY)<1oZPT2(=OEX-^P995+cQKIa-ON!ExAdu zYDy$Sx9O=rl|P;<)^n}S{qr_m(&>VHxYIMU>~kA#9XNHk%DMH7{4|@O6K9_5x7Ys@ zXF6JOU$*YC(l3X=2;Kq}4=bhi340rkGCthaeCYFs=%UM;y-%D=&8vPfG3l+{pO3aL z{`}dSAn84$CWQOOd!5GF1-W{0Nmu(WTXwA6QXHb$|L*P2>?3iaJAQMtXZ-OE@eP!c zl`)K9nLcGrccx$_t3v&umpT!eD*UVGTCgUtw_PmhI{9RQ!}6V)Gd{%DiCSjwIaQ-+T`i`K(%Z?%&62%>K28u2KrNic>EoFv(a)7%5(Rq*`OWFkWTaK~wuR zBAd20?6f_gY4du){$t@vQy*HNi9UB%{pF1Vy|3eTmESPSneuyY+3o5*OMQRuG+Y!{ zdd=zZEB(DkH~l!U|LNyq=6;14jgPm9Zo1&{d3vUp!-a}D!q$H`nAtzP&|bemKekw<7cBPp~QBZml`9p-lCk#bT!g(*N^X?`JE1 zj{jk&>XM^a)3;Zpa20!TME3520?j9y(>$8&TSKicFOI2TFW_oWuWj2_cTa^Ow}iz` zG4;~Z3ul-zGy+epyRGxLAwL}Us)nmu_A(e|#yQqKQ2F+;W%mzjiS~I54_y$jYW=VI@aM~E7c38o^B)ORI71>4?Xt&kO&X zbT*bZB6o93_)*jPAGZ^J{$ide6V$dR@4uR4kijyeKDEy$!=GedG-?*gc2Qhjp!1RO z5A(CuJ+l&LNvApZTw8ES$gwU`p}yH5(ZJ>Y((Hw*9bcAt+iNdTl`!eH>B%w_Hxw~T zo8x4ElxgChpRHTkrzJ5R@!@(Mm;EN=_HMuK(_5C_Zm3LJv)p##>}fw2=M~63Kaf)# zvMK7*xlPL#*UhoxU+&!X!RE)hxBS;qk3N4OHNQ5g*kjk-WwmkQcN_LN9ZI(BnVPrJ z^Qdl1(St9VJc}Q)9++gQbF?K+`fc9YJy$M4&XSn!n5)Frb8dgk;}Y9-`adpQ+4K3) z{I%>w#a~_d&*Q3 z7b|H7)h8?+no-_qli#6y96_=oO;TME-MRJ|T2ZkqUH64S)yjiP5v ze%(F(bjB8zsR!)KoUX0juWfIuUBDId+*j0o`@3UjH6BQqPHsG05MJk%lI_DL_vq4@ z+WkKFB9C?XPWSBJs``0O{oMtxg)X^UssEp2p2@#ei0k39ms5iHYIGENPu}<5@wY<$;q|$jI-VJS5xJ+ihi}G_Bk`K6z0BO~nV4a9HcbOgoAIwl-B6KYDy|SSCsB-X%_r> zfW;|3>Wi6zVvv$*WPHj*olo=fThFm82H7fb?3m!c=3LbO$lX_W-%`Ake(-(R!fP|v ze2RRW6lATl{>ZZ}ZWZ>SSdQ;SIhV%xz{r5MVte&YI<@)n}UfBK(E@3~GcYOHx^m$F; z)bmolibtYO?#$l8eCYPsyML9sQ$(32eb`*_=EJPRSr6lnomjPD&Yx_lyK=&BxfcI* zy{uxOcq=2b06~1YEPygUO^f>gy z9Fc?v;U|7Cof5lUam!V^y-j;x|Bes0@$2g3pO@sT@WNobz?`ioC$T1Is_AZCvVYow z&I>U;J3fXaILWI>9^K@#%dO|j$JI{H+owIqolzZHP`$#exblGcoT_P8<@|~|+l0N+ zK1#+n2VMOY=6L$gR*U6ji3iJ%E&kwrMx{>C#+IwZKe9I6(Y&F5&Bvt&RehbNRm2!+ zocjFk#jVWSd-?a~iE+L+Rg3Uiu>6k}|FuJnPbZf2{?LpOzw7ra)}V6vgR^(lHcpax zGM~RPly`oo`@?Ao1uM@AuATU0>F;xo)b-lK91feVn>%&>{`aoZ?qNT5&LtfBJlQmy zJMzfNqq4WyG4C~)6eU|ziJK3Z4=Kjt_fUt-?~aCV$Q<*g1-(=ou6#7<4OJdAGi6B{9nd#b7w@g z{9dEO9iPw3&k_DSv;J-B+S_W@rp&Gvwtjw|c(w2J^W!0NIs~e^?!9wr65CnxY|~L! zC-aG^-3M-JEp%FI{d~TOPE&T>3kQAAL%D(sd5`oHtoYxV{TH9nsUjMi{89g|kd^)3 zm`yrq1=T;Mr%m{O&@B%u!?ZmFZ8qd8@Q)X&cQSzHRUzUGIyf7VWz%zsB~TCBp74RdxyG_H=&T=SiE z9b-(AhG+caxX0i3R{T7vC45@+M=@6>|LU4)zH0?4IajOBc)sROK=s8p)-OLkou0D! zR)Zr$y3>IR=RZt8T-CMp*<-WK2R5ocKAk1)JvpH0>FjLRN2+a8}o9=ln^0$3Kq2y8h%E@&5VSAF#(d7B6|jCO`YLzSRFhlkj&|w?B9JuJGkPne?)5@0F_f zZS5~A^u!y_yqmgl@1k|)va>f1KX@`NQ-B@!VVP-}#iOwPM=b z2_M(Ksei?opPkqd8S^;f-I;~y`T`za8NUOiPKo3$t4&Celu4*%NZ85O($QqG_0#oF z3O|b#bD!1w|D2!HJ7wqlSv#Lz?lXF}&g{_r^5W+X7q3})r)?F;`}}>~j%S;9u3dWk z<9prvcg$v7IU3>LVw3H8Kl0_8ou__DXsnaW=CuELKl`CgV&KJR%>uV?F6xcFSQWTs zm(P+JuYawN+^;9@xA@r1qP8biPs=vkUE>ifv-tAES*6$SloWd>KV7)KbxMw>?jOtU z>CC&%Z;DFM+xA1n{V zum3PJ?c|x|#g9skr(E?54cRq&*Ltf9OII~dy*quqe5zQA{_&TGZtE{`Uo&m?>D+(I zTl$whHU2Q)M}G5ir=}kc5lUeP^f_)VExhQGWutgx#ss66HYGO{=CC&vOpWTi<0=wu-a>bV&Ee>&Q{`&DY|7Z4jM;CTo`IzaAWcC+uC`PK1j zPU-9aOfkmO6E+^3t=;|N#b%Sjjaq+?6tB5`!Ll&+Poa6MK;H?E_`_^s&&9Uuyj$*f z)q3lTD}wV^9j*7`{qNZN<)x0(sVyr0o>TgNAF8hso_nZX=lSu>^Z&np`#k?`%FiF4 zHz<>z?3Hxv663LlvqT@wkQa({`g8DqVYZ`K>-xe4$vh__ z8u;gJwD-1b-$)GOEoYuidMYov)J3>KH)@yX{4STtPZvzy&!XrsnbjxLfa}^)ou{(w zhte|rpO-OobUswm)jzT}i7`^D%zr56n*p7Uq+wX$cMUcei4MqOCA zM030S?i<1PZmkwcniiz_xBP_47b|Vyy)Qql=Jw{CTyTz~-LXDjKjOOla^7c74vNos z|5&ybFRLitG-ZbT{Qm*m4thL?8j?SBA5`7{YpQ(AC;QXxV)~Mz2RLVx{fjJq4K|o1p?FgUoyP>p*p?9^2e;^suQ<*)TZ$W z1uZx`;SkgG2~OU6AGcohJKS%%eM8o6(2<(|xK%v5ej2{~KV4;I#@fFYMO**J&-wHH z;{N{s3{!PBE?>f^#J0vlJ(tN}dhO;M-pobW3s0x4T;BSH=`_>EpEjq>0(YngO+P<> z{xpS4YX7$w$$E5zO!_La|B}o)Z_%eqL%ja`3UQzJI_<49{r(JX&qwP&KKB06we2ET z{VAb_Ad{IHA3p_nWFC6n_Ox`W%kM~*KilujeKg;BPWxik;z@1II!CRxl`NUxv|A@r z)sOMt<^!rRmnTdtYh_%i!s}dE#}N9ZgL%i1wM?9OHuZ)^#r>j z1Cgt&<%T{_rh6s*;BayFw{q=Z4%VC+r1>}cBu~f5py|yn2k)f+yxck^*rfk`y_D$M z+@oKo2ncg{dw6838k(PZDx|&q=^cid@^kqdTH>GjKG%%9(w)adGNtNk`P1(Qtb>{S*jHv7U-26%QRAcs=FNc|CG4L9$5fKV% z;bUa(i`;Q`OVOOR&Mh?}&zA=ma6b6Ma((Hi)AibSLu_ql8@+V+9BupP_~trA{%@vw zOaC*~Dr5+D=aqZpu8f*|OX2mz!x@TKe_Sh-S?0I?N{prc<(Kc@7C;VlI09aMzVy8p zyI1zfx(m6N?_Tv23z~I(ecbOoWl>2{3G1vxt%VQ@FtHu;JtFJ&Rw3 zy()bbyUKV~;3~UQC8^x0?us@#aaT9*^ivPmUA6Joo?W}w?fSMS?EUOF))&j49DlNK zCyR}LpZCP21@7B+|J;=oyEX6k9sS+aAFC6m%P`Jh-qEZ&(cwmt(5DRz3cQAq^B!C9 zdvl&EVG#1(BBUCwy!4rv($Zy1=SfU>Xg)fb%G=1lR}Up`BA zRza@pBmF3cnx~~H$5X0Jw{Ml*Vq17xQTATIeW`ux_PzU|=Y6U3;EJ3r8Pg5*vg%8! z-t1eXc`7sD5LeA%u8Gage6>D{nh*Vn=5$E8Fz`_D;83 z?`Qs-dG^xPSITQY@7=#~-jyRa^Xq5w{bxNlWs>QiM&naE6AWj2O8q<_#`oaUk@JQ2 zRq_wduk4z(-fUa-tKZ)?o)Or-;Y{jP_H&(jnFd@BJ}rF{x_3(A(=Br5G3JkUA3B(N zX~7fKlZQDKgHNzMe|xTfuD;k>TPKFg-Bq5^lcM*0^N#wVHD5;I{v;Wr27yakGE8qh zuX?}o;})YrtF&FlT>SzkmBd)~7(eivYV8|;Dz0{#(V3OgFGr+u|16W7IB`k*q}S@( zJH(D3IUZAR|JAj_xz2CjZk4~DR&+_I!1K0X(RG)MnUF&bdfv$g8y@R>qaxbr@_2de z8ei@|wi=7BrJJPa$8|>+KQwJPy?g8XX}e$Rzt)OXus;8FQQ4<6PYfYVpWE-FNccOSX*F8N2uX z|Fe2gZddW4^m*0A-^1UPZrr|O-{QJE)&-f1)a)i+RC~;t`7pIH@&ICWnH9mo} zA`%asxVN_U`^mefSDji_x@sSTp5=m@{-Ud1Ee0*W{(AI*&|%+(g`As(uBrYw&GcmI z(_Y(mJKcB7Kiz)u$tlL8wccw(-!Z(jiJbAKDRfxB}L|kp4PZ>+~j|GC)oFBa<1@NH;G-( z@BErEZNc_u_pJ(EA836gE*1O2FyU&$R_3}R7ah8%H-uk&blmgfxp`XWMb0mow7T1M z(z8un+d5lba4PawEz#fb>)rY4pDhATA}s={ffJ@%{+iR%^X8t^?Hb#e-Y4proBmJl zY@YETaNXPWk?SRMyhTh!(@s2DwZ1gw=QH2d*M0rPZI8aBf4#i=MqZVHRc*{o;jBf9 zc~3r`ZwjrcUKRkA1U@Jbw5I)Cf`=z439#`MTZ_A%Vw z&xaYc)~GbvH+YNbh&gSrJ^oip-@B)<=wjd1wlKA?&ac9cRO+xhm;5?h%wKJ{Yw5hD zam&kA)T@`o-!*vt^_=24xt0mXpD5k*bMiPU{&mtemHrx?KEC>r&*vJf7naZZ#Ozyq z``FTQmB%Lx8G>B7t3~!I9J8)+THw1NJV19slG+ay6Q2)Z>`VMVh}{%&o%M+MPQt_q za|CwTuzIXFap2HlU=>nhh{%I_gd2Vz3>DH%?pPr}4z4ScMyGk%sX;bX}(%g4t z`~HQ04R24I@M(#>q@&zK?I%p-cV{Ne&-|4Y^!(TN>&IK?`+x9{m@whN#hbA&-<{~H zKgl%r!@ddAmT-j46xeY0-J+O0-HV}{r5(wq4W}dcx)LR=1_e&X8j^PTy`|G?zILVRxf8U}jZ7_|3+r!K?3_=JnbA zWl`q2={xThUH$_)(I@>|U6j|MlP+DI`5*ERPij-K+pF_)ra(T^gH5Y_RsVK>RuE<2 zJb3rdm1AXhwQ8pAGdwuqn0GWLDnnos5?f7b{txur&JaR&r!= z$LV$YcZ2R;S~Ycb)Y2?P(?c&sF8)o{I`$^xm-pp^zU2>hy9V2e^NO|q5h~JG5o%7@ zRVJlzyJYgYO*5VCRNYV7Y&C3N(Q1CV<;>JYNB>Iwk4*CUYrRnab=8KDhfni&@R$Ae z)th=}b7kpXw~D>9v-d2C`yF)njH$ffTwt>XO$%jdS&UzsawRtQe4e4Ke?g24N#Ugu?2!s$s>%fyeX9^@)f zd?56$O8#ryTiZjwv-9KgEf&1jEU&guT%x9!a`m^!tZ70{y9As#Do>sKTq0>|Z1Y9^ zi>dF_$7h0bt?pNOs9UHV*5X-KJ(T5 ze<72cZii_-*R0kmJaX!}#J55t??rLT+rGX!w0f2&qo-{0=j2mIQi@-Fx_yi9*1Ro? zWO%eJo>~MgD`uGDxMfky$F+O5K9W#OVON~PwOd{B(2_8Poh${M4qPe=EQ`4htyK-P z=CyxvCuo1r^uX!raxDT(4=%ZAnN6Q?g|}tG{d<~cTTcXZyD98E@}THU^F;slH=E~X z@AiL^QTg+aNWzLo?wP+UXAsy?3|>@9_1`HJ80BwmDeFP$5Qqo>7QR$$<;s zo`~J_>RT_n_1&AcFRa_2y`5qAC+Fzt`Z?=A=UiBwAHRK;)3OPgD%+gjoVbx#AXV|< z&4avycfG3i{Ifn%^8Mj`rmx|h{g0)0^xd7YWTLL!jPD^qc5lucYAF)!DUdBUu3fyU zW4+jZ6$b^@!aJ*~mhO5a`+o1)YEQ5Jl;9AV z$r-Q6=`w6DowCFqO>>bybHYF1ckjgVXLsjDU0moinM-^%BwH?13)r+jxAeJG-~{0* zF9Vnwv`!?iD^-(a;JANu-|Yn1isbwwt2)>OQy#xN*RzK_tMucv84dXs_WpmBIqf*K8794qUp= z4>z9OcvoFx_see%@f-F`Zky+@;MuVc;?H!$rWacZUT6`iIV8sG5ZP4}wTRR4<-u9O zKVpRZrrYZ6-}{HjC$F#6D(dYF=DEicrzCZ!7}WlWEfn<7+wODkQvK-+hUv_4yf@4f zo*xfkbdD80Ua4Bu)4r2Y=eF8%p&j+GyePJW&`-=8(_{)&RlFV6}x z6fayZ$b6{u;jc9Q*>iKtcKn?9jCWQ!>w_7Yvu~>IP2M(HO)LKXLyzO_vPRYZ|4O3P zXJ~w0_b2bc-39Sk?`Q0(X75fu|0q7EUNWU~w~)9H==yDa^u{Ii>5?SP=e7M)pOsi0uy{9F(<|tB+U`R6oj!X`a5~-Kb9Q>< zThy*srt;WB(VM>}g>5STE}@df=E(jhUOSWD$($9Rm8~*eK&VUMvFVEMeow8R@TE_D zlm7Poll0#;g;T3$BZDmD9ei-6@v zt$UGYr^Q(Hl>J-bm8HN?b5Lc5#pK`B@A-Gv|2cS`pKbb*S27O#ekbQn4vn?(H$4BZ zG12d7@+phgoby6M^cE?emtP$(J$q`b{-*6KKQsw=+D*S-*#9;{y#4y`Er&1vS1|TC z&40C`=;X>{%ip=m^;~j0>mxO_L^pHYwuxyA-R9RQ{hU1|oU`l50+;LDoApZeT)6pl zLiCpCdF+Mi@Aq7BI9IFpVbZH#3#NpfeD$QSH{Mk4=8iw_qIcKdtyS2mpsKL+LG{^3 zS>5_I#+80QcuwAqe3iI!@^$^6nGbg{d)}TbrndA{?9Knc z-Te0L_V1nLJ@umJOLu#p*9(lTd$V=>^%rr;j9b>8iZo5Qy0hZ=kKL_L8!z73RI&QH zn0}@CP3fKaUrxPXylCqj-fqYFm)TD0@Nbt2*Dqn;J@3!?clE}uKd`t{ zxae`r9d3>K;1AC}-}Za>O@D@U{_zqf8EcWEjK>S5Z&lCIlG=D%{oUj{d-l~B$Jy3R zPj_ndjr$rYV_5OV|%vw%+vA}i7n5!tNc>4SM3n`t$t(|kJfk3qifeZ zh~b;JbX-PtLodR!<8t$_CS8y59JGsZ$ zQ--a>Ku>_mCY{B~z&C2^_ExGt?f1j7xSNuq0iGv*YW*3fA?48f_o6Tb5?+Sf-Jm;u4gP!BN zujQV4KUd0p3=N;(t~yIq(12sl*RPecjxAvbVu(4k^*Yy8(Lz~<=_l)!pGUOamQc05@9e%UXd zT()A-152Y9{acXs`Ht}C<@J{reVu8x{q~+4S86W*^*!nIq>|yygQ{~kEhiS|Ip5aH z-EH#nQq6=Dc6R@FoO;Y~VVT~mEfE`2YNxM`?TO{tz5dO-eZRByqMWv@o9idORYA+> z72AW!kfS_4I(2hT)eot?@iH)F&D(JA<UQ}L8-hyhzr}t%Q zj1~5D?vMT2yOnv_ao^cSsyy?U`gpmdr3NXyk98}AEFHEbD1^qe98~QSdR5|R zdAUwvZ3f#+)wjQzQ)h0leXx83yZKtZ<3T<V{rOO8 zH#_5-bJlk7@e*r8DX|16#5iL%BZ-6!*tnHjsE4KBf32&vUss z^B_lZ{4{;(_c;7e=v9U*QQz}a^R7zGsbGkBAo7FtWX$zy*`(RKn_Yx|v0k6R{%#t} zkJH?j)w9uIa<=`u`DE&R%*HLq*BSzq1s+GUeNW?Z`4&iu9e z&-~Z?zGKJE51&+j%jiv&ely{5!HJI=Gj<(3(bN(Y=fBPG+f?sRcJ1BzmUb*2{vJ!g zWmE53zbO;zLVr1ksZBnx;^5{D>bK@h-*Hmf$?|~h1QE4r&t0e5=Xlt}{tK#(czBih z>g6k+=dYf>+pau)tMG%^Jxc#V|2$q-8T)y6T0^!V!~LV(;`3(z&;5C7UcQa^bElyv+W_ePZjP z&PqGQ7J*RR<1<(Ne$|!b>9##vdfofI*JSHVxwnLG&3>pI>GA$d?&h795w=2;_?D|j zU*dkqqQ4e=!p-lE7P~Glo_!-~;EM6#S`nsPnrWm?wZ1NHLn4yAMc{TaAE{e-yU^*x98 zuCFR)U^-wjg<+}aTNkA_@0!+R1g*KFQiFU>If(Z=cNb$gX|-uSS+Zuuz(u9#*Xwmqj4KR4&{uTy*u*?}y+ zo`>J;$3c6Zf2kj$zE+gxm)?IXce^_0{cnE`-M+o664}Ba2b8I*CvLUV>N^#}7~#Wz zGRJF;kopQ~$-Kkw@5Y@!CYgRH|JL0%%17kpHO;xEEc%xBdBN$QubF0WJlHp3#rfx| zd7s)MWD;b2pG{t$V*0fIX-v+BJMX_RhM6YjKYArQ>GhP`jyC>xt`>AZoAgEcrf}qv z%l+RrUVFEIS*EnG-%IUwP@7ob|ptJL}c2i?2C;x$olIYv&uX0}2kC z&d}(n?yi=YStxY{eErl8Hr*A_qg(VH%DSr;?p!Fl^sn4>2Cr{BuNAL4SIIgt*MZ@Q z-1Bwi;#=Hj=pH@uC(zvD{7gUbcmBTGO}_8^Hs{TpdN7`8ZHe*p4S$&DR40KCdSH*% zp1QuM|8dFp^>^p(-p;i~@`dP%dO5l2e>X@z5ecfhxFlnZjRNbQLWMuKr7agsU}b2T zIq5I1JN)#GQKNvB0*vMDeeci*n<&ol? zGnTb%`!1@zB)Me$*410XKTL}eTKh=-M^lJo$huVmQg05Oekt_Y?(X3ueK$AGDw@=M zrn38Q>g(VwI>P<>yoVwzf8OR4a@??vS%as~{m-3dsfUpT8O=KxRUPUh*Di9DFq2y_ z|Ip8u>5hSY!9P^jJA8O{$+3IGS=AHzhFhN69AC*Xea>N-Q=Bg&7e4wz7yE*)h^*?9X%&#Ufhs&5AbH8N_wFql!J~c0F z%H!;U)|N{hGnf1byS8hEd^LlQqY&pL(Sj=soT<-t-8uM6cSC2?Ta!OCin%X6?R9o1|KoWa@nfre zj;-sRFHw)#`P^=+x|OAe|AyQKahViH+4uE|3yKd!1;{-9 z5MkmNUsF_TQ?p8Di)BV_0Oy`vI=e1CKeh4wlKcB3pK+I}Zt?!$^?~2x>-=~3C7tFaAJ>_43LBjXC14>doomF?wwq8hgiODsa$*Zp9lK{LY^>uTF|oS=IzL$Gs-#ZzHV>zTPe*p zr==vc;gsi7jt6XSybr9>yk(h|cqUQNA-H07%b7Tx{a^CmFaI$4a-~V*uiKT(5vJ4D zKaJiv%X~_}rQ^m)6Syu2Sj_xV`)Sb65;32ypRm%7si;$!&v@_Ty?Aro z%WFa}>utz6$g}d6+5y`Hfh{b{onOez>3g3Papsfu@mD!_%w)KQZ#zD^=EQa6!c#lP zi9L;B=E*;`&y=m%#bnX()?#5zhICu!g!XS2#rRAdg*YE`=eCLJ95Yih-e%h1w}E%5 zXk0`Q=X*5`)xzFG&$V)9KD(9Fq;7F!Y74Kl7sLN}owY~GN*~BE{(Km@LiWvvmg{4PgUHRn6&dKKIORdV^-f`TaT{^LMvgx6GLzm+rzM-8{=CrQJTBYT)^?vF6=vC27 zOMd9Cy|mQJ;si(d+sLnn^-qshdTiwNG9rV{=oO0zb zVb$Bby}ReW-KJL)Q}Oge*L?GzMejqT1$O`NsQF^Y79;hAZ~2t- z8rqxQw|=*NVcn6#VCN*wpz9$0Z0j6xlm5@T2RGcaJ+yY^Y;`^PlsWfq+9}r@nx;8@ zZ``y^f36grJtd|m8Y^D?N9{;L<_ckN|J=Lqr!2qX^-|~cpTA2rv+1l9lGECgVOF{0 zY=S@Mb%o_DoqT!B=lEwEuHEoBzxbov)eJpH>xZ}b9MpS4Jh!(`es$HOer?_*g|&`{ ze|`TLJHg(u#>FG?ppn5whW8a#eVLx0#eUB?Y}9qPA*o1i$vyr*%Aap8P4{))^E#Ak zX4c7%%MWof+i}>7L~Ez5e)-aF%ipB}i~PSIbeL=>Hffr|BbzgmOINIz|K;R^34Y5B zWXl7dcnjB;Ii0X(wc@t>G+*VM7vs&8FXv`-8Op@8@n=puyW%O!U)IdYA3m3Ucxm?~ znBlGi=ef!$)iHI|KUwDrPrtl3HU8p;EtBkggzhvHFf9IV_e}K_8_QRw?ao>CN_;iH z4Q2bycx*RY+Zx5DK7G33{i9pQ4}bdml<%6Ql4IYzt7YLI1mA!86KP_ko%&;0*k0M@ zmh0Tcj#HGggUooEwr}Qp%fiw3*}>`@XJrrQgynO|8GJt#E38kjKGZv*kofN5t_RKR zWf32)w+U?sRptF_xqUY1Rxjzf+bu(;^&Zq^*=k{DBD!6&hyBG&|K%6Eq@H z@$t{1ue!0S$27}1zR;NP=2CJ>(XReAb4vD_T)*>@^}+{6PxVya{(v7d1@gZo|9E%4 zMZnSVdZR_CvgWQBUIE*SlcZCW)%)MLpJ3;oa7Mi)<+0Ei+w9{)35h~mIs}|NE-iTS z@8rgJM{>KQjsLf<51n(L;VAMw~M;8x|HNJel-Ty2UwlRc~rH; zJ#Eg8#e0h%`#p?V<+;PvV4}*TBThz3{?02?*_^%AIJEgr@fWtgE_*!}XnQ-kaB{Zu zO}O}eMg5fjUIzb9te9;Vc=qPbthFV^Tlp04ebs86qrq8llIuX9=*?T(cda<&_x<6X z6BmEl9&%Kl!tdDq!E{ng+JoR41p@(_)9aOX`Un)0@NYjDwtK--Pxf zbDVE;D8AojcsFkK()ZgOn!=cF=$vH!Bwcw>{@#f@2j06X>lJ*j`NSQ`U6hp3tXs&T zJ5$Q?fQnj+MSV>{=Sjmu50x}Cq=H%_n*UZYH~Mc?T7BaD%a)p~yk3)lx<-RPm zIkjpXW68|AC+qzW-ue9Z?Dl2rSB8~YSNiBZI=`))o&CdE#w*i3jHVxO6nWqI+hkGN z`}u#1^p>1;syZ0|FaFm53zp4iR2RO##b*0y#{0She;aZ)B`sPsVM3o;(3!RB+imV$ zi$5Xu<4epjnRXM7b?5ZgvItFL=a?TDXVP$}{+ii|3LXA)Oe|)V4}#BRd0xEbZ+&FL zEuJ759`PTaKX2cep{ry0>qqIs31Y6=>{r+iEnYFp#c8WWoTl0gPJtU74N|tz|MU*I zYd$J)dLfeU_sw(uDc|_VMwv@3r^PohNnP(YXSQ-viotz?~MP=`{uLvlAYhDs3S9@Q+~dR?df%yZxI^Mt3OL*^OS!-k2v~lXUkiA_mXZbpD zea`oS|I-iHFQ0p8Wk!2P-I=%uyJYXCAJMMN)?yFmn0#s3&Y&R6V)kge(ox5z7Z2X5 zG4@}Z5W$|H?8F6 za^Kp!v-k4czcR-yQmP`uD_%rC`W>QK>ti%AY2EAnX@7U!=~sT(pewmx$%oue@3#v6 z`G0lQqOJe`hsXbmTalG#{c^G9!Pcg)i~(CVANsGi=%GR`yQjR7=lAYVKJjTCO|vB~ z&AiPf#k48MJhR)g?zXSglBBQZ)0Q~B)!E>&{DbxpZ$6zUC;qiben+;wnRq=jWA@#p z0qduDz1;7dRxu@6tF3quc;M{6db9V#+CS9?eJ}b=No+LsKKENQQ$8!~boccCsu#I; zwN5ysAFFpO@?pLB$J0{22UI<3A{S0P#54bC{$a;H1@-jA&f-PEl{+@LXz+&}xtzOT zt(GDyv%6lsbkW2ckDH|}uI`+$SjKPSG8Jz3eNPzT8&4d((Iwi&A?#h086?&GW9s`E zXTA!E`|U_6wmk)2y!PLEW=q_0b*m4PpR)7M`aMNeY^G~=^hKuoQ)EPYji<3l|FTG( zB|T-|lV@_V|L$8q`hUNZRVlNWX@Xsm<4g7D`wdN=PcPq;GFhSU2*X8}*uJd9tuyVi zgzmE*XgR3*RsC3d!~KJ+sCR{Pv{-1V^*=^wqU6n%JNKix0A=L23h6&upT z?jgp@6;b4}&xTboYi?5C6VPhu`O`ZeSthxUu^)$fn0UQe0&+3@G*_YFJ$7}?j% zv+!H~P+|Wg=_QB0oV>e3?9Gd2?>3Pbqb1FARaZZakm`CUGNsvB`QdI0g`FQ8GWs_z zROPdk$eWejn#q}{ak6(4cwsKz^)sw#MxRUO2Wc%0`c*!mb5YE0=ie*F4^vgHRgCV-a_^=&)E!f?E+iL*h->%`opwsUVYK3G;z5NYCC z=^2rdq`|r5{Do`hXCKLOUMZTUIrUO~?~L>*)91W-GCyZ$M&-$)(t)3ni9K8YzMfmOSD~Ld3vGUXVZcW1zMKfJ{*=`{4Mr_ z*YVk@&8(PG|NrNqxcmR3Hu#j@E0)|jb*Fu z^&YO!JJDZ!D7JI@oxqLZa;htnH_J}fjMe>j=7Rs03u*0tbY(86XKP1It9LR#_hEAd z`;=z>cZXFSmYCEmQ{Te1Xr<$>!m2oXUr7!5?d)@-R4zq!q_- z+BkPY&QY9WciwU3t);iu@LryK^ygCNl;=xz3$2zMFr6zeJm+99|F-AqyYwC|Js9h& z{FbS=SaI(Uw{I?DoY(u9jpUcOb3IhKt97kZ(f_E|;?y`R``89E!FcBFg>NsI-MxP6 z)GdWwESua@eEBYxN0xHt8)x77`8!pQ^SOk;S$W0JEuluwboC-Vysy}v;9q6DZfnZi zt*JlL`E0KlWS#l*f$gV&|HY&X>Hl}0rRCdH=FRDvzv%VU@(KJCEgu&ZoctlRU(m?| zG96s(vpI4$*H)2SyNqNL*)8@Dn6E=6#sV{_o=}ifU zx!bnwV(O(_u}@Pb?4Gve)THG-LR058HOCPLG*wuU7j&xn01C<6nbdh}zeihnaT&(2IF&GePP1+pkxa6u!JIyK296 zDvLtC;b%tqNu2CAj5t2cI1t>h@PZlJ5ta<@2L~a}lsbBHs6!WW(ukO9tw~lYqty!uHkq7tBYA)MbTdT^g zZJX_)(yn#X{7-Dq^;)0U{GhF8Uaj(eAYSBTnQt4D%fQgU;OXKRqIUk%wrQo?1x~Os zWGq{=FvldDxvfMomFwZc37=Z09Of7QaZuHMNt$iz<<`TRQZch`2ZyabQ`=Q7W|yf` zG3{B~j_o_zFCLLQH)-a9`RosJJg1!Tm*6YXoH8e<1fWhKjxR*L{rO_;wTFY%hIlOv@lq2AwgHd8#cyMG zdH(SJu9(et+;;5q+~^-SKWN&&sWr>9-e37Lnc4Sw*{cU%JwKdqJHCg{tnyY(!qMAn z-+x^d@iV7edG6+5@t?m->veLY=3VJ)FnSoN$k4s@Z1RLa4u&L$35!0qpVMSn_366m zlK4Nl!k5IY(iP<;*tbkKarSv7%J4wv{mKd7oVo7G)m3W!cNcjbnYZs@rD}rO2Vre> z`yJa^YCa3=rY>1J$)dHnM~-o+{8{!X4n)v`b`d5-6!2YV_!4xakL|AnZ=SKk3W&NX8tmm1^MAYbVsfluvmWF2nfw1bP1)uY zvg_F=cIUX$Pd)$qP4c|;!!pbyQte7ckiLC`Fpoo|QMjW2Ly@rRh=5Bmn)+w!|3$Sp zzgpyZygOXf&s%S2iPx!)%MYdA1Vp~d7cTHzdSQ|uLsLv9=w$QL>~1OGv|!WfJsCVj z|7a3p$*1YcE#VSIOjC|rpHox=I*FEH34_YgTwC8IvpH5>Dvu19v{YvK<&8Fz{!29| zmwjWB=weiTE5>Q*9r^^cOx*Mr!;vNCtCqa=j8|oCIwTeJW2tXM-ASfahdkw{R!1|O z7b$w1&HLAC^HW{5vFty0gwUtV?wzNc^~AO7Q`b%D^L9_*2y%EUvDc`|q2zpp>z~y> zju(i>JKtAq-CyJL>=1WY*90lU9fEpGg_*QlzlV3&%y_(iW*UR*P$4*K)a(Nj3%x3R=wry{}p7j2{?n0@^VvUHbO(EM$yq>%`!8q~n z)t&W!X65!W9(=le`tNw2oii#dzdj23>hRU%>z|hAA7=|~(p$TC%g0Muih0XdrS=C@m#?#A3ia3pS>xtdq1&uIvU#r_CF2CW_ zQ`4>o&l?K%eXP%Ot#UQ(p2hjuy+U4h`*B?~SX}Z{}bOPq$$2!^}J73*8S0+ zh`LOri+>L1w!CI!m^C3-eC=xO)!te{lj>!+FLdQzpF3&3XX>V?MC^;-yAm zw<81?Q&v|d{z^F;`f1h|wc3+RMtvrS@;oj2&sKKM-lLcv+^w?p!3ow>C%-##8HzE? zFVd&&k2Z4L*Cw#((O;g03vBMjPyRIfgqwG2#Kv>8@_$HeXWTCENuo^pTW8NUV^J5g zwmi{wt;OPtk1-WkCWC1^()m~O5BHAB>PaprIKgmxiIwvoF!X3lV$BXZ-Go9Fw zWT{%~JM{yjQDE`@;H+{3-{Y5D-gx)rnwVOf82jF>D!8EJ#G$yIdtzRc%oYxBjh7o- ze&{Vp2|52m_wbRmkX2a)QEAuK{`vhW`_t|BkK_;E<(c#`+RZ;R?%E!^(LgU%&}JN-F2#V_rCxLxYo+59Vi3poO1G^FaiR!;F+nUTU| zc%ONqXv^(gEgtSk(noI{S*Gf~w{AksmHm6}A8t5SntkQ2xK;+Y&)f-@k9j@3H)YTC zOE1J;&He0usPwB;YW?)N-_|TD66CG?7xDMjjWdaUkhl~(T&uPFVJC1^R!vzwc_$mbDH~uv%hz}Q;+?wdd+Xe ztPQf~R_142uc*EJnNcO7`aoR3OSh-)vHiDZ2U;(7Zd-oLtK9jr;*!L>jEaAoud(Iw z|9V>cQ(C`ple}m2Plc1e*6p0@d3{}Z(tC;bW_#01?X@>)&QVYeJW;FidUkI9pU}+? z-x=TC-|e2rcllJ(gems}?iTF&D5`j*Q^4s&<)jNP7gHi8FHB+lsCrg^?cOIPnZc7U z`o@_`z6!cny772^yQuqo*%BU$nA9+b>IdH{kN)JGuC{08-(6Xs_$0WuPu9!7!?tbJ z#a(r~O)IB=*;`u6?`!v@L?h5L*wVGqI60~COo@&T-SiuRpDxe0!hX>^;1v_;1R63iYtM8?109 zvBE3PJIeoEWOm6~#;q27m;E1_ce9#zr@cR|zjMj^qzsSUKN1#np8nq*W9)PKn}ho$ z_hL>3WpUn;-B}G;EB0*aIdSi#=Hrvak3)Wz-SpYXxnN0Sn&O=Yy;C};K8UDW8Tli; z)wJk>;l|BNuHKhf^JGr-lZCB2KW)%4Y89B4^lDZ1x&4#lwN7guJ=JIBu6uDG_ucrv z>)G`jy80%3PU#NbwW)Paa$B-nm7Bli#0lk-&Nc0Ov~kn%WCoC-WeTl1yz=%zL}iyd)Z+r{)}73 zAM!3Na5(pG(zX*NJQMkip9p+XKcaKt)g4uH(a&$>C7xAm4agO~Ug>w`$cdE~Lof1` zrQiH9R3@Hcbr(ug2-~H1Emp3yQf_^J zdEb0N#r-p-KF;-*ZhvxWUywkG*7)_5y8oA;vK^}I`I zjaPPV4SgNC_V(#NsV>(ZB|Un2QGEI&dk#h0S!c40)|?CocyMn<+2VV=@!vnb+k5x7 z+uY8sZm&x%ncrVd-hJogqu1Bei`OsH}UHi9n`s-j5 z^)JdbDt7%f^O~nb*@WE*o^t=!?`vzL=7&|UoO^Y$-nP9ydV00brp^#6KWKYy?havw zo2k>iKi|3HyUSEkOk?+!Z^n{73S<_&UgrER zP2tB2z8vb@yy3_OA&vu2MQ{34YUSp7HhW8zTzVj+ed^)5&aJ9}>x17bUH*SYQ&vND zle|K#*S{@SOfvt^fA!w}ZdJ#&1*`tvLdgQ_WW%(V}55xI|lvPKi#KUEHLnI!fqLMbsN5l6kcAVzbV+|;hGI^9r(PbSgF7JV1Cs2XWC22hIwmBZkF6sy2;ca zd%Pl!yo33#IT@rZrbU~U&)vpw>%h}J zt9LJys|pXOO2og+cR!LWP=CASRU7ueAURO}Dv}$)1|B4HzK2PnxWqmun#Xn-}lU1+(_%nVl zh}XJN@%Gcj-_!Xe1TQeWYBjU5{(MI{j@7HorvA#jm+qJAnfS7&z5brL$b!#3b<5c| zeU?1wthIjmQu0~&Zb!ys+ z=vy-9 zjqI?QzqzYw%}%7c{*A1g{IvVJrvDd@;=g}AX$Y~#SA#vrM1{8iEN(~I@aGYVY{n<3C6Yd`y5`1{rIv;GIaD=hqM z|75*Ur&eLpu89xJsyxzylS;Q=-&}ZX=7c6k^*gQc|F${CTXefQaVQ$qOHO?GKx#R+ zx?^JbzA0ZOus;%c(0?lM>AFX4XP5U`^qM^R1lm)Se{5Q}iDS0q)%x47Hws>!S7EbH z=CJ0ZFY&K(Odc23-3i{6(y-icvP%7q>BlWoU#DoN?v2>{LI3@AO@-6CSI*vj%eZG$k31IQ>vo*rjzw`>66!4N3D;s~+|_%Z4}ak#u%eVCef$$)MRUeQcTS zF225)TdKFLtmfu)z1cRU`R=3IM;ed6$GqLTL`o9>vY;#C1J>vp4YuzO=|wwKEfA5Ai>F<=#Yp z!;Kf0+^ywMd~b97tZmbzZA(%#_eD;dZgN2+Y2rywy+8-0Tg7wuY>vEPo#y^YPN8$Y z(Aw+$_0gVdgSD^BwUm2xg{w4O=A_X5rF)Mu1|+e|Dlkkem|l9%_H8ns=7Ydt3(b=D&Ys)%N5I>Bpo0ynE9)e^<%(#>LMLUb#FwOillH)`f43C*u#8vUNzG zpKmoc&Gy~q`DQBfXV*TNeod3RJwCqn^U)7S7k|IK{MzxQxe`@rKZ%`L1 zF`qlH^n!cGAqTD#8yTt|eQ}z%R&}lV|A}nrYSX5Gt`Sc@s3qvoziDdL+D+3^cAEI_ z6H2MRw|S3M)lG*w9dq3$F_{{U&o^9KS$byIj2SbwFEC=;U;5!``>uOS_Pm?5hxy8p z1ukWh4|d(StD<6Re}R#6&i|m=qf5jsmjBsi6Bsvj^Sl_nvy<1id@9se;9p(y*6FRu zTOWp+(A}&u-zWc^{;~V>YxX@tM(Z8ixUa8LKb3u|^#Yr@_Vw&_#S5PnhE+J#B!erD=aE5F@d(IwV=YWC!$W1s=~R+l3^Q{7JQ`LM8Wf*#AO zi|?%ey!@ee{Iim*xaVQ79M8_h$)AMx|K{@x^bFm(>afX$mXbO0395e%#Ri`~ZGTX; z>;mJ4j-^w#JP}%b!jiRW!N0P53?7Y9Thx1n*WSLkVe1sHl~Xdhc&nezm5)(7eemEf z_WpwB)n<7U_!0yeR@|@bXL%hhy1SxevP%<7VeI2z%a`$2jeo^Yyji!u^g!A6(9oPy zKfRafU6%Xx-kOh^-lk0^O~*k`cw5xuK87_(hGM;?KF;CUfl92f7QHszB5be z?(b!s71j7qNRN zE#TbbGi{2=$JAxv2QOsz%3DaJ-nd}BIyGyK$1L#jC+>G#nd<9U&7vmoH(HrKbN%uw zW5WdH4L6fLEe=RIq`&OCkPg}l1ckX?9 zu0Gqk!G?bxQ!`7N&vEd)!op~$HLvB^L|^Hxj^A9S%f#&waCg_=NB@rG2CnUh^3UGO z5~|~H{)l+zzE19qQsG;Dw|baL2W>dxUleTRQOB}CdRh{byh8fqi@t59l3aTm!0Fef zHJgR0fn&aWf_dewid)}GviY_tF*9F#Y5id#6JHR+^#!~N{Vmg&ulSYtd3fI7TbpaO zG;?xV_}9|XXw$h4b({@CuWUp9f0)$lmcf4}(V+K$Je$Hn)))M5j~IpgP&jyD!fgAp zKCP#L8j`m-Up96&wln#@X-oXqV99buSR!@S;V;L}-xoc=!Qt$7%~ark?}SvQloFW< zKNW0+nmu?tEtkuPgvp-Pe<<$0>4~~n$`Oajs*iPM?V7{A+RxFG$Ch>LoTi>u8Ij89 z+m5$|6_k0cUMkBd$~yb&+-(s#=P%>BHN{h7D|q8{g&2csgU1PG1<@dfy?u=5mi9`z7a=-SOYY(V+kEx4HiP9G}_q zck1qU+oh7dBk|3{<>v2FGd9}0 zYAnyturKdhw`bwcyYC8DPWl&ctG9IH&z;H(;?{;-Kc}&Gb^rNGOIMyxu@-%^;qT#R z8@@ij8+Ij5XD7o_M@vWFnsr|LwnrteP&og~wY;vO%12S|DQM&>N-%pdQ>6D@-O|fW zdvo4vZ}UE$A(p`zyx#kTfa{&D-BFVqEnc7GYMBr^!M=LZuSFir*S9^NYPdv7QN`Wm ziFr&cr((+y0l%l;pItVewF8*CTiTtwmAdo_gVR z!kZ+UOlqYr?0#_heyRG_?9$wYEc5g1d~3{h-`s6pSiP9f$9~~E79;zWZehOHIHsI1 zJar?ye{-kxPSUr>>v!c3S>7MY|)*xvqD=`6B$v`V-U6 zy){x_obLHv3)ptEy74^6{H>y9Rc}9=CS^)895Lg*6_>j^Y;V}!h`Wp_MGVhMPTnfr z6?|PRPG0n)oBq|~S<>0ZvtGYAc5>Rux=BSwfemd5c@LF8zI=Q5=gZ<;4Si|FY==5U z)?@c~$GrNq;&gH7x>L@fu9u^4J6ErJ&cFBD*N;jq7J^B$SAUq$SGec#j}sqvK5HxM z3RBUauy(R-z5IjB7rPn!-yYlk?(RB$iyT3xSrat!j&Oc_?xWmc5mghqW!vW|eZP0< zZ`iv&LeW?KG>0O~8&l~yYyN9IvD_m3WJ-Yb7mjE161x_5p3vB@-#w*BrflXTtAojo zaWno+T&AmdWS-CFn@XM2%$F?f|HAWsM$6UvYhF7&PhKv0v(~)H)Co}Es-)8` zO5K;n+f;txe!2O{1nqc{wR7b!&uy}P>MF0_DV{C7W$M;Aqx_q~lG|0AVgq}QveW@o~` zsm4+%KI{UTHEgr0i-Jy0oX|1Dq~^#=1{p=x)B1&V6Uq<8)|{Q{{BsHOmnVH?Pk0kl zj-(!sdF`VnzhZ%FYeZa91n2Wf%HF&O~3w&<}&uZ{iIl83y)AQq9MLMUK$hH-9 zlm>J(pApi&nb~CYyQ;=&XLR{B!%5Q5V!s&0L@l+vy*lBv_@`;rYb=`O=NQ%aU3Mv) z%zyUjA#dS-Ihs>1-FLllNigVulJJygzr$@-C39H+e{gBel;6VECFjcZ-iO|O+IY6* zW49TLnm^y_dnxm>d(X+PDiPyl3E#QJZKlxO>vwOP+*mHP)8?T^O4jBBymrMR{=4k? zBo|mPls2Ayx}@pc+*3!g_FXz_x-GWs&4$uf-GS3@->|BSf4tGZ_@=;w8C6oJZ6=ni zzJ2Zex@UTQ!6Tc7x&1efm~6QYHbMfMc%&8X($yVE+) zY)0Jv%o7X`n9FOeQ=DEni|r7(q^x+scZV>~YPS`vdrlbc5IN^8^C-^4+pBMepy9{D zzA0N({g%ttHZXW?I-r`{eRA$TW0$hHTa&tTcScGtvp&sK|EJw{lgLxo;@1yvbuulV z{ekPCyOo4BcT7^MId{yV*t&v~OjEY#2ro~t_713dToU!RFLllXSH1^A57d?%-uc9W z^-kYwS-$CippD>kX5CD~jla^rO)poQ_i~;z_j#^K zNuPx6mo%?!itplNyrC;8(Xn3h&f0@95?trxKW^vsd!l6*KfCnI;twUKPu`itXfSn4 z&6XQg>I=3Wy}Ihrs&0G#ja#Q#{p~RQ-yNpxz0T8He&-*Lw3DX}GtHW_Md+Yi)U?cX z<(6t<7gClQR4O^tb@un^=smTFagIRMp*I5|>!LN@4q>=TG}? z{C!k&>s!UYrJuH)yJ^O(C>#G+vU?$kVw|-D!cr+2SZCO_wyyJ$l}sILz+}1aWi6Jo+cb zdoF2z{+zz44<;}rOccGxVa+T#e^OqGL9E&V_i4{}yxD#|c=9n(-WaZhN^&BcnmPrX zELug3a!=PNy!sJU7#(A1E##oM~*~6ubj?xdV zyL4Gr-8}Jlv9^O+R1t&S!k3?ArmTupJ#le;&$~s(`!=na@x}6GRCWFF$_=Ftzg@`H zR@oH?UPbhF`vxgL@QNX?Zyjq+Dsm{Db$!41yv{;q_r(_V8+b3D(&e&t=Y**ws9Q&Xm}6URZZ(it0`a4r`)RR6$6;As82>vl(ix6H7U zZsUH@dyMs6(dWe7&ZX{?A9Hb6o{Di=`&3@DYK`MI-hIz6Oq#O$*5OU&>%fa`TIX~) z_xdRsvwZK6`s=jLo2R1ib7ET|zo-7rPpSVtC2uNUUhz4~bCt`frK?J|8YWM=C8)hy zQTBlRT%|p#5l$yJEq76WC&1Cw$Jh8K^QU9qAT{IB6*>PnH#Q@mcjR}${nx$gh0udj>dmYy@6YYRDjv%zQEY|+fFWQ(IkO{$#w z{uQ2&)D_XS46m2Y1<<)7DwX`Gei1%Af!tU$&Hu2r4dvHD-jzTSVW zOXfbAvSd%8Nu^iNi%_O_PJUsWuF;7t&6eO3*SoAHC$?<YAFj*aG<#`WK4XpS)UB`nF`( zj^Blvg`T=qTkWqGOM4&Pw%_Z>w>x>#P3zxn+2`c!=AH3lZCmYU=|=IjPnS*&(wsV} zU{l)N1l_-L{w$dMm}}|8dvjj+axZxtb8Ba-TP{B7oyiy| zq_ocEdiL&VQ!Yn_zI$DAMs1sl#K(`S^3$&WHoUFF5;HBPx@&t1gZZoGS2tf>`;%H1 z!WSJd{R6-8oXb7S|Hbg$b3A5nIQ{4CBCCI;VGQ|SWxqc5aa8$pOJ?etX-g&u*K1yG zVK~OPWbx(V=NaF!ie7F@e8D+kqMqc_K;}CwA`H_5l($4)iZt?JIuJ6~+I@4l@a;C?CaLLMJR;=9 zFh!yy>7?pwy|&InqBp1Y%Qkrb72m~ix5gvUQ*Y;=DSw3QS+D>3R?b_qCLw121%rKQN&O+2ul8OE z|Do65p<}z~>Qf8W2Wo%P!fH&_{?+Fp)^^GLe-s5dR_ytgJD)=mc#p4L6~0Ql#Jl0g zLPZC5kOeCpm1X5nO(`JaXG?rv^1YK_-o}c8m9bM&$m@7iKqn3Lf*e z3Jdltny_)xAv1y7hR2N4{=C!Pz5M;ZU*{UEL*JP^eOGpo_k{I1Caw~zC+sgW%MYxY zeD~~=`6id{I3+O_6%>Px{b5@&+q?R1L2Y!|eE(0$%elVSpZ@fHUFXwfYu^5OdOJLD z{Wcw`eHot?ZSB5QKYxow`O%|OkB5n^30Nbs+vaP{i=S6M^G!1rNqC)ibiL#5SmAx1 z9UcapOhGZ9oOEUi^EqdBI4qxiVqw2mn0L+wmkFVpCJ5?B$-gYwrRwx*(R`+8pW^KF zs~LI?|CTrU>3lqTUBaSiGas+2rKHNFBqupOi$&RgM3y+roX|A&`V!uq9&ZXWe`ua) zUTgC(@(=d}zT2rxdk%l{-my%!p`eao3aI0=C1;ZA=_k_*H*v(&zbLu2Z4z74oTX`Z z*IbKyTO#<&@)w84@yZN=&SI{7(G^D-^O8Pvo#)kB8l<)KKi^D^Q!j6%>3HdRWKJ;p zp0afETfc3#R*!C^tJilHcSnnT4;GBRcyr(SoP8=w64ocJ%3FBP^1sTyn~Peno1HxW zfx93We1y{ZEDhU`hv|==PL0V^vEbQQbCt2mX+`4KwlBUfAMe`ocGAt4tM7?ceSeWF zeM8s4BIC}%hUQX!Gv=4dZL1>goy@X3ck+JV>7~mZB8=2+?Dw8+PMO6PT~a5e&@5zO z7u@wy=^T5Ak>2F}syY*nRGlhXx$3EM24}s&M{j|jxBH)VRNQI&|G=2<=jFRsYOgk0 zuy)J~m&m(xVRHOlJ|ovR+s~b1E@xDD6hDW3$s~!AM@0+`O*TTyH$R!ElVA8Ns^jU> zf~A&ke=#2rT$d?o)>eK=;G~Tw#}me*R%~K=1f2&UQcIMq#soy(4J0Cfl zqwX8BZ{E3?#^%O0rObYfo1H(zTD2b4p7VMB{J!6Z&a->}aDJ|-`}}?&-`^|t%l{VS zZrjc8@mo)4_m9g*+b!;%J@s|g>llmDU68Y6wk(zPo;LZ4{`=zXZ!IUjD`zsl^Y^c- zdh_C>d4HJ?&pCB`O5rtOsd*pzww|g1FKc>}w?6%GkFhXlQ)|J&nc1~L|2i!0nrW;o zyHfC7*6hExT*KbGg?kyArf-k__xO|WWikKz_Ita(ht98#`^_+;Nym7>!g?2nwQ9_> zb#je<2OM@@%B@p}7@uN7}P*GWGK zSQ~RyD|Bw(eEY&5NBTG_j~wnewllQ&dM5XRE%$n>^xypD?Q{KQI5*_SWA0D=vdb2+ zE->7_e@#aB551V=liKyGf874yU+_&Rh)>S_-vy@~COi)!_8f{S^$B|$;(E|jb{)s` zV@+)Wzr5b7&r80`7PKKmh}r#*$dtn;rD9DcO8JLvQ1Q{ZsiMa7i6MhY<>_?COAV9s zc!hOeZc&*bSg)bPAW#+$6SRcJP*L>ygudzMSTP8kI3%wIN^^pRP;mflUOD1hx zyy*V1vO@yFrlF$CZt<)8J5;^DVAow0eOPch13P=Utcr9Aliw$XujO}jOBXxqU%fA8 zwKVTW`FioPA942|q}1-8;Myq6beoMWc1793r>*CfyUmrX^ZNGpId^sKe{GqgX4~)I zSy${{-utd<^J~`k74LKJc^_KeV$XG7a0SOKsgjOolb$lL9_Eu-a6HDsqnh#Z!#X>@ zIgh@(i`^4feAwmH*L6($-*Sd&jZ*}JUIaXsP`LD<|Lev^N3A6Zl?L_t5eXCR?d?65 z)*jf{tM-lSdB}(59+wX#{Y+rpCGcHAY{!%7*^kmARj0Gud9puSZ%2x!XXXOW`>j(t zF5KtN(|r%w9Jc1d>n|&hD{v=1O~1SJ@>$*IkDni}ZQs2sE6jVYS;N;=J7pHU7wet8 z^y!~^o#T_fuh?;`|FSnrQq}oSuU@1IZ?X-M()#%Cmi3WphW}wDyPdPVuSzw{_}093 zdB^Oe2+r$k(uKiiTRe}iS+_U+{d=YTe_ye97-lZDbU3qf`g65&wXe6Ix_|J!M4@5Y zM7_+%m-$m_UKQTh+xGgA<6(AP8I$bSZEG)YuY0Zh@#1fZU0>goZM$uCW7jqx(5ZAU zDr)zr#NBzbJ~yh z?)n~xrum2)H~O{ z!yC9KCVe`6@j3jv{e1LZ$Dchh2&S#-1F*ZL+DckWc5Iz32p>ZSkjMjtiAi&~Ul z`=neIero#kP@3@UzMQDF+r#Ip)wVUJ`2Dyx8!{!o27Cw~qs1Z9ezGWY zUd_^7Q>*4yiEl}{(l*i7{Vn@H1`pPqnq@P!q(1JRbRlf2#eM;8pEp0bRUa=eusZ!{ z@>R2(|KWRN5}qCuy71`Jk^dH#0>r`@nI4Hq$WE|kk~#9pTfu|zfyeC)PF5>6zbuMa z*v$}=^ugWRa3iz8InU^zM{!w)wo#^`fNNixr+Ma5%K*(C(48voI|a(+A-jlnOq)A( z-?VouGh?o=jGbM+R#Z&xnas4zb)|FH^{kVNoO{=>zO8T#_<)OYp~G8`@yqmVihlbj zPJE8F#nU;*EIz)P(Q@O@ZU*!CZjQXf7VRcnP_QV@LtDK`Sz!8GoqQa?c16a+*|e? z@L&{Z$&R|l@j=krP?4Y4dEXY$tjU2f*X@-QyvBC{V@z^Z59y-IXO+crt!d3_5)8}S7~ad z@GQAKzx12wt4H6C{L_-+(Q|~H2=PZpA_O!!Wn--cypyGqwS6wmQ_sCUBpa9;c0^)I(}d+WvRx**`B(AaWG`|9nh z)eU?4-pt6+Khbve)Xo~-)(<$q%b&@N& zWqqWODUW=n#>uK_SDiEt7A3gf=9{&8#nPV+lP7r0eAf2yIX|P@_olW^6Om>cy-?XT zZ}zdwIB`ckiQ3rqa%J*Vlp;#=3g%b7dX{v>g#zMfMU z_Mk+p;0b?7$d+W4$d;!Q*UXq+;OlLrXussKh~lfFY2J)?g~C=_xQjhm6(l7N8hY68 zcI}y6z)^vMC0Tc)!n3cg6n(s=xb98O#B;$WuT6F@zIMN}?##V~@02e&be-l658u=# zFzK_OyYBvnue?0W0^@7eZ@u^Sfpf>-@8908eI_hkG<(j0tiqazrCF=8npPcdk$rsh z@%7Dbd@WV21D9x?&@i&9DQTaw^5o`8{>2|k=4wSxZTfxHZcXg-vcFDCzV=-yB=(NrgvvPJ7%DdZ#y0pi`kz z-F_nvXV>Z2&aZ2BZc0&1z0{Jct$I>O>EV_8GWjbDYt$bq=X(^-UOdU^^pe^$FL$y} z;M>jkG5X!5<1i$LNTAitaGyx<(XQY))@TsK47_VuIj#J{-WZKb~G0kZPeYbAedsef4;>uYp1$h zXJdXUJ~1&2?7JK`PsjD=eb+S#?mFR$@1n9otjxAt?KHZy{?qCoAA|GLSGGU()BC(O zYw;uYhi!~ELYY$bG8`|IZrLK!-dgncQ0bP}5mx-M%LJ}33esA-XriQJ_Y2`;t@5_V z<+r@}esKNdSY@q{wUU9)E5Hk@RQE9V3G#`19^CBy-6?PT|CH|TXs;9Ohh7Kh{#T!q zuFDeesr}8ymNTWh1>bEssI_y6?e5m6%@=D_dyn6)SjO48Zn=Dnn(o&lFSnEgdu%9CTxWmO`_I3}Yitgke|d1BXTi)v zf6qoP>`t2HD&Hsf_v78#ps=NDe18^J^-B7u-N<^MxhlitR_m?ITQfJk+~m56^{kwk z_3bwW->22O9=>#dx%}ConeJ)xy5l5m6?UmV`MLkwKAUsLKK=ba^Xr_*8IsMCzlGWa zg0G!0)19Wn>6ywI8FX@8tHu(YtLolMJDh?zT{pR${IqbPLGvbKw!=DS<`k#>{`;}+ zSIzp*yPxgaecsZzqxf%Ood5S?`|sam=6;X4{sTU+&%ez5Cwjq-)1& z1U56gZI`#RdGVp)d)S`sPxhL2#LxW8yxL;bldrQczTaH;S;{Bl!dA!d%vSAP>t}3F z;=8o^URCz3Whe9hT-3Jv^!;!C){5;H>!*GC=X*cDYRk*`#k+g;Os$v_OzKXa6F2Xl zzq#74-STg4r`h4Ojn}qFg;`he*&4h!zdW3&%1_Rq^2w8vo}rVOCU4qkJ%`QB?er0O z^<(~byuZINU0<1`$8zfB`Gs~no}BlZ7V_*(cciD}Me%uiCN2Kox%@{)QlR6uPtx3a zOHD-%$fhx7pMQS!r0)o%g=!UF+!$ubWRFI<@D@%JQYf+b&s{tiB|Z z8*=XG9*bYszIa>DDn9!`|LJbeSN#_&Hq49nW>ueiBHj3p_s=|`+G%V4S*(@gDNBZI@zcd{#|T|B=I;F9n&E-CXvt$v(PN?y@(uO!?A$vvoi8-kr8<$UffO zKi|5J<$T%CrQUVZ_uMGj*njL>OB2@um8yAXEJJ^n-1uSpc)!Gd(*sYFi>pejA90v? zGCWqZv48lI^E^|Vm$uE%8z=MBt7UuaOe`*jblH9_`n0TjYrq{AgN@!--Iv*1lF??b zXRz(Ra@+Xpa<}#GW*C$ndTDh0l*K8Qgfg42XD5W#hU?CZe%_olzl3|QOvrtE0nNC# zR$IHX)6HnSVdWD@Q<5R!R|N8U7$3siAex7I97k$aRCgjA1J--xpH~n@MH{#emZ`rrEJJnUz z$xUuQ`jf-2KC0%C#Iql}`#+vnf3xIN=7u?biZbt4^?#Nx60Am8}l5_(Ej0Q%xulh z_d_~k);=GjIw{WX*#;YvZ*4F!xaP|>F;Svz;u+Q6LsBhA+vYAStv#gHTFPI%;;T;S zCWECpe&Ks(huCmD{PaKjq>*DZ)a?t5dnaQQo_iSI0WrpiY z`Tw6kpZt7xa=O~|y1n0@Ojir1!1w9p`7qh2uaj4C&-;I#TYi5*TJ+7_Yq{x3 zJrc@pF|*%$-LL(5d41$3c9)oqc7j*5R?JG2$nLGo{d~JG|9AG?_}6+XcHjE(>3@CJ zQQ_705u4Ti9N+LZv5&J`-Dma(uPf74EbPulq}WWYGAVU`m4995=T)Uy<*zPn3_H%S zxclSOjdqGAg+58>eD zvIOPz43;=+JWTeQdaBUZw)@U*wT*{d=WlG&5v&hOTDiMw+ofMGBk$gbdYJWp>94=5 z!u?)azsh?5s<6anpHTJONfl?xz5Xn|v)I$}?v_s$HnY++J&f#@ex32pdqu1h_v9y? zmjZTh`hQ>Iy7cD({w}*o$0v%nJSi_+8$3Pceuc02toJMQq$+cFtqt${seh$q-kMpK zJcnLR`F_^D`QxOiCA@d)PrkG>H#@JdtCX93ODbl*pRH`^E*_iKr#Y$RuDT8L`OAaj zJ>Jf|m3{t+ZT$P6Rl&=BZ=2UPU#L7QYMPuS=05*Sx?WxV4ZfB8)XL4jtgY`n7plG9 zZKjr{!cn<5B^v(}<{r+p%Bd4nS$^ZZ1;?^6V_Vm5ayoBHFs(t1>?vmZ+o1Q1^ z(%6&qD>+Eu@Iozd#YK--N88+=EA{GSj}Ggd(rfGaZe6*QHpkE&ZSJ;Xh9;*YufeVb*yxtBd#J8ZW5_%S@{i7?#;QpR_H( z_LbI?7};Q6D^>m#RwaJT4-7cHR*7ow^w@3HZ=9ZeFYvxe+}t_mJRDWxn>|I1cn(YE z9b%Y$W$V%ITi4dcZO^>>F7ExeS@*N${&WX$WY(_udVcNp3-czdo^4hB>T)o9kB@X{ z;TcYrDeeDkmi(OXh~bpxU!$leTXefhU;XF0oD+CbB|`qormZ_mIo7SOXKKElCtv(2 z>D7y*UFq{$l(|oze`3kA%kH&4*8=8J%I~W z>I)rt|7v>K6t`!mru>Oo{zLt1{G+JY9}W8>`AibCj0AXR6+gZ(!8z#GIrj;_f)^ih zHjvF%5;s^BphN7Kbd;Fx4u&`T2N$up?vRb!@Lt)*e=V>zBctz zA;YbxDLEe_SI^bwt$jIpy4V7X4e!n7&;QJS`LEMMi>*~ro40)A+~+>2&;8QQb8kGO zN;0o^n)|J@oXM-N`M}g>v4Y~taOoP2iVau!TNY|QUS2XwsQvtotA%IJ6zpm2xfIOo z7iYJvwV$*8x%>H=FE8A4cPeFG6S=Lwt#Ze=F3-xfFMN2W+`qw~wDr*0RUxOF#lQY4 zb(;LkF5$1>1*g49SL%=bo%L(=Rl~W0D*bb-cq>kYzV-Jn3yfI4aI^m0&l8uNEA{!$ z&%@cE^x^Dzu?bUt8Cm|GogMdR)-TPMrhjJbQFmK>MgEr4@#j;|zBK*iJ*RM&^~vdx z5nVH6c3l*cstRMalU3Uh#XY&-$5{VmjNc~rw!}YguP@o~^;p&E3Eh?-4mZa0#`Ldo zIW@a&3jey=>R)B2zcYSeIkDL`K|lBX)?M#knTPG~wbMPfRpaI&8>Xip%9$=bEWX4% zIZ6Gla0Y+R#&@+D1-IX9``&oDcV0r-;^)(TZx6X&l>Ss)OEpyQL44AmnLo}ywqK&m zyyTGi&5wuMr*EnLbY!;re0$TnU*7q$_1WC-n8KRarfe|z*mop=PwDft=WD;5+ljjmZ?l|IQ&R?h4cSc`Aqh_FKaSN<^SGYc|7F(>6fwn zr*_T|R}ZawzG|jOiPwESr^|-xeN)fcv|pXrR(6hSxzA@NreFISw;eFBaee*w!tPg_ zCvmyHno#}ap;~(D;VX%S=l(7Gq9D6-5tG9c=9@=zI4?=5ICwfg+3{^=x{txK*}CQ@ zqT3pFSTXe4cx*U#TBl1+HrFYU!B^y8uf6pD&_ADy-TkLq*|L2oxwLNjuHI_dedlL( zrYu}0U~-qy z{qVMS-)OUvn=4aSN=VMX&rzn!cZcV9Bdw96zZSt(1+WIqlx%Wq= zL|@r=@sJy*@Ake9`F#Zyp>lfd`KBv2mhLNEV;=Bs>mp!ChNa~8tIY07{A5Lo!IxF^IX!!m%*#PzkVf^*Oq_f>%`psl|E|=UY^`Oy=}Sb z4o^*&pc&Ckhe9HSbwlDOf4{!cXJH?B&rn0u2o@uaKxO>i>r*-aD%XW$F+!wcO z3-=ex^6;RwOZv`kzP;yHfw4w_*fq_U5=~baf?nO!_wlx#GX22QYnfu2^6ce;GVb0# z*;0Q^%h7r2sI-FjRAB1|u9KpZEF#<4_h~A2_V@e~$aLzCx;^RL3GMA54~Z|i&V4%X z-~6kG?k-lD_;}uwUC-@*?peM%Yod&a_uH(9mHXLVYQ8*Edat`W|5|ZI=#~5brN6#E z?P)akN#3HlpU?gb&iYuks-Cz$VBu}OH%FYQ;4+hO$P4m;<$9sk|-n_7yh zE;zZg+hRw{b|KTVg}qVFPn>(Q&yzFk(mcPOPj-DV$CWv?h2D8uIHor2d1ku)E?=6{ znGF{@K5Q`kJb%vbhkwp2YMU`>XE^&)x4#QsonOt|-<20vnzmg1vB`lA?bxY@KhL_pr!0Gh*pV+R(?qxOAG3XR=4<@& zzLlDq*W3TU)9RkHex`7(jePs}i4`}6eBEnT34ORU{owRpFa9?!+#Gd&|I$}eSG2ZV z@t>LG?8C&V5X9AV*~a_J^N0Ch&r7@9G{c;0 znrLhLo2ps=_E`PuyYB8h#c1x`)&CYT@3(N6a%^H&ckqj2OCMjo+Vf-OE~&WhOV&Ky z@7+CR-L&<0UY_ri{I|5mJaE0r*VLjNeucvDidAG1!99+&y%-8ZV! z>)Us4Q5RKl@;^CSCDcQO|LF(mbvms zugK)$Ax?)oE5t9Kj`=q0dY-wl;{)na2Y-+9Q=*v`H2X#Mm4AG_=L=Y@uIPEybjHGXu+E$hO|sXxFj{k)< z@4I^=XhMT*R{zAlmlcomLU^VBRyj>#()gPC>TvnoL$j|;eiiq&=i^to(w8CHc=Frw_f-kbeZQmi#-*{(wt?-$Rn^z6+}FS@7{&bwLu_v469 z8&&id-^q^KefN!gk>UD!-oo(D;V~yTr#(h=s72K{L=PnG};aFyR`)+8NWlxiLjQ57A-xeybsGT5U7gVp# zI$a_-Rv_rOyynxGOA6Hw4|!Gm+o6AXk968!(*(8Fo*+^ATk_Y1I@`nFEmT#tz2B{_ zTyZu#?)Udu!beK<1LAfVT=`tE({A>UXD2USIlo~_)vF0PR}_=^WMigke~P(u$M!8# zc(=1x@-A~e)AQO3Uq?Kw%P)0UV)>ruf6%_!Uw$86AwKz>NA!fsmo66+Q&pC7J)2Yg z?ugQU0TJnvS?gSwCJLp;9AB*IDZ7))r1HX?>q^H>c|(;y3)!mf;(xpTTdJ3nYu5Xf zRsnrmD_`2heZKWJ(@$XYh9#;hA0F;J|Hkd4%F7@BuT+0&QCb|FJ-gaAcI{le-<60%c^4fXr_6k1PM?wNdSiJf26#5vc@ zp9i_r{GAbK|34_Hd-YU(?(J#)eSz{S-@}yLCl{Tml=M8oV_9i?a>gB%Mg1k>0%20U zU1E3nO6K2vSpRQ^&8OEpZ148%SE&70R`CB?vuNHIuf5NoXO-`^jSK4)@EiQ9x#|14Th*D~ieCjQ!b^lFfc)hD-g zOaT>2nhTdRNC_-_<54+tomSGNLz8xR@13z{SAWRLnA{cbiftYoIU&q{Z-vy;>zA+j z*&2LvnttZNnWVZz&A&CXp7bv&Kfl#ycSgO=-mg=yT)C=SzkZYE4}1Q*;mIi@i~3PL&36cSSu} zvEhvE#nQUzUMsz8mz=US`Q!WkP`an>bOX;4*~gbJzL;SmetAwfr_9yF!mle|g=mHM zI~-K)VLNrTra&)BCHC^ahW;IFDN6!`Y%4F^nY_oR!Z1I#!1L`RkN@);z4=OZ8y(Hp z`nxAa>;2&eRhDmcpDTGUOSw7w$D93WC;7WG>K0#rZMt4`s;K9JKW;PoYq zWVOibRc+yp2XVcU;kGM(ZMZRcj_cg==l6cB+gB*-HF?XwLtfmPI*%_oUD+``@YIPV zjrp%4kHpTJ?z!Iars30@Cu6QeYqB29tW}89?wIALx6JB!(DCmlkBj?H4NSDS=sqcN z`G-JWy-I&B4b@m5EAiA>-q)?l-fsOOtGPW;FYVsRJv^_uzf8V&Hc~vo{)_z%yFVWu zI<~WmT>GiAf4Yr+<+*t$tM}deY`V_SC~cwfqj$Y_>g)en_tbZnoMCYM-TyYn z*T2sR{Bih0^V>=>9rIVm59z!TUL3jP)how!UW<3T+bnv$Kk#9GY3sW~QT%JA`{q8H z{7YHZaG&ts+m|K>{?1_Zd})#Ke5vx!%`#p=ag`3U_be~6EB)j+?OfOSRTC@?U!Pv% zuq8lj=Sdm;xuWw!8{8wG`y6mj{!BUh_4l`* zMBFX^D!ux?+kAbmy1>$-JJ#Cgn{J!6Skd`PD8s|8w~xQr?(u&m6Vp@1)jHkGRv*^2 zdcVK0KU+(`tgFIZe`37U@qbsYvIq0;*lsqr(1!h>;Pb=N+umQ64{m47Jr%EgUv~a< z=HPuouQc}v&Ap|)bzjxfJDW8s8NKgE-qHW!YS-si|NYu||4kbn?3<-?^fO;({akzg zlAe9Cd$OyNEZDXF^=BWjSTnh8!`aYYP>-R#c=2QJ0~0r$;MUObjMBKVwC(E(M@#c7 z!iUsZS6(UEGvki`%T5Ms)jJlU>gtzXXZ=@Q`>1(2gYZ%x-%e3JBZ1VppO(f7Wp(vfY2n{rdW){>;;_{9OIz)0}EX^UZ9xnf_*{ zm~3COYR8i^it`r;Dc1A(NR>Ra`x?_QHD3S!ug3N4?^o^Eef3?s!S9*2p51zHtxO+0 zd72#k>))S>^~aZ)%($>1yLsKu$D8M$(3%MuZesXVEA}P!&UZPDsZ3Y${-00pw##!& z)VM!aJ%2)LPz#Y0b=R|0EPMcW;5e!~WP?i%Y_-R5QhH#n&Fn z6wH*qeCk#hC5r{@1--`CNW_@?+h$J}}aMy^1230*nXdi#yv|5@v?3;4KSlRUmj zrNp^HHaz{hyu1Amy)LoZq%Edg9AAo+S}pzDlV?0MGUQfZ-oS8O=EVZlMYTUJ6&y~q zxMk&&(UiR}UhlJsUc5MY#^w!zw$91(0?$_Z*c{!v@J@}D zG;>b^m$2`S%oNGU#+$OThh;Jy-2$Q}aJn_CvzGD~RlGYIBCX35>G1pWG5fap6AyYa z%;D6Q?agu9=JCDp;_}W= zl6SDj$6@=KawGGlf11Qj|2QD>zig(k@v9$xtM+ZXVt8L(>k5bd+s|7JPwWu5bIZ== zO~k!>%*R|G=QBucRC;pr($VvOXIxeI+x2nH#|E40;*a-N)E?dW<3a7l-*J9YbGF#N z+^T(kX{q=7o%WV?Iq#*P`>U9H%k6xzFEx7Kj7P>de?@+5FxBnHO& z`}VGSvr2et{pz{p>CBJKAG;?1*>#lNS|!h{r`c^$;>@(z?60*&cP<;ZoBmQw`20G1 z`+tM9+B?6(*VUAyUYmM7Dg0Pi_ufmtW8P2QJKfDz-c;q4e1y#E@U@;1llR&r-Taqo znbBFd^UOx0*~k23H|O^#e<=#6{eNI*sojcip4)$P{P_DZZGOc38^kEBo)s87Gz<*)Yd{cm2l z`thuzwneXg%siT&Y`@_Q_x$Lu6(2qpAMbbJ>5&k;xGH=3I{UJ@{dN%#{qO(XQgmAS z+uCiFWzjw>XSiqEeYmmZc--5-vcj83?}~jdin@L3M%KpgHaUqe&o66V-B;PLt-to! zx!E6`olYOo7e4!cziI8VlRav=)l+M8Law_{pZ7cI*!B3-w_9y(S06j(qy=q7yG~U$i)5 zhq~X9za4(RQ~%jBNXhDYt`qY5J4;3{;JoBlKTn(ZTl3Qjy#8K|oOfmRio=D^s>@6N z{yB0-yYS@omGk+%?(aKi^>g>(bsOV%Wv}V_7uZ<#?)sAj_SKu;3;nmd^e&;H=aS`R z23zsu<#9eQV;3K7S$N<2mesj}`c=Qy$W-#JR$kjMwcLzN;^3BV{VaM{cg}eAX_6IN zy~?_s3wAv@y+nD1^Fx#8ldfxAU+L`^U6QcW$|^GJRF5Brl@;<^GlZdH*wd1R!IvZacnsy=piU*wDGd zS}E7%rC^|S+uIfU9`~`_SN1%v{PC=h&EyY>hrWf)-}>r$mgKLTo%P;db;}`R;U{j) zOkUUfb7|W7s7}-Qv7gV}T7IpTt>XLJFXh|M?73WYV^YFeliTTK|K99rH`{Xe_O~;~ z3+4ZR{8)Ke|Ht2-;pfVI{un-d?BafXUTpmvEBObP3{USnF19*u_AeIbJRIkGwJjbe z4s*4a%iCrB=+WJ}-Dbm5W4DaLb*?AHo|b>Rmw5jn!;RMZU1$5uCgxspeq)yzUI!f* ziuk!_`ZM3z->aU!*Pg#-&W~}#9@Q)0`^LTPd#=6x{l`C-&wC%Cz4Y0SS#zIn zjrwrw(R=CZSwVX}x!3%6dGV3IKmXX9ANFVZ%B5ul754k(dO!QP`ij40Uz4BT zGk-t5^5vxWVh@C_U;Z~Es`iy>)wU1WR;;I*{Y34f7ir5L`g2j#>G_qCUlo_;M+Ju8 z{N@sJXYbaB?z8@-tO`}stO=~HzxrUR)Zd58lULQBC}sM1M@7Qjk?ro{Bk|8(Uy0mr zRk7B;D5yEGv{r4_(G!U?{k%Bed;UN0I6(Sfp$Tc@o%VtzqP!tJ2a0+qLmAFsV4XfrA5$kx5-nMpGpB%g|WyU1X5 zwra@)k!8+*Co*Wv57*uDZdIbm_pYixyLs#nhpga8EUaM)TeR0@?*%_Mw=WA6sy_(a zdAnurxs3m-Eag+`o=ZR9f2=&dYKN6V^rHM94HudjXEQHup7r>?^^IRk=XdRkdRF>n zWz~kD|C^q_2>uqHdo_3Coa_AdF%@4wEPGf!z0&B{^%uPv_AjRY`1_DO2Q(jj^J7rW z=D&8gPv!02^46zJ`uNW^@eCPP(whz~6crEA3fyX16J?m4Y}XNKkosqeU+|*`hgUy| z|CrFaWtMmr-&)JG(<|cTZcFcddQSIyZf39GiUW~9mOspI^Y6HQ-RSE**U;>%QB~(> zo64`axuwiF%)v>C1!}s{*|E+S4$L%va zS+?$WY0Y)9+X=V1w~0z5hF_n1{;f{_>@?~0YRi8n%dT*s6OLCGp!>`KIpTmAViPxRDRsXo)C-)E5(-xMN z-`~6KrbExoUjeV@N9Sw)u9_(o=sQbg%Zc|N(%0^hN?o<&LGOjH?)km%H8YZZSgsuU))S8XE&4E z@!H{LeZrLD`Xww@v0rsweP3?BXdXjw?QXlQ?ix2P(<^ocd~GW0=dr|FI6VA0ohr*UAJ;8He9MSA>-o0-!D>9UR|y|6MX4g zaM8}vb6n3S2>zUs6~({*nRejj6Pef4crLwuwt0I=sk}quiPoRiwP_4G|3pjl?sC{$ z+I-!&XK(ndCEJbuT-db#_{zke`t$!QoStOKe|{0`bxYaLuf+XB+vAr`K2!DX*0S>Zk926@^+M;jh_n+tC`lF`|t?jDsT`!J0fBoTpm-8OgPhuyg7Rvt0esFXS{|5Iz z8x8Y6{E@fcwdrPkar;_ZLna2*116Jr8NQxcHcc-$XHn(9>Pw3*)p*?({2F$%_e0yI z_oDwxPxEig-MeGvwg+D_xc?m7vh>WN7Z)$eCx#mSGu6M!bM)P*=TqZj{#h?~*!g9< z+V?3R6e=!WO)YDZ%4pT6(h*PdrXrv_(9%siZJw_Zf!xx& zHFqreHJxD@N3r)KmrGnb9G)Z^!kXKb-Olyvbx;2k<^$cy7mugO{%*L}%HO#y z;&fz1XW_m=6L_FY>a%6!?+Iki9h-myyBE*fZod_ubjLeBCeg1Ph-P*7ynFuZd$PC= zxWcqgOfT1|axhJ9I-Mo2&Z>0jRB6kp*tI<8j`rKc@84Is=!K@E+sV~k7t9{SKCC-j z*f!~qDr@nSz;{ztuDo(|$>R%R=j4|kES&rC>-UaHlNg#LdJlUw_{lCfX*>7M@t0X* zYT_Z!C-{8|ezhVY=&!e)pUOcmJ`L^@CrZ5A{+1<7S^jro)Y)>2CfI++v^|ynHn4eDzgxUKA&p+!JSdabm5^kB0}=v#om{ z`SRnmetrix%?HW1ca&|uy?5iEC!5xu7VD8Tw#z%D-B4vA{6^&cmWKS(@(15Xv_0DU z_>R|_9tjb%x$~ksA9VO1y7uQ*`tjSl&F!y$I;wx1MIl;5a!W;5UH1j2^J4dT>$asa zom_@mvc%ghfRFLzm}_4e>fmrKzNg`armhWlQ4 zc+dA%u+m?T`@C@llbFxebIe+A9wjNcBjmScDVNhOt-bF)ZC5?>EBk;?XFlI$#_1v= zL3}%`xP!A6D(~2(EHc3-Vq$^+Aji?@Gm!p9^IZ-wFH6^jm+7x*+W6yS6%5-e>OoT|7qw9ZbHM7l-~{ zBER))@1HFdrLO~)FVLMiy(xLd7X7b!KYq-(yGiZAtp5zVtbZNnm*@R#wQK2io81PF zK5w{s@ao0BtN*UlJQjZKz3lw?H_szF%Bq<9?Jk^7Kal$4{Ziv8Y0r0C%q(8@?WMu% zn9~~$to$;i>BTT z|0{ggZ)JZbUVg*BbXiHY@Uty5E}mT^-<4;*``w#=n}5HoHq2jhcTU}@MGcM;!imAJ z_FtLl&7>_-v%pn8Z&l)Imb@?S(?sOCxutgf>f?Gnd)fQksn5PXk;zzb_o}__m3^M; zN>A;bSMoRJ>ZHTIAuj^^8^jvCw*)NU^ytdg+I22*>CJfOyt%Was)9D0m|xnz`|SKj z6Q&$;pOF7TN+sfC^GloS!izm`PMegN;<2s-v{K7`x+&woShkLtQ>NWieK7ZfUY+!& z=-b<@=bg}2`1|Hn@TQ_OJ4HPm?FGB~pSS%o zf46b}S7X6$y*=+dcRyb}X~snx3%#cwF3z#%3$^^(o@VmuNwe4IPYr91%%0}+YW+FY zrP*QGtzNIo*LO`ix4kL&sfNv>>sFStUKLL`_Hg%v7e-l5{O6~-$WPwr_pYdmf9~{c z!BPCb4k$Av4YPcm7%VrSHmvNz9I? ztA2^e?wOG`T575&lOE-dp2#|sW^X9X){hqYyckuhUzaqn%|Cw)o z`9pT+o{3XRwR-&z-_HoI*Dg3)Y<2gxEc==mKkvEIHCazxeakRWChyV&s}jS~Z;SMl zJ%5XYJySI|oL{s&c~P=Df2+4*5ub+YF{_^6c8nUDwkP(+_^eUfSbAtXr*l=!&18>$ zr{WaQ+}G}(YX$bq_~qmzWIQ+QuU^+FhgNPq?r^5g{$sA!m+F|mdiv7`5*oo&ZzvmUFF-bdq6|Xjn_}8)zNmXaF;}$=A zee!USM$Dx2=_+@e<$G&3U5WS_v&CNaX7PdWlCF7H#~2&;>F~Ac+pO?_v@z?t1T>6BkW+ z?sZVx?ZU)mk2X10biMEBcW8caX};ADz7^c3_2J$>Rlc*^gJ=n9Fwj>Qnj3 z3o%?x;#=c|_jw+$lgbGDysy&wfh@x!DJe09jptl$-93Hu$S+k(oAs4vv$t=nWHDrv z{CR$1pZcUKlhQv6N_=kpf5tz1m7~G`o6~MQit(DeZAErd@jb()RWd;V>m}sZHt4ur z3SRzh=jzw(g$jP*hxRV{6LNKG@0I88ah_hfon!XnF|aaCrz z&8%O_-p@j&%Pev|f4lsadhmbMtEZPN5LRQow$ferLcph(qQ{3kMPgq4Hd-nE`P#a7 zk4@KC_RbF0d$z~?x&F-kiwhj3&#rap_7nJXU&G^xr`XDBAt%4jTt8>6JNI=mRwrn*|Nx1LwZ~alSlfO+rur%rbN5E9BwY#+{TqK{a&|9nD z$uv!S$+p1DtgP>Lmr?c-Ygx9Z)}7r!dbyT2;_ zd%geT*O!iRp6i3PW3_vF}&tsdVw>(bHP;IMxS8ELX98D#y_J^02t)`Ad7h z7G%yobTusGb?2qszl2^`t(yPtYxsU{&qW^X3Qu2^Hym>8@n?NrSQTS%VzOkV^}3zM zCo;Rw&F^4owd{DZ;KIb`8>}w9*mK=oeDb8yCD~u5xb&5n=$AUZ67dR}B)~ddChygZ z>7by!d+u4_f;qF!`70b)S{L!>mrP^z%{dJxEE)GzGaX-99CY`#`1yGy=XZBzN1dMS z{mSF(?xlY`G=H3{zLaCX{MLQrKi)s{imd#ZWS8u5j$1nG+3ZhSJ(av_&c2HAf4TPM zm+3)FO9FP7%qmg)W35~}KXb>NG{Z}u!^NjPQR|c`TxJ=sn>O*!M46IHUoZSK(VzUz z^;1~U43CBCduIGHJ=N=E#HFEmA*6fhYDe|l!1I2Mi)S8uX}IIdtm}K8nW}em_RKsq zw;^1M{lXLt$E*Y4J4(+d&N~oUdTr%u<=_s5r>|!2(__21gHg|-vB9j;|7p!Dmai;M z{Ofksp9|IMDPanUbGHd~@_CrRXPgu&bU2px)XQ0xg6;1`-da^I-4e^RQif|aw_*`P z*Y=a?)6dQMrD?fX=K8allk=yuC}q|!4;6Ik-<6?q{m>HS2YRh-m*yLWERp@2c_Nx= zmPp>EY^^izvU-b_hjP9R`Z4={t0&hi5sRRRlTX_n?o7;@>#Y#_DRoy?oZHUpd&;b5 z3m++&`d$5?OV$*jmJLRGW(Zj$>I3JqMHnQ zlPY#cP3V|)YyR@d{T}73_@=Lp)|ea>^nHo0P;JY)#wxL;vW(LT&nvxSIuIDI{9pNC zjOD5`#+5auj4K#Eem=X;`_Jze9(R)Cq*#{!-1JAY$49k&Nob(Ksqhm&9|tjd3Wxn- zyTx>P0y`&z?^fN5>)7UIugf}cS-#iIv^sDlblS$gLXL9}P1(zYn4?~GX3I)SeT^-BCKEq9>iXpKK@H}8 zU!K1vQGD!qCbGuGX}8SNLbC@aH{2mht@e=bqc{ z;k#p^QB`F%qlsovGkd@!@%c;g+n?txQa^NOi|lm~uS)?I&z!iQPpS@Dyrw2q!CyDX0oMiGru%gU|lH)-v%?qof4y?OZo zzOeNNv*SMBiAkGMyrR5TtgBKpwBu0l74}Q%W#a2zHt$(9?b$>x{axGHVuH)I*{B3C)k%(O>RAX_u0?BR}N-hk5z6GvasB2OmHaz@pwP3{>VT^5}veyLPjwsV%N-|x$1D_ak_ zue^J8?}MAd+cMMb4< z;mWgp@-zGV{OUZe2iI2{tJNBRZT}oRX_eYh-#>*(e~#>h46!!+YlrMZdZ~P$t8(?u zx8K)DMMge5QO+vM$+c=~P;t%KbI$xczdpa5TC%)a?fuWH^VjF!{rhvp)A{n5bw55W zjK3JCx}j^aSKNL{&=l1AgDq^a(j`ZKykA?9f3iAz(keApHOs%<+TeMr12Yb63_W(! zPWRu-%iVFlbEXMQ5;*j|&-UBg`}ywspZLXEU3(?5VddpzZ)fkDiL`~u@%%OEX!+dQ zj`cS|F*AqhDP+#t^Y8_m6RYF(gxriZV|jBgr)T=9^P9ZB!med?xt+<%R`tM=IU)#TY_HOE{teXq;OMd}Zxq&Z9yu88#bH*Kj`;Fon$F_+RA zFP_;7UYWnLL$yKqYrGQ&=LEsGlb%m)JAE;!Ygc`?j)BqH?au2;_spKhezr2=&dc5I zDd#OOTzNik_WM@Bo-1z+!i|j*ugmWXSsQJ-Dr@Gv?_cL!`}}p;dvn`cU)DRvtCv{h ztghL5$~g3v&9}dcms_1*EgSy(w6*kt`lk02mpq zSYcsk`)jk2`{Mg=K6y|7qgR<)=C-|S-3OG7N6zObTQ1L<&j(%PKS!$I)2EHP{XLZ` z?w4Aw(+pE$SJ@doo%rl6Xt&b!7YliFzi+b* z{?z+{^`P;ryY=~#tKT1Z1KE4kW_o;Dcdf}b^VY+iPnXZ7tt97RghgqSrVUraJB8JbA`;RIHdpLNT<;~P$uS<6mZ7o#4EIH3~?B=i3>nB&b+_%_&?B&ANI{V7*Dcv(|Z(ck- zG3%PJ{rvay<>&3aQn+6Ea@{$(EOXl_|1#^Q|2_5VjiHysLO18fn+kP{)6!=96rX+3 z>Hq5Pnf3Gkd)Kv|Sv&Kqv2n(NJvT01@LIU@h4cEgscWyVVY6f2#&(78#M8#F4MvRL z7^LkCt#g-Ow|dXPGB>8$uXPV^M&Xp=km3;Y{Q9S#mzgwW{B4 znps?V?bGz9D}pb2^f=60T7L2VA3l>)6&j28##{gY^iTJ%_>bJL(GC8uHkW)Ud}1ZH z(MEiSZh=UwW!0-UhOfW-WIx;4Ev+xNXUEo03%M*W{$`Gj)MB~6`16|QVcSw~Mx|z7 zeHXYp@vYmmsng_jUYnWS`SR=7YJa_|qBoQC?QZRg{wY@;TDvFf=&EHMU!IHG>;J4Q z$(pRaczwy<&-=^Wu2gB%W&FAP@vpqx)x6Vx-zPrR^bWqZY|X#22isrXcPm}}KVx#< z>;>O%rUV~8{nh;Mjm*be&aQtl?Tqd;>8pwhoXgs?phyHkK3mvJiT#uXZrb4tMdB?cQmi&JK_^GE#X{Vfcix(-Y) zpZe;E@BMVQT34aPGmBU6%;@XWQ(NzuJ1Ios0e(>ap=oW7|ugt%Ddvv_H3*B#v-LHE}s_meX83zwO-1?{k6|tJLlKXYMW}H>sxD zis?s5O(}QH7NM8NIYor(6@LYs*AsVoBzh+EpK8s_FM3SxYTQeBx7=l!zCv`#L5=I* z<7c%hUC#NQ*8io}M8W7qE7Lt$P32CpxxZ|uygS@!J;^if5$pz|22625K^4s&Mn+;E9ra&xJ@ zs7k8)R+;`;^}btOcZN(hS;nHeRE2TY@ssY07fjf^U-fJtS9js`F5aErdlyM95~=w- zZ_>9VQZB1{|Lr`~sK#10<&<-x>eIH_|IR5Mo9||HK)!Xh$yNuU*ynF&%w3(fXs&bU zdC8|+m@Ixg*ip7B-0#3l7U9YQnFsw$0k;k{u2k80?z8^V*Y!7E`bI9E^M8NF^Zfs3 zZ-)ypT)cEV=)6_)Kl#klbEDtgzBH@9S-|+vonU>R>WW>=2J61>yr*5Qztk2yC)byK z;AU&SE?2aL$oYM{k`GjG!9<(sSsaVbZF}PO`M*k-errHOWY9H*+=sOls&ZewEM~Szt^MVE zzIQ&eee_9huioDos(-y>tPd6@mhs(jbX_DD@43k1l=)IO&geh058v03jeK?fzs^Jl z^}p8&7o7Y(J8A}x&L#iNRu_%!#6P|ET>YrX_0r3Sv!11X`nTs*|H8xRr0dunC+vzq94 zzc#-%zFxa2R*%J6(bKcTNZ`b8bb>6;6nX^)_{mAX53?@FSE;wIzPOVy! zWs-5+-{RRKI|I+ScD93--80uq##zNZn7DraYhSPD4y?yiE6YySpG;ij_t8hM!sUC@ zycdg~WnVUaE;=KjB%XDP{JGgvHqTq%a?azuSAE)w!!K&eOnIJ8t+Tk`;?MWyf%Ow1 zSHEY|=fB&0Fd~18kx_;*`zeNwJN2HH&tg7wIC;i>Z9k{c==WtwotMOSFRz+{mMcp) zSnOS`XQ6qqRz-i=lhY-pF@DeYTHZX>5Fh>Seo*b{xw5ym_2*Y#KXjCPxo%DB$p@~l z?Pl@KmuFD2nfv9YSA0lRWTxhf2Mey}VHT zrX4xoUuXB=v)IMDz}t)Cm`&UJ_WytP_vyFmF7-A4zPHD-(DCH@ zsxsB<*1E~vCwvY~JKWVJcWWQp&v-IzsfPH>kH<#Lph^@Y_^Jn@V{_ifA z(kJpLOf5cQdNwI!^XZqrL)%Zhobl(&i&#Frmoa-9RB{(Saxn?;Yuv5k>LU5{)uBm$ z?(8@}DLrMEdf*baiD}kPx;#AQN`3Ds+NfwHzV`Lv`EyVHoh9^T%A2&)M_Fbp{(tY` zZD)nukhMb#g*hGWtn^#{+q(2fFuS?zo4O2s*> zJGaF2os;~o^(($z%y(ITFxY0f@Xi?@b~5=WJJ^43uXMM+aW^Azam%vk%7Xi#K8#DO zkDidSbR^?YMTHsl#`cR+ct4n|46|>c#y_Y_I0Gtaoug5nIdj zs`#>bZL7EDk8`(wR-L)jw{-KP%~}hVHZ1s4wRq}8$Im(|d3J|5OxkqRkf(myw}*ds zR<7$f>HD@bB)GnUyCL&L#Ot8{?J6tFR$A>;e>LNG^SA5w)6yZx)M(frzU&iSgM z(#wOMOyaNJw~!Iz8w9{K0IgX~o&>d9zPnt+lwYWJAA`|34A-RuMa|5&^q? zSClw6@2=9Aaoyf}edA-_AHGXPKb3k_hs@_cBfPWdZ*8TX?z@w3)?ItK(EsIOi}2SB z52K{+hi2A(<$5X4u;-k>`W?z!6HLNWKdgLX^RB9HUXc7sv7IaqmG$qYE_wW5*5@Od z#&hq5rUwOOoKU@1DSvNr|4M!<&Eqkz79Um(_uh2)$~?dCtNwYkT@;wmx}*PjGH59M z_x2eQ8q<9~g?-xNBW2|-ugRhJG~?4BZ__(RwyHmv)aTs%DJ#utx%o%o%O|5tLuV}0 zShZ!hxskZUq_pWXuDmi`ee>xY*L877GA(VAzVz-Tn9So5m+^YfQVSZ4*V z{-5jUFMQzW55F(@-19Z;ELoPE_mZ4e^4{vH($xo(Hl27GGv~gqqxxC~mA7-5-EWt2Vqk&y{O)MQ+}`_~Y+2SWo@5nDjk_>-FyNUfH+53eHZBdR4+ZY5U2~3zn;| ze)KR+v!!eK5{7kE{qrguxUXlQK0f8!y11Q_StL%HEKN1OI`!ID=jTG~@uw;77 zy(ArS*lX6k1>cfB*_l}A86F9|9P=sc(3P1}{!HmQ)V(tQSLlh#4bjJUuiP(W$G!J; zTyN2O@ypq9yKT?1x>@@0eo_fkG+iI_w*1LIdn^5#IW<1(9(lfhb(rs@_o}+CU)h%b zW9R>J4%c?n?%Wl4MeXJ6AdSO`y?s}9p5uJ+Ba(5U%E^*j=W6Fnx<0wuWa-}3-dx1-9fcJivO%ey~#HO)UK`7~x)hxoK7PVX+gVVJ&v{h@HVYT3>{Pn!(zEX68I z$1_*92bnLoTlIN{$dY5G>I^fP^llkFn=@1IWnR!rZByDwa3%>@Rh44zlIvDytrJ$;mN@gb%tM6|2XcO@Tpq% zM>8$>q8C5)>G1}hY~32Mg76C$mppS(-<4TnxPE`-!>!lz{EJT=-7;g!7y zO#ixA>+5K;9@o6Gc%D69XyT*=ld4vmSe!e!s7^up@ruSwzlQho4is7i`KIldEF!(6 z{I0fihrsfE%dZLd?TPv_``yIt-n%cKymh59=}NM@_D7F?rqMR;r+fSNYJMB z)pza6ppUr=pZovqdsXgeJL^jn(_A49lW-@SiI;jlOuh7B*AbcJ2~*gPs9x$%NvMAu z<$ie5w}=0{>(+kjkE^z{eg0|rE770_92Lj68vAON@@bz8-f}|4L2K6Xhx4k+ra<>}AW>-z$DTxU_w`o%i0U zSud6N^j01aXs`W{bm+KdqwB4BUcIHC!`Uy#p1pYG;nd>EMTUiEOAU91PujNnv-Q{T z)V@8J4KHpq-UyzAWX?8mAZiTmJ9rAN3z!9{OM0y+=JNqyBGU z$*<34=Z{~PjI=knvO(Y7c_gM9p2658CD`>|7(h}?9`v>C{d)J`*w4IUqrSQ?IrJWX+x0zR9%3a58@kv3-21g|_l~Oo)G z8QPgCml$@b-`6&+kJ)wlc;VLleV$Wf|IXXd)*|5aK}4h|UX8ua?`o|_{mZCZ+-~>e zww8ao_+M{jiN?x@jXlSZuQZy_jsLn5hJAQd!0OHNfk+gX`m+A~tp_ zYj&O688}H;xA5eBwOc=Ly|{gR>y<-)r+k?eTw_r@hdEv=;mGQxyR)+Fm-5{`9$K)n zZG%WmklxNCpUhrQ6)xguox)LE%&5#K^V4)L_g_Zdo!_T4dG{P>y_RSa^UnDEj07L^ zbjvs4`nGFs=)KOgwYsaDp?_=l@*f8mS1z9Z$aH-y|L3!Lk1ghZ?cOzcN!d!XQdPMu zp*u?+2pKu-SoSuiUsyLrfAXQ!xqo-DpL_8pNM3925_==vU(ZXznBt}FPs*6Qc+-9Q zn#Y^Wxm>nZ%arFVS@*?xZ))16<16NLR~A~xue84}dtdOonpZk&y`pU6x9NVJP7yuI z8!uXBgh}t2^v9pcO8oD?fC_hG_luM3rvp`>}`FvzWc|u?f&$AkxASBZnWQfFJ}E+v1iuy+5cYtWS{=CcH+i2zEKX7v;!_3 z|B~H$c*z1wdG}9+N~b6PR@+%Gq8rlTzPv(NT=CK0(h`%8A}-xr-$OkYhH5&TWG#&O zsur|DrBYb+!m(cOdjat)RpzKc3hI z6{q@Fr=L_Wud{n~v1FC5pTp*JyBB|$xGq3_{-kp!?n~7#pX6cPQ*}S{pwCJ%%dgo< zxet0~pDzP#E%`F}{0ilbdNchzVzqQu+H;HRPO6&qY6eR{&`zb!M~8X;Ue%uH<+!S~ zcZDaD+rjGE_dBHOt$*u(R{ERPlIN8*b^q$3`_=r<_HDJ}lFV^6S3VX#|L5C3ZhOjV zLEYe>Z%bIjt}?j=DSFN7KB;>7XUv`y)!gSxR68H8lWU*XFpE=hWfR>FY_)0+vXYmwIv!h6ST9S3g>PRowNGrEt%) zod+iSJzKgfW66|>o@YDnIR9NN{6yJ^A`Ro0`rAgJH8za``{C@hNdS>v8yKfjRulzL$`iDI2bJb zX4~T1=gw{HFD*A)e^)OoyQo!F?EWOlqusyH|M>Z~dG7k&3zerlt77j?eU=1DfVx+% zbFf+M^{%&wWu0E)HgC7*`%5vN4c_kzpI$A`keL3i`+1eu!;0@skMFE#_dNCL@+wY~ z!~3VaSMz$mK%k}nL6*au_V=-Och4;6w!Z_;9+%#{vI%6+tjlAlpR~f-R(_uEiEt-> zo^nm$IrG`R=6$;A#aK9ZmfNKJ6F1-8s5Q&mYyQ-t`bve_alsku`1MwPv8pQCsq{C- zr&Ml<$b)5Zp5flUpXR-5^nMlmB%uHLo5q&`l`m$l(oK(dA0e+o!Q}kPxpOT9BUPqW%FJ=C4HBb)Smvo z@l_uRTI#=wJ)O7J$GtB7O1@zEkM#XD8;)zM{?GVdbok=YGxhOvrA;}xc;&BscxbFT z&&~VyN8VQ=+TR}BxN+jb$^9tKFlU| z=fSK)UhDc+%C|lHIX$T2?p0AQW3Q-rA~HMF)!tW%DVzRPY7zSAD&@H7M~7&}lzVo6 z*96S12{8RIX@PG0Jgeu*XFWO{GZr3^eQEN;B514nuBgmVoAycTdc(dfO!uyQXL6Qd z!#w7&QlX|R=MJj2_H1cTxy!B2yfZzkh<}}GRDWOirOB_JG)z_a8&K-wb>Hy5GH2gq zr-^@}c&#o9Cj_24y2R*>&=eg9*~yH--nys1i*>6Ws}I|E+Tqo;4eHOQe2)I+uzJ#3 z`JAh%Plaw5pWSbJZ)HqH|C-?WlBMUCU9Ho4y*U2-Zmz0R{{vpBpFX-z^!ATY;Z>g_8yP!jqpWVbJK8GR7K>PqpgN_|zO6fJv=C2iE za$$B%zf5L^u8uxY`=VLQj_F!hb?Q=)*W0IxyZ7?sI7eSl-&FX*Vg9s|nk|d@?S8(A z5Uo0t?EXF}v?TY}`|iGmi7&YflUtuHwd3E>{BC>R$x~O??!UbAh2*DLkL8a3{BQsK zUDRsl{d>j!`4Ky}Xw`qc=6;7XjZ>{v1Gik~UG{lm zCigdK1CKni(k>YN6Gh6i(DrKExSBp%iphm1oveg z>p!;S|6O0u2{mu07rXsga9H({^v-InBq4 z%BL)3|4dUo``vz~f9l^U#&rST%l5n4RGGaDIy)y<;&Y!~syx@3%p0~TOH<2FJ?YtO zX)3ON&%|i0@B1w~#1i|>a)XN9JASe!dZqoEzOz@2H~!n3)_JcKtgqSc`}p|$blx57 z!`B|WmT}nNo20Rx&D1b!^`~<$uHNSxSydPQs%_4ji_3TQX;|uhO86N5=hpN%p=)RV zw@+r*yK=HF{mEtd`gtYpy{W11H}=TJEkFJ8MBK5(ZHZqr@4l|DUAH8{NbPCPii=C` zWS(yO!0xR7+xFwk+TxT~CoPZNI;nZAc5!ySv{Ckt>{vhdZ=CZ|EI*g0q~)HzD{Rzu z?&XHEqEmP7O@H@aktVb^qS| zN3wFkpC^-kGVVO+tCL=1-Ws-*@B7)GhSm4;CHt>Mhd(U|kpJ_u@ZH5JyEpyr$=>+l z=98Bf7ax}YBL92R`Q&IX*HphP)6aJ+pZ^y5<t)h> zNzegCFMmf?8pM7weO0~8_wVVxzQn$qSN%_PO8*__G^xky-&&lUM__Ok~~o(lw=nz7_rR&UYDteyKjub#~0Uj8*eetFcojyUFXt6c9^ zTC-0N>Mi1PczD$0|6?)UDcA1@rahb*x>x(&-!C7ve$+h6di+Vft?c)%o9_1Y(gsQ! z3U3+h=bLG{{MPq>t1>r!>FU?dtoU-^?$^u7@x8U08zP@|ZOJ;nB;9uNhfZ~?`=(zq z*n12T+!h#P0$aW7v!Mpk~y(Mg$C7C*jX5woqSw0&#jgXjCsGjP3$E8h|K z>?mYo0>@+J_|J_EzcPSWV^4s-Kw>!(fS^E5?*wsT)%uJU)|5Ql6d*t-=0;UX0 zq2hO2)@HsxX>2`brrqgns~#>5p89Hvoq&PN^TpG&-MgGxzk8SRZ`fm3c{E?VEvd!r zZPGXP>8oOQKWY|xeQW0bwMB1F?_PcXW5$K=`tRQTieBmdaQ?lZKi?;;u4g#n?k2MO zrMb}is+irbqLb|ZzI_oNpStz^v+HY|ZY+AxupsPTy3*UDYQO9~%6}z)+qa=7_HCAF z#ljykb8fd>oo_qEhF87wX5qGmXS3^X#s9zcM6vYQ7Q63;^WILo{O98a|DE|l#(fn# ziv3b5&sZpZSu8$zUexWP?}sICZ~XVFKXixCoad|m^Lui+T^5V$lrHG?=D3({d4JEM zr?;-nuvU>@#rLtJ?e5Yyo?l!9=B#&Q+R-X~tL%J?!dZ>Y60x$uuNKS2r$3RdUL}*@ z$;oi%bx*d3*Tp?nXy>+){x8*suJdd780%QpEcsi>{oeDT z>AO{@T`$dhR=>rx&Zx`tH>(}Rb%YJwz_hO2NnTbf=KHp{Cp4DCs!bZ+n zno7HbP8NHnUcIOkd!H{%=U>#_$^afT>Gv6@ox|066>GU)YiQ1OYPNvw)V~s&0h~b zpZn0Tz5LA7XRZ3WPrcld-ks_GYxL{6{-($`Z$ImX_ussC@^gE+zyy)TqD+|+9!o>c zRam7fMLn3g`}H}kwx+z+|#oKn**_YheaGc=_i_VAbKW$hfyb=pvf7!jR z_1e)H4Us#Ry*|R(Al16{;mu`Nv$yeE^%`^TIezI-`BV{?V<)d{4}B?WBY*Emd$d`N z-5!a%vge*}7j}PtYR`^6hql>YF0(aUn7VVrzJ~sWe=j8Wf7x+YPVk||w~6m%*03Cq z`mJSWvt#XjoiBGT>J^5j1>T72tE*I(`xTM z!J=?c`6r*_&r;*_?oNo~)IL6+v7~&4V7jzqOijamjf!j8IeOde3xv)sKXOVcxz@h? z)X_8Y^Z&Ypd0GjuR$7UOdGf`cN?o$-z4ep@>Z0mFr?p<%Xs_L@v1(d%-j9#duBf{H z+g;|TVru%K=zQ0&kgFkzPwaw%`ug_ReB};WSqBy zbppF3--a)L`fuiS)i)6?hfjCwA5{(iwp#ftcZFjkQ$i}E?M(5>(N8*)=Kt_tq^lWv z`TMh(516krM1Npfcjj)L)!Oxar!=pfJg8xlcz4n)TfuKkf8u7UzqNmU)%w!29NXhA zXS~bRXXfsH+-Wn}x#=H+?UwWZ{QJJ2pVjmAur)`&g2mTob-I$1i_9)8eopYV5Li}5j@L%~Orb`PL zHzbKST>QZzvTXUmM*X!RdXf%17v@&DyBD1g+hEUO;ay%{_2!_<@lPRT;YE^Gi9sVw$Iw)E;`;nhK8YtNuHW ze=W?D$G`I>tL@M5*_7n}nrB`7PpWQV;k(ISJ|Eeu*)LVR z*824Qz<0{->wg`7ANOc_lO4nPI1axJ}}Ri&k%!_9chT`}tp~A-qcb z@5Eq^2dw_;-&|GaY~bcPSHSdT&PDd}?Q?GBr-Uso*mh&%0d6~c?>L`^{2wA`KRRFT zy>n;DVe##$<$F%YrS_O|zWsjdmBhhB{`}l6+aiorv=ZVi41aJJw*6jne0mz+vfU2; ze7v8ZJ%4Y~=l%Q`?{eP%8k5)yc>62dj-KH!nyXO#%X;SPVt17-_IJ!Kd^op&q<6#0B5fBCZDgC3uZRz<$GX%1d>QsrsYrKt=0uLRpN z@jeaC*Zy7=#BpNgvlq?_RiZ%6ju)pheWy?*8X zGx}qvq~Ix+h8-_o&YJ8rtF$9WBIr!ZJdOp$=cfg0*gU(F86C_u<3VfFfBp|i@t2ct z=iFCn=$^CZ`z)SYfy-=N3xSh#quXWyoeHq42 zx&m|-NdKuh9RK=LVTwM>Cc%5A*Yh`SKfnDzvb(?BKF$qAmoEBoYeVoUPj~AmGoOC{ zol%@SmrF3tf5E)|bF#kqYngA)`?xxV*XO$X>3&k17}z!MgyGwmwI5>>ubE%}YAbt{ z{eh73iOd$hioym^FtNg=%T4XR81@o6#OJ=_A$>-jbX1sZG z-6>7`{YQUghaYQlGh2E*xYm0=^Q4ho^-6UjwctiGO7vTB{% zZNx(n3kTic;U}_@8UXrYyQc5W(fT-wp|b2 zg|kR7^-C0`u0b+H81$W7$5R5avtib6;;beNpeH zsjcAwFRgHv<{m-brq$Z7S2#aWn?A=wuyC7&{N&)8inwLB7I*Hxz4)7--LLzqQ~6X) zZ3akK3 zf98MMrgF{2+TH)Fe^pfYI+#27*hzWlHh5V?Z((R##NTRnvONB$wmO3@1J{?{2az$C z1uyXBOCHdE*H`uaV|VENbp@M#G_2Y8qVlb{yXyRinDsvYQXN*w?ri(eyHGaB>xN(% z`+Y5&vK`%GyPO)#c13;Fd-3aI&X*S_%ypL~SUf%SIPb~B4dqPP#cdXMt*^C}^|)pF zdzdW<_}85F+AXG1JpG(ML-w??#xk4Lahfi6${JJ|%7q0ppoO|wj3{S_&P9uZTvvRj4hhKZ>zw)+a#1x0+ ze$Q0p+nbp=58eAC=j(r9&d+5nS?1>bm2rk1={|q0tX@sna%Fy>&)!axe&OpPmv)9< zKfbr}qsZD!mtYQyFCouUXJuP}7SY=(tl7mR5LHns&*jFgpi&ANLg$$HI8xI)$o*%~ z{&oFdrc~c|5 zNty5O2U3pdKUgpJ?NzW`_-kHKIJ5kM>f@$19ScMvhO zKk@!o$xhIj5Ai?W*-a5oVLa0FZ_%55FB*5OU3Nl&fp@F--!rbV^H*32tk;V(%us`~u_dUi-d&!2AlbPmyuv5~Wc<2a| zh@oT;TP$1T!kDj<|2=FhGoA8e>T%10DNC+bE|qWaes}#zoR1CbdDV36OWc>|RF7{#Sk5nf>?BB&!L5&#PB_OI`Q=$K`eRz3#4x^KtO$SRo%HWb)x& z#{KX=+L2K=?auS_&0NepgF)}}y%Uz1|2ZEokP={eaox*G?nCYat%SXH#}+PM{orc0 zd2^wEdg4v{CrPVT{|MMAe`}qX*`$cudt{cH?5=7R(ec`Pv9co8*ZbG@8T+`FRX>Ys zEj;h8y-UzSvwgkHhwfE=i)01Z%VW=8d~)^DRm&-p0&h*w{OK2VLex;xS?0i$Q|f_# zy#ijlY-Ul3IXRtc38FZyH2JK&0_TwS{_vG30rwEt|C=-{oOxhzovKl zr~H5V)8ft?g|sM_TgSGTPOuHPKUUlOM{nIqB{lXbdt#fG%QSpiz9cj-!t?k;(KFqT zI$lcM+5P&Ej+A8euVPQFPOESEXZBB-aknAfni@s2Z$=AtT#l8er(-WSh(805~-_hZTDOQwrknS8A_ z?K-#PmjB9c4TqeTiG0^r6qCJbsHf%fKImZy-@hFh7q`CL7H4UH;bMjSuQgk=w$_IP zoi$mX$#}AU>X%)s?}afqHn{#>&ft9ZgZ8|+xj&7J%s(%@@b7B+i~izd|IT-h#J&XG zXA6snOJBaFUHt9c;6MN5)iN2PuWk5T%D}*&TH+c}l9E`GYL#4+npl#`U}RuuscT@Q zYhV~+Xli9-Ze?PjYhYnzU@$@K<6RUDx%nxXX_dG&JoNTVWME)m@O1TaS?83{1ORic BF+upQfUsvc-XANy|N#m!Vl1j4VsI%$$TgG!#`>1g$r%I1m-EF~K$5H7A=f zWn-d#cfxyqZjDpBA~r1iq{g{m_5JGi)z5!FyJw$%ZjR;iJ3A*^GpsmXaO6==2y=r> z;64>IlL?lJmaU#uECLP;A`7^EEMit#D5$9kIY{pMAAIj_!?l2Qw@%moz4Jfbb@K7) zMT`zf3d=t(xg&Feo$>U^!K#`i?R9HUnbM-`>;r4<4dP=PrTJ#_>P`@%+9L)`B2x-MJ)$^Z1gIuOL(*3Z&+d3 znFY4j&Yb6)`r21zMlP?G2Pt6lJz$i3<@yCI>0Amv+&x^`U*DV5A{xuz8zx1%Mu`A9_qv4nS z;i#uq<(6nyaf>K#au9zaBfz-nVgAgdDH#>pTt2X5wb-9M*Saz7u-g0Ip2tJvF9|kx zIvz1d;<(6m{djF*zKu}a-^AiWwgu-G?$~!zco)ap7WbCdEqsUGt~``dMWxpw{zw&)+z_F=jF}l|Z&i7Yt*tc4q|DJN)mLp>D=U;Cy3q zt;du>t&Au8h+d-m6PrC|^E9p5RY=W9A2WXVW*i-nqDVh96&Ff%w$-e^0G_%ImywxpEKp{gTS#j30EG zPxa(m@!e_WD`#}5|L&XqO-k?gQU;mK$EQ?Sy=3+FpZFhm<>Bl_>^%?erScjVG36bQ z{?VKuz_-ZJ#)B<5VJ>UIT(1Kr2Eu2UmoAXeVbwkuwLvh4(fUB%26>Z)v;w9%jN*>c zJNVum+IopIt|9IN-xp;LC#MV!*^ACSg4{>=3Y}~O;#DL%U6LlIsED3)aOvQg*?GqO z)I_fo-A$4!UF+QWl+`DE_OLFJuIwmLUO&-aqb(>f_lm$)m*WfMG8m*9k1d{jk;jBz z*U9_B(HB`;*zUGoTdaPe`$g#%=Gw+@i44IyP_C4FA|(q5p5HgR+Fc z8;&>@vqUDvW`o`UuN6u;$}&oG+TQ!AKa`#5ks`3RpmvuRE(dk9WR3 za{P#$Q0Ef&P0BwfEb^2IvRgT2OWT(2lAe;*E1tg=ELxSc=Ft+NMUxgO1!;z@47$0p zDCB9^so<$~o7iqPy>abR`+VZ^N$(TUCw-rse&SbT?>afj_2a@L!A6>&HCAg@Yb@9N zu8|)S8tNNr8+v>-SLpLq>%#6Xuk!o4@Rj>2v8&TgE%IA*YDM?T+bfT++`ckD!(^7~ z+4<9)PA}J8cJ-3qCAkZBD=uDjTfOmWMON{ZRaakKUAJOyi2Ca273?eg1NDQ}2iY&E zi`?tpye4tw!AlP^&Rkn@&0t3#qTsw1d)_on)!WNfm#y!c*4OQ~$Zw(F)^mb$ z`PhzaNZBBHvq!b(Z;y13zcP|9Jr=%Eurn8ShF`< zZJS%$!nUNtrfurH)jbk!|J-J}Y4`L=l}cakt?g|+rrpyoX`lYefO*EvGgr?9pNSJ@ zo@^jI^JLoOu*r47+Lu`_CtcpV?E6`bG`X~wY1(I^&QzW4J$roSd6V_V`;#5Feb^un zv1XG+_Q!jdP8ppnYD{f@YOEcry)ATN=-jKe*|WCJFP&G~`_)u>QE$8Nw6j&S&7!Yd zyBd}dom+jAZ)@>p;ah9BXUn@vJhk*PS(SFhw|Z4=Lv3U2+`oq}_FSB}xZB;lr_Sr{*EaUo`qWz3&91eonYnM}zS%X_e|-K})S1@x z|2g}o^55-0^Vx2&q_Dl=s*rpkpuuS)@?+l9qqB}$$+fVyvQ1^(%9hQl&a$4jP9RBY zl3KJ ztzPC=G&jWR<*}62CtD|<_rExQlI_zIAzxR{S;-#6xzcl`b+CQ$B1_Xb`%KDi?b;Hj z*S)*zchmFp(Fb2Y`~5-tr~l>JOWK#`r~XLMIsZcJ2j?mlTc>FSYd?ghMLaup=6mGD z$lIIQHcj62{n?5qOPn%T4L z!~Ji(jk0g*Z)#`V$9k1jewnJDb-;>94Xr&Ucgub)dg`kubgxiIzP?Vp~XXM6E@KbvOrPxZ_6 zT|rxeJYSw=y~k~)cSM{^e4AcT+?KS9*Nx(CwidO2TJ`C?=$$U^{Z{{W{kq<9{>xZS}wESMSZgxxdM`d{CHh-m$y+HQz=4saKW1UVdFI z&B80&d$RX!`(gg=CY9#0zmmVm-=6I|Yjyt%naH9kMO&V{IIwY1a_#Y>$E*EP=2YA< z+WPd}Nlx|kbEWE6eJwfivN+x8+#DO}f^Ro0=NF%SzW1E`z6XU1A1=Cm^q2R#>3n}* z{(8PJZ2Prer{7JNzHM`R)~(~)Q@yV}Dn%v+-yBOZ~iluWX*IyBwSCo-Z2jF5W$U=eYhn zkNGw4bY3nh4Yf6`i~4=$Wn^)=NBQl~o#(CNJL125pYm_%&+EIUU(@IR_xAtyrS~}Z z&Rwsvo-HnX&&BG^f7hPAJ#F8NeeCax?tR^_|9ipr#N+P${pt3qwNn!V-|j13f?d02G$GxC_3*vd9szsBW>8H0xK)O6!kg;SrGG##i zF^?hVJG(-n9>c^B(y7cjViQ(=(EHEQ|DAos=FbJvBAL53Prf8BIxm7lo{jCE1d~G+ z(}V7mga!3~4;+}j*5Jl%2AT80^EjU>**E7&DKFo4Zu$fFpXM3&3bkHv1?^^FU{FZ* z2=ZlMs8VBKXlP+z_{G4$(C~tRq11qZ;Z*_ygVhWM2JwP9y8>+(7#P@+yxmywFr#OX$_EAp2KEw9Usv`=JR;0SIsvx3wHX*17(87ZLn>~) z*;_tC?(|4qrVo3RlpEZrgRKkJ++2@egQ_0Fo+Z|>qZHbP=G zX}jWDELg*TzuEdiWBDaVMfLDU&u^HVeBSzLrGMGp8)5M->6v}umzJ+TmY(a@KC?7d z{l=9O9iN=mh+R%!Gsi_~qKAr5r^}_k0ru5`cb#qC$ZehAu}g9O%j1gmkwTqd`APcn zYKJe_Ckw_#dzAb8e(L11<$BUt{jFUpm)q%r0NdQLnT; zd9$;KOU3tT3;V7K%NGZ`+jH85Tkbz>)G9DT#IZQoIB;^;51ac>Y_i*Ug2Gq6IoW5sLsDs7_@Zr%+)19e(Bd=X4%X5F4tt+dslN$9Dms6muqJC zLBm1lWT$~@?_>Qb{yGYBf0cL|6D4%rFCQ0W4GGO&yioYK?w^Y5d9O=WzpY%#{nNkZ zY>8#=*OK`cwSI3-`8kKn^6Z>I$DPJ=U#AOC>8}%(J@;0a`;wP9>(g!QQPZ`pbk`{# zFOi)xd+nRTUyFR3FQr|ZIX!sQvV~bvaNEUuAMaZ8c2mQO1_K^GGjri%2c|?W=k8Dm z-XS=p$|hpXH=j?7e_CmNcA53W$MX1O>DzPdH&)F&zs}RHbKjI2zLKrW`?I&)`D$}Y zQ|GnulF(ZmTht@v6<^F+SsCSGxAwg)ELpHlkK$W#gkj=Czo^lzM{JGSIX%LCCYumYgRf(eh%sG zkbVFE{f}EOn`3XWeXQg-J^xv312{nmb-ENyOL~>#t7m0=`OS+4kJ+)&K54T`w6mor zM{W-3&Qg16^svZHqnxWO-2H-1pWoGkvYaQo7A;tnx!lo-L&Wu<0gp(iE9)1r;`e@* zqE4TdKPVQQ^*51of zzAJQZx&G5f&ed|)=KWih*YUMJ`cmG`DXSU;ZcYd?b?4jg zZt>O~pQZxTc%IOicXt0Pl8BVklxka-G5>dHlgAqemAN@@;9f?@xN>J3fnESTlIAp`B&zb<^P+Z+aA<2 z-)LgjT_dlF=ZPTphJ(Cv;XWELodM;;gG1%l(^=(H+>7J=>et}aK|D7;V znP>6T`AZf*O?zG7Q+L?l(($Oi@-Iq@YHKgwx^pe6Y(Z#jUtRufvx|DZugwa4WT%7$ zg{Z|o`_Z>mJ7WL-nU2ov$=hb#no{Yc`}w)AwBqOBwl7xm+{FFPo%-35Ic4(s>qYAp zt8`6FIotkzQ_*m$k`E}p=mwIXW6}k3D z0zKW6r@d!=uuW^n8ugihpi&1Es6S;av^2J_*>*O~*t-1Pl0}a^q!l?22sABPq{PN2 zbtN&ejUl!@MN2m)Q!6GUM`>C1o@xi>RniHclQx?N3uiAlljIzDbulYP^Qz|Rh%cvB z^K{P84bTd}0%)5TgSSoq@)1_{& z_q~pZuxs4BCgH_w-v+O4}VYvt5SuS#}p`S2hz-p%icuP#O1RyO*mknuvA-R(#ucBo~-`zS?f)9 zTTuI{J~jcTg-Z;khm?vq`!8CltEJ+qD9QWrVUzia)%i(xo33w^GF&t@dgZ#6>U*Bd zpP{Rz{QGD0w#ZdEe={_t`&(w3tm1ziwqE^)#N(;z^PaW+ey>_op{;NGS!rWS zKUux~{)u`0_D_B;k3X1GKB4RQ1(~=%a@xFGzq%*|w#Ao7UXQb#9s7Fv$5Wd>-@5Yf z)$4tS-hGw-^k=euBR^>+h7QC%f_HTtD9?m;O?E z{e~a&-!IP8vJ%tV@ou$7fAnMf{^`S!_y2v~cN)*MS;nt#^M}LqYof@) z$ES23|N8yzXx^32-{b$z+p%!rD@~q-fiYnYxuvET6FLs-@MnveB=!4 z=Cqxt&^gs|`@MrL#clF;l~*6QHq9>Q?lo=e7w<2hOTQC-JIW+y_4IcRyZ=f+{=L3%;cKgh z1ro8>3wOrMIQuO4zPzumiV)|gPoHkhUA=0Rl34e}&FTJGSy>w*bS&ojmA$>?8W}0+ zw|ukjuLJ`RS69~BUtbQUy!!X|c4&*hk)(}lOeY>q`WbWVQOQIfHBS{Ghv?O-H}8#& zb>whVns^{-$lZ< z*S!u24V^f7a`V+&_d>KLdI)f}8qDRZ` z-Q2+`K;J^Rm4> zyMIWfot@+3{4}8~@9yyn-Ro1&9SyeF^LO!=uOc2UhgoLNdTCiABdnI6_OgvDZ^!ez zKV}t~|GX`{+lpTEZoauUPjdaC3J>s8q~S-t*mw$Q$f zcfKC3j{N+sdrjQdwo{&qE51LSUO0{C>9lE9d;Wd8%_C>ar8`wgsLSZ@uh%ndDtROX z*UWR;S9Ja8lxLPRzklEV;pD5*9tq0{*ZO5$Ls#C~-F!5E-{}wC>mS|mRQ~Z`YW;>E zORw+v-p+535un-qZ10I{PS$}B(;hwfsPg)I(5-Tf={~cVE=tY&cUn2;=C1yWf9IH7 zGdC)@wKT>mnQ6W4FYz+@6_5M!YbRfL=zFIAneVso5aIr|%oUtPZ?Et7`)^md=hut- z72ltlOPK74Kh4eE>ak&7d3@IWYmLh7#@j;c%lZ8dNk^$KJDa=pbnM57yI-?wSCz>A z{=sx0r|?19%p03?rLQ`ly0I&lJuF`Dor|#Sp4V1|6)*K7I3y16F7~}+am&To<-xYC zGeXKITwQYT{`BQPPPeb*JMu@L{cK_LbTiY>o~}JDdM8RVVhoQQUXgTLa`k2QYUMY% z9-n$U8`rt=Sv#AUm>A^Wv)Pt&(@AUUBKLl|nDV=&%WF37D0vz5?d|R3f4|?qpXazc z>#CNPw)W1?=d5R%=gXz0ruxjcoBMoz{k++EyE;F8Dl*Hz=i}+g+33*l_xJbDuU4=B zbT+?Exc_+ZvfUr1T)c4MKz{x2=#p6X(9kn!#=*<|Qg3a^OrOiZ`r#nE{Dy*uhrU=T z${c%HvPgqVJif*-c)6eL<*UDAR<2+F{N?ib&%WKx&);s5d-m1U)sSoSntbH>IDS_lA4(#l`N$Csn5l^t%20JpcchwcGD`{r~s(e989`>0O5{G`kL6 zSm?a-&8E`@7BX|=K0i3vd}globooCI*mK`iAK!EC+#W+y-Zu89}W}aCsCqFvmUJ4C&g>%80(|B>hh_a>0e*!|Uq)uDU-f4lAC z+bWj9{s2Mu@r#B}}UTavzh-j!+J)878_dX=qZQM5#EebCWKVWlfpO{ zT)w}hSZ%Iz+`jo0CN7?tjMdlHr)1kW=EfSYo406g%t8&Wt?l20`&w4s+1`AV*L}tM zY$d5R%9BK5Dz0vQ^XsPn%?;-sEPG$u|ENa(?6Rak`|hs!d40Fb;zXs7DsHEL%zVSg z)+gy3xjyd6G?B|0;VyTIKrK{f~+JbNqM9;fZ3K z(^{V$yHyf>HThOmPJKDI6(>vGo+qb1ids+ znoGUp1>S>`v!!na&wp_3qFs@!6nA;+&$E}T{@rh_obr6h{XNHmn=(!vNDSMU;q`6C zvSt6xS$61TNk1>1*y4DKVY;hhuxLxe4n2dF0=`^t(pV?267c3+R5@Zh!Rk*GWEVob88a_$*tq zVDW3Kl_6ZdmpfdPB6P%Zwk_6IwK^r-eYC?xNoBIuE|khH zzdSS6hC`8~{qT|?%{zJJ+vESZDG8Rnxp9!0-=^XG^W@9Rd~JR{nask(=+-CmbLB0` z{^Ohf&b`QyvSrWAfBnZFXUu9_mU-lHp^sXyS^vqDR~vfHSfn4@ugsYG;=;lwPoD+` z2OpOI|HECb=7VEVQIUp@jthsQfdtEer$r05+ipBZb)SAxBq96nwt9M{r&kFCS7OKPTw=1bW+7^_S(buYhLg3UK+F{ zNORWg*_)%*@@$OgIWAv6$3fx1_kG`OSFKuA@#o`lO&y&le?FhjzueB(u55i?TT83r z$HVrYAN%Wl`sHkI)y}u8wK|z1G3W6}QOhCIyEPhWL=`PFsC;`F39lTNDK-jjhbnaDReMlLWq{DiqJuWo;RPFFI^K~7{HNHecphlIl-V~ z)vDZMCk=YuSZvjMo>Qkcr?XJXw#tN^UoPd}pPw1Cj+tJMnOvJT!{_DOg&!+qW}j_a z_d0Xtc2~DkJ&x_G!xdRtTU&V~t)_@-hb@>;myseT@b-}IR-OgxoGsWkTD0giEwgyD zPFRCWWaok_%)4J5$a*wm@5DzLQ*NBuciksLTCs203!mAlmwqpg_BNii>fuTKB@5Q@ z9M)L4;k*Oe7u}-->!^ybaZtLHNEq*^~v@t3l}WqJ8R9C=Eacke%aMMpPqdFB5ghK(LCD~ z78`b7-lo;C>gEYeJw^Qoipy7YeA+1X>5p8H@4_!%Tm5uoToX2AJ&fM7If3=@GT-G2 z`UlqO-rn@~%sXQoz-?M=+xRrh2ow*Kq6G?B^V z_=AAOKZ0h5%x7mC!ZwZ|oVl7jBHx-tc~Ycg}z_)1as zRJ1_xWbEUbBSB5i%}p*#T>W?}Bv0yPs>3`}Vb?X$oAdA6ndRL%;a~Sj_}90$+z0NK z-#;szzsFHWOnIWmhc}zg7d<$@XyTiEZjPnTT&te;!ur{CS(ee(SI=d0uYYPq_)u8H2>_v=^Hx;>w~ z6#hCHOZ9%bcAx&H6#c%7&%9U)q-|Ns5n`LIoT)1lJxqj}qn3m!Op`uthaBtsy2TTbHb zZMi)%mYjP8cJuqQ*X?X$WM-T#mhiDz;*5Bh08!KD4fBx%ryZacK*#d%t4|nVDJMr)D z?~S+L#uOeE?UAwEkg$Dfr3v=Kd$HY+q4G<8n0ZxUi^CPb|J3Tif=d5EsLITgsq->T7UnXjmgL7 zy?kh;w`$cQ4X)qc-abBSe*Z|?=8dJV!)om0zuzh5um1KX@#G}c89r+FYCij}+xIK$ z+nbw*_f&qq)f-v9?CTs|UES2Ludb?0_MA0)cIBIm$9H_a7QK0z0=Kx{lWn*2lK=kt zdgbcXtv}B)=iJ#bQ9Q1KG4}e)+uQX&R_vL}>SZIhe9@vN&&g^Rm;1~A`ts5_F)^`B z`q;O(w`bbd+xhzXK6(DU_%N^egYUm9KOPmoxvNyW-}akCczmrXx0p`C>uYOme!Wu)@g_exF|ox-abCqE&UL%r?K11@7w%-y-~Y#G ze$6M&tk+GhtLzwxJ^R6#*pZ6tP^w!^y{$FsGl(gZ_VoB>%qq_D^4~n5lOBA08Wbh-V#C_wAML-gKC#d* z-gT$!;?{pBEi{)mD`e~LpZewf;REkZrY7uNI`1+s}hkXYfqP2Fmf%> zs>$?SwfoS&HWkL*ZY#~#_Gb1;n;a`y_O0xab%L6RdBu;#Y`>mG{J3{TMka0QbGuBX zh1_Q0M{fqsez0s}oSy81qP5kzchhJi9%6g}pE!~=uet3ecO~r&cb7Z=@x;7nWYGl}cuS%Ph zl~u;7q+`w;nSFoX<{Q`kDw$XP&a&yC!oq-#?f2`XXP-Ux_xpYS%uLPV=jT+#x(lD2 zm{?Znv`}Exs!g@O%h-4%4lwiEIG9L1Z55AWcyMB(@|&BRm;2tGbW&xuS+0^?zj5v@ z6EU`d&Sz^PH&;ApWWTYqSe;v3Z^_D)o$2!`+q(63 zb+F4-FocG#T)EQna%`PbN$m1<>-uipin_Ko+P$>YbZ^zyr1SG^WAoB;b9EmU%y5^h zY>C@j_3?r;|HZ}b{Bo5~1d}#OwDZX({r~sZ;g{U@^z(A7RONZWuvI)GFYnpy`~S=yAMbzOsXlMPzJ2?= zb8|8>6r_5KzTYh`KCQcbL&im=vbVR6o||h8Dp87`oq2dIIzRPzpX{2LokEH%AP+5C zq-4FGPtN9sANM8MK#|sG&(i*VU0;83b-4b!{r~Hl4l3mD{VJAxyzl9p;&X|YmUy0- zXFEH?_e`@~sq;3UdG`N# zsvo%_fl;o%ebXkRrh^LoHlH|Ze}8*weg7w4+3vaZe;&)vIGgtD%uMI?aeFuEe@#0( z%XNL+-W?y0Nx!_gSsfIl@9*v9P`t4vQ`oxf&4Xjo`A;T!>m~mC^YhBJYnzgf_vxRJ zu@z2ce_8FPHT6_h8Na+;&-K@5qheHqIN$Af%-6;%ea!g0jkAWxqKz9j-nC8s{_bwh z-Ca|^->dGwoxguBs8jvwRo0`A7T(iz5`TVrdZ+YyEU06?=kK@MId^w`T~h;U?wxWG z&dSJGux?$StaaIj)YD>B-`>pZyRkW$y|lE{;aB9|%Fk&^6Fs)cgoalB|NA|1XOZgm zyJfcndy96z?Q(kXvBDz#PX&Wbo_}}24YknO=Xde7t=q6aaKp0&j=^tMEC`?6;IDN_ zGggiDXvWn4EB18WeVTY<$3&gzPjc^{{+wY|YVxm%=}noo6NgWj^}PoE(BKWb?^Nt% zVs%`9(D`KKrb*`WDyK>B|5>WH^C4SY)vHRm-&g-DuzZoU?X=lHyTayPn8Eg)JD&WS zUGef|dgZU}`#s-Zlq=VBX8(44hE*?XzV4%m|8F*3eW|j10mI~Fi5smyJo^=BD(Wh_ zgSqaFo;63~(P?(iKRq}4yi2;`=ga3af3N<3Fk_{2;(^l#!+y3V2ACDF?ESU=+1H)* zN7KraidYMF&QY1{*^?Zn@!eO z-xB!!xBbSdqgHLbvggm`p8xzNzrOe8Lxq5lhO>DGQ#L&~&zZ1&+1V-?KYqJd*H96Y z$*l1T?7K~K)ABz@L~j02I&Ht4YvGTbe~z90XpwtkZk~-?K?PGx&E2ni{(Rxiy*=yk z+n9S+^Ax`A)Vt)L)L-OYYA0H1ooE?*QRnbS)4N<|yScaDb})^UFsb25yz1T0=)7^J zsf?bv=5*$*m*1VpSa+VO^QDejCWp_erd3O1tM=|RJ<-)1m~~iFPx?t(%*wfUOF7$~ zL`Uwk@5(zc?UlvB9eu9B5}`SZ{SP$VU-$9aHi2Hl=Ch%NPMpomy`HO_tlAoULkrcu zmps_G>WtizW%YT#%I){H&-I;J7$(~z)FW+j{IcJkb8Ug=XGpGEb?f0zMrO7I3AWeQ z*DqhSs_WOUUu%AbWoBxM#}qI&Iy9Wt+ubAo|Htu=&``sI2M#5%`P1Dw4sJ+1Jj15a zNUXc{wBBwVZI%_QZY9bZNm;JH-hKI{in+OY_M^EcHy1v3b6P0C&M&uQ)he#T2B16! zGIi4`+-Jg_8p(kSxeg2*{t99D{K1g z$e^G@-`?KtbWw__dbyN^i81Z$tfTGnbtfh&yWjd+o~_$({Bh#9H#fh$y*)km_BKJT z))}_dW|o$gcdA~mbx{J9oFKo&@2^`EvvX2{fk$epYCFHYngGX~4Re3~{5hxk-Oe}n z_Re0mOl{Ihm9JmFhJ=P*++Ci(%x7lPvdl9x44Y4#@@f&7GIi?4l#@dLex9#4N;<-! z<};%p%(6&ZR~J;)9en-ODDRHNw|95Dk0u>_Tb8{2cH!>3Ik&g1y=?gUUA38U7vJ*B zCmWgBZ){9vZ*6UT`z-QV_3QI9--8??*KeGCO{b`+Xoh96+WWoV<94s@TD7VO)RHfG z5fHXE>geqJeJA%+ey(`2uzf>>4#=^J&TSi#j&kuFPB=YH*F|aKn!}6s?6FxBrd_w+ z{{Ls^YhOa zi~CosT%yxR3uCh>PNyYgUYu7IIpKlkTBPOO3 zabSjF@{T{BPH(*Zc1`4Fwd(#WrE?>6jy*d&`{niZ_anc@SO`C6$voYgKEHMvE4SE& z>hF2W{O8ZR>X%-pFWI-ZZ{6!PvAe|%8{F7cs=e%So~cOFzNb$e+zV@6clWn3n(<~i zGx;{kXy=N3?0xtpZkMB_z;)|AN~=zu>dDSay>w=^!EKovvSxYzuaz4_zFiVzrCRG= z@VuG1V5e-4<$IyFr95k-M83)$`uqQ1yvk%Yz4hY74=vvP-d;3oR?`e!Gp3z8-~6gD z)ec|R_@+*YIp0xUZ)UAv{O^zc8Cg6@M$sRiK1(p2J;UI!5nJC}AEkwlzf>GB*swdM zV~*w>;{qAI?aZ!?tM`BNH7UNeZ}0sV#XHkKRK(<|?>v0$-7a%SL&LILJZiBu zlFaV@Dnh-9#k)ST|4~_-`0oG7f;)Els}>bp=dW||TCwAf-n-W~=X+Rmoqc*}iRKI) zGaa?5>udkB->GjUy30DsUU6}lP-~ZY-de*zAFFWzpddBT{J2)qHB$roz z{JlqF*SQy;(+z#hH_Qw<)js#|^?z&gcW;|_@k79Izw2d%Q;pSkX)w-LIJvTS^&Qj1 z$dZU@9_Oc?G?uiBdr)%Apt8E)-%ncx1D?5(;deVOoVjuK)yH`0Ez;7ap#6mJq*-`Q9h_eM=LRPnWJbm)O4 z%jO!Yl{Vg8vS=mOE=7ywSN!vgUR4{XKb?;MC$#?h?AVyHt#j}HIA+dpi{pr- zy1`5jnVJ7YoED^S^W~gasx)mT_sp7^g=H2pEX|HB0v2=6z4>o`w?vqgl{Gf&IcW6z zQK!0uZIwyfdUI_b4#is>+B0{(-gR#0^LgFJA18i&b=BtIkH?^4tm^lBw|n1<-_0Dh z+Vu9kb1ln#>gBD|?nY|Q4CII{K5P2q`E%wE%kBSmvT}jTe!S?bvC34h zt(Di(=V{L@bv?fRQFLU&GGFVdZyL|+mo;@YU$EUj(M`UWyDB^8LiE$7xS967+|}7< zUM#+JdwSe~(zcsf>Jz57pML8S8hR%#zWZ75f|)uM(JhYMcW0KZ_OCM9xwnpKr{xSE zDJPCQ_ZXJiRtE+|G~8SwsG*beVum4)tnI9Yi`|WLzDTg~yy`x=+kqr%#+cfAneD9SpTfhZGQXZ$A>E063{Y~Y;_ep|N*7Pm@dp|OIx9IW>R&(x!0<0@qFeV7&0f1U$6b|iiG=5_q|L@u9_AsXAu0v?DHLU-z#mUAqnNN zP0D9A-9&F|l*~L)I_t&T?FQ)!W`Am6-MlQ}(v&xRTps;hj=x+}{@ZYyZ9eouNP0R? zfnY&Kgiq%2*fxu*_k5~6+ro~|ym|lV9LKypW>cq3tgM(*A%1Y?vORm|?Ac=zSMiYb z)$7-pd3n#~mfw45zW*mLFE4M-y*)D(o!biDZoPhKv3viUrr1D{Z}0A&-c|aVqy6xj zn4Lz_RY{kZ`ObZIb4Q`_?d|#M5!mw)&P-DEmawn00gcS7P41MnE}QdG&t7rWs!5Y4 zJI}YPonf5Lw|?I*t=`^V9tnd5r{CV$mMg8Qsw&ZT@YdGsP8TImrRK3;=3~`f8H<93 zTeqV4WGoKMv#q{mao{A#EdovbcE36bEY4KF-+SEnyiN1bq=otS_wBfwr?>A%(y<=N z$)Yo?7k~YccY7P}^wUSbUXMTj;^N}V)8g9>URdaSb9cG^>aewsK0G|E6SYMn``q@d zt6GO18f?E)#BH2@F6D3=ugl_##*&~A_O;Yo8Pav^ zn46Tev}Mf?19pD7Bac5OY>m2T(z`YqG~RV%M`7}{otHNLO^{%7b#+yl>r z`s%9etX4AZ|7SvghKr7v_xj?yd0pMzCr_VVT>k#vnOUaV(oTAxjr7I3 zQ+;#$|K6)wVu#hn^Gn+T{e5fByNa)$7yl?kdf>w?}gI)vmK?$-lq7ZE;fEmV0~JUtZ3M_pGvwX1=(;-@a`3 z+?B!0%O0G0ZPBe<=+ks<+Wf9X0{%;GR2j5*RsBy+Kec?9TdPZq|8cc9FQgW&RnRly z+Z-j7V!b|g7hm3kwSA&fzAf3XCVcm=lzUrJzdcQpR-M8(XZqItVYgp*nQ{AEeApGd z#d^xeWpj5!+_$&Byt3)`z+_hY5c{9;p0<~6e}#Z_OrXuA9N zt6g(UoBos@ITFnSbnDR;=$J4*H|Y7 zcidf5bl$Q~?%#^W{}Ca*ch?AYN!O%qn0=7d%!GBF^t;4+%hK-M5L1~Y6k~X!*ORqB z^l)C@#d{iDb-UY^Y4M!iYOt|=?*00Repm0=rS~n*^)GWcurM*J(*F78l`$8tpS=89 zy0X4sax&LXJz=f~8&*`T$=W`Tjjz3V_xpXbL4y+i|D6ASrfU73hi%dt+S-r*d_MpD z=kxi`KRi7A<8R%mQ>Ow#MHN|g?%cU1Vq+64x7dX{ch2yd-*L#x%UfRgrp)^9udk2a zZohwS7T^8i^R_Eibsc_q;B6W6%fIb>?aZ!$AAbLh2cS~CQOYE-&j9v$-Eq~ zuf|e4eBF~p-FmNztkwPIbXqwLNitlw_ghqqp7`~cVo)vjbpOBC z`#ang_sCjryL{t>rPHq-<)^Xs_xt7`4i{Q_fJez6zgtnWM(gVIyL;$r_=h6 zKUSPLfByWj zsHn(FX7joko9Am9CI8!f|1V$nQ75CBOO`BYu`GV};A6#(4~MvCT9@l}i|ZeI{yFt* zO#k1rx8Iiisj~--(SwF!FD`Ng1yja_1!Yn{?%avd+wp+u*}1vh#~&+7^-h{T{rH=k zn?L^k8&iDN^vryF``1%Gd^)Y~9vZqQPQRI*|JlQK`DfDif3PiGy7cCIe!CwEdegP7 z@9&Ar50k$3EAGnn{QHm*lI5$k?B=g`&VM(vcxF}VJNK?d2AP*suC0l@{A}Jt!5elz z9x&gl|6d!lGUQs0wY~lP+TY(kUY`F?M3IG+mDOjK31|1wqHDI|yAE6MN*FYJe0==# zzu)hTQ%(qg-23m>_5GWwzk|w~mseMZhn;@^{db^9>rCVHP5JljK;gacOl--9y-#kR zY&_?p(7MGkJ-1f*>)D552e+yE-~L*8T6}85b&uqu0i0pQx23PVxA*w8`l#l=w@SuLKW^^!tgBeq$-aD+^mX z%JPoY6IYwunPTJ_8!OA#&fI?3QA6a=+{q}w!?tqe%7b~^Cr_V#eWhpNve>xw;=bO|Ua4D@XI3U} zjp{s_v?gxvtWTecY~=V)PuEWeW#{Mh|J2KN&;9kQ3gnXtn6`SPl8ZXRrS{PtfbGGx?8tX-`t-gN!8acUc^UXO|OB|zw!$Lz_1)Ao}krC>2 z3EN@&%{+hmZ9%7i=xA>fsk8U&=lG@X-kB=FcJ=Djkg%|%?YA|xwX==izkdCC^Sr~+ z=Wk}1csV;WH##uPK6~w1^|2U}VD-&6d(NgEHt2b?S3{_?X5q6JFXgNiSN>S60mm+eKeL;%Gd)5_Y)zQ8yQocm1)rZ};?}-etD{yM+^K{%F@N+f`!h__L?LYToKtQ6GC}+1qcwzwz2V zMQ_fZDNi-#%Dvd7pzpzb>aU-I#{VBm#&XiJU5j;CQ%}B3d3|l|o{z_*K?AhMO}9_= z@M{auI5Pj=7xh=KUl;C-2?+};x?6hPF!$CL<5<&9mI=inNj zxgv{Bv-eMb!^3eZ_e|jKS4BUrTyy3+HAl9o$a~Vcx6($>?OHd~-1!o#M@#rfwLF@?C|+ffzvuqCzdPRV`@QS$?23~& zTTefI1S-LHm%mpN>I?`Ab1N+TICI@Jmr1VY0z02PG30&!)bes^hIs3;(wzHxsdl|z zr_`MMafK=7x{=m=v)9ixBj!15GAo%Iv18|D`4+A}$+2DA&0c337Zlboe@}>6P?Y0R z+WF-3!4#v?*L%w+Z5CgC`Q(R(htHRos!Z~mSRA5cY3UR8dFK7*1?3WH&I@L45qp2} z_{o}C*RK_6{#SimEHY(kvR%2FbOnLNHQ)1QRj=W=!3X(V9wAO!May|T$ z>f_+*CcBNNUtiC74_tJ+Llv}S}<}SY2lf&fPzi^jw#O%c_!k&vJ z8kp_goV#Rha!6sO{;jvkBIRO_R`lLkxJoy1U7snVy%?*j?W^+<633padEj~yH?6=32E`LxRGfS zZ##9tvTf4}bV_?RC~%mW23)_&CgO;9H ze}AJs%O|zf%{A0N@{EIP;EEf&=ax!uneZIoSTUL1 zdWrK2_~z&(hL(Jags+)yDv~leb^P6dH!5o;T`*j9l|xx}N|fPEgPAomeAFIolm?c>lkC=sCte4_sO;?iulXFAUxp`~lH-FkNvs3u|!8}7}?X@cfG8yefv!C^f&r3Miwf07inb1d# zr^?HVe*1<$G**xNb#K3hgiWWS?dJ=;%mD#c!&0|!{ER%fYboE%V=|$qv{uYIbnolw z$f$+J2_F>J%xb#0s>f%SS*eKo)jG+w<(LW(9&GF#lg?>B;RH)ixHh`sy$6``xE2BHv$sxk-2@>0n7|Ep#^ArNN%T45IT%-`>cxGqv#~*D85^Qnn z*=KKR?J7`iyE@HGIIr|;h>>6ISwXu$37W>sdKVu^b`BKzmLGloRR8t|(otQC4lg{j zR;|v+;7cz5t)X+`@S2%3Z615Isa^W5{W^QKp@{IqXKYED8ai6XUY7-I{GEI+a#Le> z{+`({7ym9yc zR%KV`SX(QphtJ&3l!>dk9(?!lVc879Hpkjn*|gp4BFu>|Os<3!>{uzeT)+L$9~qHL zheAxQxVWZ@YWj$Xh&?P_I(MGx&&tKcw?SKhU|*Ftp*=NzC4|`{(krVCF~2EvpdvG*6n|t_e-qm=H7?O^J+i( zdR2c(k14!0$L>nR@pHQl&v=v9)&0qErPuP(Yr&jSucR;fXz_mis*%R-dbIV1kCX=2 z#RO&1saIdDYF+e5DW>eT;WSn*C;y<>P{(AAw%gO49pf52f@S4(s@f;4w%=Plb;A4a z3l;^(7Iyr4X5i=Fu;`GDk5CR)pD#?kD4z=7)JeacaVd?I`s5L@DhgvQw#k9|s zx)EA0arM!X9XkZNJw2~)ZT`loDBz}L{a!}*^SLD*X2k~|eF~g0-+9H#d8b}Q<>={q zDwjUm;CcK_p1@oc%c+MpISBY4aeT3SLkMJ(pHSzL>JOSMPlfdl-(GI9FFwA}!SLor zvDhj9@>DwBRyW?5w>;o?J@3_<(wlE~osaQeylTRwQk|^o)rH+Rz3wi2yXNMm={G00 zB^+^H6SJ|aWl`ca5dlxTm>*X9`!=#nzU!tl>CvCl>z{YcoP2X{nWpw?p1j>@V(V>w zx?hv!NH%xWkXW+fzstVD`)5@a2bMS=e7PmLe3yjfOQn7BcjPypJ@|XQ{lR&!thDae zF!o2Lo~t}u@ptAg8|9^kJgyHKlrdgfN(e{t{_uqB2ka4^o6Z7}#ZOi&U0Tn5@6lTE`A_G(e!n;*&|qe+$n~T^-|dTe2 zJ1o&`rlyoppHVQgXq6oQol@a4r9Gd{9ByGcytAZde#U1H7=AU?R`pv`U^7yoSv*lBs-(?NIbKuR%=Kgc6zKN%fh8O)iVI)`m zL-1QDd!fafj4Fxv|An#lnQGshUw1kFWqQF2UX{t7W$$iuuJVY^>2$dyr^vE0glqE2 zl2hCEgZJbWSy{F&yT0h@{qz4Ed_wm6zj`oP!)?xuK8WO=wu?aaKmJLT_tS-!7*n(x2wSl?lB<3;~H+`h*j`&|Fue%h3;i|#i# z&M{6@YUbv)ct7d%%?*X}%zArgT8j51eqU7{@#SLPy1kFHd)~Z#{xx0x>C@@=QXZJ+ zE6L;>n|Qgq`rV#Bxu11n=j{(&`kKvgZpML$b7xx!gzVV0XU4B6$7JWGnb+?hu>AX} z;`Pqs6>qneOPKCZ_ZHuG@}67oZ@E8makDDE=+{5)WtA`ZJyqDS(rd@ld8c2@&3?Gk z{ok=$SLa{&|H#qA)Z}9Jr=>CWziVv7=DOE^*4nn|qRoe^H?O?=(_#PjX{FWIGdd+s zXM2xKwpzi}HgVC)jT5Wd4^51fz7Z{(@@PtoNlDO017-F2(`P!FE1t{ue>&K{+M@o^ z2ftT)rbwsrEm^p4;=N$~$H#64NA50=Tdx~=a_MFD4X^H+TMBF|_+MV%Vp%d_|Npm| z|Gqv9$eU5i_ku~JHDyKTQ}1a3HgA~n_SP89zP?T+@rb1DQ=4-)e;6u0cX+oa(QMI8HJ|bQmo|^I<%ARyAD^yBBVq z+07reF7elle=W14&$im=bH~;6G%IRRd8=l84HT{q^>?)!&XJZJhJ+6(m_+n)5_!yY}hVlcSr;SIqAVYmz%0 zFn7hq!wZfdd}H@3EUF}?FUiRG-yP09KVR*?vF|;{*6i>FqF+)y{H3SfcGgJKw*CF( z;o*L>54YC&UXihAG5G57zA{wAXy2OFtmGRL3ctLLbULx{vhjun7p>0A_q%t?{oxZf zt2uOS?d_Wz(m1{E#h;7iubW+5^7|IU~QhWY|DR+Dv=*6FY;FL%OBnHaq&lM zpBRhBHP`;V)m@d9Wl^!C>EYvaqc!#VAw0|a<&Rn4{=Rs%_@(70^LewUZ8Dtts36yP zZx6Igdpu|o8_lq6(Rz<&G-rJT{@oM7! z2isPeTr-~#Z`b!yxGf=M{c+J3XQy~3Yv_D2OnVZrO~=|rN$A;ug~~kg^@X#o{udu- zRN7c~BPQ>dpGEtnMM^=7+dscMe%iy$NychRf5n+2UEjqPNfZWEHQk!U73AcYEm<-7 zs$b=MIi0BAcK+^K8t=^~bUL#?EStIEcJ2#5amM}avC-cqZV)dx_~oR?v+5Z(Z<(gm zZJ1Wu{+i*1Z3aiO8{e^&jj0zjxN^IHq@0#Jb<_1yyUiUO;qCLTU!yd+*fXrUFKtlgoG zkJAm367oX=kNte?zIoR_Td$au-TSqrST!Jej&|tV z`)3!nsVr8Tcl?Lu^T6r?_WeI^)^9F69Om|2;-XT)_9x+?C4W~u*0f!!EW+yQJAK`j zE7uKJTKBRA#U&))e6;k+`*kUIrcQrou=MQJ(%AE}Bwp|8Q;?nKadOJdOYK!&?iYPz zZtk0QeBKQA{~LuRFRY%is(H7-+V2~*7P(6Jg@>4bnI_bu*Ipvzl(6;p48zw(RnK-9 zM#@f^HuYhtwMB$#fUnsZuUkK7EX&MfwBFmgJ$7zj^tp+4`5cV_QG438xfVS9_$2V! zVv(Z;8ai5wmz-C)a!qm7nXis#dpFH(9OpcC76ftQClB-GJy zf%?~Jl^2ZER=Mq1*tA@(wkPG?#mey8C2wMkH_hk0n=$k8qNAEI*H57i(a(+Y)MSmR6Wz?ql{hL zFMmF@J3aEY0zF#}%<6Gpk==E<{z-)!?|b(hI~!e2d2oBIwGl~Yd15xNKeV+=R{rz! z`+w(^--|vuv5SlE&28sonLn16Gfa)ugk^5*?cViFqW1Uqg=x>(#Ogl?{{Eixc&)kk zBOMLp>u--3L{;XAF?C-)e$~`-Uz2S$RxxShwyr2ET){f)UnxAj=wQc?a5^WBftzO(*ZoaU<_^0hJqXm1WbTPAY z9A&w9OxfmtfBlBC--(YNR>jTzE_Bt<+Bp4uT1KT|=+&!BcvyEIb$U9{S!T_eqw$y6 z+vCpOey{iJK;vJXy6?HGg85E)7jL>QuAAJHmG))F#*n<&!?SXy_Q)DD6?dE5kJ;XS z{eIo_y}!S2d~tXCN}q_A*SD+hwa$uvZx=De;_KN$wjP=DFCPBwNO=~!dS@HM*;fm~ z&l}mr?VQkWck|e@QtgO6HKv>sx0b&6w0`Ye0iEqa@G*ecWw&qnW}OQPS>6BQU-*T? z?iUt^MtnZrFzLSDS_RqioAM_dgH3+*NgVG`)IK3;H}g@$WUcn*ISmJ9M<2T4sT}d; z*yc6S8OI-+OrHMSnKehxJg&cQ)v5}nx;J{#6Rw%<`fImlmD~Tq;2GO$`SbS}OlM6q zUKujUZt*gs+CvFCe8P*5^*)%|n*Y(SZ}$2ai{8_EHb-{r+aFaDeKXH#-TtRWfAqB9 zHORN0zUEbSQNkq4 zJ(e=7Y9^`w|5oh~aG~0ySZ`KOzgye4{~hUaKW)pFvv(`A&p+L`Z01LHrW?9yEd~bG zHD~ty%6evJ+JFD=`}H3x_D=csw)t|R>Qt{~TleZ}ba8dHnF}09yLKT&x^BOGsAsnQ z=7;m6=G6RT-nS*ZNYUW-n;(1gLe8GqDSPGTCdubXqNZzKO%prxw~l{RTH-6$VvB+w zo^pR5=U)(M{UgV`bA`$LiURrUY3q-K8MIui{XJQJ!^d-{{WpH#6`pnU>>Ta$$KP%- zuKA%6X8+yj{GKm-{CA2h&10%1_4j6_@d#q zs57P3DrZXC`Q-X7_J+lrWwY1%T2^T}8i*WzT9$Z8V*D3i~&neyTLJ zConv`KftHr4qp)4jyZloA2~Q>=kQri3=lYDHph(n&;`!crU==*HEF^tU3y-v(rS9B z5w?7GQ&ZELhUs3%GPAgnHd+_%mXTP=Qd(MiWU^IWzx@2;Hy>TK?6?$Ux5V^r^!0t` z5BvX}yuR*ryN^^%QzD1ZMh?zcg&@vZ&Vq0B^cm+{OFy<0J}pp^F-)#q}N}6ib*2 zUyj&nl=q5AU^((2|uZixRV5zY<)%S~-*_fSy!_@mc}A#Xaj@{JqkevV48h&ocEj><^)*ew}$qJ6Q} z%g@f4`exq~v&w!=aS@TPD>T;_M7^J37ghGUD&8!z3*47Q}|^^)}l53 z_FYWx-q=0sJM}6<#;8^)Ds!&&>f+a*Zq?+kOg`qe)7Sdw#eeTttndHk9)3*avcJ|= zcC$~r_n&e7`t4zM{O-9)`!~nl@{uo3^|_RrVgJ5wZuW8M{pTz#J;NS*q%J+Zu{p8Ae z|IfYpz*u>esa^{;7R>aSF}H5t*ZKP&O)`ACd`Y~@q|KtxW@gUuE1`|wr$pqGCD^Xg zSfE|@XxcxHy_0LY*m)Y0I2XMB=J)zi)C`Mn#jEp6G>W%)6|V`n^zHY?Lxs25WTVTh zAAXzhiRbWwnkypvuIKaYbKooMzLoaBD81CGYF}-vOWNhvg};_>{lcU5eX6-tlKZ^7ar=Tf>~eqP__p(JKYj1yomanRC|J!i zo_qbfi{H0f-wMO)7asRj-8JdOXT_;K!GX6Ad{`O(@YBn~H#R-1omcr!a!zB=zWU!G zQdwFbzRDc5HIH*#lcoQ^;+baM=imE{YmVdx@UMHazy9NXmruV6|9_WTbVKOb%ANfm zcE|sf@pqIA_mjN!Eb3!|vFTOQB|&VR8OFQ^1vcmHpDeg^=Zh+?{6zmZYR%u{EBo&M z`zp^fvr2V)e6?Wv_H~ag?zcU6zVO3p`8x;aZoltbyklaezx}bk1(Scj-!n6&zqwJ> za`}E{Rp|W0@+o?&o1@k*^k4dN>&=v9nkmbSg8B@;_+9372sM4fXB_wYa|E8z+;O-|bM*rggDvUDf`7O39mUXy|A$`7Js7w&Ai;#WJBU&=2_XV7U1x4*wr5t`4#HRZeffivD|396oJip=Y4Q>D+I_09QnJ&Es# z&z~pN_K#+HW-i>}zHj#LcaHz+NT+@%)~ zDykfH`Zu?D!b{6(@_#-=^XPp%bR=-c+_JE@eG6~Z#F%7@9cj*q{%FR||D^Ea()fi( z(rxd`s>5nA8H2fN9#nhztczZlDfQ1m&#wNu*SQ&I=VhH+QKNNa=H7ivoc*F4&ZXu2 z{9i0+cDs6k{k>GXQc=I%8`dmH5a2PrbRp#Yz25m7)?0al3 zZFhAdcCtmEOxRP_t6aL(!mF+D+yc9sm%M)SBu4D7`F7n*$e%0cnxE8S4HxILHh*T% z|GewGr`L5!%dbM~EXoy1*|R0h>a6y1?R~jL&3f~?{j{IzMqzILA(0yzjjgGtLq|>`(9wxEvsYYzsG^~Jw+?O+*tYVD~Cad@!{h>$;Vn8XXx^=@yee)@JT4hxBUWd z^To^h508J3Js({Ega2NP;Nm4F{2|<`jW$YCxg)n`itk;<%l_a&A8*c`z@rP+|4_Vl zU=siJ;0x6p%+=3>#CazMT%D%ziD~uaqhHJG=ALBs+I01za_N(AXVNeJ@_l1@<-mn` zbr+I#D!#H_d)HZT+;)4=lhT?vW=&{1usl2`%JFCLy1BfE9j@Pg=N~4*8X9`}V}k?8-E zo6qk*w8D|!;O!yFGu4%h^*^7U?+m}^HF0xi`n-xsQfJSb>+KO({L`TR$5DU7>2nX4 zZg$`N;PBOms=kAdw_Q|F5aZkYU~2fy=vS`(DjF{=8@SKUKf)@0_St;E@Vdmls~09?`Yx z;dyO`@4FZ!41U$*?Ra8)?`Mi?^tZUfI}(L|ey{&8xWE31r`*ra>kU?GzWEol@Bd zPpuEOoQ#?KHv8S5>r;9c-`W3U%27~T<(2fT4ZHX3c$1sFj%(rNI|1zmmLIBOG#pH> z_!L`o-4;KPXxc1bVs<4cG`2fWgXI&~iSu6&>egE=&#ymszMNaxi7%R-@~o za<})qb>Aj|rVNu?=cLw}&RUkZ{kHYj#zpFnuUxs7ym9x3-*6!X$PG_~VPyTQ=FU@A|bWwVFY|f8!^-3>48+$ExlF;aCI+|sx z7PhwakIjY|vpLl#9Z^cm>FaBK8my?Gy{fB;r&r~r+8i_YsF~bzZ(jGAbeX}Olq8cszCOPpA)fK_a{u2`nu@woyYHu5eRepe>~8qW@CQ#nY}>l$ z((+=h7BXcvamK&-uM2QW zTFhA&{7x|%G&jwE^Zt^edsjGb_;g+I^WNuj;-=n2k8^q^zKi4c*X7*a*8A+)v$Cd5 z#~&|@-(NSE>*R!M8sXvLK{xKqIXAs)bN2BvtyfMW;?EYW&|0-9?S7VO7 zY3=>ZzhsZQpqtU5d9l@oma!_$yfSN6EM8Gw&o*_s;414w*6YtceyV!=y)Xetf27T`u z&v89 zamUwAn*aY@;UUfwx_WN;c1_PTTmnP+a({RH|9&}sL#DrJn(u>Ut8e*ddM|d9oHAqmpP%)M?Y{({)3rSm|L>Fex?Sqi zZ6}uA{9OO*=y}e&Mds}GRrC7!4(9*+CeF6w@Sj)h_migIYPSFT@%oNWOBU!%^iWwY zY%1(+@|*pxlSkW^!{^?XO`bg2*|X5lwyW51Qs5W9GmC{MDqJ<)dL<-uL-KFGEa~PW zN{-*&-8%VhFMEetaAxB*kHFZ%j@;YZ3LkigY94mbNK-q%W76)tSTZ`G-wX!|cl4b7gpDt@%ep#ezk%mB4-jZ$G z`nsBb-`JSVf6${RAz9XXd;aqS6SYbf&o0@vq*}@9=VVX`X4$pKpyWjW7Z=y2rcDA( zO-)Q~e6qJvUmLpya$J*ra#Vf(LbIf&)2jC$y(-?bY~3=$zi<3oD*m<}|M_9Eb4=}T z^Pd-;zbCA^dFamaXMxvcl&@X!h`n-#H@^6OI(D~OUg7dHM_J4OPikY_M zT0|h{Rrx3V`~NU2CLZ!o^GsS+e%@)7mUi?fpGOCOuYbOnb^V7|)vGt&mT^%^Eb6LG zGCXwUnz@kotR3IJtuH+Iqmg5$c~p_noWR`8A;sP*lioERf1LQ`#YLT%9TQxX8jmJ{ zI2AVY)`>6DXpvfdBV2gN{}#)dugkJGy7O%+yJ&xNfwPU!lLPZ?IfT~4?(C4A<#^@7 zh1at?js$VA96sjzpN8ME3vgRMohf={aW`Wh}fyKCZmyC0@LyzCiaTtB|?|L?U7ZrhM{ zHR{!DCkdygmsaOLbGQG)Xng+V;ZLF297lsdvx*M|oH!2o`p&RjdguFv?>}$pP7mH# zt7_eUWm~0n?hDOXucTiZXKC60xmYe4l9BZKpIYAD_loa!{xs^#i9g)&HYi?}+5hU!dXczoPH?bw-_ck15ny;F16b;nz^ z7KZ7kn>?9agD*!PG?M$ytGxH@D&f}~lh1#bFqyQG**kK3mgak%*v{1@2EuJq=fBjO z2FgyV%hm23Om}TeiFtmsJ$ypP(hqDhA+lL1A8rImtvVbcm-Xb)Yqv|Q>*LDw3#?lX z$i6ta<6*OAiHe4<-hsR29JZ&uIKRI=+f@46le2eT?XM3H-K8sXud8M8$vDlZT5>vA z#Ax>%^og(~>n>lj-f-YmiDA(liK;Ie3*9{pOa0Vqe}7SV*YjZGK4$|lcMiw?f6Wp$ z3-mv(Qem~U#nJQvdGa;er}_SqgO)2?UF z(%gEbT5sO@x_6J0gv6l((i&+I>z(e!Y@QMS_k4YFzf8i}dr~&FtYXWjGd8zt67!Z`PJ?3_h%Iv zJU_O3=d(%05ns=X|2!N&f5FKU4>xt|sT_Z+qQ8Ga!}(^e8E^f#y1PE^yxVZBGC0kk zFu+oJdi{xCYuh*MiV+bLeN-0B7<~KlZ9Yb=$LhXn_mf#yPv@Mhp72^s+Y~gy`RSsq z$tm3(d&*++wB`2Coiwj*(L@%x{mo55@@IJ`K7J;;<*S13EsjN*il&nM!T0ARoMC+| zX=^fn`n7%TN~!Ei*QUSjY0!z9zEq<{s_(-s>-WY9UqXIWOr5s<(3X>vcgDF1pkOFRuk}EQczWM!=!mXt+d-k>ZW0q?*>dUq1xvP`nwCTwnY0@hZuBHU5`2jE5W5Sfx6j zOi|=ntSWQo;%v97{)N}hRqu6C`uSKs3nUYeP#c!?bM|C`LDnHY3rZ%@ssxZpbP0N<@c-AZHr{mcgU`qez3FejhaN4H#TPa7io2NR&p%%O*Dh~;Z}z*nC%Efl{Y)l3SjN8W!>h@wIn9pk z_`Cb^%B&oPDJF^Am&?1%JhfcN&}pMe-gIu?#sH0uq-_tkM0*}x+>+#JlV`3Zx_sL@ zxrH-6?2ui;Q(!rF$s<7_D+}2&rG*MJaxHSjS{4d(v2L`wGegv|xR_)0<<_e#2cHR; zO_yHnsVv1g(L^LhPuw}0moP+xIF_4m9}r%!K; z&`IBAmCn1cElMj}y5J|%lc#JKo}9MrbWyTQF5z~(ndx_N_mYNXZ5*psJ^B>EX?j$E zX^x!s!~mwl1|NROzOjvQ-QgOL7ya?ey@x$NOKv%rx(VO?HRqDw$|;6x&%N=^n{6(9 zYGLS3rM0WG%I9pmZXXr)`?2iPi5`B^r#xlzn%BKHF*h~ZB+Yf|q|2P==Ajpt>|D_; zt>JJ=;EMaB7U|bBVw0XtT4-BnF*kkJDxs6&Qm~Q2=RNC-L|KD0=T<27U7g7%BH?|+ z(E8G=1uj~RH`g&peRP=<;>CXS%E5j2wmrL<+j@T6?{@3YvvjS_-2PS_Znyp4y{ns4 zCS5)yUc$dQQ6j=8Z5FTe>)Dee?Y~8Y7;~=f{CkpPVS$7qi{JFuKH=e@np&vS<&v-E z5QlYj} zYLdsMlB)&}L$W6B-e}k&z}3dM>)xZ?-VJA$BpJ<&jGFpjahKW@mnp@c58p9ZCNh8b z-n7Vm^Uv$c?=Pr0!qfKUL~}^XbGg6D-e%^-pJP?8guZ!n-e338^}nZYe*CC)&o<}b zpYC&u%Z|6Nv6V;YTO0zmGewzDs=l?5)q7O8(&Ee2afxs1iCUvdf_G;MMI$MvQ!_r>fuYJzOl$ zuKC`2!arMX!)z|ztye>?UP&yh(A3pDl=MnRW7UF<>)KAOiaJv!uBv+UhK`y*)3IMt zt5&_fu%^t*%WFgQS(QbOQilpfY&~n8jV)P^?pRy!@ ziRhEWOCqygWqT$lnLkjM%X;!*qShp*#S2!j>_6T&(JLrEynmVIm1|d%zdi|=@_5;& z6M~00n-@(A+T$nva2eYpHi?rOUwIwxG}dqx|JB{6KIuhO+2b>wU$#TevG;sl!tMCe zrR00-PM4x<7g*PB`Z4ACF77r)OQUtgu6%LuhE(y1MS~d|EY6?ig3`dnM{_Selfk^5)F|9^PVtaQioXQwT8WNP03|1^1pnz3KF|G&5P zatTLQ*FT^7z5MZJO&8s~cR$bdTh>RM=d-dr`Dpt5rx%yUDK2ZuI{on5?|r8Y;zj>| zJ*~du)x!G6O>t>;|K#nL-#=SW5Mv+-I}4;l1qPD<39I486t?0@;U za3r2an zp5KnCKiV2o_qEqYBJ3D%dHrnbb+bAxg_lMKdK_soF*6OaDX`fdV>fqU^7*Ig_W#6M zw@LWh|76+5`26GZ{ePyuzi->Fdfxu5diAPX*DZsN7x$lY)t-G|{r~s;e~xc|zbNbR z?>Ck~z6Lo3?^LgUT>byI>HXdR-L~o1%s*_J*b_2o*DH6eG% z`+wTMn_=4decHMutG3O0KCf<5v(=S^B|%rNAKmff*Y6)s_3JzuZ?`;qb+G*Z-~B)C z-~ZE;7}v@#zW!~2)fM?$=Y;*wrQbTs|Ka-nf32t0zCBraeBRM0t2S=+X`c?=mwUFJ z|NmU6iN{qht4-!Izm$Ao=bty}`ybW+`@CDDH#%bPUjvT0zh*_T&8z0z|M%~Iqm4(- z9D7?@^IU!Z1NHkm=PJwp?cO`}tI8qAZy_!-YF}^vaW}vIhr1^mxVl2TsDEn z{L`CY|FV^^81p+Kd0pf6E$%LSC)?!A9KS>Sn}q}xnmzw?Z> zg*_79o{nkBRXjq9%q=*_X3*VMqPEV{gYev)0ye_cDL z4bKZ~YpbMWr4+A(y;-az@#w>~t!LKXadQr3e)vnkx3G0}{+{lu+Hy~JmYdf7J$^sv z(Pjbj=|PjWx0c8KV|VhuAkf*U=)f^YPJZ&TdESYIlFX;70^XMzE{NwWJf6U%!|8NU z!-?aV->fsN!tRf*JlLeYKINvecLvw-&41-HS0rA4c32|zMLuMzW0G*;E!L;W*YB?k z;hG(}&^O@H0vRDsjz|{Y!W*&b0U{bj zjw{*At~N%p9==s+`SQoB<2CjNrP&mt)+9vT$&EHOKDzgp!Ge0jU5N%6Pb%8JWa@9x z>}pbCsq)I#*`y1c!M3QOL-2H>shhFg4bUEeorr!EIjBs$9Ri+sP*>Cku27(jFV3~&;L|6<%xyi zsow?XO3YpBtXD1*@f3B@@=P{hJFv=p-s6jv@&y-Fi(gEAobvlu|LreJFMWzs)V0~3 zb@Af2r91`R~(8K|>t-B)N;aI({4?Xv5SW}Ig-XlY!e5wRvrXWEzfSF?`h zl~0&I-MMfc`|N2-Vx03g{L!mC93~Rt>wMYI_p+#~Yi=RSl2ux(s>D`3P?;3AW#+;3 znUdR_x);ag&Co$#?W}tYY7>1aaN_my70} zIOei_=btNbt5UANbB?gt`9(Iy#npF$opW2D%!Un_uhW8LV=hQ)EO5UcGp{;JF1qY} z`qf9%f1Y!;PkOvhODgO2%Zuqe#>;w?C9@V73OPG6CMHO%eY{CJH)*qUTch#>_xp~r zR~9Z6Z7ba3d+}M-B2RU3zugW0R2ln1!WLgH{QqRyw}lt7FNj>1G%I4679l*lT;9>& zt>WRyc2BXs=j&dpefo2_KGnZem^YNyg4uRw!Z9AEZ!A|!d2ddN&fjR*av?_76D^HMpi$Z{J*Y*i5KX!draL$xo%X1&?cn1cpl9^SZQFaIUPtT-6tGFRUc2-mGp8 z-!5U|sxURkLv`UYNxPK|w*qcGoW8C+rtRd*^gmOIBftF$K2u$+8X@yU!JzkY-|4c=h+?3FBI0?(*EP_|9`DrD|%sW=K7T(Tt{C$T~;lppucajeEo07x}Vc4zgU*DHJ_?B%01Q^@~&oTe$D6GH9KA! zJhkmI$kY;RpS-83L^dF##&YKl+n2doX{#sSVX`+X=QF=|b06Q%IB~Jv&3l;amx;ND zAN_mWAhT!o{eR!T|G4@8?9qp36|X;cVgC}c%6D#U>)#)@=9+*0xcl>t|F7epU;Oz# z=+J%#u(oFbKR>g-a@!P@aT8>KWzQ`H}%Z1+gotMM`Kp& zojo;go^2_deE6yIgYOF%R;4Mb>+`p()~;J{;LxL={huB<+b4wIIea~Tzu)&pfF2S}iprgXZ&q8LI z<*LUY#I_h84hxxj>TdRr%XhZ#c-xqNY5j@$SJ!BCO}hFcLu$|eSC?)6Ueh|}KlkX? z@R*L{iHuXGRjkh4D!F#u1kn{!OKgOA6a!4B@;|pfcZsXHdVdw``G5be&-gw6wCwfx zCeDeX9jzYOu3sW1q`u$pe(-@O&&!$Zc{1)RrhDwt+_g?PCod)CztEa##_ygj;O&f< z(jLxoQ}T4;QATFn&wi_VIZLam-yD(C`kPaF$M|CJQf7OOl`cKI+SGFR_~oYe1{4dc z?dINnv}K;x1&OYQpQ|oB7EJPF-hR7PiRJ9sUYBIH#u)oFU)4Jy{<+S=YO}ll{E6XP z8+mTF{2GOot1_k2oR3G_O?sUk+aQ&@WZ6E>vyT+??Zvx2T_rSMWoLIBDXpoqSDJe0 z;kO34<(iWYJTG0$c=n*0$Y$p4cb&8Zt*iV3Z(UK;H)rqmw4HVAv0L`WmQL4ME7xWp z$tiwV7}3${RP*84>o-|E3s=3|C!A*eJg!Rg`IP5@@xi{q8eOL!tExXxm&{d@xcc#T zZH89Mtl1tMqWh2g7AM>K9^}yL=ils^u*qUW=7l>qTJF9+oOG$9b(W_DS6;vL7ZKlH z-yO_yalg;{M_zpMWH()-X?5{L2;rqM0 zQ>IT(-WYMNq*Miz@hvm`Zfkw7xU^%D#+%#Q`(M4vGVwi}wmH#kHYZ!NL|fzSw`yYD zjz+B>M-6$ZuicTnl>H#Y$j{kXS@W>RadF;b0iJ>J_gfANFIb^4-J{lYe$G9&#QTXc zB?l{H!h{cb1O`Vs))q@FS9b_Zlv7)pTRVB9fbT<#G~a;u`yD4=En1-=znU#Ufk$@p z;lrZa@AHbutFfGnXzA+mO44kevxkW(PJFBTw~yaeufJ^nZs`qsvHsJAyN*XI{XDez z^@ZE3AG}&ymSBEIh%?Gzp0{IvNnGE_q{RykO#A!p+pkUYO((a?#8-V>e^VyH=%fg1 z|MmXk)mvnin>SpRK2i65>bHbLJEh!K^B(=?peW{h@Xg1|hacxhCtr2(s}r<59u}~9 zVzOnFegE@}zdqBpJ?~rV?4HSx!^gjr&;Q%*t4T=@ZWM|f5pv%hWO`BgI4fv|mE(!* z+5FOq4cm6D-5GWDYXU*})v+GFY7e=|mIlj$X&A4NqBjoWwhByAUtS8#CjyImHM zb8hk9{!59`kW$#gZ<@cV#{3?xsFz7qvHpohGdD!&MCxuhn&imQcsA{Dicz8jTiWiK z5k{RyZ{6eezf}|c_`$NLfBv1kZg~6ci;of$m$bgl+sz#OHZ81vQ}dLc2LATRPxtL! zmw5Wq_L<+W{mwAc{y5{{7w`3tK6RfzdPk{!^{QhB&uag;Y@DB1dZY9BA{QmibF+(e zub-JUEzU-ZtIOll!A08xJ{>E(>dIXCva>yM-&+nb--fH_b+_L=^X>cnH2+fBsrmaF zjxQ;=x+ZglQ`S_~bpE$@)l2m+aYfCo4HYz5n!4O=*`ebVLU*LYUOc^EfB9RtfAjtw zJ%vY8(|4`j)snT%FyY7w;~4H%&G36iSe%bunyWtF1vFMMNnb==S7ZK~x!+DMIX}aj zTX93w+M5-pW-XiOwUjl}H`wsYWA5oyMxM#ZGuGEU6+0&zcBH%i-%S4Swa=u@&!_!4 z=FefpUUXeIJkjLc!>8B946}}1Ju}a)>+Add2lrf$PyF!VAkXWGvrDH;_Byh1<*Fxt zR=-d9c4gw4m>)vx_kPq$KE~soX&4zI&~jKYqK?J=3&yipV6@blJPMXEtSRGDx3rYT>J{#d0gGy;rZ8@~k?*_ma+* z-N{=mH`IMg0_VMn9=m+L-%6OVa>~h;n>lWQB0Pr`ggkG%XU*~wSK7kBRPLv*)+6T@ z*w{U>`Pb)z4^_h2xp&`nJM~gY{Xz2eYkyVVu*MkF8nynEQ}f^5uvC6c;{AtJZQoB= zS1Brfc=rA2p_h|2PklG|`iJ{x!BaIYsip|)3+pXyEx+h-efT$9KXBt_d*-&jgn<1) zTQYdfR&(|r??0ZZzSP?>Qps{^#w@q{n`(Yddph@n*~H4DrME&)fy#@C9x9Xa&U;o` z{=8%2d0b>pVbju8T4lRsW3NZI+6u<5a<{7Ka*%i#G3TXGKi58|C3AB*@1Dq7!F$-F zy*smT(_)Rak0zyE)=ZGl{b`-iC2+%*^-kS;=9tQpC0ksS^=-bD-;3MC$E^C6$Nl9y zwlzX0BYPHYIl^xDPrgg5H3d?s33;}!kYvqGuP>-NquZ+Ju{20!^2wYNQ(TlBEo9Eg z2|xO=H6wS|-CNbA$6l9OeOozEEUZ;w`4$zF%%tPk(0=?D|+$ z65@LB?Z?K`U6-e~n_fL5T>slvZE~0OcKPS?t=V=TK6|22xgzyp^dI8~k6zup{M0V> zq`27L%?d|_I-hViF3U{c#RZP^nmmo$e#tUV`#M|~Tb$MW^-ag=dTCZxmcdM)8*j^$ zCUUF{;R4@XuwvDzb$>-$CcXT;_F^z+(?SWoe(5hP3p1FupKe{IrIM8RNV8z)oFj)6 zd5*SRp4g|pDB(gtyt{_b@mB>Cg+DCTY)Y|l(~vp-`18i%qp$xI-mBX8_igpYY)_N! z$#-PzXK{VZXxYK1w|s7fMb8DD9dE)WedH)Dh^9gs?vdoIT_skYbMJqpsEVa#f-edh)=w|&T{%f;l zWwkK0ANI45aSRlR(Yt=Jy-+=AZ`!5@-=i--yS|Ed|IultN4MRGjqscuZ5{Kj#8A>W zH{_4yPnM#oi`8y1`G{H9t(J8S+-P-IMC{qak3Tg(+%kw-y7|W3o15n9nNP4w-(;ns zQ?_7B%ZigR=To*#oSmK>%-;E=yKU8~3Y&T7<0iN$z1&u!rUFhzpIjgGaG&a8(u`FX z>YU`EcJi#Y!bA^&?xO`eZ8aJ$KUKP-WU{495{owQi5^_Q;HfqO6L)b#u(JS3hF?YAPzT zZq2cPGf%`t0w+9o51mr>lhNd?mtWuXf?(T+6r__D=s>p(s7@*HSlz@VgL@7l>aOGAB9J6%4px~WvYd};sriT8h}goGDt zvz9H?FnF>@`{z&o{f(9(2mjZ6R}7Qu`gC~yUwPx>Us)IYI-n+3@k0HI`Ei5UXO{$N zvh~e7Q~SNPH}R=zf=cAibJ~9%UR1ZdAaJm$@oe}3-|hDgmrjrMc-!cE=l=!&71>=$ zMzaOm`t!>>btY5VQo>#uDSZyD<$hJKvv zU$!|~K!$!&_9S*b;qG2!Sog{Ghj;tN^v_<;_I(f&*5BI~Il1k@$@h(Sd3KxWrr%V* zy^>Gz&+EOSbIjN2i#hloTe%`P<;a%PJClBSe9MVDoa}GkU%>j~ZM}`l*<<`af9luy z>eMUT|M9Q<#Puc{)9-?HpS$A&?_>)w%W`UENlTigifukG+qm#`MwtE2ot1OykIM4c zt)JO{uWq9GwD(VL-F}~*Q7N~VjrY-szOR4&-n{$-!pEv_V*cCJ|N8xT&!=x1 z`>NlIp158u)ag?6i-WCshL4&I-|?C2x?GfAK66nLY&!VhrlQ93Ak8;vt#YlEDNMSY zjJxJ7+O@3j*0HD-!2{0<=Dl3yr84O}Yp>(tW5yXGZ*&4G{`K-pnEa~QS9J5pmrTL* zo9w3xzQ~T6{r`?0 zoH6tN```8{=UNUje0dvr#yemC_$;GKyK78xymA1(|@|JV<#0a~|Yzdii^!(qc%Gq%TFO=4(Dpx+p zHlO&|C|z4sK=1Tlx6o{f$*0vw5V=<`k>>B_3NaF=)09B&U921r>xG zm(CSgeW=OZ7*S~PreaUpH~DpnLS_XPv2))!28y&MBB?b9C%+TUmwP?;1hIJfs*Gk(n zrZX~EiR5X%D9+hmV5WuuC3mYAFkbgv+ghF{r!)-@(PltLW|iNv0Zno z^ul7j4a<&cM*+O5t@P%ZiA}Crvu&sA>|2j6n#HzF60O?wl1EuPMgFY50nfy}1+V{PHhMb-MpvI| zjaboly#BX+&RYKm8)gYQB@|Zvd}YgTuqvFsm$@4&SAdk z#nnomU0bI-YLHfR2}{=g_y5~DYrig~>Ie7V&71T18PDlS#XALR=d`Ze_51troR*`;a~GK3=Rf@JTuJEOx~q4C z*T-G0{(VxA``YI_Q`lFSmp}jZTJK<*K=jijd&zIplmC|`H?qb3xL%vH!uzGdOpk=q zI!~q;A2VL#AMO%$s(gy80BhNrxMP#JW^R(Zd(mr)-@1|)-Ov6BeE;0AefsLXeF7{^ zU#own+%)fzFlJkSwPVq$xv%%EWej+v6TOE+F`LEK`*T3*(+$ykKg?E?O#65#)kJkt z&DWPY*J}7ULlfUlpC24&%eh)%frp#ltPC6P3%^tH9x<+p7uL<$&u>;{!F@Z)AyFj2<3hf?l@Tyqj%)+ zkBip>ws!m5|COnZ<7fMRCh2S2ULM(7{kidVr_$yhS*b4D>{u)@|3LnfQ*WhjJPnty zSzw;|b-Cpoi#I$^A8ac!^Q~k{-#<^{jM|zoZLx0F-FN#=K3Tj-uV;~jwaZ~Kmd0hj zZ1?;*mt@5OE9${r>YI~ch@yZQL6p*Na+T+gufFX_(uww%R|fAX9ABs!LD43kHW=aS@qs-*WV!+Tq780Z*H^ ztv=z&bdbZjfBuOZ3zz+oUfA?^sj7b?#tB<>a~hY zOdmajMMbBou*%ByH@n0fdswhws)fUJx3wqE`DV}jeXd4krz_8^i>#Xi-Bn&J-n5D> z^2)@ylY5WeD2!%cTB&-yQ{s2oRAHXuAJbAFZU?I#@5Tc81F|)g#jD_w}WmO1d#2uOrJ+W$~KWT@$81whlVDXU)wyCS^Q% zlleTpZe6uPcGtG7!(9LVZ9o2^_RY>Y^}hn&-2K5Sx{t-#=(Azg<@;YUPfxJl#K4gM z-P6S}%rrV$)t*{%c8Iad*oU z{c?B9pDoFT%hqm-Z_zz{@A=-M)_s33w-wDg-0z&Hv3l+Qr3T_plxE&q6tlhJ+m-tPSJW>W8}Lz%*Q2NL3&;vk2`T$P??`1I(`j9xdPhXzvJvvb<}R=u)nTBzaPGy6d6a(VZkyUtf8 z9*Z_Dv0_0UIb{moWK?MYM4%&C5P zmFL7&r7o#E^K?Fa{v7;r0}G>PK*X<)PHDO~Pi-~7*R!q8*5Bqo-|~2ynK^~>m*amJ z8qGgksm_!5tvfg}T+mra=!lb|&OM=ZJAQc;@iMJ6UCFd+(u2*dn=7jSEkCiSLu;nR z+d179bt&=zNo#&(v9{Ls9S~XnaJA@bi3_Iey)zzsZrvQQ;g57rRi}rETg--ndrQNO z7R{a>sqgo6_PJZ8Tpf$p^3(WSTUj+&Sw)^zRKCdnzPaXS)~{(55!(vH^53eT?sWf_ z`z~F8^~Sz$#q;j3*FR_d*`>upcD?;~lhfw!j;5F`yzH&AH0Rx(zgbSZ!;StPHIFWh zG+f$Vc)9h8bbq)X!QIJro+0*Nb0|E4SR~SA1*pAK?pl;f@r&Q@x=zXeJ1r%$e$ytS4YPvJHyb9a7d=tW{#kW0XqBJcq&;@-AKLHVKRYEo z?!ioTIY;GF5qEyxDZ5@ObJ2MAu1{W@jOV|*Ss7zdD6{K}+EbIvOMe`$q%hSh9I7<` z+sfVl);syDUR=!^#=M>1tk$eQ^I*lMeKwzdh}gt>F>;t*3*48k-<7gt!wP}fHyt)C zoVQ}pq9e{%uKX;lC_Zuan*x`Xr)B4jIc>pHg+t!m_4RdT*nHSK&{3~2p1GHE>Kh;a zNemk&^h?E0S+j;`OVDX&U&ROcDK`&o-{rOE-pjqk|T%eUNHr-Si0rT?eDq6B;(c?`*imNQ?;({E`yh*3szmZ9e1@e%NFh8 zTYJ(gE|1S`uUN>`W4p3eWxbmCMeWn4PX>}(jwmxqH3hP$I@UTfl!xh=*}l2Go8L8A z#onHOvESvGe-4t59x{CW$I&EpT)rnLAg0I7H(+DA{y`6>ayPM=_nNDJhFUsa-M9A& zyVH%kGoPeYcCt!ma=4v5amq!T^T(^~j)lsH->L}Zq}<=9CA7*VEKg2vHm9oO@^$MN zJw;ZmUVl8{^MuD6gCaNfFI(Z{S7*o9Jy9)QIndR$w|!+kPf}^>q`4QBX7^l@Vs`oa z@0Z`?XW|bgNS|U?;NV!)A=4|E|7G#U8G>6i4OKmzCb>*GQuo`Fb?UKKR%d3milpoe z5RnXJ-Fx(OX8PRLhC{Q~n_c*SVzPu~jXy;s;gW${q?_->+8qCda1&QK_`?A)(; z>g4nI0~ejE7Yi$QT$9n;^(v&x)+%o9KNan;H4FXk8C*%3a`gS#l=riy^oY3|rq8$S zYdqYacJq05#ow!v8BM!CPT@^-=(pLnPVaxAQT@(Ilij|kKFL?mvEEVlDP>KO?B#hC zlUU{J&K*_f_gd*IlCkE-sX064jVkU`%Ght|Isa_`u{}3Gzqr3##CuWK*4bsoKhCT^ z!L{!3S5@tmvgWg7+S@92be@`JF#T?UEi>2JJ%8^^J~QvLob7u1{t53D-p-qp6&@-g z;_4a}k~2@H>WfBFcCu;c?(IhD_u6^vRxZ|I{x#Lie+I`9=iW23ZFf)JC()31-pKCv zvZmtobA0YIX>Xoi-}UqqC#OelPL=<8`1FL! zZJ+wp_Ftc9W4+g2En6$4{^f!|q!t7ov@KzBqQ@xXekl_n?4O^UO7Ga&pxE*4v#l$-K1WC5z`` z)%^=v*2V62y1uSN|K##dYq)0^S93b&NgVzh{I6d)zk15Qa~kD$D%h=GF`qaoF!S!Q zk3SZdZ~k%9y)x%p`;510HH`{}y_4fL{Q9*AFQ5NATQ5#ne*Y(l^EL0DpHzQ$JX`Sg)N2=4Ul+Kx=dX@FkHo1`VfU?h z;X zUb%L)@Lg~Hlk)3xjxT%qdB^X2>^DNiodQ&j#n*9{$C(eZP0Tykus>FxLm znP{7qsIQjV+utRAKJ!w-gljop(K5CBsuR!7-*?(w)Sxaz(pX$?=^njP&iuB={{MZ; zuTrl2{Gg_P@YDXqr?igGE7iOnQYpM*ct9R=j}|9tT#*m#US=)pM4@nEg^%zvM(kGKYG<wlcSTk(YJU&-UUCR|ldDta`ks!FsI-p{+sA|<6MVbS;d zUkPvb{hB^W`I^R)YgBKDsI4nB`1$R=VOhcUu!$^;+iLzY^1m%#$9}ndmdSpW_LC;Q z$5YpyOb`y0uRV6meO}6KW8(m!PQK%1S#2CnpC|cRT~X~);!xUI$(phFtImwyeDl}G z_y(idUtNOz`Rj2B`?X{e{ z&N^~S7nw0MT#Y@m;$icNFIF2PWJI2)Z74jNerE3e3ALq+E}^Z@YWJG$ZtmgHmz0#; zVO!=Rc;;P<+8pb7vD$X2=?4y2rq5}KT(0=s{gjSNvWk#r$i)K-9=c!NzNSOZ_E+(f z1#wrdg$ej-Y1VI2xDc?jyDdOC)Y|;Ry=v~8(c%hHD-;DfHx(b2DhvP3aqaCgErBJ= zlDO~Z-M+p{eP{H3#vZ9V?Qizz$&}y8NaEG(Hw$|H_LvY?$)`2JjDb1RmcDd|{IgJ_ zX~HCPku~YZ=l%VqB)D|ZvPRq8-zEiLYZq*;nmjlB&`0&2gJ~BRuTt7s*~Rv`GvXbj}Y;)Bw9-Ah)Snd~5*V->J z@qe7*Gi!dETe`yG*GH2)DvQ6WX~*TWRry9Auy&kMu*0~(GACn;)t)$gvBm42IiLnSO`DgM##cYm3=!tEWtSS-8dR{B#qmJ@Ee8)(@gGjs|?NK zN`?G=e{!!byRzjNE91;#4s$j>k2$Hme*dZF?{UX>?R*~?C+py+vSMYA!N*(GGe5q0 zJmXb)fO^T}pV#j_`1ZN~`QFy~0fC_>x76FW?R>9!W}fx*_IJA`{JgyU)6r#n?LAtX z&K-Iwte@tw-R9iRPho1l7Bh|2^bHC z^G?mr+dF4fY4^=t>t{K*q+RiR{_(^V_npO`cIN!~;r#6y>x9XiuU;$l%r0T>|MNJm zr1W;?f)4g&K2I+d9{%xUa%xCexZ&fY;(SvQt4+O(Je&1T?I>RV_#pc~k8PpN8QPDw z+5emBF28qv9N*0IcK;@wbDDbX{mld1`umQ~_^kf<$rg2s#~n3sbN(6qTI4v#U`@mI zWws};9A6%|I<4T8p|Yo$Y0#Bwk^24r1?7I6o~u#%I&xo~zJ}+k?AI>}pPclmd?VQQ zBs*g7(s{T4e_{B3ua0wfcY(;WU$tDnp1T$*QfSNVq5wNoBxNhe+X^qa~JmI z-Sg)V_kHjCnV0?q$<)cEin6R_dw*i#4z};fclYuK7d*4ziM;n-L;1m}GYT=EO`lFJ zO_}m^(JBG%tDj#!KYjA;t=am|&L+54HYvOFAI_bAW6$1oTPwUT=!OOcX7c$gUD9O1 znf0o;S7qYNnZ9v#YF(4O9XG3l>Z%z=LBKkC7Z+i6X z%=H(IQ@&W7le?__^k7i%>4hPplRio`DNOJ=)^Q|RgLC3f5M(@`|v>to4py)T-sw*mHbB~-_A8uHc z5U3S8`HoPG&eZQwp;7xftn;@oO>`8Qt!@zVXX@`88#d0_BiMW~`-xwQ;lxDapyk`< zZV$8BpkF1PzM*ini;0w~qBcj9i-n9^n8=lij`I68ODF&Hf4P2Z5~IVRw9k{`C$uln zNJ)J@Ir(;y9w>7MCDsz;KyqI43 zVtxIx`1y5n3O;3uRLbZ~6m^p=w@H_&W)b7{+4V$8%yvUVTY}10*)^*aBwk!yub+SJ zXT$MpdTIBU`Kt8To;>mT;6`Sz$UhI&Wo8<8e6?P5r}p{_zdf2-s}?-|qn)4l^Z4W+ zm;YYgu_1ob&(-lumQ?$Pt*!3P?Dto^wPAXO3Zs|SyO!lknbN9@z$6mWM^B#GW zSQh$Q;;QX(=W5?x(>JR)j6Zf6Yo0vmbk?f3{;PEF!6fws?@#^W<*xktRJ`+tR&XCr zsB1u|%+0%!woB|;pJ^P8?b`4~&c(4z%(r%za?M9m-q7DS_N?S;{F}1k(?hqT6K{75 z_t`b+^mD38F03vzJ8fS(eZ9y`S<&7p(@N|b<9C-!haP08He37P>ndN9u%8^}KRz61 zIk)qL*1la2tL{apd^~VP^NBUDi)rhV3bRl5Zm(aYqGZzc_`)3BBPqoOMPDX9c_JDV z72&e>u2=sirle^Md4E^4C<%syrXDyEIL9DMK#X@z;kB1K^^b)A_+ES_%6Z{R#OY_g zQ#R*lk*(`(fKCG@Pb z{XOZm*{1Ar%OdY9Pt)}En;cXgDC=cQn$@t}{j~RobnV*Bm0fd|B;{L5o9BVSR6Mg9iWF8W*lOMk2?+gRppx_nMOtNFbGTk|^=#^Q4e zO>SRbpOhcV5IE_^wklS6-Jg@@MqfAn|MzIbCZi2Ac{yG=-n!bMQ8a1RDchX8+on#9 zuRXR!eOchd4aP-37>tU#3*M-E|D1R0*_`vbwGS7n+%I*AS7GJ7TQ&V+#6RBr^Oe(| z&aXXlrc(ZrNN~VYm3_b3{7P3|l?+Hq+?J=Wz9>smOLLi~h{ECp+NrUhR@~V;d)W%F zBjE>Muity@ly7zL=I2?nC$8Q0&9Ula=`2u#{U_&^*%Nr*+&#BR-+V&o1^&IuRFj*1 z;_hFx`T1z`oa#e@SMvULNw1rCX8HYhjzX?eTJ9Ltdk6U~(8%3ngW8b&zXM4W!^=+!-(X;k=pdWhh$%&`$_TP1W@Y=lM z%hH=iLO2yK9uemMJ6HT(q;J`DqpI(k&Wp463oX}GZdmFToxiloTTe+nyS#j&){|>q3xu=Sl zLi6h1o&9yK#=dsB=V$Li38_z50G4fAuP@v~WK0*g8qQtkKVJ-zULE&rw@Z(d8unHegAb1$}>e(W%XMZh)3PhG8>QS`zq zUkh749y9ahJ{ID+kHzG6{+GA;dg->tG##TiXP%*91~2oN!RKwxuX|N>t#0p>35t_9AD@3VQuv%o+|I_W zw_i(`rJd=YeDk%-r4EHx7rMDN$JKPz+yCWycBpYq%^i;|lh}5jZuL`sc;?k1%g6p{ zpErH~dHnwWgKzk!O^lt+s>@3r`AKcr}aCT+-&qdoS zKRm1t7Tj*|aZPZcgz1V!+dh|>YRcC9y_h3cTf%kT?!S~^vf1@J1-iD}cPxy&`6j4p z+w)J3>%NzCG;i|)V{41Ab2{0-v>wk|wBftp9Q%JRWozP2{c)W?d1ide(Iu`XGbF37 zykIWX?7Csg`sMwsle_cx9=kp}TcY^U%P;3HeLj=i|KY?G^_5(jPoAd=a9`V)bW{Az z$_$3ULrLWchSE{fuSCBs^ErFE^)jTglkZYezY)A|YOl2Uhnwo$J6`^JePc>N!8ggp z6Q?}$5mj3A=Yj09vp*XC9iM+PG~CZg|9a3}35Q84cGd4|@XNT%)$CFgF%t71QlOh4v) zOu4+dp0RpWO^Lk46sPr#q1j;*{|K($s?y8nsk|}wFxwyBi`Ty0%DT@@ax{M!uuD(OM?ot`iFsoek7x-`KKONcG;4?DM|LGcBi_R_94S z_GM+x>Z}C|m&xs2-SF7oigSJB!gUAlSJ`T;x|H>!+-K^_ZF!v{s-0K8#f;M*3YF#k zZMz;<&t6?;=C_*BH8en!b+vKxySw}5@4LL@=TeJ3)8bZZ9b9Y8$T9VOX6+o-?eC6E znatW$aN(pwXzQO%jdyCoIYUjio>$aYxhDG}g#t8HUZZ zD%+>=9L&0?$YG@xVtB zrMS|zYf-}IXT@K8w_kkWEt_tZ5$MY6wz$tb|EKN7mODD@yEMFRyfJ%pZt_c0ukO1^ zOve-6{fpIHbg$*+g{hb94%cruvwbSx;U%7vCa|*gN*w?3xJsx?rC^6mgm0C|%@a#j z=R2iuQdpe$!caZ!o^ZJLMAwbV-CY|)*>!i$J{u;TwwTW~FtGJp>coH-wNEU( zQbp%32)a;fu>J1crOxp$wo1MFma}}5;u+27F%I*tA3Sxm_~s7%PPf(RKQ1m0xb?k* zxn$*37rkBo%+B!~H;8^SgC|OF|G%(TucQN9dHI%GtHy7#uK#g*{>9n48mo@1e5}5( z%St5D?Mk%8>!#HczNkHUZ@3ZMz|6d~#r?{Q!W(P%*nRz9@jX&@lg`Dh?-iEat2RFD zW!&*wt047h(v_Dd^z@dL{W6ZJdT8Czb!Ne1{bx7+#YX&Sbn@%#vpjEi+(w)}u!qZ)%HQF~X&dTYLt+)FA_jTs6`Crv`?zAk?*K}Atae`0S zrXt%66Q&NKb3Ht3{Dc{-7Ee7BW6tKzcKu!<|Mx%d@_nT2@5qU(R$6ALa-PgNID76S#=s*FKPGH4e|;q3;OwV| z622R~T$Nqau{-Qp)x693?HuJ_Sjhj9f7RmTl9*g}ky1qS#87+kpjurbS z>e^1*e2g#a?*2Rb&ipefw=j3ixXZqGf1Ru2!^4rrHzW)SkG7^KPso_78Mf%){~Pb> zq%0CL1D03a^Pl&(Uy!A7SzE1*d9GA+ma>J9lvGn-pLMd*a#>p@&j9{Kr^}4Co+;ZN z<9ycp)omAnsQ#BLKKIT4du?Ls%!C8(|MPafC~dzU6Fti$laK$=M~mCr^Ot*1*LzX& z@~YW?&d`Y;O|M)O>0GXMNmW;G`|i@ElUAMhQQIrAhL0zT_n`yxRMS!pDdw9uZ%TZX zirbNHBySqEG8p^Y3H>$BUA*$5drfDTs0(smmgss|W--Bq!|Y06=>3js8LHOC%zMLr z_5ZrPJzqX-wdm}%XIJ7MtiAd*2!_*b9x?fA1k=A_QXbmiA%c9 zW;q6gGW~N?GGv{8yfsXS<^1gXPi89CgFPce8caTQ&n6HP4xm(WK@}FlpN#i z2$VPdT4!RVe2+)JYHiCZl@+Vks>)0*w3s`=Cu!47fhQLK0(jc*wyCUOVX9-9E4ez* z-AL@9eBW|A;g<@{*B?8spXzZ;`G>Ra@g>I$Cr+5Zt4dyKFYD|`|23K?j=w*X7M&~* z=+hf4(mT;`xlckxq}W%}t3PHd&B!g=lPBKk(U1^v?4{RN*Wd&0F%t`JtYwjY{eksR z#2da6sR<=&JyMMO&-Y(^=CFQh2MhC&P{!G3yR_6l{ARilW0q$3DSeu0)Q;!Rv^z~f zXEE%UyOMKy#ga~sNmKp>TH9VX$*l8ZYGgQ=FyYRf|9jRxo*;edVg`$>A=jq6YuTcf zn@BtF)_&c&_;`ZBzYT5wt_Or3dMME3HviP)@5aL2EuJfw(k5?A=2`u}X!reA1{Wts zEv?lZ$5M0r%C*!}s&1Y;-=%Y!<&WL3lCwt^Twbz0ZMo;&vZqblyDv;3Qk6{}oRR`(6cCtnh!qesSi)Klc~az1Q3J z=2o-QZI#_8r~1C%q$iVSHdm&9)63cU89m2tAIn|{>fjw;la|(HQ%Cl zeErp3|6%J)wX^5Xu0ItpEp=wY`gsK_IG@_Mwiz;dB16|JJ+j7SEk;{`=3B z5m!(9?)pR?HRCq<$gl|}DNq0J+H>MeE%E#ueK>D>^7h;OTO~3tE&177 z81W=9Ok}2u(#3~UeboLxo(Gy>Uh-3q#Zf`9dt>!Sr&-_LItuW0yT^e{gfxr~&vCZP znvy!xWeKO|&5uiu{b~7AdSubW`S(0#nPmP`4`iHsv*+5oC1p329$n@O~n^#=(F@E6ZH4`sZEwBWnztti#p`IQtL#~q*=GB zUe%PY=;CgEIh`wczMAi>`&^!p%RWx_H<%V45WHP9`+8gokKE6_r@9v1(DOI{@k%RV zYmw}+`Zt|8q&2CNtAtI`K<$q0g5MX)r?cIV^o=dB@ z>v=duy$6L2C~3@ZcYenf7|QA>aN*_G7bPpF$E`n}VKVVy>Ey>-7fu&)IkfQo)b*hm z-@bG#s<^=_l*t)&?O*8%kC)HePu6ObDsW`3kDVporob`f{jKIT8Xg-hc%`KOofp3l z68-t^^|=ZQRA&8nbJ#LxWrl;0>x9K;=Kd7Q-@ku~gG$r-GV%3i{&Cc+`29S#Rp;+7 zX`wEMtKUR*qBiyHP};&~on5W=_j&f-zZ;#Fzwdr~FMoG?)co@Q8*AFjZ%-A!b?-Oh z`Sa6$Z~A)U@4J`ZzW+El?=1TqW*G(N-_Ko-KV~Ug8-1p|+g^3$vU5M%Us%I~Jwc|Z z-$-k1^Xk9sX;yRPCZFs$yDs(mzNkZrtdnmRU0|G}z`;^m`sQ}HtB_#1o7qe`b)|N- z<@xQGLsI_ib6do@(>E^g$h(4t|6Pr*aG&^Su=@HM)!otZo90gYFTfk2`r`XSj;Ne# z^PgP}50$K6+@E1{a}k41n046Zht}U0b2m9$J$E+5MDxqbtFmX$zcdwMadGr`-~NBC zB8SUiMy0RO8~Ej1dJns<3|sQ>>Tv_T{{oLs3J0?lq_5E8ow_^1AYJTklH8^>e>;Dc zZLU4-$0k>*=lpBR#gMJ@T#PywdG%Oi+Soo$P&oGK>3pMn=Z>aBdLqv&E@Zj<;oW-W z+V2aJYg5uw7hL6->-US3iAC!5ij~zD4;5cqI)a)9X$nX*NS_E$|yRYEw| zn+xy0+A+uHLG~BLPNrTvd#lxxd6u`{TBW7L$(kP~W;S_Vzybf5z`)6dHUHFf_{`(B zFW$L-l1A69PoYikt6zcQcYjfcq^YFx$`$*6R>z2%oLJNsAHJ{CB$F>+g|XyP`TG;r zU)nyEFSwX-A;aXzqCg$CiB|E-fq_SEoHWXDY?ymr@A=W`=|Q3$i!VC_nG{`mDJ*GS zWgxY2ak+Eo)GN)QFJAo2G?a?&51w)@F)8|InYZ?L?fFJKSX$Eh{k&^0E~}dN|M}mX zoQ~v6*@2;=bB~^54UawFu*hdh+0XjuTLD++s}MwQR+Ur7h6=FR@Z@b%fdJ9T$eZ}23oj(MYg_+QbR`*}JS4sie9)z4e1DXSvP znKQF>PSLT7wy!U{JiLGQZQ@vTpzrGGh#Ym%G$(=^{Q_) z?mO4rJ6U10C*(<)`j$V=HFwz`X}-Q$CA-8b()a2_p3g5Av97rvdAcuBKe^|4;Op=W zWxwUNnVoe}*6Mlr)IlIf+xXP=+Ee0F3+_A!dRD)y>r{PrTafqpy3LO}Iy`R59}^hXp7iWH?vOf_zh2?M3}dyzdn=D`O8*-) zuX5vJp6BxlZKlrN|EzKOJ(mj^1~ZFh)*NnK6Zb_*zGkQN>^Oy0rp51`PL{l>{Jml6 zlcq@zKJ!2ObnFd(2dCDedna!`k9VB8cAlpH{-Za%)s2yX=Y{%? zc>J68d{=0zRkpp^lQ_2^g9&xBmU@2voN&QuV(^kP%0&kApC;yNbT8_-nsqd3W5V{^ zhBIqIuCxYB zVB$vST}r397A-mWw)Db{0B@hG4+^auJcGi++plFDPBeb7di`HPqx9qp96zL_166e| zs@X5h5ZZ3ey?k%#Bu78Vg$!LjrB~x>d=uC3@z0dhJCe3H>`lb^SH4e=Y*F0y&UWU( zrI9OEc}>3W?h_a&99Z*DK&*W>PrtC^RQ_A*l+-y{lzD9Hl#<)0JzBs1Npt1q8UKEm z^pxK_cwpU4jgv>}Htq0?-OaV?)*oMu3!d>E4GuP!TwGmMJssPRKUNT&UT7hcp~`n* zX`uF|;>%Ib_8H5&=Lt4fl^o2uBFOn`#hO))Vx=k1Q<4o|SxfC(<&*5Jyh%au)9(Nm z+nYDzoO(;I{C2ju6Srqw$*it;F-FZO=7m`-d*^xBGzG>qX|xK=a(W_arg&`jiC57c zM~mf^Yd=VteYce8sR|IiP(49ad_mK(6C2g%9&6*B{i5tr>Kvn0@)O%+%yRFzY!}vl zye07OhquwQrB-n;bw&s`F7Qg5{%=m@AA@K49ELw{d4G7Z*v9g8ZpqQZKR*9>yy8b0 z=lSz9eCIRPN=8hYI4dS|ow8(J%DdV5N@@HL-gz#6bbo7K`2Dy8?`8#h2(}(v?4Nez zgW{FExO12Fb(dzn`Pfk;6kpp@yl&?t#hXI^_x*RQnRoHdzum7RcYi2-wl?DMN9Er} z<>%Hgr@S!FU@(h3_PoFF*->%Bw19G+AfAg;4vSu&@7NiBKHJ~!#yG5U9r=v+P1zZ`1bum`7D#ny001z&bhqX zWalSMKHTt2+C+gh#L*!@)Twal1Zz*HteIDQC##+HSCwdPE6A$3tR4KOWbZLW^C?%a z{kK|vbocVBSs!=Y{V{9O5n;}fs@mPjF5mLv4$I9y@+ABG()8&$A)3mDmc3w?=d zeEWKu;oqY(T~c&UR?K+Rc)Z|h>*JO8HuPxfpL5MNnZ(h4G2%{&bxBC*bwM6i%cMIj zdNzM8PF=4($8CIXk>MPdsiAE$#ahq%&T^Z`Neec7dE9y?YSYy0{5_Jnf2-zI{FC+D zUtx6fk=?TWACgn-+CTeA9C{h=9Hatm9gK%uVENgyS3?%oqIyp^$%CON_k$)YIgJa@bgflPQ*W6x4kYZ zQp$P}7Vj@L9JbCeY@z0+-gk=^uZfpwikNg`TI!0fo?wp6r!pMAzjS{fr1>J!$41Ka?2GoT zDHbN{*KLT%oxu|&QzjnP&FOon&2#zD#PX!=vp#I!a(mYyXV=BX4>rs+kP5NU6F+(E z?VDRs%~~NJAO7aR$?*9L!x@ZoY(kvaLIkzJ=709icbU?m$ z`8h^|h&4P?RxNw>#CUBg{t)R~qIpg|kLTgFW1L5mR%m^ZwCI?~T%CN8OUC+}OW2-& zq6Z7sRI0MOF!6%?|4IJKTs7ZW@%Dz2-`GN`IF!1UEWUSV?gqW)`*=3fx#R$;gx zz13qgD*=J{*xHiRTboH!iy7%bmmd)p!91_olOTVap;@Kl(*RiVi zbj7<~)sT5bOe&Lp{0%&3^}FcT^Nbn4J}9%yQQrCW-Rj7#A4>22&k|LayV(A-_}PN< z@<(ii`_j+fI$!Z|?awc=CIY+X9Jw#@^vP%a2QL=xz1S(}lFcU`TdH+bPIkRt{UlNC zYZuh>C9;f8J^G%usodAgt$Xo|&FAHgA9(1#`ORn3J^xj%YajMN=fxBFE`1Jo4 zGxx`w^i=*O^vPUcnrw`HrBT?kQhjHf&f77Mu{G>o z-8;{;^OgPcuFrQrQ9hx}-*f9~Ev~LbO4?dx{(QDoA{)1EoVDt1^i1RT&TR9q1Wsf8 z>ot421vhQhiT>n{2+jP&Zm`p9=fAGaH$Qxg-t*~Qmfr54X}c^%m-)>-wu*Q6i+RuU zv?o8?-xnt|(L`wL)~gk}e}25a%H`FaDPqueb>cuMX0 zeKH{If#SAe@9au}6?eAHQlD4XE$7~|B!7F%t5fAQ&-dATCM&LA(4o^VxO727-QhD8 zZ`md}&RpV<#prGhOW-Q)tOl3M?89?UH1)|wKK{6~P4Po?WV5b< zX5z1$dX8de(<;-h@U>21zU@&POfu{KuF&#bHM1jy-BERx#gBr&9}K3jdK^hfv)enT z!ld(V>Y}Rk{qveXXRP52+-(~G^|JKAcG zJPOpxV!pw+diRXL!|W4ZnoaC@sd?_~gW00!zZ*gnSU2(_Nc}3@{xzZKu*U8k!He@I`CL4ymtXpz}!E>EcETm2l6iI*EU z{csKpVytC#I#^ZqW~ICOme<`bQ@UocEs3o@srmYFhvVLjuT=Z&6M0_8|CI_AJ$Y6; z<#%7IY>B3a)EYi%skLI$Pxt!yZb410ZA%PElRgeRS9k6TQ((QLHc^e z5?w8YxS$T}&5d!XOH(8tKiQevQoGQ(HvI2-!O5&ly@4Wc?ToxkbmnR&Z+UWAqV>uq z8wY_CS~?5vT@zioL%jO+wMT~CFI;o8|F3FTaZ$0usx0ST-}-bpD@Kvf%deB;Vw5Ossd3ocJnNCvxY+N%vO%USAuV);@cVllteh zz24jYnmFyg{_)qGrE}`fPW>VM_sJ9fq$>~h)ojc#YxL+z`)rh|u>IWqEw=wdOt+Tb z&nOSParN}NzbP+nl?O!sOk3p3n8Ycp+}3ws_4m(lxwb!9S1!ACdiA15XS5U!uev1S zf0XyewpZL{VI~*X*SCDXUpIHjc6s67hyGkozp(g+7}HnAp1znqiBgru+|w(+vqov> z<{v4W(Gd`A*p2Sev`n?y=5SVo7|`6>KJ$+r9AM)%6-4(UwIq* z&NBJ$8p6_(G-;BMxZ2`>=0Z2T9X#4DT$c!Zbs+bu(W=?9CTVrvai2{x>nd3mO%#ZK z$0hj8K7X=}4Nq#Q>+0zlpWH8Gn97*%<2ROzKGin0dS=OCje9k}xcB|3h~B*LPGlux zcpBToS?e{|uTfsCBOclsv0|RTeY{`Z^?Nmj`E1&!gk>zNB=UA2dG@ySZOAnnhv1;* zyI0^(#i%GbJr;^7Q$gi=G4pT$&u`8u#e7`O-adUZ*Z^%>T?auQpLF)!8i| zY>M;OdAYgE_PU*)aPhH4wa@x1m$E}uHPr({J&q-FG&vYaDhu^5RB?HBzNR&F_hkF1 zv=C8C&C9Q{gQiS5cyrbEEBu~D6Q|DgUUt~U&ri&jZD!%?)O(Q|xq7cUKK;fUveGlh zs&i`m`lK%hlvo%=cB<}((0adh7024fiB{R$uUcN2^NZE-X#CE8Ug^^(r41$ay1jD| z6P7enE`7VlKkDOweJ^6?96YvG=wil{mNg0-LJtchA_D^+E8eU9Jk{@4eC~GtEv|N) zv(I|hIm$IHnlN>)f1W4*k!PWNZ=D}=zAfQf@@vsf`%5ol>Jvj)earNZL^RaAXOq(6 z*-`C(XxdHHt?WBx*%*JhCtZ1Wr|Vj1*M+6(i%woDo3m<>>8%&`83yam&h5RP`20(< zcVXI-B%>vAGbdeZxSZ)M#9ET3ZkEmD7<%%p8AJa+@3@L*qIr8?TlrZ(XPQ@=$M*g2 zQ*M{Jjd9BTM-vSv_^4U)l)B8FdRZ;*1o!#9XO!#zYd0-ee168p63r8;MNc^5>VL=n z`!xOhhl&`HClwbvm=@h@Ii2i2iABI=jh{MOw_L!*SG_xI%u2-6m;22)vibbpTaIkY zuevNg*l{}9eM->z{xgr_cwX$C@_dtuAD>%j*IRw)@j^a~ZCzU1OrC(aZLWbnxUDG6=e zv#CyG_r1qor^g*ExX5xxtua;p+~d1TXYYJ67Q=k4!ww{_*Le?VRq9&!%2w zwvozJju3SXG`M}IG4ieqa!vYG2dnH_mI6TWEd{c!BTT1RrhdPzk>7Y z=a1dwuRXgZV(EGGUXfYel%R_nU3nvSRtnrXE1iFC@o~fD3SoH>esi|r=NwM zzPZKRY5SkpHFtx~YCexYz9aE2L&Vk>y7S_9bnPuYuIMFnJ8b=f#j5c}3yz%o^Ibgo z^duF z#Jc&cv**6b`Sbd{cJF;JWS#LjU??DfvnMdZ{o_l+}&atdZ3G!xrha9z}CRuFSU3C2YdsV6Mf*U*;v43~` zo#!M~4B=`15>Xz0PP3zLKUBoF46|DRVC&k&e>W-J0(dhv&a3lb*yd`TdUW7OkZ1Q9B;L zOJZ8ck=D)IyF@9I_k}4__`OPTwr){N&clAT?TxD%+)uM^o{900avmG z!eeUqHU@BPzT0>?&C&1F6khw8ihlZY6C!@FPk5OFR&(dZ8J|DdDz$5w5m(Ix&R~B=>n&@s+{I2~@`F*b(Gi}H! zx`P>qo)#*2F?A@JEf=1Aw1F$*tJ)kncDC8GgVpAo{;a?Epz`x6pS(F@#!X8O=9zE)wD@LD#46TM*Yv`olctkDu8sFPG2x`^QM9}fdLUGZwy+1?_7O>=BbnvswQ;E`Sy8F~= z`NV>>(uZ?Csv8&I`(3lSL~+rQ=QD!UAFRI9d@}mbgnO|UUVm+H=s)Ou@^6yu)9O`% z0vjy9>coFB>u&OWu}0EoR*4qhVz$sLW%t&pb=}Q-_PRgq^V@W(Crf(F_j6yrQ=n$c zeWzp9TZYJEJ=WZlAxr4I7eSWL+5EqBhOhSF##tM_*fo7VZvNq7y7$ekH~rG;1i$Wn z)WK%#+%I)j`n}Gp72bhI-f1qVzwUFd_OE5iYXiS%@X7Lm_q4DC}$4bc~ zO;6WEd}D1p6UU>oExCB-t&%*0hhHp$|0;IeknuYcc6i~wx&M|6urOK*s@@Vw^e$m( z-RtKgd6K=dfP=}RsCz}sUheHXdN*8bJDK)ALB~7FRxUqFw0qU6h}>=07(ym3IhuKW z;VP|3_gc~k=*K4U2eC(-TY^?Wsgek@lUH(Ei#WhX?8PX zX65DACR4t=YgV6@6YZnu@x;PMgTvxZ+?;vares7li%#E|yXi#E*6lCPKRoiRWZTLe zKWAL2UU~nkn0DK>&zBR5tpr(Fz1_@aswtN(m)?8(YGUWMZFB!w+h2FCEnNm#I-gnB z3t6sWpd{3pa8d1KkwxXdhyELH@%rre#c^-OwjK|Qb4y;k8$Lg}p7YnDn5y3rt7>{p zR81s$k1_kZ32?C2hFra(v|6dGUAq5v;|-aJWg8dH%ZNFcwYflrnf{^j1yU$l-gD#cVaJr${E3$)y+ki!MqQ?hbBCTl7<6_0c93GxvC2*32(U z`;4WxtXRZ0InX`$fWOYfs6#7OX)V%7+8KJ;YkuZ_Ql?AI zFmsa4#|yO)RXxdn3T-)Eg)Cy^)~MNb=?G_WEi#V0kZHnk!sOa@VV>g#GuK+oEfcyZ zFtMfUZdT@Jm6cq+jSQewY3uEdX3d|Nt!FdaMWyHFvjeHy72BmN-yEzjxwlSf`>O@2 z<(qG29nQJ3^;xE90N(^xAw@Ax;mM&oX~v5bWG;wTt!>+7D;@dcYX5wlino>rma5?jl>YQ@2aCCi_9t8uW*x-eT; zqD08|;NNwVJ(yN+U2E^MmT|IUxkmol|5BTDF5d`fzrIGPv)!G=&r#rm>9x&ePt&J- zm1?^B%whRuMbDu2Irmqsn&hL_dHk`pt43;puuHb;*(e11Evt`@;-SxC?{ic&HQ+DlQ&|A+w*X_-R z$l$|q)7IZUEa=X2aMAyjb|S5!+|NJdE-P77*p;>N3RC7a6dc;mH+$*z zi_5I+BDZwhS~A(#&z5iY!w1)Wa*y)a{do{9!@Aev-;2bIOZ*?_vzwm06{=l#!C1k! zYgt0W(b<2maNKO@3RO2)x0`M9w*$%i-2I!lB+go?-L>Ef6uBsE(z8EDV-=U$>UqX` zlC!w#Qm-xiwQupfw0tM=*_*$<7wd0Q@Y=C=Cc8|3%8M6MH*WoW>P3&m*JIbJbj;Ml zcCT&@U-s8q58Y@PGxyY*%~rRb2)ew{6W2Z1RM&Hcuem{n^TDYT z2VQM@eDcJhyL+aGg@&>O9?2>i- z28WyFyPi%9-Kr4Bw;|KD5)2{@XRKMPS-!>1*z)JJYX>q{ty1!klF>_FE&e9qy65qK z7B1#j{wu6nm1HpMrR@WaFIBQqy~~`}ef`$4s6)l`MX8n7FFqBK56R!t{wTQ>b+6eU z5^dkTr>p41cA*zM$FBXh_nmcE%=BDv|Gm?bjqQ%S-J+;2B=Pp=os#eKXBJ#qFh6xt z`r$`6_v}?Yc;H{^p0CHe1B#A)ms1JP`Q4?RKlhxn|KHj7SkLjzzxpojeEjk~hr9Rw z{;%PDGkkBeWPbg*AD_>u-Cmh`_mh9c@|g3IkNs2c@9N+2?wP&uj~RtBhE*2x_gJ%^ zW#Co+&*E%(c46wp)t7gCKUr@1wCVN4+pXNwlwwcQ8ia!UdEAyAm zf1Q8c{>gpS{hC);^7s8Sa=L!`a@(i+iOzW%+)OHyjW$;B?6njvH9*aU7=McuV3rv=0dVX}iZ9Bv63iH%k zj`RNZ2ZoAv8@Frc@0#3oN^4P}vub7|L*_)E|*SvHr;*w^<)Ey zeZl{m6ik-SoNOS$b0f#>#98OiI&DqYy2VSr|6Tqwbisvy>nA2GKL2?A_n4kLA*V0b z@9BQ2zvm3+aUK^gbAe#fikPwh(N|fk1bNG3(s)g;Zhi4ZD0FJ*g6%;uAuWN^rG#0f zvo!7pH(q?FsHh$=B}8D>k~uCDZZ`>BLpe7g_xQ;}DK#&F$EM z6=olw9+zKOC|>kYRXiXl;s9@boj^~Vi^EjmkT+V(kKJ#%JlokZPff|F)by%Vao zk3RV@Y5L@ke|xh+7p~hTWwLC_y2fK|QHwS?OuO!^a>JqWr<_p6No}n|xu%NhkFSK7 zKRESa(RHsQ%8pr5hf-2g1wNV!bRF$%WpZ&5{wWz&<6Eer>KE!O8y2qCS{a$)!Wilo zu(RCUl$Y@gpMERr&KD|;3C^|;E1DvL8RA2hEm*;!_$>YUyeuC-e%GItYo7-H;&@nc z>twjKub9)0uQDZ1#6=%%T4r;$Gh;^V)2A%ToJK{TPMj=JUSU+a)l`L5w{cfi%1MXB zM7zdYwISD8KRkIddGf#IqK`e%3VkYF@$Tbs36Zmljue|beC4|)c4y;bwL2$0-FYonm|F^RZ_ZA)JebjR zW=_qfy?QK+k1iTim%UXI=zDnfoU-}lX07GxoO@LQVuQO6&h)Hno4a>+nPAV!C*q>z z7u+i=Eqx7V&e`K+ba&g8(wNX@4bCgqexEp@e=PfI(8lKn>Z~PXU+XGe&Rw`}S(k=Y zkZ;P5pmJPOW7P|SA;`Drc}(BWhKv-Z?^MhoC~8+kb`?Pe*#Klhfzq?S#9^wCb`uWH2s^t^HV>uirO4R?z3_M-h*1>2BA3JKc<tz8_9fBg>j#svY4xY)*VU`O)q4o1A&_eA4f<-ML&lxO{hB`qO)C zUyc+$<9)P{{lDY7kfZYJDqCjD)z8@1s<*B7r`~qI=wqAx@1GYxZ+Ggi{yE3R7Xzv; zsjQQne0pttEzjbUO#)wnC!2iQz2E-SPxUWP=Y}u)bl^((1*kE%ZdU8jc`|tRhPd}Ah|KC=B?{8JL$$X*jpRL<(d*)^H=ZY`- z=AYhe|7x+sg16N17oU{Ff-L#atzTc*w))goS=DmeHE(YRsU-}nWT6tqW|11*UWiSWvy4QT)Fz`9qIoklesW=>L5uA5(B@!k1OO^E-ZAUi~A}V^YiE zr>fTLJMJ`1dS8?JT=Zc<MUyLjzikaMH#2_fU##PvX%AQ3Uo4!4c+_NV`_`A&? zlg;t<(;EFcGYon(R4RVoRKFo%Wcar?a*xSdMXm6&&%f@BZ`6`kcpja<*WF|OQQ!T$ z8}(w@i|-!}w#Z8`oH(Js?U)-EyUgjc)90LApnTk@^wjK~Xn{qE81{BsF8D;vK*JF6trzj#H|x_y7`?#(c4 zetll*NHzC;?VGAk)@%#A^6~7W@``uOU;q4!zg+qJ?erIFp+>pCvRjfkU#-r5 zS^UUykJV#Uzc-VeZ8YCr&*%Q&9x|y@;^5yWvEN_LVSY9D*B2kRITjO>G*8s>-#K3U zf0oy|6_YLVGQSC42S3=xoRGi&=rs7$l&sXae z!n;1+$zEUl`{(h`?+*E|=snWOUYm1M`q7`+|BuaHzyHj}om#=mY+REqL{owVT%}H3 zFWWI)&uZJ0w03UzP{^~IqBMI_UYvs$vNex1UnXWEa;d!g|Yka(H+75 zYWl{L1Jz~D3eVr`yhroEvAw@7vK}q%d2^pvH+X7V(v`K!B^t^dK9$LM^boiUE z$1Sat`_af&3^ex*4EOwyA}Ga=8E-S?mId~L0y~K zQ#yBA^pdLcVr8bkZtSemJpV4v`|ds~rmK#xm=!NXN*ez%Y5Vf(*WzCbk1x15W#+u} zkKwN+Pk0^bo7eMZ;%R05_s>og>m^pK7h2HLeNOW1XYK2kMP#oSu4`3LF!6U^(RHd} zqI=Ps6^b)lZY*C_v5V))lw|h_FHMW?1Ugq_7xRQBF9_ei@64RfjKS9-#})M64K9ol znY!<=yHQC(dD8AhCFf72T)F6c;+%G(!}KJ(kJmSCuQA>&{a3U0O_cYZb$vmLw>A45 znf0UWyIkL#3Q6T(Q5gaeu;xCkIOZ>GEdgq*HFH$e&VWK3NLhe ztRAuK5`QKve`Wfbd8ItEH>M|Lw@f-D^D3K(J!VSRxnnnq^&fWJi)|3nv#eBk|Mq4} z#y*DGce|2`CqLX){vpC@j$DoybDY0g*k;DHhkMV?_;owa^y9_Z=RZGsw4|bWP6*hU=y}R5$KHR+?xn@<4*7}9%Gbi23dR|cY z$5VY${Eb$-qp>RrBVJx!J!8gE|80713Nj1+tuDP{$`<+QV9d1_3(N$TZ_Lv7y{IDA z-hKJle8=mDVjLER@87p)XZ-%orbFrP_Z<7Y+3tnH`)TX<+b+1*cFOjF#)Ea$?-LGX zK36QUZ!`Z_D(S}Aaz{@jZC%>4;>!+wLBc%^YZi2I&6`x$V_T$g^cqKOb@_3#FCJaR zn>qPp9g3k|Kee)H{GPjvbG zf0!(B<#7UI9gB;j!?fE;JsMs{q7#A{Sa>BPS--rz&Ym(;&i41krRM`!mcC@LjOOuj z4c(t?f9~MrFPUHcPE~zbrg6CI&Sv&xztRcs%@(du_|xLr@0M@3`1qIPkL{apI;%1t zo8vL>I?t*uncT~}WhMp+GB&zwYAmpM+88{^Xw|J{<$((x7nM6^{0*MO;^L_ivU&3U zdv)3e?;M;{dMM+}L9bP>ZfEaR{hzsN)wEyHS2;7j@_}qMINY_QWZ{kH&b@hg)?OKX zZ-qU##TIYsnH3_yDyX{hg3>mbUXN|KPHhuf7f9oB`!esj`^JZ!(tjN7SW734^-=>=C0Z;m(-oJ^H zs_mLv^ZlWq>fh^U6yvLS7ibuDx-2@ZJ=5pkaof|Gmmu3;Oxn4bI9F+J`|$SI*){Ro zXPMutYqvV;)OWk{Et-{34Hk)1nm-dv1Pt6MtZymi%3IzNHtk6Ryq*RpF0VG5wa&(iK8` z-M^$C4xj(=)SUhsyAoCX{cC6K`XwN>xYe$<>QVL$=P=LmUsFXlReqJqYVuTRWPTbF zywjM+%J^1>Zua$jl}meScBTr4Wq&wZ<|^u{xOoSArC&zRDIQ5X9_Jm6SEb7oJCs(O z^1t&~ukg{0$v3|Jv_2ENZSG`03$DQIh4KA*HD>8j^0gbMZH=!vA^g6^Yx=y}!l<)H z4x8@z{!TsT{ zea9^xt-5Wo&f{>r{0ujPi3{rDO~0)P^0Uvjk$h$$y7lXpI|p*FPrPOI@xnhT<_Rhj z&)=(_KmEB*((AUVHlMF_r#wl2v`sR4yY#j@-n?($Ej0y2@Bi7t)9lsT@5rj0HaeLh z9JO(AppG`z!W}*5GFo{FvV~kwVjvbgg&0UeV_H%%FZtS zxQ6qI-p1%$bohN>-+Bn{>^?ZJD&^CF6gKPOiC8AiX`V zR#D8KZ>H;AjT0q`97<)=*$<{M%$=P5eoy=T|Nq{jY>R%aFxM?zO!`(8wu<^E(gk1J!83S1EVKiOixg>>NE@`gDxXXdF0 z+E^}~V`4I0^ZQ@M@@t!S%!#{mPJk()D1GuwO{LBTg$D)I2i6^*dAt4o-7?$y`9^cU zr}-RCDJptc`aQu|x*#LuW9I>&j*#I`qkRIn6#ZI`mWx5mduYT~`*W5stZ zXV3WdTe`wY`QZuu$9?vGhZY2%$OykTE%eMwVgDy9|Nio|Inj3^WN)<9mFnyA=TlD} zn)##0_0Q9`=n3EW9{#d^m;O-vxk1*SihI94{onE7&E^xQ&#G>&))Z?No4f38sfve% zvC#&rpzC|iWvFUj?{-sOv}(fS#mVP3^M5KyRsHrx=-+$&$s47&-k6ievU=g+y;~N1 zjoY<5*5#9ly;$Pqdk+-vCQ5W4HRv+r+7$CPP2O?ZbtijMaj)MnPd6w#xNnz zV@e?*VM>b+&VHyU=9^G{<3b11#scBrTB`zFd1v0d8PQWEV%fNU-^T|jCenubl8Tv)c||E$~jdzuBs|F`bev);Eg zN9U4DYO1KGNs8%w=?0npP3spmwP-#1TGgSN62m9`al!l*N)HN_e0!{xBq%j|(T+KN zX|b#v>LD^^)+ZxcPMy*cacn=HtUfQG;70C|rhV6}LM$DVYn7*^*|z<)OUim>9Q8$z zC)xD%$94a#A8m6z;p%?T;K}+On?7CeV%q2-EGim$!s&Q%fRN!TEp5)DlP&H>?wBja z5_<6YXT?{+zkH4yGBG}{{ctig+Elc0 zHgAL1iTCQ7FE-rvQArY%s!5E=Q;+KPP!e0XY#rm|qm75tj=axyN#C^9kEg`V>mApu zMMir0o9{R*SvhgltEv(;Yrf5LlDuM@`Qmr~Ub^?+mA~r(Ed>{EN|Jxr(sg=v=k2`4 zw7=VBoQvm|y)l<;zgriOT=~Dsw#<0nv8qW;V%rb%b@@2w%1%1jb~tOn#&vB~@xo=x z+2^`BCU00EKFNe-@!T)Y#qzC>Qg-C*DL7wRb0y>9zx{VSZ@fSJ?%ShMYk`1|zyGd1 z-|+YS(lb2eR<>6hGq~6y_T2REoxr;yqLk_Ju55$SqE5LYXG^Kxglj4+Z%)8~G z(sN#Xf#y1)saqv_(*6a{-_FnTE!J`>KkHmp9qW)!or^kFX|eMjxf0j@d;j`X3SBGp zYh8azJ`r!(Hv6Z*vXZQu!pk(g+V9tA#kO~Cd!73~<~~#ItA7u7`AMGwZAte3lP`5D zvrhBheAX`6rgCV7{GFy~VXqJ-7Xl0J|@XW2S}#wD>8- zzb>irQByYCJuUWk7H7MNPugimIZ!sd`t_)`Hr(&mdbKg~mfWc#qdxl!EhkqbKfR>tac>=u zbU@n&VV-D5kF!1RZcpxV$td4>^5iL()q?e3R1O{N`SU+yS=RG3tyvaP69!1C7hugRzW+pKUoq`2rJi`8Pm$qRGC>;$=myIdx2Vbf82TtBAJD#ReO}%7Z^&Bv=y|lKEHUg`PSGcho&vy)!px`wZQGS2~X38`Rb+nKWXwW zYEa;kRr=^U`&H$?`QJBhRP6Iy?;09fB4#ZfQ)txULxF@sUhDz{QC0$hc+b?yx^6~K` z^Q^$og*U737PdO1KcB{>qqsTgXHe2Eo<4~?+#*ZA-k7p(N}6QQr)?D*SOXJ|ze?3v zIpO8a-Rjoi`pF;dW^RI9A@Xd?``=-bEQ|~1s_$94?z~pYuJ3Oe>zJK_{tuJU;{`^_?xkH%&k$WEK-m816TExXA5j4xuIhJ?p*Io}R z<;a^iXMBlDdZ_R(fXC7E!RPjZw3MWaj-jchT}#xh)wvh@d1vJZ@|u?RzUT=n+GKn& zb7{!W1ePl*2LqAUmAYl}I>lyey$$O(3Wi<+-CEM6R#?5c;8wJnQ6nhy1g@=r=K}AcjD<5;dTXg=QnFtsK|If`jYaf^tNEgRJHy65kYE_+z+$69(_uC zSb8fXYfbvecJ7Q#C5v}xi1%OZIRD&P;D9{GqYuv>Y|2=<@{YH&Ld(r(O4g^nPc2`+ zL@C#)_=xY)r}JJddG9)l$xdmbN@qrggw>}aql;VeG6DlaX8wA`_05!ZM=BSTvN%Tm>D=UYL3ppy(t_B**u zl31(^^t&;f{CHxkh(sJ&zocj5Af9m^RsXmo?W~;)wZ_YdWG(7gi zt5)O4J!#@>YdwODir*~_H&zJm*2pTT-gM&pdG~3e0xT+yi7$_H&)k}#ef?gA@vUE9 zZ9e|7o>TWRWz*4GpZbT#Pt@=|pKH)^Q{OK&)|JQ8Lx8(=LxuN_N3XO`nr?Y|C3q^! zeBC)mZ-2IT6>|+adE}+Aexl3ygDH~Y8?)YuRf+Yf1Z?lm-}WZ?#LVSMmo?A){t(OZ zUf`T{s)F}7{rY#RUru&?Jo2S{M#*1a5s6t9Z~nDwN)%d6pJ&_V|9tPkXC0G^{okBk zm0wyP4<$%z%T7&M& z?(ig&SsgA;FW%gYG5sl(bzLv*>n~Xqlh8d$8;kFC{+;pfMe>#7p)7M}8mq;&ElktR zKl7jE-8@@6M`T|8|$xQ@=a=z7@}Vf8qOb!^}U1I?)!hBjxKF!xpP< zzRr=iqeQ7Jc@O2ZA^V$yYEQIpu{YL|GUapNVczN_=6TSm zD$Uw=)_jlGKg_32KD~hTr2_M#!|zurEt&T7)J2JpZuU~$k{d0x=BjvlOzN0)L*HT2 zf=dyx?UtJ-H0g+UDJ@>LV0(q-+cld70wO+NW&0&xDWu%azbNam(Y)$p&Z*1iFm$Ta z)lb=cZr_}LmQu^?{%|lp_sHA(J1b1LX72a>QAa!7za;*fWBKAUd*S(($2T{BGmfjt zwyS05J))=n(cIPY=O1p1s*L=SB$kV_4!efW_1frueD&q|DM!0ZznqkAo7J=F$KIK5 zVkb|!_( zvS0U~*Au!f`IfN%rU!@5@BHz^QFCi2Q?F?BOylp)Z2i{#kK3nxy!q$)4C`#YzyB+2 zn}3z0{(Lo+#b4ssIo@oJ4n4MS@0eCOX^6ONQp;Fzqs-xF<>qIBRexD{y5{>@uQr&m z`o_L;{)4`=Gjm>?-}ctJ@ayYlsjq_9qh16~<6kw&NI}(hv7ZfZU_Ja6#(?awc|yFu3@B*YwVy&mU0P49?szwhJjskbfP-$;)9d^T#COg|a@Ur&&-r3t0lvivx_E*NO@Evs2?R{@tQ%t*9q>)g5?f329mrfHY zIkAW>GN+Jl<^GeOeI`GfzU*kAc4xp75zC02TZfMXlS8Hl~l{EcCwv4pmiJ)quA)DsjEfo>_H~>&u+TZ;%aPkTw|91%X75|Nq1wLaZdiz^OIH4d8<{^l z>^+>4mt`07Rd3I8xng>0?RkSFU(=jh8>eQ)PZHegB{22V73KK5`?XnJ9Mhin-A$SL zE%C^NeW0yg^9xK?&#USG<&d>MFtC5#mzRNEGX-M3xhEuh_M~yEEoQu#bMWbgNBKb) zXX^6ETTXhkC&x_ZZ`TgP$;roiRJ0EtJ8|*&r<;>Rr@YTTuy=lar~nIMTi9=xDYd3` zE(>$81*-|G2BVr_JwNLSUjw1oL`H@OiYo*zRvJq3u6S9R zka_7(j>HyC<(+4g<-VM=^5J<}wn8Dpw8EY5sOb***Y9d)G#MXHJn^Z{U6Oie?={%7T!da)iI-r#UL?R@R*@DFdejWwl>nl>$xS@k_9+xV?%Ra=kT z@tet2OF;)#dRlg_ax#*07GRllQet8SkE)_k{WI+~x90x2Xta3M3D2FX)~2f#mBi%T z)|sY)a&)*)#acFmFBQv|NY|Ks*^Ro%WsdiS=Rw6oW>4R3CKd2efn zX!QPnM^j;orS)7kiId}1~ zt{!vJdl}lc^P%*;>VL9sa|;d2y5)N9`V(ure-^xxPO;?uqVZ`{?pdR_s$9F{zPtMN zrN++K{zTU9-y3bVvcgaIcFQN7JaatRSbjqGPF1z33LO$9RolYfCFhsgs%`Z(UA5}b z$=6MhGY?nG*PMCj&YyHtoqI*-nUb@g1NK+%w+a>AeAan7(|5+&`^6t7@Ezwmu_kI? zi|`&@ZIa z%>L<-^!uXkxB55S%#qk?6)^jeqbIFQT zP4`}bMi!UUef|BWTVluOo{t=%t#|&fpZ=rhvA{20=|kM>&;EXBl)dzdzbE%ch4X$p z=OL zytypC{py7|O`Z-DR73)g&b;|_!Bf_7Gu54Xt8!I$$eOiW(z_v3*0p=eLgCPmwVlG} z?fOqGpZDmb`}^Y=7X*7%Uetd%DPz2dzfAMg#=~X?pJ!_BQRS4#xfxUS)t;7rM;~8KPphgD?c|GJ!YdxRa$oO}Z&TsuI6HBHY^3U(%2ONnd^k3{=HtQJ8jBi( z)#IEzr!`v{K3yxu;5bQug>~)ClQ)ddC1m~HCqMO7bz_<&3nSx9-__GMW;CDTJT7}R zYTwc0Ym^rKnYKdz^NlIc#s#S7PYGO$$gOP!ug|4BIu8~2Ap`n$jft87gu7QP>fkDXU zg`y}La`RI%(<*UmP+cdvl7WFi17t&HaTzcvb1_lNOPgg&ebxsLQ054{@ Ae*gdg literal 0 HcmV?d00001 diff --git a/tensorflow/lite/g3doc/models/image/label/images/classifydemo_img1.png b/tensorflow/lite/g3doc/models/image_classification/images/classifydemo_img1.png similarity index 100% rename from tensorflow/lite/g3doc/models/image/label/images/classifydemo_img1.png rename to tensorflow/lite/g3doc/models/image_classification/images/classifydemo_img1.png diff --git a/tensorflow/lite/g3doc/models/image/label/images/classifydemo_img2.png b/tensorflow/lite/g3doc/models/image_classification/images/classifydemo_img2.png similarity index 100% rename from tensorflow/lite/g3doc/models/image/label/images/classifydemo_img2.png rename to tensorflow/lite/g3doc/models/image_classification/images/classifydemo_img2.png diff --git a/tensorflow/lite/g3doc/models/image/label/images/classifydemo_img4.png b/tensorflow/lite/g3doc/models/image_classification/images/classifydemo_img4.png similarity index 100% rename from tensorflow/lite/g3doc/models/image/label/images/classifydemo_img4.png rename to tensorflow/lite/g3doc/models/image_classification/images/classifydemo_img4.png diff --git a/tensorflow/lite/g3doc/models/image/label/images/classifydemo_img5.png b/tensorflow/lite/g3doc/models/image_classification/images/classifydemo_img5.png similarity index 100% rename from tensorflow/lite/g3doc/models/image/label/images/classifydemo_img5.png rename to tensorflow/lite/g3doc/models/image_classification/images/classifydemo_img5.png diff --git a/tensorflow/lite/g3doc/models/image/label/images/classifydemo_img6.png b/tensorflow/lite/g3doc/models/image_classification/images/classifydemo_img6.png similarity index 100% rename from tensorflow/lite/g3doc/models/image/label/images/classifydemo_img6.png rename to tensorflow/lite/g3doc/models/image_classification/images/classifydemo_img6.png diff --git a/tensorflow/lite/g3doc/models/image/label/images/classifydemo_img7.png b/tensorflow/lite/g3doc/models/image_classification/images/classifydemo_img7.png similarity index 100% rename from tensorflow/lite/g3doc/models/image/label/images/classifydemo_img7.png rename to tensorflow/lite/g3doc/models/image_classification/images/classifydemo_img7.png diff --git a/tensorflow/lite/g3doc/models/image/label/images/classifydemo_img8.png b/tensorflow/lite/g3doc/models/image_classification/images/classifydemo_img8.png similarity index 100% rename from tensorflow/lite/g3doc/models/image/label/images/classifydemo_img8.png rename to tensorflow/lite/g3doc/models/image_classification/images/classifydemo_img8.png diff --git a/tensorflow/lite/g3doc/models/image_classification/images/device_selection.png b/tensorflow/lite/g3doc/models/image_classification/images/device_selection.png new file mode 100644 index 0000000000000000000000000000000000000000..1565fa0f2b65d48f4308ba50caacf745125c7431 GIT binary patch literal 119832 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¬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 k{sV$y7lux4a2m)v$^I=_bvLed7h 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 literal 0 HcmV?d00001 diff --git a/tensorflow/lite/g3doc/models/image_classification/images/dog.png b/tensorflow/lite/g3doc/models/image_classification/images/dog.png new file mode 100644 index 0000000000000000000000000000000000000000..65c6eb551468be3b53dc805009688c7b5808c660 GIT binary patch literal 474297 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

s-Ty_B^P8@@-um;WhN)Gu?!nj7 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 J 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 z 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\A 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 tflite_camera_example -> Targets -> +tflite_camera_example -> General` change the bundle identifier by pre-pending +your name: + +![pre-pend your name to the bundle identifier](images/bundle_identifier.png) + +Plug in your iOS device. Note that the app must be executed with a real device with +a camera. Select the iOS device from the drop-down menu. + +![Device selection](images/device_selection.png) + +Click the "Run" button to build and run the app + +![Build and execute](images/build_and_execute.png) + +Note that, as mentioned earlier, you must already have a device set up and linked +to your Apple Developer account in order to deploy the app onto a device. + +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! + +## Understanding iOS App Code + +### Get camera input + +The main logic of this app is in the Objective C++ source file +`tensorflow/lite/examples/ios/camera/CameraExampleViewController.mm`. + +The `setupAVCapture` method constructs a `AVCaptureSession` and set itself as a +delegate. The `captureOutput:didOutputSampleBuffer:fromConnection:` method is +called for every captured frame. It calls `runModelOnFrame` to run the model for +every frame. + +### Create an interpreter + +To create the interpreter, we need to load the model file. The following code +will load a model and create an interpreter. + +``` +model = tflite::FlatBufferModel::BuildFromFile([graph_path UTF8String]); +``` + +Behind the scenes, the model is loaded as a memory-mapped file. It offers faster +load times and reduce the dirty pages in memory. + +Construct a `BuiltinOpResolver` to use the TensorFliw Lite buildin ops. Then, +create the interpreter object using `InterpreterBuilder` that takes the model +file as argument as shown below. + +``` +tflite::ops::builtin::BuiltinOpResolver resolver; +tflite::InterpreterBuilder(*model, resolver)(&interpreter); +``` + +### Obtain the input buffer + +By default, the app uses a quantized model since it's smaller and faster. The +buffer is a raw pointer to an array of 8 bit unsigned integers (`uint8_t`). The +following code obtains the input buffer from the interpreter: + +``` +// Get the index of first input tensor. +int input_tensor_index = interpreter->inputs()[0]; +// Get the pointer to the input buffer. +uint8_t* buffer = interpreter->typed_tensor(input_tensor_index); +``` + +Throughout this document, it's assumed that a quantized model is used. + +### Pre-process bitmap image + +The MobileNet model that we're using takes 224x224x3 inputs, where the dimensions are +width, height, and colors (RGB). The images returned from `AVCaptureSession` is +bigger and has 4 color channels (RGBA). + +Many image classification models (like MobileNet) take fixe-sized inputs. It's +required to scale or crop the image before feeding it into the model and change +the channels from RGBA to RGB. + +The code to pre-process the images is in `ProcessInputWithQuantizedModel` +function in +`tensorflow/lite/examples/ios/camera/CameraExampleViewController.mm`. It's a +simple implementation for nearest neighbor color sampling and it only copies +the first 3 bytes for each pixel. + +``` +void ProcessInputWithQuantizedModel( + uint8_t* input, uint8_t* output, int image_width, int image_height, int image_channels) { + for (int y = 0; y < wanted_input_height; ++y) { + uint8_t* out_row = output + (y * wanted_input_width * wanted_input_channels); + for (int x = 0; x < wanted_input_width; ++x) { + const int in_x = (y * image_width) / wanted_input_width; + const int in_y = (x * image_height) / wanted_input_height; + uint8_t* in_pixel = input + (in_y * image_width * image_channels) + (in_x * image_channels); + uint8_t* out_pixel = out_row + (x * wanted_input_channels); + for (int c = 0; c < wanted_input_channels; ++c) { + out_pixel[c] = in_pixel[c]; + } + } + } +} +``` + +Note that the code pre-processes and prepares the model input from the camera +data. Therefore, the first parameter `input` should be the camera buffer. The +second parameter `output` should be the buffer of model input. + +### Run inference and obtain output buffer + +After pre-processing and filling the data into the input buffer of the +interpreter, it's really easy to run the interpreter: + +``` +if (interpreter->Invoke() != kTfLiteOk) { + NSLog("Failed to invoke!"); +} +``` + +The result is stored in the output tensor buffer of the interpreter. The +following code obtains the pointer to the buffer: + +``` +// Get the index of first output tensor. +const int output_tensor_index = interpreter->outputs()[0]; +// Get the pointer to the output buffer. +uint8_t* buffer = interpreter->typed_tensor(output_tensor_index); +``` + +### Post-process values + +The output buffer contains an array of `uint8_t`, and the value range is from 0-255. +We need to convert the value to float to get the probabilities with a value range from +0.0-1.0. The formula of the quantization value mapping is: + + float_value = (quantized_value - zero_point) * scale + +The following code converts quantized values back to float values, using the +quantizaiton parameters in tensors: + +``` +uint8_t* quantized_output = interpreter->typed_output_tensor(0); +int32_t zero_point = input_tensor->params.zero_point; +float scale = input_tensor->params.scale; +float output[output_size]; +for (int i = 0; i < output_size; ++i) { + output[i] = (quantized_output[i] - zero_point) * scale; +} +``` + +Finally, we find the best set of classifications by storing them in a priority +queue based on their confidence scores. See the `GetTopN` function in +`tensorflow/lite/examples/ios/camera/CameraExampleViewController.mm`. diff --git a/tensorflow/lite/g3doc/models/image_classification/overview.md b/tensorflow/lite/g3doc/models/image_classification/overview.md new file mode 100644 index 0000000000..3ffaf33d35 --- /dev/null +++ b/tensorflow/lite/g3doc/models/image_classification/overview.md @@ -0,0 +1,211 @@ +# Image classification + + +Use a pre-trained and optimized model to identify hundreds of classes of objects, including people, activities, animals, plants, and places. + +## Get started + +If you are unfamiliar with the concept of image classification, you should start by reading What is image classification? + +If you understand image classification, you’re new to TensorFlow Lite, and you’re working with Android or iOS, we recommend following the corresponding tutorial that will walk you through our sample code. + +Android +iOS + +If you are using a platform other than Android or iOS, or you are already familiar with the TensorFlow Lite APIs, you can download our starter image classification model and the accompanying labels. + +Once you have the starter model running on your target device, you can experiment with different models to find the optimal balance between performance, accuracy, and model size. For guidance, see Choose a different model. + + +If you are using a platform other than Android or iOS, or you are already familiar with the TensorFlow Lite APIs, you can download our starter image classification model and the accompanying labels. + +Download starter model and labels + +## What is image classification? +A common use of machine learning is to identify what an image represents. For example, we might want to know what type of animal appears in the following photograph. + +dog + +The task of predicting what an image represents is called image classification. An image classification model is trained to recognize various classes of images. For example, a model might be trained to recognize photos representing three different types of animals: rabbits, hamsters, and dogs. + +When we subsequently provide a new image as input to the model, it will output the probabilities of the image representing each of the types of animal it was trained on. An example output might be as follows: + + + + + + + + + + + + + + + + + + + + + + +
Animal typeProbability
Rabbit0.07
Hamster0.02
Dog0.91
+ +Based on the output, we can see that the classification model has predicted that the image has a high probability of representing a dog. + +Note: Image classification can only tell you the probability that an image represents one or more of the classes that the model was trained on. It cannot tell you the position or identity of objects within the image. If you need to identify objects and their positions within images, you should use an object detection model. + +### Training, labels, and inference + +During training, an image classification model is fed images and their associated labels. Each label is the name of a distinct concept, or class, that the model will learn to recognize. Here are some examples of labels and training data for our hypothetical model that classifies animal photos: + + + + + + + + + + + + + + + + + + + + + + +
LabelTraining data
rabbit[three different images of rabbits]
hamster[three different images of hamsters]
dog[three different images of dogs]
+ +Given sufficient training data (often hundreds or thousands of images per label), an image classification model can learn to predict whether new images belong to any of the classes it has been trained on. This process of prediction is called inference. + +To perform inference, an image is passed as input to a model. The model will then output an array of probabilities between 0 and 1. With our example model, this process might look like the following: + + + + + + +
dog[0.07, 0.02, 0.91]
+ +Each number in the output corresponds to a label in our training data. Associating our output with the three labels the model was trained on, we can see the model has predicted a high probability that the image represents a dog. + + + + + + + + + + + + + + + + + + + + + + +
LabelProbability
rabbit0.07
hamster0.02
dog0.91
+ +You might notice that the sum of all the probabilities (for rabbit, hamster, and dog) is equal to 1. This is a common type of output for models with multiple classes (see Softmax for more information). + +### Ambiguous results + +Since the probabilities will always sum to 1, if the image is not confidently recognized as belonging to any of the classes the model was trained on you may see the probability distributed throughout the labels without any one value being significantly larger. + +For example, the following might indicate an ambiguous result: + + + + + + + + + + + + + + + + + + + + + + +
LabelProbability
rabbit0.31
hamster0.35
dog0.34
+ +### Uses and limitations + +The image classification models that we provide are useful for single-label classification, which means predicting which single label the image is most likely to represent. They are trained to recognize 1000 classes of image. For a full list of classes, see the labels file. + +If you want to train a model to recognize new classes, see Customize model. + +For the following use cases, you should use a different type of model: + +

    +
  • Predicting the type and position of one or more objects within an image (see object detection)
  • +
  • Predicting the composition of an image, for example subject versus background (see segmentation)
  • +
+ +Once you have the starter model running on your target device, you can experiment with different models to find the optimal balance between performance, accuracy, and model size. For guidance, see Choose a different model. + +## Choose a different model + +There are a large number of image classification models available on our List of hosted models. You should aim to choose the optimal model for your application based on performance, accuracy and model size. There are trade-offs between each of them. + +### Performance + +We measure performance in terms of the amount of time it takes for a model to run inference on a given piece of hardware. The less time, the faster the model. + +The performance you require depends on your application. Performance can be important for applications like real-time video, where it may be important to analyze each frame in the time before the next frame is drawn (e.g. inference must be faster than 33ms to perform real-time inference on a 30fps video stream). + +Our quantized Mobilenet models’ performance ranges from 3.7ms to 80.3 ms. + +### Accuracy +We measure accuracy in terms of how often the model correctly classifies an image. For example, a model with a stated accuracy of 60% can be expected to classify an image correctly an average of 60% of the time. + +Our List of hosted models provides Top-1 and Top-5 accuracy statistics. Top-1 refers to how often the correct label appears as the label with the highest probability in the model’s output. Top-5 refers to how often the correct label appears in the top 5 highest probabilities in the model’s output. + +Our quantized Mobilenet models’ Top-5 accuracy ranges from 64.4 to 89.9%. + +### Size +The size of a model on-disk varies with its performance and accuracy. Size may be important for mobile development (where it might impact app download sizes) or when working with hardware (where available storage might be limited). + +Our quantized Mobilenet models’ size ranges from 0.5 to 3.4 Mb. + +### Architecture +There are several different architectures of models available on List of hosted models, indicated by the model’s name. For example, you can choose between Mobilenet, Inception, and others. + +The architecture of a model impacts its performance, accuracy, and size. All of our hosted models are trained on the same data, meaning you can use the provided statistics to compare them and choose which is optimal for your application. + +Note: The image classification models we provide accept varying sizes of input. For some models, this is indicated in the filename. For example, the Mobilenet_V1_1.0_224 model accepts an input of 224x224 pixels.

All of the models require three color channels per pixel (red, green, and blue). Quantized models require 1 byte per channel, and float models require 4 bytes per channel.

Our Android and iOS code samples demonstrate how to process full-sized camera images into the required format for each model. + +## Customize model +The pre-trained models we provide are trained to recognize 1000 classes of image. For a full list of classes, see the labels file. + +You can use a technique known as transfer learning to re-train a model to recognize classes not in the original set. For example, you could re-train the model to distinguish between different species of tree, despite there being no trees in the original training data. To do this, you will need a set of training images for each of the new labels you wish to train. + +Learn how to perform transfer learning in the TensorFlow for Poets codelab. + +## Read more about this +
    +
  • Blog post:
  • +
  • Image classification GitHub:
  • +
diff --git a/tensorflow/lite/g3doc/models/images/audio.png b/tensorflow/lite/g3doc/models/images/audio.png new file mode 100644 index 0000000000000000000000000000000000000000..ce6b25c442016a21600eb8249eafa55bacbba4e9 GIT binary patch literal 16541 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 literal 0 HcmV?d00001 diff --git a/tensorflow/lite/g3doc/models/images/blank.png b/tensorflow/lite/g3doc/models/images/blank.png new file mode 100644 index 0000000000000000000000000000000000000000..d099da5da07271410883554e07e37765ca048590 GIT binary patch literal 14062 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 literal 0 HcmV?d00001 diff --git a/tensorflow/lite/g3doc/models/images/camera.png b/tensorflow/lite/g3doc/models/images/camera.png new file mode 100644 index 0000000000000000000000000000000000000000..95a9218d47864aba12255bd32b67bb74b0d6704e GIT binary patch literal 16072 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

r-@-iq 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 E02odSPXGV_ literal 0 HcmV?d00001 diff --git a/tensorflow/lite/g3doc/models/images/detection.png b/tensorflow/lite/g3doc/models/images/detection.png new file mode 100644 index 0000000000000000000000000000000000000000..30e10f59cd53af21fe9b6a86aa5b45ca07131b1b GIT binary patch literal 17105 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 literal 0 HcmV?d00001 diff --git a/tensorflow/lite/g3doc/models/images/object.png b/tensorflow/lite/g3doc/models/images/object.png new file mode 100644 index 0000000000000000000000000000000000000000..aa8ed428ed15e7b166bdde560669563a224e6f6c GIT binary patch literal 16928 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%0AC3VSdb&1Sx$73yxXVW(C42gdK!cvWC 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 TkT}f1z`)??>gTe~DWM4f-%XIN literal 0 HcmV?d00001 diff --git a/tensorflow/lite/g3doc/models/images/pose.png b/tensorflow/lite/g3doc/models/images/pose.png new file mode 100644 index 0000000000000000000000000000000000000000..f071d789963d0f48efb5ba20633391403f75ddf8 GIT binary patch literal 16273 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 literal 0 HcmV?d00001 diff --git a/tensorflow/lite/g3doc/models/images/segmentation.png b/tensorflow/lite/g3doc/models/images/segmentation.png new file mode 100644 index 0000000000000000000000000000000000000000..8c61330687cc9a388a443bc6b771027d15b66d98 GIT binary patch literal 16515 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 z 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 E0J*hU)Bpeg literal 0 HcmV?d00001 diff --git a/tensorflow/lite/g3doc/models/images/tflite_models.png b/tensorflow/lite/g3doc/models/images/tflite_models.png new file mode 100644 index 0000000000000000000000000000000000000000..f60cd26a3177f95e40875ed92aa4a30c59a7623f GIT binary patch literal 158753 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 z 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�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 literal 0 HcmV?d00001 diff --git a/tensorflow/lite/g3doc/models/images/video.png b/tensorflow/lite/g3doc/models/images/video.png new file mode 100644 index 0000000000000000000000000000000000000000..88b3b7d3c76840625abec821220413a03d384a45 GIT binary patch literal 16431 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 Hl+XkKdqK9n literal 0 HcmV?d00001 diff --git a/tensorflow/lite/g3doc/models/object_detection/overview.md b/tensorflow/lite/g3doc/models/object_detection/overview.md new file mode 100644 index 0000000000..4f62d017bf --- /dev/null +++ b/tensorflow/lite/g3doc/models/object_detection/overview.md @@ -0,0 +1,220 @@ +# Object detection + + +Detect multiple objects with bounding boxes. Yes, dogs and cats too. + +Download starter model and labels + +## Tutorials (coming soon) +iOS +Android + +## What is object detection? +Given an image or a video stream, an object detection model can identify which of a known set of objects might be present and provide information about their positions within the image. + + +For example, this screenshot of our object detection sample app shows how several objects have been recognized and their positions annotated: + + + +TODO: Insert image + +An object detection model is trained to detect the presence and location of multiple classes of objects. For example, a model might be trained with images that contain various pieces of computer hardware, along with a label that specifies the class of hardware they represent (e.g. a laptop, a keyboard, or a monitor), and data specifying where each object appears in the image. + +When we subsequently provide an image to the model, it will output a list of the objects it detects, the location of a bounding box that contains each object, and a score that indicates the confidence that detection was correct. + +### Model output + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClassScoreLocation
Laptop0.92[18, 21, 57, 63]
Keyboard0.88[100, 30, 180, 150]
Monitor0.87[7, 82, 89, 163]
Keyboard0.23[42, 66, 57, 83]
Monitor0.11[6, 42, 31, 58]
+ +### Confidence score + +To interpret these results, we can look at the score and the location for each detected object. The score is a number between 0 and 1 that indicates confidence that the object was genuinely detected. The closer the number is to 1, the more confident the model is. + +Depending on your application, you can decide a cut-off threshold below which you will discard detection results. For our example, we might decide a sensible cut-off is a score of 0.5 (meaning a 50% probability that the detection is valid). In that case, we would ignore the last two objects in the array, because those confidence scores are below 0.5: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClassScoreLocation
Laptop0.92[18, 21, 57, 63]
Keyboard0.88[100, 30, 180, 150]
Monitor0.87[7, 82, 89, 163]
Keyboard0.23[42, 66, 57, 83]
Monitor0.11[6, 42, 31, 58]
+ +The cut-off you use should be based on whether you are more comfortable with false positives (objects that are wrongly identified, or areas of the image that are erroneously identified as objects when they are not), or false negatives (genuine objects that are missed because their confidence was low). + + +TODO: Insert screenshot showing both + +### Location + +For each detected object, the model will return an array of four numbers representing a bounding rectangle that surrounds its position. The numbers are ordered as follows: + + + + + + + + + + + + +
[top,left,bottom,right]
+ +The top value represents the distance of the rectangle’s top edge from the top of the image, in pixels. The left value represents the left edge’s distance from the left of the input image. The other values represent the bottom and right edges in a similar manner. + + +Note: Object detection models accept input images of a specific size. This is likely to be different from the size of the raw image captured by your device’s camera, and you will have to write code to crop and scale your raw image to fit the model’s input size (there are examples of this in our sample code).

The pixel values output by the model refer to the position in the cropped and scaled image, so you must scale them to fit the raw image in order to interpret them correctly. + + +### Uses and limitations + + +The object detection model we provide can identify and locate up to 10 objects in an image. It is trained to recognize 80 classes of object. For a full list of classes, see the labels file in the model zip. + +If you want to train a model to recognize new classes, see Customize model. + +For the following use cases, you should use a different type of model: + +

    +
  • Predicting which single label the image most likely represents (see image classification)
  • +
  • Predicting the composition of an image, for example subject versus background (see segmentation)
  • +
+ +Get started +If you are new to TensorFlow Lite and are working with Android or iOS, we recommend following the corresponding tutorial that will walk you through our sample code. + + +iOS +Android + +If you are using a platform other than Android or iOS, or you are already familiar with the TensorFlow Lite APIs, you can download our starter object detection model and the accompanying labels. + +Download starter model and labels + +The model will return 10 detection results... + +## Starter model +We recommend starting to implement object detection using the quantized COCO SSD MobileNet v1 model, available with labels from this download link: + +Download starter model and labels + +### Input +The model takes an image as input. The expected image is 300x300 pixels, with three channels (red, blue, and green) per pixel. This should be fed to the model as a flattened buffer of 270,000 byte values (300x300x3). Since the model is quantized, each value should be a single byte representing a value between 0 and 255. + +### Output +The model outputs four arrays, mapped to the indices 0-4. Arrays 0, 1, and 2 describe 10 detected objects, with one element in each array corresponding to each object. There will always be 10 objects detected. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IndexNameDescription
0LocationsMultidimensional array of [10][4] floating point values between 0 and 1, the inner arrays representing bounding boxes in the form [top, left, bottom, right]
1ClassesArray of 10 integers (output as floating point values) each indicating the index of a class label from the labels file
2ScoresArray of 10 floating point values between 0 and 1 representing probability that a class was detected
3Number and detectionsArray of length 1 containing a floating point value expressing the total number of detection results
+ +## Customize model + + +The pre-trained models we provide are trained to detect 80 classes of object. For a full list of classes, see the labels file in the model zip. + +You can use a technique known as transfer learning to re-train a model to recognize classes not in the original set. For example, you could re-train the model to detect multiple types of vegetable, despite there only being one vegetable in the original training data. To do this, you will need a set of training images for each of the new labels you wish to train. + +Learn how to perform transfer learning in the Training and serving a real-time mobile object detector in 30 minutes blog post. + + +Read more about this +
    +
  • Blog post:
  • +
  • Object detection GitHub:
  • +
diff --git a/tensorflow/lite/g3doc/models/pose_estimation/overview.md b/tensorflow/lite/g3doc/models/pose_estimation/overview.md new file mode 100644 index 0000000000..f19a5a10ed --- /dev/null +++ b/tensorflow/lite/g3doc/models/pose_estimation/overview.md @@ -0,0 +1,128 @@ +# Pose estimation + + +PoseNet is a vision model that can be used to estimate the pose of a person in an image/video by estimating where key body joints are. + +Download starter model + +## Tutorials (coming soon) +iOS +Android + +## How it works +Pose estimation refers to computer vision techniques that detect human figures in images and videos, so that one could determine, for example, where someone’s elbow shows up in an image. + +To be clear, this technology is not recognizing who is in an image — there is no personal identifiable information associated to pose detection. The algorithm is simply estimating where key body joints are. + +The key points detected are indexed by part id with a confidence score between 0.0 and 1.0; 1.0 being the highest. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdPart
0nose
1leftEye
2rightEye
3leftEar
4rightEar
5leftShoulder
6rightShoulder
7leftElbow
8rightElbow
9leftWrist
10rightWrist
11leftHip
12rightHip
13leftKnee
14rightKnee
15leftAnkle
16rightAnkle
+ +## Example output + + +## Get started +Android and iOS end-to-end tutorials are coming soon. In the meantime, if you want to experiment this on a web browser, check out the TensorFlow.js GitHub repository. + + +## How it performs +Performance varies based on your device and output stride (heatmaps and offset vectors). The PoseNet model is image size invariant, which means it can predict pose positions in the same scale as the original image regardless of whether the image is downscaled. This means PoseNet can be configured to have a higher accuracy at the expense of performance. + +The output stride determines how much we’re scaling down the output relative to the input image size. It affects the size of the layers and the model outputs. The higher the output stride, the smaller the resolution of layers in the network and the outputs, and correspondingly their accuracy. In this implementation, the output stride can have values of 8, 16, or 32. In other words, an output stride of 32 will result in the fastest performance but lowest accuracy, while 8 will result in the highest accuracy but slowest performance. We recommend starting with 16. + + +The output stride determines how much we’re scaling down the output relative to the input image size. A higher output stride is faster but results in lower accuracy. + +## Read more about this + + +## Users + diff --git a/tensorflow/lite/g3doc/models/segmentation/images/segmentation.gif b/tensorflow/lite/g3doc/models/segmentation/images/segmentation.gif new file mode 100644 index 0000000000000000000000000000000000000000..e664adf700b396b9cd06d48378b782efbbca4282 GIT binary patch literal 305126 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�F%xK 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

_A(MQ+9p2d^}frxkJ6BTh}vxzt;k}-jYu^~x# zM~22RGclvmTqQB3$E{~V3OS2gzX%ylzNzZuWn37)C_Oh>>w=UKv!95HFo&eaF40XK zvwc(y_%C|A&)R~HywS?Ox>L_rX|2OrMxF0G zM|t^TvZYU?if?ZSkl0otzVl7|lQ!WyUv-)9M;%B}V!GTS;&L}5u>0C7E8*=IQZ&Ur z{=cazcd_`g#DYkZiUWyU1eI+{u?XyN$KW`5;jt56MhXYi9{V)fTq73T;j zI4xkZ+^o@T`|Dtf-j&6yx(19~et~TgDGPZtIvm7h7#)-=7D{OeunA0g!0CTQ$x67V zp>|rZWYY0hEb{B^8>)9eAiWljQ{PR2qO=Dg+))falWKQ^%2UT_jFc)-ji z@{!q2qKQ9^p}VkUQM>I2N9k1$nr&(hFv!nn^TKQmq;q==5=aRzS zi_)@@N;m&sT#)l%F3ZVHAwsKMZdzXCoXx{Lr+|xrfr0OWBgX>iVX5ne%nG!T-t%HP* z!&VN5Kb=nc9?oVRPRBS7@E9~3|KaMJI9cpu_kt-*ralfF6%FDcjcg1K>>15w2TrQ4 za8fzqbfDpo-B+eV2O0!&7BaLj@EmYpuV|=QG=&`R;ylF4(drRVsCc$cZSwf8W6 z+T&z>g2%Nt;p~Mi zZj2HP;S-oG4PuU^{y(4*;&dqWK-(WiwKJ~!Z4ax?VK|!T#3RCVxKa4(+uJ@0M-(5l zyb?GT$m0A#!{I{!ud2h)Y16>C zhv8EUOZJ`U5QV!DHx^xzIiz@?N%4fQvO&X>1DClP4yL?$^qwQdtMmxd2A4MqO>bkw zz6kg_f9Y{{IIqgW%KTs|Tg-u^J4sj1xCwo6W>C1!mgB(kqDe2OL2ph|u0Xi%3n$}0 zu|=U56a)?_d}vh9aj@-S6gEh;b#Rt)Xcks*lvZ&3#o)o9!pP0yz--dMtJ0`8qoLTP z-QLB)Zprbo6W5E*I0-yCUQ_zNz0Ad3bP8jAiX-0_h7WfcLxtp9_VnljHn8E;XqYvFg-d~PfplSmbn8h*nFlfqT~k+_RQv1HpWOH}uN!vtHpYf!6fRg3X|B4zrPES1QZEoSbVd8=9OPC(~;o9zd<)Y64 zJ;CM;ytn%sn7;B(+(1AT)IAF{YSar5>h(ns=mn zoWy#XZf!Yo%jWF<$|Eg3ObU0L6jVY}Z4W!!By0a^&4^_=smQ9z!mp~bgy~OGU~6W^ z0al46n%-|78H+S1&WL#|qvH65&uxQS{8Fbge_R}-4k_z61YenRbz|G(DMuBrB&i*V zx|MK(S>{mc$rkrbY+1h3Tq~X^eOY<^Y_xO76NfkdWxSF)ZWuTi$Q%`#FiWn1!MuS% z>w?Pn3n>C6{r*yM4=fKVTxk+YIjF#MNOysg>JMk;2*%)3&f*4*+&7%LJ)W{YbttQF zx9m75{f620#X)Hn#|D-YIyv)Lk1SEwaAfUFEqTIFTR7pNpi{|(rJ5DaJX07_PBkVk zn()E2am~gU&6lTHUmV%@f~8G$h9u98hD-+j9}FBW!Yl3^U=^9jRPxM1gjrDFplJb< zeBrI`z`$cG)RgbY>)vouKQe>k3zJpkNpY{|xl9Kb1s3F8^PD-yb9Jte0xzeY#mzN3 zoCblC^QUwcPh+y4aA-zLW67xn3pXBG-@;⪚P`Fw&4zM%@_Y2Bs{u#-ZW_#a?Rjr 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�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 + +DeepLab is a state-of-art deep learning model for semantic image segmentation, where the goal is to assign semantic labels (e.g., person, dog, cat and so on) to every pixel in the input image. + +Download starter model + +## Tutorials (coming soon) +iOS +Android + +## How it works +It all started with classification where the model predicts an entire input. With advances in data, hardware, and software, object detection can infer objects with spatial location. Semantic segmentation offers the highest level of granularity with labels at a pixel level. + +Current implementation includes the following features: +

    +
  1. DeepLabv1: We use atrous convolution to explicitly control the resolution at which feature responses are computed within Deep Convolutional Neural Networks.
  2. +
  3. DeepLabv2: We use atrous spatial pyramid pooling (ASPP) to robustly segment objects at multiple scales with filters at multiple sampling rates and effective fields-of-views.
  4. +
  5. DeepLabv3: We augment the ASPP module with image-level feature [5, 6] to capture longer range information. We also include batch normalization [7] parameters to facilitate the training. In particular, we applying atrous convolution to extract output features at different output strides during training and evaluation, which efficiently enables training BN at output stride = 16 and attains a high performance at output stride = 8 during evaluation.
  6. +
  7. DeepLabv3+: We extend DeepLabv3 to include a simple yet effective decoder module to refine the segmentation results especially along object boundaries. Furthermore, in this encoder-decoder structure one can arbitrarily control the resolution of extracted encoder features by atrous convolution to trade-off precision and runtime.
  8. +
+ +## Example output +The model will create a mask over the target objects with high accuracy. + + +## Read more about this + diff --git a/tensorflow/lite/g3doc/models/smart_reply/images/smart_reply.gif b/tensorflow/lite/g3doc/models/smart_reply/images/smart_reply.gif new file mode 100644 index 0000000000000000000000000000000000000000..4a61691fd8714102409d290e7f6d6e361d9cbf13 GIT binary patch literal 742789 zcmZ?wbhEHb+`#gV>HB|%+b-tMpFjWl_3QWV-{%-AIXO9bd3n8m|Nh>+dr?tQ$;rvZ z#l^>t9oxHi@40j5OiWDHu3ej*oxODF(yd#!9y@owr>E!r`|m9+E!VDHn=@xlP*4yT z7uO0y&Fbpv3>BFvQ>L`4%d4uY-g7ea+Pe0zrQx;ptKWbBZs6o~?%cKS|NlRK|GuiD zJ9uyRhdtYNT53M8Dzfql+B0j)^XKm;&6)H4*ugeK%{xJEYq#z=cH?)~l$qzQUAuPg zUS4(6|L@;#Tk0Pz%}Xz?oV9d~hNbh?z55p(`R@~zyye`#cYSSf*~MWg*?+ftw)9MO z4vISW{D0Tx->+J#j~zSbp(wFp?Y0{){>{-;T)KAc%++V!FPUpCD)b^f=KJ^mr7ONK z3vl}W{zJ;FZ+{=$wrTz5)BElJo7Z12ojcpnxb4Bsb;)sGovIhJTdXW_NmGe$5NwAib8Tu&sk_ zL2;GYwq~#I-9LBa@RTWY;u<$A8rrO@tUvMMW_bIRrfkErGpvPGwfgdM{+-FGbaT?S znEvbgdqrQ%meho$OV_>r{?0hg?MH|1`*1HlC5e0A-$|HjESWg*(AG^$I+~r#71m7- z&5l$w*5r~_vpaF`(w=4WD$R^Tcc3O&Deuem1f(Y-4NE+4&q?QC3B(EtD6eLc;MH?7fH z`R?=o?~YD=3wN$vFlSP7a!z!(TTV=DbWQfTIa^xR&zv%M@}8chMS;_|Z(Ws>owRoC z(sSpIw$AR(XfEErWN*WS<|$L#o0}%=)lpc#f9r~w2X;PtH-GI?CnvAvnu@i_y~)|x zeLeL(l`}8jzh<}f&Vnh6*DYBa6c}^v==po!zuS3v#RLZb|NcKHD(cPqk1{ec3=9na z!9ejpx1VcBu(M-;tC5}oGa~~7gW`Yg2;Tq&m&B692;Tr3`-+0Zv0i>ry1t>MrKP@sk-m|UZc$2_ZgFK^Nn(X=Ua>OF1ees}w&$amzVPg@)E?axdpzy_?717!c`ZS1f?R} zi`C%bkjjEo{h-w1{L-T2)S%QfWssW+telHd6HD@oLh|!-V4)b0kzbNuoRMFk;OqS0amD(vFl7DJmimg(S zlD!?{|9^k~{QmXx$Me zX-RQWVL^UgZccVqW=48iYD#iaVnTdeY)o`iWJGvaXh?8SV1U1$uaCEvr-!?ntBbRf zql3Mjt&O#nrG>efsfn?Xp@F`hu8y{rriQwjs*19bqJq4ftc{6Yj{!JV8-B z;J}X$cXyVG>a5uCMf3Lgz{_27zPq-(yu2cKb==uqTVJnVkX&!WV9peKN>Hri!rg`2 z-{0eI`hSmi&X0n}`zrax`(!FVJU=yAJ-zPF_D`>`ERJ5!x3{9?)q%?L|M_;YzrHnNLH3iXmC!e-PGD&3!-0V=9mhq@lqfaBLOK0AWq;7*_8jpJ>?)6yQYjRE_ ziNofb#GzzMw#)`LX)f`YhLYF|4e4Tjt`8>%xM@C}5)zjAbZWF|We|ga#)8JQU5pmt zy`?uxZ_dnEw({AmoNX_k%`P~m`Fu{vwan*pE1tbfn>k7JgKGQ1#A%JeQBJK-7j~$v zda{Ui+YnurJljMcc`+3{eR@|PMTK4MY@&#>LuU0IXmi21oie;-_ty;6~)v8sQ zP5&h#nRd#0hO=rINNRfYe|zhcH5n@*KfB!Xw!bTmTF(TZuF8lAH1EL5q0akB8~96$e<#Sg&uL$$Dn)_a{@r^L`$QzQ6Qf zGAr|p1MG^*9Y0h1Q&{?1_9ttzmsbp^Zr zpAScr*H*F5wby+gbNSx8Kc6pz|8qDt`@QT6rte&Ff4^ldj9{vKddGG#qQo)7tZ*adjSKx#zyuVu8I9S`*S4>8g4v! z-zd}v1f}VZ<{P=oIi5`SNMrTVqqz|VmgXj zOnw}cy7i&$4@V>8F&C%vh72ZpJc|VWx4$^Fl10H`_soYe=h_deSq0XC_m=k2!rk5YLwfZ!%^0uN>>Es43mPsmYXO^g$Tk5Px zNQjdbR+LlpJQeiLXVT}!_|s|+RQz^*nz|t*+LN!r<>}qjW~71tc%kLWm|;$R2wk>E7>Clj}Xow_t_ z*O#RRy(@jvt=6{amBj4(BED1 z?D}cJr(@qNw3fg5 zbzQ~T)p6fXh3|Q0ur}hTY>?&i^Q;BIYdU=27`iq!RGMDS%C2>Y_Ge&Lv@vDbR;b1p z>UH^C)wj*__huzrEe+Cqrp9Q0WJyx^>03TsOM?F0iB(>`EsXhp&lgeOqb;7yOKvj8 zkQPF5BXEd&7#n5WTk0-0bDvvAS(441K)G`K8;!SU)r`nPGA8 zO2&zU@{Hy?U-+)Cy2u^zEN}Ie&xJ8rD}(O-y0yJk@t{V7hedSBy9;K2ZZOzev@iK~ z%g^q$?}jGPJAvP4z4i|JGM7nD;Kc8!ABXr^PBMJDWMprtmYvNju*K`geyM+ATQvL% zGi2`=thGBdFW|3J)`_F9X zCoto=Px|887DmatE0~(rJhOW(ct|=olI=zGb0=x31o=i?hrLgpyXlMUi+NgP&hPuu zt9;V5?~T`{Z+rd1Z~COO(Y3`dp5(j?nmsG<_^QXBe!O`Zc74MMTOS8zwc1xvw|99l z9VpIWe)THOxu{)m+UW(UHLsJTcRQVq*}_gNY|{XTY;FJWY5)`L;`Uix;l4Ump{rz5dn7x7V2LEUGde(|0 zwhgu1I~rGCZMgKLPBcPS$fK6IqE>uIkmQU8nH^1+pEj~Rtyh#ts4{P!DBk?_cqIGM zMx7na4o{o-C7K-~^vpXNtxhz%X$0DFG&$~Qd0^C{5z*o<(fV*oi@}MOuob#~8oHV@ z>Vj^xdfW&Kt!Pb((0zA3DEdWfhDV?*Lvb8)V{%68j_rXH7+W(V+Gl^M_I%!!zoX?w zdtKR&_6IDr>!<6=1$4Akbac$<=-ScIbEBj0|BsFd5}lJQI;TW*POIpgF{5+Vj?Otd zIu@5y)p2wz%J6Bu-geuGVa1HDRXe)Y+~`{Oqicgi_a=+(EfL+@D!O;f=-#!Xd(VyT zeLuR_o?r}YXnj{)&vBx4@r<6fFSUsox(9yrT#)F!WYK#iqW4-w@A(sq5e_{ZEmg-? z^gO6&4>#|)(bW5_qVL6wzE?Z?-eh#&I?+2(uyVPJ}>4iR~%Ic#L6SO)e$Vj?ydG=iw zny6$sN#*8*5Cfjv0=D!C9ETlPUsp`LzNze9#st~O$@iVhD*w;uSKT?;Msrfg1a^A| zHm6LskOz~td$6WWU|;XSn(~0(@8^`1gel&dQ&&Yy3CWzgkz?}7=c*q&>dk&m)lTVe zTi$LPIW6sFzkLCl|IaD50ZcIo-6029V?Rt==fSGFVVd7fHqoD5DHAwM3Z|w#;9xdj zD{JVAnZUvPfz_U&JIb>y=49D@j%kx-PT09|!p)*-GiJ_wo-w_mb9%97ci~Oee-6{v zo|sv&a{BrRrt|=&ZcheRPlnzLtepw$n+w<~1K9fP?9I*L)ZSwy12+WKVI4{<*1u}a?Zb>^LKf$RtL~kfq~TGO&+YB7iQWjEHYj-mwVR~zF%_%R&|v+@WftVt1e(m-Y`8x zfLX?iA*O&q;TEgXEe@5eMT|RpnImlvom>>!|6rzotCc+2uHkLs*hIcEOZ&I~U5nTB2yRNcB~hMwdh6uBA;?R_23XjFf4<^0xoHg^*66O!gk9N-J72qj$m>Gs=2@%S zHqSpBq1*9mm4Vk{+Xwu%2NtYZv0!fI>Zw{Q|9x1t+Jh}NfxU79M|uEz+J+fLJK1|T zu$OwVI?rN15H+JNYL($jnwqc%+?5Y1=&4?A=Fwt{^#%zdy)EbV zw!Gil3Z%CeS#K|i-d7A3TcTS1kIjwrRl^l?^?Bc*P7eA z)>ZGC+dVOYYuBRa-5o`9bfR~y-@SX!?cMu+?>->CXQK4(tG9Z$WbZx^t*X6x^P$^& z&i&qVL3;0{=p9FPPp#43aB}wEFsEHtjrQLAz4yU?>3xrG@3{JU#^&z5w`T7P6xwmy zXy3cv`#wnT-xIy>z)y+mtM>iiR(-Qt_tWkD|9YC%T_p_~0xO1AHanAvs zI|trdA7F_*z;1JJ^}$t5uMbGfIVi=mN8nG1kk3Jd94WThyYKEks8(}GW6w^3npH2P z59#$tan3npWOLZ$%nt20rMiC(S=mU5-PvdQ=kQ8xCOd;8E-^<|X&-UcVRG=(85)t|%e4y$b^oLaT#)ISI2(3+j? zHf)=0PH%}hG3_<8uET1<-P1N#vCPP3_Pfb!$FS3vM|bv}lPB&77W15%q|LPa&8ek6 zhm|Ir-hJludY;q%6VBdRb9P(JX%huD1_QR;cbIh<&ZO|1&az}X{Ab6}n*AsLoQvLg z=EkKni+Ijnv^jh^=IpgKJM{z3`cK$t=fG?`fz|Eq|MEV{!SzTt|`*(>fF&MDqK zp!{~q+zG6eJ(o4kp8v;lA>u86`UfVv39L`oT=ZXgdFpMZ^a2*UwL8q`Y?b!BQV=V+ z$O~5w+T82EeJ}gxz0#Zq<+k@Hc3&3g z+wr#M%+I|y=gHnlzrcA{<^i|OgIzHTINt5x`ZtBgZs*6^>shrsBR@>pzITVLj;mPR z!-BaF557Gumv`$@?LFmtJ9PJ*=Ih<5UvR0smQB`gr_jB}3>S{&t-Wiq@2=Ur$4hgV zlQ*0Sn|CTn;K@ww8{u{*9qwFv_veYY+|!_S$1`f4E$=>(zUB!Z&og7aXTrJtmY&B` zJu$IdRVo^X|{;eHr-XuIina%icYlU-xp+zZc&-<}9sywPD@$ueOtS z?|tQLd);03^)9)ydG}sy`**H(-|Ituhb#SF`~7=;pssYH-TA}!-o*3uSM|M_x$pk$ zcWNp-|7*c3aSf6qKWb!z$jobxA*_`g@$|7eN-(N_PXqy9&A{l2>Y-wWm(Xqx|HlKsyq@js{a z|LC57w{QNNImb0 zy=3t`-TCsr_uT)z@Bi;*{8!%Q{pR(5Teai&f%$((c-HzxV$CeW3sC#{WOJ^A9x2|9dh2->dcKH}@aj8vjrD&fnMe|G)hI z`}X~(<9z>r{62YuNr2-IuS&#)2aYYAqIxMeEZRVg z@-lcu;8MS(QCDAuu8G{5_w<%%1}iIff>c0)!&0wC)vN1LZ(n;Gy(61n;lP0d?_&38 z2z2zU{`TbNR4$V}J(io7F|6+t)la)~<74uPiK_m~Vs3s)J)LlLpANS~>aBeXUHjEy zrQS6#Xna_gc9y}lfpMAYKiktm&Hou^7-il%a_9E<{09eH`PJj@{3v`9fB2DF_BG>- zn-`b*FTeM_;nlU9pAW5DH({ICZPp{N?dIJ1Q~BlP)%EH3@BRHfH^I8+?VmlrYybTG zn(ngT?*CVNze&H?J>`(}d4qcof(snkq)R?HawyBJYvK@-SYvtqN28wupY@jjy*K}a zFK%UKQ4vsO=Dy8q$QfPo(N!XON}(I8>EiR22h9EyxXV?CykKB=6Pd)w&UEAbkppbj z1+G%FOFnsOES^&2wM1hM|Mp)+UkbhTYFUa-D0W}rKPjS}GRe#2a!9eS*=-ZQeYXv^ ztoOEhedfKl!C@Uid!uL}|D&Joh?E34ahra*u=vUD|Ax-8Z%e*hyuh|X+utDY#^&92 z`Ya}nF4kYa1P3_RUUWNGV6!^VBRF*G62@PPP5fDs4qw?Ua`JIVNKEn7((pKa`o?P~R5S@Fee zJ#%`)4ko-RPv|keR+0Rp?yOK^YwG2Ulu6!ZKU1d2%U!$Crume6F{4#Rf>PRao+-(5 zimz2ZKV@^yOf`hpiIHiMVunIStDZxnE|0>22Md@LW;mpFu&wz2;AI!zg{tgzyTAR) z-k`54wso3j?^G2LqaO`fZQp87M$UUYtvYYl%1@T}+N>g8HnKZCSl-mZbmIY2m&6Z; zH;sA^mNzu3JWyvj!kd+l+amDe_M#1Y`7}SD*`2!MjhFi_4Q3Xj1GiMV^i3Gv?TRk{ zTY95(SG8Z8RYgKEi%7eIlDFon2Ki3zT?g{nxTFp=2q?sR{93a4|C%qR^K5V2l9%}2ARzQ%c_XLHj)b&&i4_Z&Iaw4m znwRpjF#eu=U@4nIBX`Jx3g#0HO#i}c9@;Ko5Y*^k{xOl^>Ca>a0gnflm?r#Z-v&02PHkpPJmP`j$J%+|Y#Rtr?CmcSoeKKVeaZq6wXt<_iu(dZMm%;W&<51H)yaI`M@wmz^{@8ZH@dqEsWZki}_3|grIfioCmX*AJfzx6Q#}Q6g`rJ+~A4 zpT|(ZcjUed<8ywVmIOxT#>Mtb0```lm?!V&e!$f5yq|xM@PtJn&w2l+dDQdpXLp@R z;YeB9DBvK#IGts=+2ziY2Y=`@{CVNv=waE=Ah&=)qvHT8%ZEl*g#`>7s~VUM0vh-= z9%S#l^K^p1gZ&)_1x#EY;~2y*EoU)UUUO+lrf;s70c*_5gj=VgIYd5OHoJGO=f;=g zOilteAyO;n2;68`x+RQ(^~d365(5vsD_jugue!$W*O ziWQ_L%xB=7b*=Dj|7zLZv+gplw#qn}sBk76;D7#!fi1%!Th=0mwa@BOE~mqERoTEb zGgeGs_GjMsy-vla`WcTOY>{b2XDyQ z^nO;3^IL~qzxH4I^?2(ajYMW1n?twco^1Pk=LECTg9hF;A&e8O53o(hOkkEix#M4g zw&N7*D=j<@_3Afe9p$3#?sE!aX7zDkRVlFM)HuMn{qWO6V$pH`-#p#OEy?hZtBKLa zj%C?L+sU=PH7P3TX_gEAZ(Zh;b3A_0KR%Omor^){6MUKuw5$sIdc3K1dD!7^<%g18 z^Bm3YByL|fS$_Ji`;1xw4xME|yJDRWT;Zx{;8Zx!qJQNUyY>cgjyI`Heov)53+Fuk z?8L(8I4!h4@OzwUs^Q7W%Sx8+{Ny^XO;GW#?>pt>SfFGJhEvt+n#}GyL=%S;ZS~95LSW$WVyM$-z-L-2djB$6}TIYh`b| ziBMGg!p_AJy@=c6_9l_y&a-)`nFa09cRUZ8T#!k!o+8z9`6u^5wt!6xkyAI{;xG8l z#+9&yamQqNX^s~T0T=R8gpX|4l=b-kyklz|_bzqW%ac*=T$y`P!9U@?#zs@F7kl6L z-YdN+z5e5kwx4g_xXf0PpRlle?O6uSkh0qo)IOa(_P1!paT`1Ddn|hs6+Wv3!k&plV>!A%-PW=nE2XwN~ereN( zSD(B+zTc{=^y=p6`c^OBoc^=M$Nu-<|MfjP_s!3={i*Hn>cO5{%=@pO*n4Tm8fMG= z|Ck*(=Nu?Gxc>WLYvwZtE-kQUpP_WjXrb1j{eK>tbywQ`TkIg>b1<%PGB=AdZ_UAr z5BB`~;GoLncwx4vhqmKJ3rEp6j`CkbxK-??HajZma7v~yNa;umT-o@snfv)|$1lGf zHRm`vChmQt?WEJwE%%Ed$aXcVR3Iap$>lR7>e7-$gf#C&%*FxL23( zd>3{vy5pW7>DFg)yr1Jl!VL+ z-Z@>h=Jf7Ar`>9-_dfAHB;%%C!`toSb2P^1SdP!}8lMwAJ}2k+oLb{^dXLYUGd^eU z_?&y=b3VtXQ|Qb}9iK~cT-s$0XtVfSJ#z3?MyC-ar)al94c_cDdMGUwd$J?H*U-m}c- z?8`TP`+pogc*O5x%KpnK&hKsfyCsZvY&rKa$N#})zbi+)zRdA&DLC@5#Q#so-p3_g zzux$_C!G9n#-Fj)=8ux+{V)D3YyF>{Im?(EFeCZY51#fEi>N7M`ip>~5Kk^v{vVf|c|-%HO*ehwInwUY=yZYE zCV^3bH%RP4vk?cY_+0Vk6b_aNoNK0>k_~B)+8e0(R)?>Y!|O+&;@L|ISDT$IF0~r! zZlAy~lVejG!-eO+S8B`+GTo~A|7^$Pw^RI^I9P2j1X+m&*>?1?d0guGaH;BK10RF+ zqymPS5)1!}oZ&KTUID?)Ux2H6-ib~ZGu(CXllP;Zl9 zsJjrnK$~G-rbcRw+|x4+tUrQxyy$Sx_C2;*fcHm3WL8MbUQMCeV9&Letu0!EIKl!e zn!S00Yz!`SoM1S1(K25$bjwBMIoc}i2O8KLg4d>m&Q#Djqs_UtGgPv7^0CvQg>M(h z)E;dKVDZ`8%=?19Ecbey2CLQ4X0?#8lQ(7APc(4O@a$Zz}eDw-j#|FNR2CfSn z3{o7?Ivin29Go{+Wl2YTbz(@4V(1c5|F3M>z}>-cYUgzxjT;+N6lQYV%u&5EX~7B( zk6qJ+&M8TUPrIu0!^FRpgVjouwS4W(x*HAV7cN=1a#V@R{WR46@_B7w^CgxW47~}f zIRhAuSuW?E!LaGaTED4Fn<^N-u9}|3%@}?3*3X>^lZ@9KmR!tnz?3UVi`BwrZB7H1 z;LQoV(LrC?4PzOMa-~0*2v5(B+`V<5mr1~rm=4Rcm#jBL&OREcyOd>KY*^EdhWVv8 zI1A=;axnfEyvjCt&5DogHjg>8X*UF$~hj884@c#$a-V8o)MXa=Ap6#6@tXAJw zHuyEg2u)gW>*@l2hma>17qHK0;C>O(RMD_w??S!=?fY2_SEV{GpIv_QP%m%CtZSur zC#-GwD;Sy0yYXZ3rKwkAe@~U>XpL;q2vhjl@*p?7Jo);Zvq6VU;|}}Ic`{f2yJcLu zsn}v6!vj(c$9(669g=VIV340Je=P9Y*H9ld-glZeiecly z%^V%^%?S)MZ(P&fbvJ)^qU||}d#W6E7cTARy{8byq`IM{>}kB12jkuVS$JIrtCK>C)4rCnrI(rB9iO-{idU(l z@MA}Y90%i$rC$$if2(@4=RjKKySZ-0a-pSi%(KqTnccy#qk%hPz4bZg*mIBL`kt4* zeB5~8N^rt;-h$TD1lE*y=}rvy7`FxgJt4d3(PiDrL%*h`x2`+jbtUniO^4jMARC5z z|D6Mv!Zx&Yg)uoY0YKcHhsHW<6dTD2JfxSXr2(+nRY$Q z;aT#xbSJfz&=>510xu`Kx&8LZm?rlsw(zBpE5}T+Ae$HLbsQ~o*0I*F%T(oPaemRR zbnepJznKwFPMuN8T7PV6{JJ$A=Yp1szTOv^sBY~hOV?aeh}Fa@_%jQ7PBX_cwc-g z>tOq#yi#xhpSgNC@2(>s?fy+{;J;xl`d;YGv0RDzB5})t`VX--JD$(tWzm`c%Hcp^ zxokpZYlqzatwM(z6xOd2;{5!-=F`5_f$o-ZMc1ZWoKZEKvAxJjeyWUIZmY)!t@Sr~ zBv^I+f3`GekV$8ZeGz03FP$yi$@3w`c)tACpjiwZ49XX|&Er4HIaGQ!BUF!TR8B`-Hq_?E(=#hwgEoXejwS??ys{(CQ^WJr~%o zE>TlIEOoCWsb1jXw=c4~%|SOB!XL0$K43AaU<~GG3Hs1z_<`AS0&{#qOWg`K*99GU zOYfY>G>JTUqAJlY;r>K5{j#$334Z-oI#x8~%>SNV!6dKH>L9=vG=n95e!2aI2PMay zwsT%D)35kiQU3SqG?N$QmK^Om3ax=2U;mrTD7SQIZV-22-;>*X|3}!K;*|Luk`gUB z&s)SQ7^QYFB^H#~N3?9)`(r|6d0&6!=NIo}*?ziiXj9(Nr0mdano&9Zx%F3-ikb4i z9#lli{;k|5^JAS_)w1`zn)^Mu4VYZ#amdVIvRiuJC%6Z(d!ipwDsG zaxQa~!3xg8#^5566BlBORG7HS;IADH&?*QkGB zUoGDuqVc!=LeozDTHP5{!X3=l*4J`#{5kl&{p$M*|F?_$I`Y5vSVh&#A6qu8*NQyZ zAgJ_lOF;t{M|ty#yTa^`c{nze?EBMrfyrP)l`u!M+J|<7fF~FEn|G8q?K|Fh`FfMC zN9~pGO$Ilb_Izho;%GK7Xwv+^WbmO$a|YA*?@jO6Ih6hWWk);6+z2`)^=@nYGKvj)i6*9QfcZ-I&Rfs@G!A~ zk6CIF$Ay9etsDL+#O%1>cqpo!Lyzajfdz@|B7)caCR}jLJFLrG_`fAlLj9eNEds{XN+-zbs5aST~=zgTzUOAdW>LL?c`!wCPCqKP=``i4( zM*01HI*|`{cumt=Yn1=)kL8yamzD>w&--ij?ai%?;Q<>QoEg`}b$)i1`BUS(IpeBZ zi*~qE8zZw-LWRb|HO*WNf*}bH>KZG1HeS1s>{pT4lH=zi*v1(rQZmz7?8wCc;a<(Q z1c|Lj4^9x9W+~{FvsrXWYqLef0f8#bRXdcr1$n=VdaBKx664L3Q?A@+`D%x9zwNId z$`c&9W(Jo>wO$vke7comP8HwMsn?y~nU=&mOPV~mz$lw>(~%|bV*4RhjTh|yA2@3^ zTx>~US(dOsIQP*rR>9~88(Qb^`5G>?7oGINb+(_$!lP~fBsvm1w){*!n4RdeQDt$j z)vk-(N)wkH^weA%@_53`tX-PR=T`mFT(PjrDtKbFqUXHI{-P@>&i>oP!<~6+E(EY@ zSg<@}WMr9eF!IaK%LiHOC;pt!xM5{xi}+mMi#OZZ8ZXXp);zLNBROiz%?q0vvJ%y{ zDx7k=E#hZTvUb(eS+6seDEdmxTYBoIh1Q;rr>yn&e!W$_zW;7%!mElSORL_w3OO`f zW2$8ReU0@iH(S=~G;=P; zF|pvBh5rL27I#L>7O$z|F*s+*JYCOYa+ufSq90tgCKp|$YfUaKPTKl@;@`Tj>(2?v z7C21lP-IAD2>8{^#1<9*_rle9{s+_S2uaPjNdfBrEMF|_ zw)^GWeY_%O_l%@{RXPZNJ}@Ut9C`thc@0&!?09Y+v1|-f#E&?e70} zeX~B_-ud(E?fU&Y_nOc9U-jX`@Bj7;%o+z4Oj(ur<*+=H@QMTXU+C8!XuiXstZ|St zXhQ9;S@YkDtvJYAbV2z^qw}&}O#h z;c_mCeam-lXty(oXLf#}CnL16!_8()6hDv}Em3mit`cEIaE-L(v~) zg~%2=qi_EGZ8j=O50^VX`)DwsuSSL0eg(s=*z_Ajha)lqbE4_>wD|Fms;mhSbs>g9iI)AWUNo{AJ|o=Frc_Q-jn zC{t#Gp6aNnJm`yeW%foc_Ku}c=O8>3YUsz-TR|vv9id=ZC~K*XFbY1oR)_R zHf^5!P9-7CKx1F=rO(})d(v4|1RBaeZJz&cOQ+uYm1nAiN}6A8nZl?f(8&2`^FqGb zCt3)SpLQoR~FXw${NFZq}xafGS6nzT5#*(E7$NM$pgj8n{KV$D#p6@sctmOgjgPt2h5BD zI%V=j4IyjI_a0)sUBJJZVd1O2Qv4|I4q7-w_ZG-{Y9FgRUn zVEQ86cv9>E)4zlR%whsf+8@6kV||bv`zJ7!#e(4+|BfI=xqv5HtqP2MGd6p2U6{Zv zA>}1t!Ivu9@R*N(fy$B?Z+0$$MkQwj7Qr9h&Nu#K$gTh7#hv!~fZBsBso#q)rJrM9 zRuW*6y{vW0{F^(o+=a$Oolxana@x=QT8KK3@0o92Y1G@ ze*(;NQkhsIyiXfmNbW8y)${+}&X`u(usbbbqu{>p%ty2sJ-Vm9kZt=FFR_4e(*L83 z#r%%73iq<3-RB(7klbN!x8Vj`NiLIty#vQG6VrqCPd553pLLc0UkaOa9fP#m>~$+& zJN|#vuwhb`ulWn-`Fl&dO^#|<*s`_pHF7+Nu;*%UVEgkXwe{a^Mb>pEA{7(FO2amb6f2_ z9FC+qI30{)+!DLy=S_y3hVNe`>|Hon+zza4b%=OyZ+>m8^O1-u9Wka2_dIiAoLOei zsEs-2*ubWBt2^}C(aLZpl>>y@)ty*}`~<>(@lz-;T13|6(2#N?lzrQ|6HDzn{$s zit)@Q41Ww4hE9xUxECrLb~0f}*rvHhne12;_!#ULI4JH)yq?n_)5glCb(hVKWkKx% z`-}tOh7F7p63+Z({kb%OnMaKKSWK-$Wb~8S4Le!cI%c;Q%0=Crvh(W#L%HOq6Ylwk zwse`gWGXT=@10q9@U$b@+4g#&X32EIu=x zBNkhAB^8B=F-b76y$N+*@IPkLN@e>5d1UK! zab^XF`BS5u(iqs!wKB3WEO?sqU3LkR3K!c ztTH@uUALw>Foe4{tgUHavyop?st|wme>9U$Lv+WoG&>fqj05L1r~Pqcd3SL3{H3ik zUxrPcar2iZ!@^WgraiOlH5@$7#W1r(pF0}s`Dd}SNy9-`hugKX*=eH8BG03CJcti~ZBNVFN z%;w>Xh=1|4Z{dR-b3>OYh8V6}+Q7GB&mV=|o-Nm2Om}sNSo3zNy+cHtiblO}#uS^3 z>I9dTpcl~@8ZAmITrY&JER}7}%V?dpqBZVCr7yo_QMW=T@{Id(n}# zqUYH)fo<1%*_3;Dlp7YQHu7mstm@jABN%gV%Hov&&$XE*^>!u%d}LF$Q?2>Lwo`EN z1hJLV@?Q2!WW3Zp$zXFsIk91en`WfOjKr_Y4IV9c&Z;qepXMC3iRBGbqGzp~>vcYL zuFLGS%sJmy&Ub5{uBY6V=gVuvmAmW&HDBoVi<+#CR;`t3tGA_H z^q-eCS1)76K8aPe4K*&DEsiNc>t1wiT2bY?V$q`&J=@yrp0-|_`Cp@JpKFU01M{@k zEoBSsIU4qVTD|L5_MT%>JJnw7*18|%qjn?bZgW}2lq*Vm_!5IIskJ07?XML*q}I1p zNOXRl_K7x~0FC6BP?t-LY+fx6$)-1%8I)^#UDnAQU!0r3$a8DGOVszJZ??$JUvqGd z^MfVzUvD(IN@=>yV#$bFzgB+3jhT!#4E8G?Ft<3&vy5GG)t$rT*1EFD$(z=&RYY@y zOq=WacFmEP=@0L=NFKQU>{?+9Ln2$`*{-=WS`VDNnsU}@+R+S$D^jwlnzQ5mMEe7I zAILso4{_jVX*m7u2E&`TA&gO%{iIkvI9IsL*?u+Wa^~A5Qx_}?Ww^IXs@_uf&(!}e z_v~KWlUi~v=HCV3g~r#`NRe8kUdjJcXW@q`5dGn4^4HV`NQk z6-BVDd$@CfNO;5Co3nai3qKs%bhRn{*1=C#SsV}oyDVJ4&hYnES*h$- ztM|=dt7*_>kBm52R9rY$PWb_}NRchH1wRfLpoV}Tu zTKej(OzKf%mU_`J2hL4i2eh|IE!N$1R`V%?%YlToERhqn6ShaGrt8HUmaLxg$ocfD zU}<&E*{ahP<+=HPo1bT*RlddLNW3+lkMFMkM=y#s7-uK-Z~ek7abW74#QD5#v070~ z0lh2}^z!9&@4KBWbrMZgb*Pnauwj1WoWc;N<8a3})VD0x*DbQPLN(eX*PShHiR1jO ztw-5q+K+9T$vUfjE9-%_sbwDLju*~hjgdKipr)Mh##?qCpFcal6?)8_yYnbZNG9uy z?+YzC&wTy5HByT0$<6gg?iQYT-@IvNroBd==9ExQH^xJ`L3U5$=D7a(I?Ytfp(em2 zrCBT0|HlfptdrXWEbnWoOx-X$Xh(bg%GS*V+>TG)XI3Z_{y1K@qf0oWV|f<8?DWq3 zikA2U7Rb*&DpA~7ou$~?X#;cCb5@?2$4zGd%Y z%cbSh{uF#^baGjI(!KI0Gn+~rGmp<-rD(=4VFx1<@?F0s?KN1U{FB#f;@t3EYx2cr zxKF*m_R-V>4=0y0>}mXUJk{ahsiSNmr><_ST7CIv`@vnD=dCtBpEBk4uj?=GP4<0o z>g6exbqOWq4QyYg9Tr`*QGR#l^`D!}t#_7(tc^di)_eE5?*;Kcs($je`EvbN+4MH% z=egE7&DH@wD|WvB?IkO`s;x3jI)?p^tM4@4R902DF6lI>n!JY*Of9S~Om~YO@1FU} zdVl;MXIx{YGQ)|1v z%eCj6?ar|OuX|d)FFm&IVz%A2@Gmc`_Pu;B_@a2vi~TjHmj8IUe_!WPyZP(uju+d# zxxbHLZqbX&|L)FS>gsRzj=i2?>(e)%?RMPW_d$I>>r2aL-FrV?w@aD6@00)jT}LNB z6#n_Sy5_aD<=686YIkcs|E~QeU;o8_*Z2AMpVs{S5p45m@&4bx6@I+`_se;|fmz>| z{r3O3`+uGPzmeI6v5uMZQ}qOg=0+w~F&zT|#io{eVa>2D6@tlbz0#ItUw#OtcuZ9G zoMuuflOIq}^p{9h z$jacAVOy(2v%}U#?<|YeD)_&`v4vaGszoC(J!)(A&26Q>#qwfy7Qei6zx`@ASp+DQ?G`_){0sKWO@yHl^n)$aAaW}YiM zEkf<+l}&4W>tm09qkyPc;ibk_b=kIIU}S!Zp2K|kRwQGCI7!{MSN#;<+Z!L>Hc>w`ti^E`7Aou_R{tK`Tzc3 zOJ1p+UHN?L{kn^HR{q~-d1mAFIln)h4WFF-@?iXZ)0NVv{{8v!bh=Q?_3m`}|5uOH z&;RrD@BjU-kx?8k1g6_F3%O-1do9l(dE-D|uUasR%>x$U&HGsooNtu8vYU5wae`bconOqrCw0-0`JLKkbrV-~1+{&e#*x|lmZW{IqRq*wT? zp!9E_muMBPa0}e@aq3Q&Mc*?lUEKPDrXPHxs9zoFee9sX1_hD4$OIGCV6ANg5RfE$9PVc>|5cRH!rwu`IKafjVDhl-9dnvn&0+FZG`@++XtFBuuEduWR^F>$@vV*$ z)js@Z=auzwSLa@oc^xI*c6oDG-jb};*D()UkJkVDxVEc%wdKoO;rTmF*H_&YH~JfO zW7i^sZa;3FH;1w!j&t!hb>==wl6)O9&rWRfxlOAhHKTXNKQh|9u=T3z-dmCT-->Qo z6so&o%BrBtRo6BZ#lFpvWxa8|Ztcb`S9Ry-RNr21su}Ed_icgn)#&^GbhaM}T{ly1 z)s4r8-fsR7^RDEgi~mA9@vH;AGe7otUNR8Crv7hbUOehz9pSgDW&MWi8 z_t`1wx?G=qcT1W1zMrvy6|b`mHVTIAXPl;=boKx3_zkMz2RQHfXRptG@NrxC4z6h% zS{1H_{<7MU`sC>Q7RxgSIo{+ntUFrJ#{T;X-~0&sKe8WpF6vGaf9`%%d!1NQd`$BF zGuL+-ExS?go0BY`t$xgkFDl3UPqOlJw+2?RPt6}!3e-*gej@N+kGXZ_L)Gq{LcwXD zrcIRbKDq4EDfY)7Q+LKZ({v9znfOh=r8+0gJbl?3rLfO?GybGm?_b)SInQ`sR86}5 z=98zZ?rlDL3fxN=6l&w^J@NE-+497c>nFQX+F#{4+`@P4zezN zSgpM0)-O4W!@qr>v^&qa>A0+7l3ee{@W(SQN%zk_I`wT~R`Z_AO6x7QN5&RS){nlo zG_-Qz1lzaM7fU}qI`8Lwhr6F(1u6k8ST;-7L(#Eh5p#Q8@kgotJUtyYK&( zeP4O~_r0Hb<;oEWn!yd#_c`M$4$IGbZxP|eRnSmz++OZe^?z#-ecRG)=6B!3_g4#B z+^;-eFZX4k_}>?v@l}`S@B8dl&?Gh~sZsXdTK4 zz6J>XzNPQ~YO!-F_u(R@2NN$Hl8dUtjm5*QhRPs=emr_5Z%ke^fAixBSJVbEhUa{kzcr z@6W-1eysl|IQ_rS|9?)>ONN5eyUuHJO$eK&uPJmPMC^lx)CVT{34T}B{Z$dTlyjQN zpOtxbKgT@%U;jV7|6>>MSBhEj#`^~z7hWX!UYPQT#aET}Xg=%7^{kQ~SOW!Ec4z!8 zIr1rI!1yfor}1x8SoGZXbBx>hQ6D(0DN+=*4Pn-hG7tgX8-sak<-J$T~Pb0@R1=bZwT-zQngfa^*O%h?u z7l~ZYxvk;rEG3>51>9#04AL%Y$p{Ml3=q2CER=mgh&AxRzHV{^E#x~X#GVwW(I2Q3tiU64 zNp-7(#@q!`%^TFWKTuz+&r>6$6d$1EE~xQ8`O=?n`Qk?pUC=UAUz(%7cahZQLcYBV zI13I)Jr!hM#_)aGCq`Xi_LfPX=M?fyKCi7GtSu&_)$>4Op%BlkLaw?=U+XThs9e%G zbU?L!gYM3alCl2(c3jkmxGdkZNpbOd&JCZ8Z7&+KUS{Ro{6CA&Q26r+vCZ05MXXyH zl=e1QbsqTo%}DU~N5C?dRs6l4yX6F6tCH8+7bTsC3G1fkHS<@#$zyG7w|NZ_NjQyssFUucd(4QG( zCpf`&yMlT11oPU<_O`}Oll>ifgE=M&GkN+szVBK6?W|*8PZSEJ_yTTfYCSpr3b1xAV$(?LFThRTzuw?3G&V5F1jhEfq zgEbO`w9W`>&1(1=l)%L$qN(>qBPuXpvhv?a!LO%%_Q>^NFgA2zW_C87thfIC+c^$8 zca0Pm_W!fk__3ftX{El*QRBevhg{|^`sg%K>8HPZ{&|fahVKzVl56?HYy&?oSTAQC zWZrv-$)?m?v|s6Pen@bGFnfu?+(X*3mzjDdD$d;cZ-J0$L%#FeMIry4H=Nw{_L0=r zqgp293!drI#<@{B=QFILPR(sqx^uCU>(5w*GY8Cfj)`toPMz!`%p5yANhkJFyy6%2 zjlr6aLjRmFa;+BT`L@Z({fpr9jVXs8FxCqx9bCYi$&^@3W%SUbO2OuQ=5{8>FwG^%?e$J}l5jQnQNL9zcE zO?aMNP5o}M72 zdtQ`STBbDhQwT#jBdO89d_C)Je$MxhUQ{ zM0@|1AiZxzGeuq37V1p*muogsv=@*1UzC?>;-pv-$s5EKIh|v{X4^;;)8GT4^OMRZ zAG*8gYnkerMD}JYNdulKMFz9VP4lNkwTtJqo5b#)Xy0$BdooDj@MVo-L2-u}KICjP zES&PyXY&7V;zr@y3uKKnIKTh873?e3jLJN;E;)lm@9{35D; zrTlt0_kXJ!u}6!YK3>)M^R>D9daJJkw@(7sopOcbY4)s>w3N&Yf@ZYzFN(LF@iQaB zroP;u=em~H4~^AMb++MJv!)et8@uP|HOrb#bMlc`ti-xvllgvqUEKpF-qWOFjqER9 zY2Y_<`F=@s>9&-u-$K7nN|pbZ`t`EcUI!y@vCgT7+V?U^#}@UiHY%8Gn6@{fH_*9r zqf^@a84kz7W%G?V*GKG^GksDLQ1(^WJM|j(j_VqhMy9um#bye*2VXPVYwWB&xhnpL z_WEMJ$Z5v!BlI}8wZAkstP}bsvq|FrxrV|@t-F&dIF%=6PU>oIN>`nkSQt=#_Oi*! zNm{Lwel0V!SH6<+|NMvFhl-`#D=i-XD4i>4=F5=!QMjiyxGYoBurA2vuCeoV;a?wu z`0v4uQp@SH zC!e3KZ#>Jqe46I=IR_nVk`3qPUNO2j$>;o3ht4ZesRlb<-JJLN^lO!Htvv}Y(@mu< zBO6#}Sxq+l<#uzHyNUCC)2WQNtY&YYz5i$TZ$Wm6^*J9W&*48jr`~vaVX@O=hlbBW zjFl3Gk8V1&@3auvw!~E8-;)AQ`5F1q8I&#&tX zQ-3aB&fxvMQgZsDese>fbC$nYB)|V(V_Cp`E6M&A)5(6{=9x` z<d0oi$&|d50Tq>H*7Hv*pL{o?fM1_p6K1II-WPn_|(7WYMbXB-R%*1 zZqNDZNyXuNw_Euwv-VpR9kA|pj_a?gtl9gfo}RtW=lLY2{SRICKWy3m{K)>Tr_0!7 zRy*pj3(nbFyPF7 ziyyBRdYiH@^3U#^)1r694&R;4c>m@>-3acrQpcyCe)%Ne>%8cb)90L=edpwSnNy2n zPA#8vYWf`E+1r#?$DH0i=k)G9rw)s2PP%jY_?$DRV@`$kPuv*2k9E(9@Yg3e1OFTf zJokLgxz~5jy_Y%vIp+NLIp?3tWKBP`^LNYz_PH0h_nx1>k&`F(qWIj4-(_}bu0NS! zFzI&8)`H&$F6zBis^!-FyKQ3RUc*{9p(&TSwO=^=b(_$8!hP;Gi7Qw3vK$JIy?Qq$ z{Y}dKsMxYwua70iZePQ9F8A*>Q`u{!wp&)LIaFKwVpH^uUYqXjyEp1<)90Vz{XhBc z#vQwFIxnfI4!b44`pW9NmT?_tH_u(CBl~j4-z)tF*ALomHrP1n&AeL!7#OAQJ(QPwq#XB1 zecmJOdyn+x9vjC!HlO#{`rc!ExhKwXPu%A{QMO~?s=F*$cf&UJKD(T4+}{VGa;vV` zZjaq}XTI%?)Veh*?mqi@_g-n-?YFz1R@>DmSl(*1TXkgai^}KEC(gUFdDr#n`(E@` zUz&Swc}3i-`G*VF*4>Ky_i|I6C0Cr{4({fi|6W}$dvS1ItwPV6jd$PlcU`?2XY2C! zt+v+v{c-QE+gPJ|KvUhu6W65|8SvP5dZy%Hk)Un^B3LQ z^YMEv1Bc!3u$WKPr#`SgX882<^F*aDpO-R9J$Tx??~VEV#ToxD#WFM6F~svNV4l## zsB$1dN`XmYLDjXG&!PX9KALAKkie+*Adc?=lhOkXi3G-+{RvVF6n*yJ*01?95D)|Jk?pbQ&)|H`k{6*B?%k6&Dt|bW7QGnXJ6D)MvI; zEtl!4D=R}*KUyP@$mqqa?m5ZjgQ1Qqi>!&;3x7F0%4-q*JHh^|1zBoo0c-H?vb{xCPrKiuHo|$c3&u6>q z%gZaPqxZkM+3@W`OCfWA#BTHE_fPIBNoX)GY+18cyh5M-`ZAMa`XWC4ocPnE1`I#i2I0XMPW?&Qk!pb;R zLo4z=gJ0f^3ygdY4Yw-F;vyKQv^jMOPK)eX7T-YWR%Gq!zs6DMY zd)}!7tbD8&8W(JG>u_LM6C#&sNYVovC8*!=mHIpgHzk8Z8-I-xl&?;x9ePr;!SvDMG|f1542cKE!T zj$sYs4KLLLZJazS56RW@6rE5qKVGmqKD4Lkl-Bw?(~cdp_4;ta=)6zySu^3Qk?gx> z*<9MK^Pi{W!tbm#+GqaV?J2qBrT@3^+?%pj>n;TO`)-}Gf$zR~rj$ zx}OwZO`G3a78kzT;={$b{k-LOio|zhGA29ED7#nn{_n+GZthm)51RP>ex#~8*BagL z(mz+xbw8`2^mc=|U**$j@qJew9ydEz`8+iKY~h(1{eD$1mub(CT|T?|T-E>ATFw8y zzua`*@AsYAW^Y!$-t~Uo+SA8UW2-+L;@2;}TR#14^`}#l-M_q_B+Os?<&t>K?~kYB z`)zCLnC)x7->bLJ{Mzw-PVLWU+qeDw{9yh0y5HtaygTnco;>#xgM@be6L;n(^Z)<+ zc5lD%@^dIWKFgo~A+do)?gm3w;1lN3Q_@>i7#P_Z1k40ECc3jc;uq0r zNad?p!1Bj+p5@bJl{v}^Oz|9tC5qe_6OE@Ge#G3!ZLok*zQEtz$bfNgsRNVsn**$7 zHzqJPyii`^6~rFF*2ra$(6#^PIhLeQDaN*QxkhgmiSL&XU4GM`{6pgbc9BnwzYcJ+ zN*v(+A7RkY)WP^MF7L~NE3>w);@EIZp0R)-^Tml{p^X|0^)?-Q0|bt@m`v0OSR>~A z=t)Dz-P8UpZzi*A&s4G3dZN(zMVM*bjzjr-d#V$3mTLNQPGQY-uUoC%FDa_jXl(SF zS$svq#3xRt`Bt!Yuzy%+FScO)f`^8R2bTD<+civ6d050)$uyB!tYJb%U{9o|Lt2N} z&m&U;lTg8bRKsJ1M{U%?tFqwF2z;GGxzIEk$)Pr!g$}Y^5{7-TJJm0*DUID zT(c)p^1MYudf#$|e*z7B^_KG(Ll*eG{Nu27QLD?;hk^cr5)S$Yg&0_69z^}KaA3K` zv4}C`guL*CbSJ6*0xtC_3;b@hJYtt(2;MM5P?2Xs*cKki#^!{l49q(i*!Vs$Zk^+< z%bn5iS8UdVh_*=vzamvNzj9t(QFbwN)#v5f$_p4+T@q{-En^a% z!N9g;hI;;?E1M*~E=`M_Z1-Qht8CAczw?-Uf6HF6`^+$-RoE%+_o;WSsD4VUGHlON-Hn~i%e|&V@V9?@lR1~+sGH_CVP?~<6Kc+HesG&hi^0+~gDqpt z=?q5(X1`7Mh0gvAZ1Z@)#JAw8B=hP`TeMtScn&l)Ow~M@w@Up`O^*X}z=gwHlhd~I z6&x^}ZZ(HRWO?f%>y5MT`&fNkC~i@AHzk1mLPM66>Bf^&4L#>R^g3sRp?mGgi({6#2FIvZvCA<&qN^H`E+x zX5R6LT_V8oL)WEBmA1`m^JOhoJ<`iMU$u7YvsXLsJGE^3Cmq#M{xvd2yZUCV@>_{Y zUG4bWr#%m*%vmDKbVXVGcjUfgWAT~1?+Pya^z8PVvxY}o@yXL%h8KQGEZc4M8Ot

Q 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 _?{(}G1fi-8Qw|VYd z@NxQ-@PHYLw|drTOy1QoGxh_MiNf>~-}@t?X7+e4GZEMz`?HH%vwKU%ynPZIKd-6{ z-8s>ISFLn(-BhhfQzN=obkx*G^)8FpG?}CG^p6^~-4k@JH;b>X@%-O8{X=-y_3It% zGd9I_bzYI$WLn*5lid(3IraSa<@+tRm_%%m=$Pc=-4j+W>hZPhzEX7A|Qy`$&$j=tYJCP?p`bbC8r0|#pZ6UT&|vv%*Cb9?8! z-#Zsb?^ol- zz2AEu{FmPM$a>$C=zY(s_q~|C?^XBSi>H^p`@Qdj^!`uQ`@cl*|5m;K$L#&TcJKdl zd;h=R`x#{V-bx=}i8;VlbAV&c0j@m8OtI#W)}BK;cMfsPIix3Z*vRIvNzXx!fWsDZ4qL4`WU%M3 z-Jin_dk)yj9C3*`;?}d@x#o!1o+CbQ_&x6&36MEz_2)=X%+atjheB$OM(sH&H|HqN zhocEKhd3rMFf|-tHelqoVVG&nz-q(5Tfp#d0t3&1qsnuR$_6kBO*k%4a7f|&G~ck zm-XKIne;*iG9ZqeL>6;V7AiCisvjD?Y zv(q_Rr+3adU2k)8-JFxFWKQ?aIX!jF{^>TSxdIruChWZbg|UE#aZg$(Z-9Z|hU4G5 z8LFnn2aY4$tJDm88h|d^oyC<~X-N-<$uf7w^91Uv6_f{?9oLh1)MA ze0T~NF5NyKZ^O9H_WGr>{1f*wu9WSQEoX@Qdvs0cY1Rp+ZhSfVk?msJ8-AV%4F4Dy zr`JAU+sDAPfpN+o#(975&X8q#$Z>zR3`2YjRtIl3BT+8@S;x_w&uw8F%o9o_L zr+Z)q&z*z5S9RB&KB0T>bnT@e-n*B4?=mLb{btQ*w~Rqi;iBNvyVL(Nu-2W5kM(Dp zaPfs~A4frPZtNwmJO&Ac`)q#qtKU9+C=sQx_lftr8{2puus(P?FZapAAB?lsGF+U? zD0txk`-VqbZ5~dKeJFm8pIw1r<;7Irq^+j_0fbkM;h??Z5cu zq%q%_`ZEmTAMRYtePDI&@fYsBKYA`Y$=$m@H`MRl@wC{zoagwJ`52iC7WH z_t{%g_w;M+{eK2wzs@qOdv~9q?#4Q~7orI#mfdB%Xv@fLz))EGtVpgetnXp**<-tE z@2{?XR{Zad>bo2N3K%rb@oVv2ZlCw!`|cM;b$8`IFz&y@n9=ii%DH3Kb$36nz93k@ zAiRNbw%?P*`|iD!xiG7jk*(p{lDx}`0(V#T?W}#vuQ7q4H22|#c~3*WT^G>1!79MO zw1MHroa;F;m$dFCu*mfZTzJOTaF$=;N%5OF#tX-f|9d6Dcb~6;L2AQu`FH%h4aYbN zu5W#NeAoZn*Am}O--vs0tMA1fz5S6fjQi)Fl4Uq8$>8U5_S1tNrn=nA;tSq=zI{B$ zHDt5j+wEd~`?rR(G92eCIQQet;}<6wET&$4_=3SA^#0rSvIRePonesNaBb)M--qV@IdlJy&Hg{< z#vBMx&}wEo|#`G;Tb|MyP+ z@Z10YKJ7pB(f0BpxZ{RZqLK+bA~c@fC&!(aRSN@ZYv#beDYcmG!ZIKKZdFaOj(&#k3@ zy?y>Zv$lqTxhCIk+pFYNJonx`$Upek?U$~c0 zaKyTzx!Fl1IOUwv78B! z_?wwTUOZsroM7$1$nu~&cnPCXM}uFZTE_!sTlu6*p7jbV7MO`Bb$nHhs1R}p~^showw7Sua z`Fs*;x8L-?JG@w=t}Qev(2cXOdTN~&+svQ|!XMNxFbd4r$;6s*`pSIXLwC&^c*3*} zG&20~v?{n2QYM_j#&_VrM=lq?PmKl2J039XH(bH+^6|E3QA>ElIHGgx#6Ml0&y!)R z(4;BN$J^ggocKj3>FKr|@?RYeI|uE_m5i z$avsYx}br5h5xMQXAkQtw6M!@D7szhHU9f$Ld4(IrLA%935{$5KOQtsyz=h2=v_hY zhMp|e4|(o3{V#j-Pf5>xASKY?l=y&AIO72Gn=LySJsg<%4TK7WCj4j2veaW-=)~~s z1pAhP+7IcT+4Bn@n2Y~XUBZ~Q`Wn012fGOe9+&++a7dBGf{QUuiHoge!V-tAYaibF zrZzB}Y;e*))73bsh~eIs!_3kJw;6h$GU`;kG@c^iw@$3Um@Pq}N_fTr2Bw0>OhX4o zBdN=Ol@_dI^l@O6T+wi0Q}_v1jRg}uZ%HyMDKxOPoT^wN)u1VEpk}7p-dEa_7&;|! zsd>%;=D!)NiEx}~VK+S2g(f`K9q4;b(L=bRvL z{mxR2t`lb-I4od@WL7VnmpL^F&)EsN#OE)P@qz+?F&gwOwjqk_jo*0L8NeBK=|-NH>S zw_Xd;7V~KKQ~%f!n9|5&`P4-!B_O0lPIRSbXgBKx0Zz+354aq@HjA$73T?@oI<4TP zWAxQu*LrG1))lk%2feJizVC$S`s&)A*q^_`fBg{M(CphC$5|b*PAP0-zTulB+1XQ$ z*@gYzlqUElMfdcS-UXqXXFttJw>^Dk3Xd!3+K^1&>ZmKr!nQhR#--~2zO~pzY}@9o z9XXlRw>xf#ZQq^RkyrUUx_5`zj>Biy7IaqMxhEyQ^E9ts@!V*^`*z>AU7Y$ZXJz!= z>v7_{r(Jzlrg}T}y_MLWhjUZg7FpPTx>Ob*yux8##PtdM8V>tbubS+vq!7>QqEN^u z=)jWkRbXwB$^qt?AyxWC_O`VQOz%@Yn7BSP&TVyI7UXnf(z+1$MUY34nM1R2g)v#m<1L4gzT{_R=6@UxrvelN%3D_-+i1nW z6=j~RYI%u)^Z$mA^LttvgMKbP;qK-*Nic!omS<&DXxS47wgyJU(-N${dK(=C7#K}# zy_v)pe4HnBDM>#+;rL=M#$Q$~>sasaU}C9s5I0@WV6{D{R?(w#F3*PadY^;~9+Qih zUfDSAziP>1VKS4^X@iOD^~j@&btcS8z8Obs9T@*41n>Iuaj%tlDg*a~EnTN|o^r?p zU2ETyQh5JOQoTomy!|mrCZ>kXDy0ibEzFGVwGQR{fgBFEf`hT>N&N-D$ychMWZxh4+^+ zHYnd`{_uD|r^c1}yh4qfw^yELay+=t`QEzIt*05d1v<8|FM7=wagi}CjNy^ffr&no zEcgDid$J%!_CpizpF?KC9io?~+i95nVbl#{So5bb$EChY;ZKL$aoxWq0lUvAsOfW@ zVh=Kz_I@_w<2tLA>;|7_eSZ9`Z}Neo+?R6ddIDcK_kU$ld%(}~MfN4{LEbru>3QrY zb~1L?Cma^^6{3L`bQ&Uqd~&`Y5zrzg>Q0G zNIoq;PoCq&U&X3R+y`zPthYWJZvS21{qL0mo+%De%d0;L)iR1R>{u(^lg(l9hsi3B z;RtgGMshJ_b(k45QfzN6?`vm#M4R&vmEpmT1{PK2S&sLe8 zZF`(^p!gK{=LW6Tl9lx1K+{@O#-UT5)GCs4QI~W zq4L<`fb#+N)x2UNTW=j?VD>oB_+Uxn3x+7i1{Q{0JQ)Y)T5o02XfO?IXiQ)bw{GC; zICOraFsFpmq-s-z*9_An4;$7vH6HGG_Q9yz<1qIki~kC%^{pG7&3Za4dz{-=9JZ?2 zs?yjg`nfA>qnSa^edLXoi|Z3Tx11yIt6V%p^gZXe9F=nMzN6)H z=E%%jE`A{z{(p`XbGZi2Q4P{LDsJT(Vj~+G<63`m&G|0Z$T_Z2b6g|VxW=4ujlFYJ zB-1tAl{W%uM<6dauTBLJ4WQ+C%#SRVQ{d;F0rxo|ES2u0B5T07F~0D(_+U2^V^Fy(BnauxmXr4B|e~rPA@AVS@YOjHS0uSoEfx zX#297U!-Gl;$F5J3~N?Q<2le}9XXj(;KY++A(o6KG1|h}iX1!%(|Lrfrf%8D>A+Ck z=$vcWP$8^pU41y+`Z>*FR@t9$4Da7U>(RQSl!=Pbx^nBhMJNYXb)^_gK z5V2mM*}(f?L08DW{l8D$y1|fc?A_J9pIw82S-`sK#_^3Q61!>`cyBax%5+SNp1!Nb zV9puq4;xSO-(X?QJ%LOim(@DQ~pJtr1H|Pn&+Oz$@M02d_&s}LdLe^I!zQ`i?oU3si4`ukS zAwzR*L`SRnv#5yYCp`Xl?7h+PH9T?2@!hYad;Z>NU%Tn?qKM-K;uCBmn@v4lKZ@9) zvMKbY>mOal3h^0xBWISn-21g@_S;Byj~l0zB9Bx|ocH#o*(+q@QET7cT=n+WYTM;1oVS-fLj$Iw$sWpRA3Ypc^~4cCAXI!tK2gddac>@A}TXw>P4q`|jacvAa9wJgmLJekk_v z)!PgQ8YH9c%_zOMH8+-J#=WO!=Q9*6yTcpTy)-V>_}*jN*ehGFzqxzQaPz&_p&iUK z8q|5VPm!6veO2t++Sqqr@4ffs_*QK$wP)s(iKd^P&N|^4e|s(i&x`BJQ#<~t-r-|- zP-4A=Q7z%Z!3NF)@&3xwS>rg4c+QxUGNaTm{-8zz`;G@^=PJf0-+#V$?cG{IVX-?$ znY~2g62<%^#oH1KKAP?@y?fV)_kv@C(6-y$&#p@@OB54pkauHryL5}Kp~1!LA?LR{ z_dh-S-7Unid0l#RePlLEb;$?Uo)$ZHk2DCyDeI9 zv^$wAsiBtjl$>_VYFj-swo@xkig#QMH#q!w>WzoIYI+YmW>&L$8Dyu}f7>eedcjkt zdq)=Vs)RjZh*iMbE23;}cjd~pD{Y9k~Hx{-Z?huBo7&|@zejKQA_PsMR3lrPsOP0y`LRO5e_LGfD z%x+zse0rYm?rpi(B;wWgoZY?s{rv;Juk9@UBz$;$VzT!4*gXlKn@%tGp6~bf)91~S ztF!N)i~UtP_vY^I|MLEI-@ey9|2;MS{Jy=u>)83_f75L1^Z?`1FaZ4z*-S^ zp>gi4lb<^0Hn3$apEWb^^U0JnrIu%`o7PlDu~)iDWiFnX>vVSMjHFdJ)ny&DGUsuZ zOqi)tA;cBA=vd$}&82WUXG4A@}R#a@icGRcU39Zb__S3v}~k zKeX-C&D9%>c1dpDn0IS6`|Lelz3jPNS#i_2ytZkZ79LxyisdKw}BsN~~-FEVrZ2z;DyW8*H+^}o@ zu0J1X-!#_y8v9?oga zO}eG~?yW_7CHI#fPiDHVlY2TNIIZ&89QSRN&lgl5+tIUddfkoZ%cl2Ly;`vToYnJn z`}->2Z1VT3e%p8W_(8~uvAvTOz$?c7d_1AP{?Df~=I{R?tr$DLo8iy5+q)ZB86LFj z|NHS|dj7wkFP5*zSTXj@z48CQKi}W~{|`P6^o0Fi!4L4`KzE&Q;OkkyqV92!D@ve7 zU=L))Sk;Fb$v+D?Kr6<$3|iED7IKB3IP_;LXvNq=jzEpWQnx;|u`f~ND?f2q?v#JK z9S>;5n1r2z(8mssH46nNpE#m6tDw`bXOW00$5G9wk6k=#lt3%S7_v5YC-5l$-KTNP zXjWitip^rF>m80}yFT_Rv@Di+KI53>t-!9*J&WZg&p2-TE3l*X&tipgi4%@eL2a!z zOOzsKoN%?;)Y4nCM8#I(q-WHprm1_DsHx64>04FQIQP#I4Ni$ufx7}(jGrvx2=Lq= z+H`*EGL`xK>prkBY?#zww&Uq9F3)CVmkaC%c$VqiFK^MlaGovPU>V1x%;t!`^Xw)c zj!UjfV3y~Zz-GJSkio_4?is%h&*1&y$bH1Y&#;DZ&Ie&fli8Z~(q9~S7(O&gR2}d$ z>r$BU zY@BVLNBWz}Eo4>TUFp%k;rzF+mluevP4#?f&epJQN=;?YGf}Bm7c%xK%!vQsu6sDW zxo_X5mK_Rei~MoOauRZdgt)LmEFA8|N4ec9$& z{4Mb&{R{k;+f8k_SL67j@59;9eG1EM7dVPNs%$I%*088p^p)_AEMMt4i99B`2V*yP zUXjZPndiMVUTl@u)rFfr%=ymJ5U9S|-|&q|-R3`sT(-XQZ#XAfZ}ubS_a%w5`Zf$^PEIrRn@^e*s6ado$0-{qIqnS8khb@=)_iQyR7u+b8dYNHC~mjONKGimj26N^xgS#pL)Tx z&5L9gn6GcR^(?GpA(QBGdzM?wRrfA0bNYHv;+yt^zg_v$Zhdid6P*)tMDKg8QpP&_ z`O=3|i^LhicjW%8`M?@~@BO?3TO2D`Z%3A#(&uK2&6l~o-KWd%+fmLltL!r_`0W3s zy!Gtdm)195-kTX`vV%cE-M4l_#Dsm554Jyj@lUDd@w-U@Eba>!wHB|Ax_ars_Rrc` z73&?C-+XhBWIOvT@b&c=F7+$5o9AroO124;-tqlEckSCF_P=k(%XT-;)lYcg%DAA% z@X5_1ANMf1_Uf>yXiu+K+VPS%{q#9ykFOW|*-Y#g|BS1j6*m6|^Rmg^C;S~RJ2b7+ z{ouF%LUYZ%&Frxis?nZR7ddWUlehc2*6wcPizQYOy_-dvqjEFDe6RZ-n>+ROEW@gM z#{2cp>i$^jd(iy0@fxG!+hTO`sy{G=JTu{TVvV1tY2|bO)+bK)ZCfW@-EpR}?9rVU z?^+7#eKVW=gxCDte9(o_I_l@vABT8Ka&3-BU76!la@1Qj;QN>WS_m|?z7hI|L@%VP<8t^$H!2GDeDz(9c>bxY4U#oqiNyl*J3Mb z*1q_nmc3IZAWbQF=9!XbjhPF28g3OeNa~gS^3weD(RS+8H9Qvi_5ZwLyBrTcbzXX9 zqF?FUZvIor?-VZDuK4maEo^yM`lD;sCm!lo%$+ld&st%2#kEggOKxO6e|Y1u-}i;r z|GwjzV}0-YJ-2{{Q!myaS_vK?CSG&>yfBWB>m%NHjoKj8!yn%mA+# zTT%ZxJW~L&Vl1Lj47_4YYDc5Y4bX})1&Jns9}P+oO==ZQ8sHUUIyaicJ(~0+nvE<# zE5^(!n&+@KTkL4IxdB=+X1)TvVl1M?t)k_PA-RzT8oyd8;qh880Tm( zdUP=IGjwWnFt8dhh}~ciO=x7?z#z4Nku!loKA}rdf`Pw*>7M|Ta6#vcj#l9tjNKhU zj0Ox}p7S4)U|>tF@Av58Na&I3sGW1Phpm)>nW1aZ3;u&ELN8SG9{AC-=>`AQ7rpm6 z`q;iQvNi;r%joNnU|=g?Jk!A-c!BZ2jb65;y|-TYGc)wwd*Q$1MC5mk{_iIupI0#a zSkWy#qjP^ozhD3(*M@55V8#g+jG_#h+!q*xI2k2Y^zjQYaX#?pTEHMF$t3Z>U+@6G zlw{Yx2Mmf6ChoLgp3?t@)$6P ze_&v0U{p9cPtjq{AA#vRGXhTRoF^~9#LmFD!;&FDbAt2)(5eUN1q_Ey@K3Xtk$Iw9 z=!RycQ@vn9&pVB7jSGx_Z%+PqfI&8)i_u`A_)7+!0EU$u)BasyWc$#T_>%uh#Vn2n zrh=FA*<9zHd@+}QL$$_91|fs_(x>||*&l$bG@ec zYSnM}#SlJg<-ZS%4{lDD6qpuU#ZWtIb<(Uw_f9TOuVP5q$?(#1{Khgl8o-V79>RVz2I6K8pUY5a(dFu$pRM^rg1Ji?6vIJ zDSq}1iYpqDegf^*_@*fx(8yI9KbU%8v z;^}{@34$8+Tn+0(U-G}RTDIMiQ9@xB(@B2W1_s^*4C_zw|JyRl;Pw2Sm0LWe8CKp} zzuId)cLT%rogIP;roYRWc(Ake_sk{Y6S{VAF}hSP=lH-NuE4O)tj2U8s!}}ywU91&8fNRC4cQ}e+_}| zdl8Hl(v1=g(-Jc0a!=?w&}q?o}-pBM?-uj1jihW*wY?9=V**h zd-R>7347Y&Wsarjv?a$J%kXLUpK~ln=UDcgV+A?K@@0;ftT|R%bG)eMc-5KK^gYMx zdYZ4X*EiXmXo)$|R&%0b&WW!7IwxxX9PhJf?)`IOl1*E&&BAeclmItIh;1n zIj!TtS#^NvNX!|X2KLkhCf*%q&aA25XVZ9KPScbI_G2+;kH)Z_opbi)pEG`MS~LyL zT$4F>d=1mBm~&hn2kz9g`dTnN$T_p<0q61uoQpoNU0uW7nsDw%Ps7VUr&jD~e(Q6l ze1XWkfU^|~&it&oz`WzU@vZab5o}j=xHt>BuC8I{h`q?T;zIt6le`ByW#%$EuW3?S zzaO98%<7AB^m9cd7J*9 zVVZY=f1Lxz8i8A7wH)mWSf*XzU%KG(s@i(q4HNI&W_(!L@3E6%&B`g|MT^-#Fw8Al z%w@@-(J+_U;KA$2`qw8X$=zNkdVoK_d%8P!m-vI3OMh?E?!7CiaC6e%%NOQcJ9V3p zJL{3gkMk7)Tz?d9wG?nJ_T^Z#mSyz?{x7-rSL=5ExVtc0dk2SBefJK=Z4$S)?|twt zdd|_+E7tH%|8bn5vyX=Vco!>m~fR%fsrj>{r45q zfBe6`^N!@xYti-0c~9kcPUZ<<^t@RgbMNV7?q^~vuD!d#ps`?sX2-L28=ftWeN}ds z>#xK4qdn|D-JcidJ$KODG4g$ zd~O~!XU>UP><1?6oMh1Wxq)lf1L+G3zq3!4ESNSma*6PUDaZVtzB$3bKVhodsW<-G#2S!PUdWix? z;mCU44GcmLE=J#c@bmvZ^-Y!iQ~1`gcHLl|wUw1iGwd9L>HUXyWX>wA;Hmn{CVHQL zZNl|N1Fpsk_iojFJho#zuo5&F{Hb8ox{J|6a5H zgRjTsqP4=G9k^QTxpEJ1FdJUTSpP$-_oC$6mg5T7dmeBXDex>&__81kvoDW>m z1za=N{}em>bME_=y9(^v@_+k3U~W{1epB$fXaaKx|L=|ZzoYY;j0`wd>9gM~U|BqY zwRpj=ITP68bpNpTp4(*qJ7@=g#ss#a{}WjK9k^}@aE2)`|IlW5R{!tC{C}_Z|9f-) z-@E_+KFt63t^BV>=pU^aOeM=$zpU^6hHu4K7pp+R6U2(KO$*&7Ylf}^uNdpmNQAB! z<4k=9Suw_6&ZZi5^lJZ3o1$jYx4bK^tekxo(lv^zIGCZCw7>c1@J z=BKBJVh`s%yYq8F+WCd9)vUI+zGN<68n`}5QC#>UKj(_(LL`|o*cd+GaUGiHC! zf8ey3O+7AVQU2qToyy_&YRrqDUrcqL9#?Bx`ue78^7VVQ#^vuH7V;%1Bpzz)Iec_| zyp_UXRt{0C1@`Og5?fh*Dtk;=p-|7nYA11EA*0X1W;A*6K!Hsp;({idmPAGqzs44RMP4C^ zjz(T7feZ!~fd%L1vvYGDbZZoNpnjZzjYDA-bC*cM0*1adLAFZ0Ed~tCLLNm6nE755 z9nd_UbIDtYx8uP<=0!5zUPfzWR(PAHFX3e5)Ju49{=1Aq;EuMcJ(K4%^9TG_U=eIB zX_(JBB_(J9yVZy6z&f^q2aN(h98a$2W@%~&uG9JI9_S`Nfq~J_Rp!ckRuhc{m;ZX4 z{dmRIHKifcjhjQESwQ4Q0u!f`#DvC3XNQ*!91^?_9c(j`${4d&$0{%>DlR%3&}ejn zfwf+wf}xc`sluUw*=j@n$;LT<92z3k6CN}OC}{*W3p##fXLVuDXqe9_UU8t2!S2EG z69#Xyw>2`TJb2E?#Ix(P4r^kVj`NzhKCq4c;CNQ%JeA>YDQ|^Ssy@0=rW+&&XUnp;!%=+m)L){PI z{~C=8Oebtyz+oY?AgD2bVb-g8b6pY{n8gH6BnUaCHOLp#_B~)oIH;=cKRvu@<}Dtp z6=iAdT5C*>td%b{Ye{U?b6{ju`oUmZG4ss>#-@2!%+u3tc@;dGx0q=#@v`kQ$XRgT zu7;W8LfCEjW_G5XHL!1U3y0J!@rsb+*xe6y`7CbK68AssrPA+mk{8XAhBTm$=H`6sZ|VK@3i@89_!h0XYH~+a>;LiT?KsVc>2JxNC8TdUMP75q( zTp*~z_)Gdg-G(cTOac71zdo_E>4Y?J@pw#7?mnf+EwG#a$3=O;1~DccfvG$^n{tD4vdYIR>GoSY{p;DL%wtl0;r=5sph-?-A!FzZr{6aN+Mfw85?uQ{-k>L^wSJ?+h2iaL>QP;l`=hjtAeI13t6h2n&}<;D`>{CkY&b8GktTbg6BL7S+3D4 z?x6YWa+8n9igzNf0*m^RJCxcctW~f& z^UUKAyCS>Chjnh1XBjP?^Y6`D`@hvTx+8(}k^Xd^1&l9l%E*dGi`+8NXS5XdVD3;C z;E^%ZDo;L;rg-(h0>uZ7{}Lpa6%3-UI3;Z6G4{w(H;`dhU|`)9dymnqyMZ%Dfw|b$ z;fNFimG;&aB)odGh~9WybYRj`lJNjDN)rte^U2N@bwBK>oAsSzgZb z?Hf!K_xpJ5zWp_Gd(YQx3@QrlY!fa%`ysrLHz2?#wduSW!G^$uMBjqY_GoS~|JYU2^hdj{>&n=B1~aU4_X zo?_0qC}!fqj;woMw0qdUBux3*>|rU#Fts>iP2&#n$-b}Ll{~|r@TOdE35;~$xTo!? z&gpqD{`JPRhta9p(=}LjJn`rDJ$-;XAWy-Hd-Izg8yk65-tJ9z6J)vaXWP|XJI|l7 zz93Y4tv|L*@O+is3gXKM&1*rotZu}A34*{ zt+G8mtuKH1)j!5o+isrAmzy$YavMX4aLWmgQ^k(o-elQ_KZx)D@zw3DKW;OZv%O5a)>(6J z*|D-!vIz+sAC*~Vt$UIwdv7hT=!30HnPHQh4lFcC-DAhnbYKR{g_5R(2h5TYZ}Zzz zz6Luk@VI%YYO;e~>Dt44CdvOeP#|^Sz?#)B83Y#in=jv6_RAt+deCw!M}ZCAYzzMS zdtFZs+~xOh*QdEAvgLEJM*h+`ZRlD2fO$ z?4LQCVTD+x)PV=l9c>f$KNad=cD83%>H4YE;hj6>>soYddgYyLU-RY6GSkCVDf#JY_#;Q^a-7Eb_ki*{_60X3Azp}M>bznH($f0 zY3bsA#mPv;<)wwQdx?v2=aH(cBVINxJTDweCb>LcaKzW;q&D=W_g5xG2!S+Ew@Jk z49+fYmLei)M`XiOx+ZkGWriFMT5`0UOH%QUo8_BhrMr%OvtY0hIc}ojZuiHnB*Z;$ zjoa!Xw+9UF6*2CWGImvedUN);8>AdBHgaFWAzA;KEb(T#@r_-=dGD^h3Qmm=cJC1 z6Fw$h>=VQbSx(BvcrD90rCGQrH-};B6i=Tm>P=I0*IaSkc*Sw$6pcQfzUVtb8+u$T z6TR2*oY>yty;x+O@~tD!Pwiei=V%;@<{=%QLoU;$T%7MR9eJL`z`nuzaF5T)D=Q}D zbRE!`ta*^Z#Aea_J%>0itUazHB*(+!dd551#+&8BnQK-`=R~}(r6{wVaJ*>KkSem` z)Euq@9g~$jeVjK}ZdnMn^_R@_cGvSZD% zCpCWATEdrNR=hmJbcj^StKW zu(ceE+4fHO>U0%!`j7AKZ&^L-L~q>#ujN=;?0l4C&yJ6ePgHZ;@A#AO>G_4l-s|t| zdBAoc!Xe}SxxKr;zkhHTw3cJvo}ZszTwV=Y%klgB{lyH=*ca^k`}@b|*Z0rw-~azV z1DnQ!1{Sf52aOzRD;_lQymI#v;dRq^*dh{^@vv1QO+&Fos_ezXc7-;LM;**X9*;UT zmaTZyrE@JksoUU~#^WB7Pv4VzEuO7-+$VAG#N&PkHq9q>c6^ynCVCu;NSWkg_Hs#^ zx1HwGDIro4sZ%4;Rxa(1E_<0eExb5H=Q5wq z<-FGUd|u7J<>~Vq*iJ27&?4rwWMPNeuk=Mdi*{x#o>2EQW66}J$d^kC{kmQ*^T|5( za=CLA*Q*t&ld`m?Et7|e7|XV=mKr_<>0Mb&znz5vTHZ-A2zC4 zv+uyG^3OZ@-F@`uxpycYlk`yU-6Ap9X5q#Lnev+VlHoJ77ji^~>|NWSv}etyQ?K?h zycPYY5_4bBWQ#)C)^M4`!=ffKf%DHruIFvo@gtG^KBlS{%dsi;%1tLxqu6Z17UZ69*zp18L~ zB!&5O>N@+XO$WvQd~DU;@}SkAX5j-3mz{O82cxvtOylnhF#B^*JK4r(#+&~vKUn0~ z1SA?KPy6Jx&EcWpiAI5*1>DW0thY_q71}l2`L%;phkutqwe0JbYg3Ny*PnC2?QPV; z-|Jpj@ZZel(Y26$Gvny~Q?UozPae7}o662`>!L%Y$%z;-zj6b$e|{N4QcotwDX{HN zTwglj%^}|IRx^XJcX9C(ZePBT%FM)-5vUb%Y(0mVS@y^G5x-v_U zzi+l~el$_H=IW~^t@Q7D@+O6C?6;OojEY!m7k4Hp%sGJ9`WmxTkCBtv<2wx-zWWGn z)lW{D{rKgtYp&Nkb*dI!mNW~TE`RIhx$-s^IcCPChx9$pSAE#yWIA=_x>LTZ^F^{8 z)mmr%m-h{v>~eigzus>)OGBB5tR_3|pO{n0+ML?cf9BBiV>v%sM9U6MZ|9LSc+PT2 z;?RX0u0=6wlPwnteS6*0ZZ&~xrRk!Eu7>tx2C<(x5xUJ+gQtGAyt~P-{dA8bUp!x_ z_#TEG7cDn#?B#xRL*6*0+4#x3$%02_)|#=e?fQDA-OJ|s*=1I3^1pPr&1T%(zhsu- zlxtd1p~kOUK5$;uKe)->=FWr75?3}$z4da>zuM2gEbPioyO--MKZW|g{~WaaXQ)p@ zYEUfy>!nAGzHMY^UXzk*dh?Xi$xSns2B(?tRye!@hTk33ev*wGk z3dwxkx}kS%PP+H)TmNr;+qUEE+Pw19x9|P>w*3I_y8OisZI%-r?l|GQu4ww{JI}1X z@4V2vu4K9Q-B(fHcilLcrY!e)sulh7RK&SW)8@-O z)vf+{I)QKVjN?Af6w7y>$?)4e>-w2zX7hLcKU>hZdCqg6bjAHU&sCh;Jn#FPr`Df; zo^RmWvY_$Jb4Tf47dn;|FBCre!p-{E>F&BMOVoWcRo!=8nsIK+vZZA&eXD<6?tN#n z!rk|kefO>_EBs35xY{-_@A$>QeBd*qgg|yE^UiC$91Uxq>b&AEd2n5J2g5wW4-L#Q zGjD7aD`F4`NZ|=lVB|W{ux@Ej!^dCBEW$Uwu9_+PgwN-})nn_7Hy)0C%IL7*grot( zMD2hE9t8%*H)#!1*bN#ZlA~`Ih?TNSDQsm-X}WY-DPY zX5xNPxlNs+v1fG*6L-PSy-Wp-Yow!}@7lNhN{Qd60_|Ex$qv&qk{^mi5)vMX9xtEt z^)7>8dkmwkeFKZt0|vH$JMu#L4a`~$T#j z^z(-_$ZIq>Fv(3|ob~xJ)12q!j3(z9_=RPeSTzo?|0!%NF5br^{-FIo)1Qltyg7C( z{2kwzi|QK$bR3xYJsjRotbdzRp2P6czLtsY!EcTfg}=MDGqO!!;Io#!YaU^Iip0HA>ojCk#s>b|A7+jjFew&jPKc!m<1SjwJ{0?WOHm_VE^CHsLfIN zZ$qQ3MhbgE(@gUwUyr)5j*^h@oH^5LSqmzSZx~7nFx@(yvv5YO^Z^DT0Vek2{LU8j zF36IdjBwKckTxA$tyVAyBQ$b2$q zk4F`&`UHsw{D(bSZ>RV9Y2=ujGdy|GGp9nIZ)F?L0e;>FhOO9iRg4FWAKzH;@{A(S;@#MkS!39!?S@=c4rgo z&%PLo&b1yjvX%_B&nM_wcJDi$|3iFIgh!lTMH}aa`n~A|@|Kh2PUdqKtG>Okhm7KWmJAmOxHj8E1+;lTgT)|IsI^ULjDx@U=|8?|4bh4MX0lKE4TzKJ5%r zQTfMa&KFQ%WY%I7y3i=Rpe6T3p6H2OIkkL&1NpoM3S^rZ{+BTdI+Tj4FG?<7*z`SD zz<`12L+-x`*&*TG+A|n^yz&jCip0HoA1+(+=jQzIQ^{hx<^_dK-SUi|L(5dMfKllb z|MO`}o654P!lxc;V_dIfcE%klaEXC~$s!yfY zMy=?mn%O>UMbH0L<=wYdOjwoGFST-tRo3LFl`~dlPM@`Mj#cLDTPqiw%9t;;YKc|G z;;2>tqW>f_fBg4cc%59Uak8QwfkKH`~$% zfpUS3imx}H;MyX4IWxN=fpJx8mB2>#-GbKK8`TbM@qNAJ>52^kAF>4>6yE!mYWjP_ zq74EwE^K7Hy*b8vP2B2b+}HDiqfFwT%EwlG;u5c?ycHv*_v&PP2S1b9NTwT zF&1<&1kdVoy4|BUYl(OPqrAcjzceP-Y=Nq3PA>-zriYwf(MfIQNsDJEJ(yaVP)GHyV;j-V4SvqWlr^;qWNL4Y?022~IoAw3~6=1n#-J4g3a-{9V$Am_K9-`y9Md z-N9VI$a#QaK6`QZjVykHQ}35AVmwfxb?n$huNC?_DUt=tT(2{V8te`)4vLHVhI6`2Slcg-S38*i7AC z&M;^B*@b@?wtUN3*S6@eo2ezn9Wm~|o&g=klOg__u7L_uF?U@YuTGL){pRLq>>by-H z;{pc1wMFs^iWGRa7|%{&X4t^Ln^Eope`C#_MuYQnZ}Ue8uzLR9&>E1$aqE2h>nt~| zi;8Rsj0!tWv+Yaf$@Yxhp?vMS(zYC~svSyf3o2z97V6y4d~^$8}cvk&wa>f zx8|DH0aob?l8ZiUs93NjfW4AUdizA(v;zv)rrzcMw2^C94rj_)!3Er#PDwGaL~dNR zHvPjHrbFHWv*vQIa}e0P_I$qdnz-LvtIQa#AGpnOk#X^aYnigVMvS|5G3+>+d*}Gy ztKMo16&G&z{C~jxo`K=KEQgW-=bQr^r@Gk-YwsSax^rT0I`aj-^#?eM6xb&^a489J z{Ltn3s>{Bff%)~>dk0?Kd%rh5wt?-hF8c;!F6$@kU(a$jKe)lvmn`o6;B(cj%rvGj z(4;%hrDB%%4)>cCn1%nX7W=nFVqcok296IcY@AV#c2_-e)P8KV?ykwXRc3XMSIl~B z<+sY_-s1(o9y^>{>16k0M${8Gzm*>Io=n*F#OK@!zkg3Uv=SbKJq@dS8Zqx_)V`-N z_nyZ6dzv8kEXnR!O5C%wx@Q^lp2f;N4cfOn=ial(U(X88%`dWhZWsByOs=D1-t)d$ zk80#rSO0syTkJ)X-pabT7c2jJz37O`>e}~WTGfj_xmCS#FXx%PoU(4^gu0izkJYQ? zrp;XUa%CG6w*aHyf^$t8TR0k+R`D%xU-oMCy_dFWON+!9rk#8)t&!h5qx|`wWT^xE zD__2zXr1!s!D~f>H=iU@e0Fxav|R`)E3_%Q*zt~GpLehM&ewL==1qQaa!pOi_d~@` zBvKU8Qk?&$^sRaG_uI>NwhRpFJA@f7@Mvtg!M9BM!6J@K2G!|r3r@Ygx1vbYfKgVV zUTOI*y{NO_#Lqd)mU!N((o1h&^t?}2nNjjX)%Bje?{+t>Wxv1^&(K#<%$vZ#vAy%( zGRE!47e>52XRB9aZ@zP%SlL9~tdD&R(gk_;|0SwyyO_?zSpKThU!}?LXeDF!i#ZS2 ztM+Q-eAYbh=ta}N@7)jobYHI1-#wE-egWf(6@~jVa`bL6$_p@c?3nw@V+Pv_hIK!h zwpEl&R?nILynbKC?&G=bd3U~XC%lS~O5t3<_}+|h--)vkHzrw@d%90Q`Sdu0&HBsy zCrgYul2{dTBJ*>WsLz$U|JLw5|Jh2~*Nn3>uBuyLgr95~R_BL72k%7J``HUV~-u$T`G_nR2CQBVI#Kg%E} zrZeM#{)+$(TW*CDng)tYb@G2AQzjU@u+C)SO86SC*wWC#{=-uupkYx{2j5XO-y02@ z&NBs6gXUBT9y~Zz&heM9=cN?4W&hYr1R4|znCo2Y^&o3Gto!v|7(6_5fi zxqp0XS7>TrWyuv_%shGV}xnDj0-kzT)XBcK*J1M!mZOgwSO)h~7 zN16q=SqyF{B&xnyGr^mq>GwP56*{}LK&udDIvuc^ZnPrL@sga8!y>*(tF;(7oG+|f z%__LyoIIylM`z>j56&En%-j+yLBp4!X=HKAm;!@7O8@Ulgu(13MUv1R(P#dnCrvg`G`&9(s4$a7cUs}YHbv} zT6dXja!qld;)dd+V1VAIV~q z%M5BC3_(}Be!b-D4!+v8cVkFsH0WyA)FjiesO+s%ulVO*Ee(%*J!flpLbK?#2=&S062l&U=4lTXex;)A!MZr%lTZi_V9Z$Ij+D zz~E8wcxriE)$6U_<7z&Gu6F(X^?Q5+vsgt!6Svund(4+o%M;r~(|$ZGmTjv@Do|ba z<59Nmv5Mq$(`P>(C)=`BrX;wk{d^Mb>sFZ>5t{b%X-I5aWm;hBvY%-&h1Yh5&o2M= z^TND(v03L9WO@~4ERk;B*4TC| zm#Gp^t$S4W>Z5>&!rRX+1{Deh3)$+o-r{!pz+T28_rag>JX^(qT%Ku@`S)}E@p;3* z%r#@3b-fwKL0#TmpEX$6B6#%9u|G=t=#+Y#ORGiU^{!8PZ1avRkS~`#@xJOlhd*;# zkWA;Vi3^!q4rCo$EOtLkNRGwfK(%;)L*xdgM&6JYdb|hDUAZ>tKg0W0mYTnk0rnGG z#1F2~SKPtbs4vTGUSs)+q`vAvHCPrbW0w1k63qOe*;^(}%%<`{8hf_<2 zfQ(j4CFeq4{>PUc>s=I>&N?#+=?5^VZd(;4xAOq=Dy4=M7tYIv*d(*AlMt&DabWCv z(k)7KWl~hg-`sfMg+V?40VR8d36_G#QkdgS7IMcryNTJc>Syt!N>2`Uw)nq~Z))nF zM7FPPUK@8za?usKqgyyrnC;79W6qNf_Aiw7GMOYWP1_x3*uBVkO-bO)Gd-Rx9hQwu z69TLFC#c&_W%UZ&B{)ZVhl8cR}64LL!sEORc$ zx{yP*M^)DJes*bK5fYfpuCa_!vEwA8*aiijS?4sia5}Pd1fO0majA^|f#SVyLRSui zDCmCXKFB)bOJnJ?iP~HiG49TxTTaYTQ{-b<78=-g^*E1%Le{K#;rm}5@Um=oWHo$! z@_>}&%`IQo6fMoJyccyn`kv@|uPd+PdasA?KgYYF(f3vYr?tspxo_#6p>NJf+>V%I zEB1er$>cXFiD8kaH}PzqIafQ~cz5LaKAtTL|K7;(+--Py-nXQcp>K~w+`iSmS8N;i zW)t>_=56nYj@iX3zO&i-UD4X3E)uqxx#vo=3)BS| zm;X{`7Cdl@K}&$KFKGKNi-SJ@tTr@oupF>_5jv^DHTy1WN;nhui+Ai|59Gg>eRDT> z6eRSSd(*Rj=8=qI1t+FNC(IR;ICv=NLz8z|0{i(j2L2*X02xYO~1%%MisTw&drIWO0knB1QkUS>8e=g*zEL!WkrGX>}~1w3GIS$x32 zkEtYkRg(XWhdzn~#wy*79COYX#N@hT_)#d7^CTo~JybM@=>yrE8?3K~2 zDxt@vu4r5+iTiA0WH0P_ZTGekhCgqvF8vdE%{@d$-+4tiD6Y>fdFSuBrYko|L z_kGFcTi2eknD0Bf_G&Hrr+fdz%>T1JyK9=?yy`v+zxjcmQSUor+aLb4v$!PGry1cN zbGNW|hY-60Lr8Yat$ljikKb4MRPcKCJvn}cQ-^yQUzE&gWUAPHdg)t+zV&x1uCLsA zw(@PkGp9L;*Jo}yU(Q={kgGQFx?|(T`nW=2;|VYQ?5(cUmVNXRocn5%o7JJI=k(^~ z+g^*+e{=nM>0AaAhc^qCZM`a+T~@lV_U*!RTW$;Ud@nw^>}m1(FVjxs74CT+dnxO= zwVQ84fi4IJnUENmBXg`;6}ko%HM8@;V%Rs3>1~)VSe8bpD^m_V(s` zv**1Mu9*H*U%%jJaspFGxHVHv?)UBYjlS~D*n2)Z_5IIDujl!`e`kA2`oXka-_1TG z$lEb@*?)YjZ}t4(^}P%t{S5cAYrb`t?Y+MC*T>h36PV76moqCIV0ia9E+c|p^vBiL zsvJFaw}c$Cw13dEV$EiSyo> zESD4f4aZ^k&YYaA5e&U|#vu9B9M8>aCpFB9U)?#mdHMPI4$ZHQXl_|?ak0l_t=LnO zG%ha>SnM^|>+0gmt0FdM-95GS$<=iUhr49Czi8jwoN;;8-kqSC=ftyz=6Y|>3*1@u z`PE%+VSUiOGxcAzcWijLvxQ$f&d1E~@$m^8t@?a+Zn|`GnsN5G&dSZt&l_1c%lS$z zeQ{|;aCGR|U0cJiZ%Cfs=PUi`&8;2$_s^Y`{_yVJfzSW={AAvJcy!{bod3C+H=mwe z*lE}AxA)bTS2q^Moj+&$^4q%yt#$wT{r5fp@oA~+9tMR+8NZhqy4sxl6w>ndw^8ne z=`&O7>%S=ebZC|cPx&Yp;J#(Ue6QUnuRC-5bQT;D6^Sc!5wqK)!o>UUhQMTA+YZJC z{-iGt6dU9?U-aACUf6K2(Yo6M#`0Es_dVq-ye2jm z=kbfBU1Z`7uXyr|JABWFd98|_4>WnXwwXVlST^ndGiUDfgcpa`?szoo=uAUatruN- zR#NjfsC7PjAfBSO`c2@3WfqxB&1xQ=mrVLm%sSil$K{ifbAK4O@#m#IJU@+L&jWtm zTsgxR;y2fQ&zjqHXv$H^{g(``uTzivcvSMwr|P$BuGjrI)M8Q-_-K7tlI7%mv(~BH z;=Va+<(VTQ+K>5XIdpoe9Z7$bncJ2;<3;mK{T~U;d)bO2G$w8SC7jt6-xK(P`&7+u zy-8*_19UeOKlF62_Mh`HW%3tJi4Q(GX)EMcDz3lby5i^842@{*n+C^rh^F}_?y+0B z;`xH9ObWSTDR$PII^L{|UU`baM*EPYt;8+)BS|x2RMx~CUh;Wo$;1CQovPhWtjXDA zk#t9BHiOPw1>WQXdmo91KZ!Nnd*A9!&5u71L-roFo;EWcUTtWj_IEob#FjS=6^Zk+aw^~D3ec{;xPKK)qm(D<&G&eiGP z8TJO9^{UX@5pFC{&1163ufn4$=d(oj5*59ox36l-o5QmfU2PT1=ym%2TC~0HYG=S* zeKp+|jyJMcCO>!Wcb+KZ*vWImko)2TALY+sO>C+1?@zwS$TphX`a;zFi)Wtkr=1FH z|Ls=@X?;!Lnfbc&RhY@vi9GFW;Zdj7w0xVW-tjR1Q{!DpEz?cxGhcH5&TN#JbVTvj zUWPo`KZnGH#5W7B{MbJ4;vAXmMT`72R&<)}jC$>HL(FxeX9&ZO^UIv3SoG{&S*Vi9 zf77RIk-ZvU-yOz?g*{IDlQf^@ObVX5tDr+^mVlww^M_n&-a>~B%Yw|i0?(gzQ(CAo z?~=j7nx#78rl+H%RHtn_^E9F4r1!ZmpQbxAE;Gn%I-NLg(~M&-f<}`&=cO7=wmDa$ zR;@L6tk{p7i_U7zQE=t(zUzky3c z;laEgXO`Q2Kl%TB-LKE{8F*JXuzOu-lKQfM!*_*~_^AtRRweTx)1?=?qP{GWIJ?5l z{M5z1sxON{rwF)vU79rO%MuOWm0sap7s1n|9V>m(y)MtX^<|mG*_D3fr!LR?^<}vO z@2Y@yuPckBzOL}t`qF#i@hi)$zOD@DT@|w2>*}hgud5=?t_s_J>gu|x($(I7S4AB6 zy0&T7*EJcwtE2WyUE8|r>)L|e)iKY#uCLs-bzQ~T)p6fXUEg-q-XH3{tA5i9>~ z-O%B?CQ1DDwZlf=HcseWld|yDjgwK|HqB7YNi{xw^IX-pwX^@O$#D0MTHCg5%L?DM zS*)eEuI~D_b#AV1PP+H)+qUCuZbE8w@DVu&CT5510)6WkP6dbU%~RhpDQ#$Y zR>rVVqT&GKxh{q}p72%d7J8w97aBQS9x&bdo5kSNz*ygsz;r@%O^)@2hM7*`yE0lG zLhS<@R`M-iVx7RiCNrUt$-rR^bH@70i?ibntlQ4mBz1sYs-Tg9CxJ|@Z z1x%702UzqzG=2&zWf1UiVB+}0$PsdYL43wKdo_h5&I9HQ;sy@vOb__kN)|A22{10y+t5|n84^5S2XwhZ*7(bmPfUn8ku+k z8ehsOF!_}=FuU+PWtwfa$3Hc{N%R9F+aIw#8W9KT6&GZ()GRp3(cr+oPWvjC&4EMG zKk}KJ0vb769x$6eJI<>1f#D_J0tT)Vo7f!oFfykkFtB+rFe@ppUJXD+Uy?|G4;yy=B48gK`Hd^ba&7_|KNtkl3(*FX5? z4f3ov4D1!&W~^Sfedp=cOqPtA45{lsm=yk(75BRDO|cB0$|$F{#a?K^6TR#M@?0Ja z)0SE@zV@5FN8rYRf}?Ny8B=atP2*$aQh89!*3z)((z?8De-}0e&aZOi>S(Zkv0I5Z zCc!o2+i~W7@A!GHTxaCxa5xygr-5;b^qXJn-e0S|_nDcc;k0mv)gjSyi+OAkTsbZ{ zFen9lex;n}r&bA!B1(ytr+zZvT;*j>GS%*cL0-G`Rn$&6Af z^xpowSheoH70cGPeXql(G-{VWcr6fM#4J|O5VHOS^RK+U|F<~{q}KUdKf3&S^`W0< z7IUxJ_u`+%f!Alb8INRN|NXJO=E=&c2Q0~QjI+|yWiM3DKVGybJTzM@=7U-B>f^~D z-ODbPGW?zDuh3SlUBtkAfPrg6p=1KXPZfrP;@L;SV~>e5ayC@(H29l5t6^ne{Ap7A zuOacLc=N|=47u&~4~|!93pJGYtyqXKP_iJz;Gk1SG z@K~M)&78|~PPx@7N#wG(Con3seY{>~IxRQmIlr1j3`au(|A#8c4>6Me&5IMu823+W zU|zuRaC>uAMETveMtOyn8n-}gvl@pVB^erN&yKhJd(aXf(K^Mn^~7d|3E`b;?TqGO zjJ#@TtP)i-+(RV~)NK70BekG3HY{H7D+5~q!-rBv#eyEr3zcFT`HBk|`4=!UG4!7K z5iRzjS-GQ-?E^ywTQ%#2UJkJq{_vi=FVebqDDefv-*qeIzre^Jo~#(aaAHMDMO)|7 z^zyB4{Z8!+xhK*sw<~cc^t`lS;56t}6i8dKqH9e==%!W%tz*gtsV(14V#>-JZ<#U( zdxkPMF!2^t953~6c%GPjf`Kui=3fI7&jW^or3}I18N3FGHjgIi>`V!MK8fl7^77J* zOq)eznkSS?D;i5z#0r--`p;+-e#T(KS;2E4HmaRLPK?3avea&8o1yAtzJ#=gPZ@Y0 zB)XbU(Kcc*eaRo0nJ##9LSSjM?Da|~jWC{qQdjoOckN8J?g<%|ky$S@ZnI@GK4?r) z&A2eV&vqxn7m2j;%*e{h8C^SPe0<*3^K-_8nKLF>&XiT3Ii+&uuGE<`cFx?qbmp9& zGgm#GxxjMPV%1qoDre0}owZ`;tcsU2*8H5c^yRD#ma|uC&fZcvdu`|JT`wb7?VP=@ zGhBaa$RW!)Mj0C~u7xtI*jp7A z3VSW|4_%NP${~4bp~k6&sSJx0wHAGQIls!4Q=^Mr=L1`L0eil~V%bxRF27u)zbnG1 zYtixx{4QQgmVRKFS1|8}Y~$9(Y3b!gOOy4k#-%<> zFk)Sox@`3>sbKZC#vh`=Wy{yy?_#*87R;!y{`;)peLIu61L6*7tvM_edh}LS;?4DG zOW8GEasEBP-(JA6#$fH50#2_BTz><&jBl;FQ#F-8oKY$uRU^Ffvjo%IuZ&y87~e%@ zb1N{IFPq5E5S*&EI-H~Ml13g^TaUp1fNK5;Qy;o*{!$wEYu9?&1)1Cz7~@_}Fo;?s z`ypO?(iZm93o9Bpr3*N`3OM<-*M?YcU0%NN)~t<;%ZkN66y#TyU;5h4v5iq+CIeps z16x9+>n7F(qcsyRHk0rzP&A*>p`yQg)+<19lyEa`j731vxvPm zZB4xDPN^>TzYn;kTd%#Qy|(Mr3Y`XaiQlW@taowzzp!bUbnvEE49lJ|XnbJMjMy!H zfpNRlIzv?k{^zUt7VO@0dV5`Wi*+T#;nl&%UeEqNdu4Fw8iivqvJ4x7S-JgoZ&+rv zc5%Yqog29RF4%kX)!s0PU5};raX4g`sc-ty)yT%cX#Trc=Vd>?dGHt0SVOj)yldUx zOZR{Mwa)GM{v*FP>uJq87rjX17HCeCw`T7w0ak^ArCPUENf#WEW?+mgi!WP!ka+{c zzVazQrseZZV~}v)yxnZKu_}Y80Mnm>CfT11e*_p66&U8L1^+fn3^0fL%|9e>F4AwRptnEE; zLRx`qvh-Zuh8BfeOI1#VvKK7sy2BsQz`D9%@3ak!5e`e|ZCJR5Yc7XD;>?^A?Pu16 z{W@`J7WcYV z*Xfw6%-0Rh^gQ5xufTLn20Y#QfyNk~mN{N@mHEL3uJa1)MHASTC@?iT za4jj|`=G7ni4%;0a#fE?jMG35856&Osyui5jTuCZp z#sl`E4?Ge__~tNhgefpHD=;Y8UQ&s@q*i-LW9}uby_a-yFBz#`^vjAg`oKN^|0O03 z26juH%X2+1THT$;bocU>pO+nYXFJ(mSrd81t#_8k+$&3VUh(0b<@fi>9LcLey)#2% zuTH7F8X-GV;=|gwzgH7vuO-=DONqUfR(maD?zODF*K+P&%lmt+K=yi(?e&zkSEJ9) zD675RHS>B^?ev+-k1~{Jtg+0 zb??nLl{aU-jhb`!=0eGv3+Be%{JN6K=GKhNWXJB6;i}OwHyJkM-U?#9z2)x`;RQQt zOUq}RO%ZNm@VR~K{Yu70Nv8bp&_4n*{<)O}9^HOEG*sw7YMd%#nWlzb%*|)kF#-nl z-O`~-VLP_`-&GhY$|SGd_$OfbiRQ+zR0h-Qp$ERzajM;JJImm?Bk5M`!aHk=6L&Id z9ZzCyD5_evV~v`IqCu8RTY73)5?jJ{SN9b07yAw!Te<3VZF6`wmqS+WwMHoe#=TwH zaW8ZJ=9WJUW4LpjL6Cv*pJ|Tvp`&aQ9vq&U${LXFY0DsfAm-lF*wd}I*J^|+9aEOr z!1&^7x5PIFuW9=(=f-`0TPB{6qd0+Kv39RyL%vs8cKq?}YzmA4F>$BO@{e1j-&^s( zrl^o>0;BYgK3R_V?dqNU1x+fe86^vL?<;4NI8buXbb|ba+^FZxaoe&!=w57QFRI(e za3MGR{m-1Pv`4H81x^1m+kI10B^nrKe%p0St%r9*xx)^|;@kHPC786fr7?Zz6FF?v~FseKnyC-)*~#pA}G zM@`yZZLMH@y7$Eq-#h&oIi5Ed>yIn{N!UJP-7dkl2N%<(3wV~k{`=H?yHc`v#!9h5 zZh^#O_u|&smG4ZCeV$e%Rk=MhZ{@CYG29;*rPzx0&70Odk3q>d=V0i5lk`q5hW9o` z557N1(ClmR{{2Q<=xvZ({ekRG<6{*klwa)^V!W$YmeS6&@>%o$yiVb=cYCM5b6fuM zMDQ-Q5BcdgFYoS)+g2YnjZs2AM8K$=Dz!-BHl0MpedTX3NX~943w|`$Ly>C{z`ONU=RNwq7yZK(cyB?!wzv$;V<=i}BZqKk_VL^1K42X=^;*zmLb0|}Ufh~}uI(?2 z-S6n`{yhEt(YBK%8}ugp_}ftSZI}19Uz^;19PeegJFU7RI{DwTYR>D^kDujl%DB@0 z?Z@fVd%mjC3p^$o|D0&tY4iP5$mRT&Qi~Yg{Ys1ildu23I+3?vn#6*7@s)qIuDv_Y zpQ#m@_Hb%!SjFEap?`~f=kpmbZi)SG`8K5c?54T>w>Gj0aQxv_iMa5>XTwWRaUS#L3 zudgSrNIbtRH=yi)?wt+!&!5e$D$jq=QqQiwue_r0NlCwY`o6Nt;uk6N-Iw1httx#J zwm$v&zmn?m4<7rCWiBwJR(#R>?PcIl(7?#XA|5iMqTxPEvw(@rj?RRdUpsysN?_)Z zy0xIWk%i-7^RIWtD;oLOc4#(m1aLLTi!dzl-N0)k@uI1nEh(Y7gN;Lhfs@@Tf#Imk z7k7q#5g~;P!as}k8UBUYoNy80jN0hd5+=gX$SWamV1Z2ao{xLPbH5xvdBjAHD@Wo^|Uao`#jI3QcFB>Cd z4IVIZKAywS%=#mfx0P3hBjE_=j4$a2SY#3uSXk%PC@@8uUPx$QG`77kpIchutM8Kvf3+ zPPYTEn-0nS4%ocj?Ph}r_qXE3?b|0!{()IBbfGtpG7`uw?#^BT(P-E=POW0z}Nz|avA|2MBy=7oE1 zy~>OBeT`kY2~F%#s(%vxJX+@eak**_!!u5{GI{#XsD*o~3RyH}*mB zoUVDRGmbK}f5=wgRG6^3;j1EB<$A5foDPgUH~u@?$}V7L)tKDEH=$v@EQ1|$rUPRD z10!eEgw{RQ2ber8tS8uJwxn83vR9h0i_dPopUp~!>nv_7kF3;XFw>bl*+}8Q3CSGB zMVAyp{w)?_S(4%MV0BW-x*3yLR1%nCE;)$r{_Hx5C81s1W1{57W;gR=6BP@lB+EWM zEm#~Inq zypmF3ar2}xlhBmDKTkC7Yj_2VZJM(3i;`C4%j2Q@CRuKrqFR&@>HX-A(DVZrpXya! z_KttVHsj!(3EG{R#}ef}o1P4LmNnz1&wUBuS^X}m6JnOCF#gxboOSahH;YmMi+G7!0gpX|7rm%_;qo>puv^Q0k>pc8H_KmQ{dHF?rA0MMouY!a%n@07 zN%5su`pux}w}h4%o_*?*dCGhCy)V-(udXzxymdK=>FQ*A(N{*DQ6cGaqAM@PXqivF z6>@2X$f|pluR_?BLsx6Du8tJ-58Wwsas57%H79Srif9fB+x(AZZKi2Y_)V_qTla~o z7fyX0awjT$>jlyE#us15`4@-p-(<4EQFcupLv%!ttk}l;(QC4Q-MVo@L+tCOTPKxz zb*dxJq=jt;-#ha=@1q@sU4Gc0@gQvdL?dvo+z`&v& zagfuEk!g;K$M3S#q(@N&t=exMFtQslh-@op7oT&~!R?2Z@aKX~?Vcv~+kr1 z?n#(^vhZl2B}03B&0`7aNe8wFGH~VndCcg#L&waNGULae{Pcih|jU;cdX4vtUjUH5Ew zxOhd!H=o3=ib+pbcKm*xoVIfP{Qq@d6n;85tLvsP&i7Z6F=VUPd}%h{URP)815uHn z8Lo9Yoes_RW(QK7`O_I5G)vf)a3r`h{d(-rpS-8xp`V$?=0}Z7cTQPgZ(=buiQnei zgY%;L%a$m%%Qa~zb*jyS&jhlZ4C>NarEyF!@Q_BEef))tNjc`vRy=O=zj5HnG@g0o z^WC#o-Mly$7O!4j){=Ri z$5Ia0?fLh88i#I8;QZ-wsTY}>b!EFU7U^D#oIID;t*T{yT3Ghlz^RKmG*^Ho1|{yf zye;BuYjdHau=rO8CXfYZKM=d}8&vE4lqPT`#9?RAZBVA@1-?A#0(= z+h2!7buWBq?e7pebI&KdW?|+QE!&66`(@;#L%LFpPA+bo!Nhq$;&|Wf=A{f3)rqU@ zN&*)h{I{WJkwm=9&!+iyce=BE&E^Cy-Jg5Z`*5Xy%}1y8|2Z=@`4r{7(%7pdq%oPd zQ|R?AujGQrhu1}({kq&|vo2!w_B5sfmt~K;K0dqEvW)A%HYJX?OZjd0FW4wyd;foz zil5_Si|{?VbCy5wxU6*hQuW)^k1U7dOM?7_~NkVd_lEIl29OJu~0yn9bEKMaOMB0$+Q7tlFJ3RcGG=OK+b| zDbmk5*h61dI$aO{b;@5#VvoE3pBYK*N;9-~oy}*k=h{X`=l)jL z>|PEF(wsL z+2Tt#&bZO<9kD3hJL*F7@`Xpt|8plW+7`72p>%eFr&;XwaJauW>t}jasdeLVBr^qc};E-s5&jk9e3YvZjb0+Xr zU}M#ubyZ(iC-AODo(a5u>e{wlU)Sb5)dI}~?z;6AG84!ZzVp*p=uF^^LzAwqZv)K) zp1yI+N^E0a@0t{K@0+LkL^n-4yC%*2^v(TA+cq!YU7O)9eeL|DZ(COQ=BE0tzIAQa zx2+AS;F-W%|JQ|X+je$sUU|31oloDkA1Ga$-|T(oQB&EDW4`N(RITq!N&ChiAmI?l zJR$1UFJ>l=2))$y+l_oN4|a>5&|#A+VA#_azAMF*f%(iS23C;;4BQ78Vg&`5M1Cl< z2w!-Yvh#KW^AGX8Q)Tlj-&-;Kp7xcQRU?~~>jFcI(*lN{t=WxByW{^FneR?|>%c5v z+Q4O!z<9H@q5S&mel{NkX4xMMCDs9qJkv`VBt0CM6c#Mv_j$m?`huZKS|OQp!gYD( zhz3S0hgi0l2h8C4!GH#igdYq(YMbnPco>;A6n3+BG}uqGVU)~|a22@Vz+|@Pfk24^ zcz#f2!6I%ChyRQ@(;C>=)+n+3sbG*da9~k5kj9a+fQc=_f#H)3$n&3uO&tP1ZLxE3^+h$k@Y^=n{ZOK4y* zP+(HzIIwB7-lD&u@oSm_OWiYe=3Rdn%*-3)B^wy* zGh!MNmRBwLH^G79%$t6$4Lg}WoMW&TdC<=%Q_aNxfPF3RgSX5d9y9S=Xkff>mj8Fy z4ll9zhRxi28o5m#>}CFMP|l<#(8#-{n)ws!)~KDg@A5TNFtmPipSCtTB_o~Ttku1u zbEj`H@W?nUdFb0PO?ux)7K1zT8Vc+8u`4u`Bs<&>U1fe%RiGhJe!;6h1r01e8q?~g ze7W<|Yy+E^K?A#k0|RqF!!*G)ixh)v5B{rg*tl?B0{aAUR_XSy3{D50H5u<=mi~U6 z{lvelI+?$385zuv+rG;)1*zxmI5@4P&0b(Bzi8>v*+v zI778~4eJ92nHgEfp7S#mFuW3H;+ViFeWQ_`p`It9?3r5aKLJM03zfnL8u=!~mTb?F zcWC~mTBjAnxIdiXbtvP*uZheIRb9{Pj*ByC88P;i=W7@+@HUjmUeD+VV)Wmh`Y$0f zv?>1Ia-}!V`584D(p?)un~JZMRyllQwBO#a>3YLf_b{dp4EEv-iUEy2;RXB(v62pP zd>a_~7*Zq|Vtdll>u*%@Y-oDDG)r?uOYpI#p#L7p>;g>o>8Vqjs!F_iv1 z+We$Eja49)?Lb6K8s8QFh^a)5~YIiXU z21SS3r>#tXJ}`VcQQ)*piEDdHUpa%Id4I<9_J14lSFEW0w}Ii+G5-DTxt~w)=Ui+3 z_n|dWqNhEyDvq`MbW`?=XGu;=mGn6$XNWSaEGvmli#7YdQi-pDk@-QAQb(d~r-q4U zmi25FkGTp;@J}_)uma?cVCHy%5 z)aBC`w#FE-GulWnuuYhr)5??;miaGZijP=VM`cvk%$X%mr|O)XxoYdo1(vhaG(j_g zM(LoLz|%`-t@%0Y$Wzcv;BHmWOyHK(**kX5Ub}Smo}aUqJe_^Oa?V`UIY%nzOiP_} zV&|NROXr;VIj8ICoC}t7n@xlMmCn5}bMCF3bMM@od++Dm2a@w1{kNR=By!%f%6Ttl z&U>|U-kY2AZfi0)EDil+IsZ%K{BM=>f6Sc!OLP7u&$<6jhX4IJpQ$RG(P{zPt?&tz z3%F)QuVVqObGs}@RiEs(you=45xxm61lPK9euU{bW2rJNP6 z37$XH`LJjg_^9FEk&FFG7o|;IRGi9G^KG62EkDUMQRYFShavOY=W0sd8AFq!r?kwe)n;(i~Q9hhJ>VFYxER zTDG)+-9c(uvemM*s%74*B3d^rlm4)*?iT-|2a5uumX}p6*LoBk?b$3dV|m{HsZ33; z7WaHuUjJ*Uy%blA*V4AC6_QqrPB&tmB6_M^IUTgPCjDAg8^u*zwQ@$*imsoGt?5%f zEf4SiwP@)re(4Kb?GKhsd9~OiD#YDAWdEuV-m*{*)%qVhJ1_jqW&4(KnQbBaw^hOq zR`yhdscl#`scLo00+yBs%jOxZo{|vq)-~k%wh*4M(3xemnKMI}3>c4Dg3=f_!Yw75PwOsuP7T8{7nhCdS++{Cv^PT02l+9tj1;#cfR z%qLc?J5{Z6tzZFHxXtfH72tWrTUHy|SF=oiz$MMF#C!Dy9_tXBXZ+$57?e7??aE3P zJ+E*q3lMw|&;MbHB17&ywUCk@EiO|T3r{mF-kl)9J<&g-mDwPUF@RBO!A7@N^S#a1 zn?$RwjjA}rHu+;#c;jiNiVdKBmn_+wEUQ`86mXut&EI!=%S7#v$-lSS{$$YS-gdfe zYe+St&3{V<9rm0UNhUsnWM@%^g|AnOE=c=xA<_T!RQ>~#SvQn%Cu|ZuQ0CRT=E}~L zy{~syMOW}Rr2LdBIkI}o-wAup?mlofoAd96)yKT|x|Hrs$WGUT#-2gduy_LgKX%jgPYez3%D7o*$-{xhpt8Yi4)*}XPGfVKJ0BIl|Qcdl}0 z*E9RPN>%Tyo7Z{f)#@`ReL@s>oH@4VjB@mu-@Df<{yLRW%47G7txAS<(}4plyE!8m zn4}fB>~Y-c2Z}|R;5h;b#S>098gQ++u>8-T9Rj(Fg={a* zF=eRwuwMED*FFWFB^xd_DlpwVz`6c^!G?+jOv=7XRC&*5nle0s%t8uqiVJZpao{?n zaHZ%0w`ap;%f0heV>iz=Wyn@wpQCU^^}{7jCXO&&_KXK-{C(%A?z$}E8^PSbR(F8& ze1prI1Dp*FY+?e}zEv(Ul)M^b8+Gafx73#w1I_DSGOx!Sz1(0sr^)ul{>U3`duMmd zy|I1gjULsa9tbMU}+H3Ee zSbO8t-aEZ_@0{bkc|rE>WZSz}a&KO%y*qR6-CMjdjQ=N0-G21$-M!JT(u>b_g&jVc zEx^F|w>9kb-g{fq!h{xVo)p@2(YHE=Gqra1V((Rq?41*Sl!g6C=;w$mO8FZ6GdxW4 z!>YWgjO`~UEPj25k6~NFw)#J|VMbx9;upG8CBjN0RFlJdZt$dN-wfU1)+rznw(Lcy z^1X*M?w%A)2ofmBe6e?lB;RD|55-Hkr3p`%R1bjp8A+GK(%&8{2QLa>OMwQ&L>kXE5sS*zxmIxE#zD}|AN%h zVL1XT8F?=-{!xf$`p(Fkz_7!DG3sAts1V~Hhn@xh7!ECad1YyV*|D4pp<9I&7`odV zB@D`(mNMiWpAogKNy7c}P{54w#Pp&`5oO;*r#r}HYZT;4H!ywJ zS>khJO5lm95*_U?L^E=q_lvnF&#ti%oRDC3{YA#J_U7mP-^D^2!;-g26uh6E7hjgY zb47;WhsVu#U+=S9e_-Bo3BLz_5}wx1=s9(Qzn=TapMnpswk3Xe*)LmA8G2}9|2zIC z-(qEMZrR!g&OE0TW`2)R@EyZKzmLYRLmm4xxX)MftydDLfBW9`qkCFj{pyb$hdxGr|M+%W z6Vv*hn9NVz?2q36&O5a)wes4w*!=51=EYA~_TF4R%-pa2;nPnCEFL+=t2(c*SieH) znON-gcX6_Lh3kJYetY_5{=JV?%as()GZ@`?s9pCd>i)^8e%~82I;}Ei^!L7Z;oF>6 z|IlLp^W15(6#BO(`aPA5$@xBwQPuT{WaiulY4KTMRRQu3K=Dat{|C!PM)5?58blO%9&(G2u-sk&I z%ZXF1kL>;QU-V;9|AZLRrz~+5J{I3YH!+;6f2%8B7H#&7qw^rc%DwUjI#z4M-nQ@m zRLXEEKTVfU;2#Sgd?qmE#)XH@9fJIF9UBfjI?5_2<)YKDAhCgg#gR*HYZc*(EkIn|?HOV%KGnR}!0WUW@Mi?2f0 zL~hNy+qLLm_y%5yH7z$LzP>qK(QX0kJn==Q*5cYNy!@+fJ-vPXef)u%*KBzf49t;- zKr?}BDheRyiPwB91kVKK)y({yeqo_&`d6DH~p8KoxO)htW#f7`S%Rf9k3fhDE zr}E3otLxM6-}_trXk{*?*ac+9_S*ve-wa&e1*yUEARLjECwn?=G)3^$3zPr0~p?-Qwp z#zj?JJR2qXMe>?hs=OK+8^um6SjhZi<^lx{p^laMEIbXbPRjii=4<2)5Xflawd(NF zRhg>O(a2h{MZ1B+iR%I5!c|)~xwV*CT$<14RNe4$dZl7^j5|0!|Y) z1u*MzENrm<>-d0CU-d%6asj=HhWP?!o3=6}q^eF*;OUqvx{9TSwM;j~`M`oEfnS^l zPdDbRoh-jMDCkT$<0Z;p>-R3;s!0<=KBkem=p{B%{#6;&AD~Ic7PI zZK|r*O%j&!%SCAF^DqS{u(pK~%W&NEgR&Tbk zYm^7hakV~|&B60Qh_%kZ;`iOgA1fEcZ{TL#ChzdK^Va!AHz+5k~!=ZU) zw))1{^0z&njqDlUmpAg>Dq%=@B_&tO%L?Dr)~-8xv+?V*JnZN zqDN0UZ7QcR$1GsrzmcrzTjCXdOh|FVlvG`IC-3NMLeqElJk_g=^oswtY0^OvHIB~6 zGq+@gXIcb5GqSzvlOD!7>*Ab?CM$m`W&e*WcD;FJnWp(o->f3xIZ8s$Eo?*linBQ8 zzI^k<{HEra(tV#DKTdhx^dizs-hXULY~-&t`ezw5ppsz zboD&p)sdmk!*<>}w_eX=P37cQ5rx5Fn~P4Y&I}cZy7^0L`#w>Z?5Wy~FQTrSKMZv% zy!!h0uUAU@{+XIIit2Q|i@NbzKy2fzz&A;eB@xH%mS5dCaqgKEO>X5Aab}j&uIl6% z-Hu$+;gP(zVsGZ_>hd-+r1`zV~I4aDbuwyc> z=C==WUfXOvnu9gUl-eXTw>?E=mVjlmuQjsT9b5>~z%{CURROwu~^}=C^nr|=D z`NI=-PMQ*Y?8YJPrc)`+RUg)>%r`P`^HgY7ih1b2fZ@i12jby#Je;{Bdp^3h=;r-k z;dOtIet5b4GWAyy`WybIFYl;0_vQZD?P@EhwIvsvcvhQPRgvc1A|~}%+JZQLgps3ISxRb%LT{C`E|c9?a75IkB0Fd5>$55atIn-rS-Vjp`N!lL zm+cn5uhRWd%(9s!%`lH&?e~XP`;I)1)h8ACwI7Kq{`=j^|Mi;JX*WF47zKAm*y;gr}}DN>CmenV=YJe2@avzzho~ui(7n8P+1#y{Qs6sSMnHp?_anYmR){d$9MM8L%L6_Q~9;e zwd6O3PFIm`RQBe2c<5c)sl^*}>LZeN=j6|Lx&0N}+ld_dJwJ|~s(9Y>!+X}XuTJW6 z7cZRL_q)t#%Xy8^R~E;fN^#UYlB&Kb^z1lut8Sh4<>jiUcC7op(A_`gR#|uQ7lEQr z@?Q1j_o{Et4*qQ4?|8V%Lfr1HPlA7C+te58ar+bWOZ_(n-Pkt6cGu)C;YEII%RTpc z=7t=av@1`oss5;6e)+7qX&DM^_CGW01C=IhJa%Bw@3l`dJ%i_Yf7w3eU+anJj$hNi zi?nPi{Fz+GV#Pe$XS!xltAWdtO#=NXD!Z?k6eO~9KN9`l#yUAka=p5NSK!ka=E;UC z-St&gm$|>C%-4CsUBvvxQGMM4{mjxQnW|htpZ*jdRi4A>618aNsUr#p-#+Q6b9i}= zS65t^|5Hn(Uev4xzmi3-AEvwcd_M8oZGkw(&EEB*peQEZPnZ7F6Oy4}{Oy6sM@JydCWTr0< zGSesBeWxwz8-tJpLmYF%E#@U2Ov_^PQYPMRd{_3JY1+{P%xkj4MOqwoB_DNQK6R^s zrR4#mV8MYH!2|qkkeR-+&D_`c)`%y+_}U)8XtR`I|J4J*Li&}ge?nP|Z$z*?TOVN` zz_6+800WOe7BiPZqm$*2g9>^D3paJgKeGIB_}5bH^8d3JF~%e?adWIcGUU;lOf&n~_=N0rTUp#~GCp8aQT5W8_}pz{J+T zprw6a-#-q9scYZxvy~_?`%Qa4fRym^1!4ae&2%;U1eyH4~e}dIq@-4L;%t3<5VCSkw#{+>&R?Gg>fgm;CUI z(IbKRpYMU~%o`Z%FPJm(t~kK5?eu=kU81l;@WGc!!ABan8-8C+)BD1-!aJ-*Y8Qj(2X`i=35&SB|33QX zaIiwLAWpBHfmz_e@hNg27xeh0^NGDak}K)FWb!R>vX=d@-#ef>OF8&bB+>o zh{KASdWS2U;}Y3=t~0WIaA$sVuAlGsVkYtJyX=h~d`p{ca+tAV`yEfdS0W$p-s5~> za`mvycZLtjyQP1;XO39&d7syf)v`0bGV<9rEcngLEa;%mBvw$iqlKWFfMDaN(d`nTOPgsdg$J;gl*STU$ipGUZ{5YUY#gblVr*uCD8P=tmb5T zDJw(VcJnw%h7c~+yu9g3><6-!x2JJG&12kA)YjHeBF?ywy@1O#_Vw{ZcZqub9~G|K za{9$f4a^u;Uavn@9?O_fc~&e$SfPc>LUn?<>b0lk0v3$IGnD>Zk6u&Wn6x!XPOL^> zEkXW5qJ>%0f#W4fTXR#}8IFaQhc2(nWKU&pVEj|gAYV|H*UtD=wVdYwL+JmEjtgI# zAF7uZmN6&>r1+_Y2p`D5A6Czr5PRlWi{>Q8sxStDfIQi2jD^b>cbYSBKB$k_kMPH>AJd&&+7d@d(R1(dctL+b*2(nkl14dPz=O zd(zc1>2QWG?KPT7nL*1K{{%4HOpBe;Uc2*nX>bvvVgiGrL6!OR+_LYrso%N_-Qyog zFh!^3^S9U9Z!2PJ$ar(2@AcD$v*C&Vk9B>R%5b8M@!j-ViRFwNUu199V0gQ|FUh=4 zbr}PvL)~i$roQKKZ^hHq-IGLjw4MABdcw8g*ro)gX#uQlIZWFUbS%Rd4=`*_kJ|NL zBJIyn28{!$YR4IFPHk=tnk-bt;LzE8^Jr>^T8oB4eCs0yNi$UrhP3VJlQo+do`mN8 zYz_GKjNyeUgUe2aknPzXHAz# zGn{$Bq`5Dn=19(3GP7rK-=vs=jXT1|8;ZzMz)#%Bo{OsozE1tAklRJ+pGoC zSr>5KTJZbi0zRpQ|1}qiaD{WM3fG*lP%>&^XXU~&QLfYl>^cUEl)Kog0yy5yT;zXr zVJ^qQiK1LeUW<&p7OAgVWVS0rr)!elEzpdf@vp@uRg0avLM(SJm=nro|BJ1B!;<_D zi>nNl1bBtG-fFO372%<^I6r{v?}nw(r#Q+R*o}TI36xqIY^5r9E3DOEasCFzm|M&K zKHyC7T9j(AEG25$VbiI-GnR+m3OgsTT;h zSs6aXVDUVG6)~rlEj3u2l(oYA*8GZ>p^GisTaBvc-U_R;S~LYbsW)xI(q^wkzY{_h zS%p~c2oZLe7uw3e=g{z)bMjeB2FVXABBwL%u?n02s^a_g;_9q0HG{?F0xSL`aDry{ zHY_dBT3j3zGSe-D=R%ifXmMyM!>nlx{0pXesWJ#TR0p%pW!=Hh(#gav(3BRsMp=S^ zX+y>0Sz!~k7N0$}baerT*N6393pjJCxMpO5=J;GpSF`NMc4?h@uPb94*G8wMtLJMn zIA3MtbZFo+4~g&$d0AEzw{zOADh8$t8$CtmG8Qntsj7ZIYfa(S_i0bOYl*skIE+YcIXxk~UcE79GOw9rE@mzj#4mR6B#QM(XPHo@=Z*yVVo; z8Kyp;y3M(?$ksDNvY?BnAkN2nWdt~p!Dpw*pa&Az!WoNIvNcvp5+}b4@qc^S5D#`{^Chm5lY~+opaif5X0wD}dpRR)~NAqo4p&+|HcR zZ#!rIj}BqHz*u#h-}`pgt7X$3t=P*D&R7<{?xW`JYZB|Xd6yWLm8nF8@9hTd89ZdY zgJI9AwXb%cwhrliy(i|{R`G_lwx52vnMus zmqx&@qsR6pTQlzKU_AS5>X&59+7z#1a)p<1== zQ~#?t|2D7&3LIC7TH&V3uqkIFbHc_=Iw4#B%ycz9#`$`q;)K(#t{b=NoH?ko+J*JZ z#+>E5r6Z0zEXr><7V&^Fn`gu923FGx{EGyZnElGXXu9mwoJghzi*-JnS-ppG9m4^q zZq_Sai!`bhAE{ZPzUNHi*F!oR*2i?6@|wWC?!vNp28*oLtkmpUF>g=UDTPJf?(mBm za4nbNSS-NZ@M{?h&jqut6`i{xnHRAC{>3%t1K*Mb7ogL51{*3EP7A%AClY&c_Ee^K z54ejyT&i>6QWD@;^522${fA3M2RJ?D>0p)%EM;G_CVprVJJViu)OMXT-+% z%U(ZWdA-DUPFd~s12eBzot<5C_xg^X*Bg9iH`(6U5P75R?5vKtH&*Pt(c?R-@9&Md zm2)3P-ker@bH?18v-aLJI$D>dd-JN~%>{d>FS5O*9(ilo*|rsPZ>`;aYt7nO^Z(u| zmA$>G_xhID+tszVcf7s6YwzvGySMlG-Z&t8r``6>k+nCD)!ylyd*>8y4EqCythcw$ zu8rhP*e>`Wy<+#`OD{46HtZJq{~%-W?&XebYkFImn%i&ho?g7Edj|Wi#Zm%HA-@^^ z6x>Tb${*Za`^TX^sg!|@p*ba#(f3A}clMfAri0&4-QfFBUM==uoqAZ)oupsWic_w( zYYLUk{27{J6DHMA%9O`&vfa@5u z6`7fy`n)LGZyVFMrl7Wdo&d&d^UCQb7~cPXdzf_rW3~k2p903`VoVCl8P`Y`_8w>G zwtF;rTB137DSytsbvvqfA56IRjgfBwgW`cZ-!#)Q`kobDdyuG_FTlWfa7Kmt^9qfC z7e|Dd-n?RXd$e0*X3bf(hkY{=8~h4`gHm6W-TSklOU6z$dksZi-se-e|1cwpvA^|5{nH?`KS%kquC?Xm{r_LT-2d&j zQicm*f7V|sTh9Lbdg?wyk3TV{sYm|5V4t@*c-!wIX`d2VKQoCXEYeC6U=v{e!>jTl zzrmr2nNP;SV!?sd7DhgiFXajhhZ?yx<$O8>laF?@nik1;TzYV*U(7xz<>sZQ-ZKng zr_JgK8a%i;xv_bw5x2sLNRj6ZB8o;5pqqn(R|GEgTbfmz5xPR)hbTt_=;q+WqkXMa zQ{VV9N;J)b%=8(aJqEcsIOX{>$V?yecQ!T9&A}_11@+VJ+}N0~s9)88*_m+gOkeWe zJ)k{*rq=Chv9rE__WT9A?Y;Fi`|#St^UHd}!83g~?_IllyZkDXgczTC+?^kVPfkwN zPrrBPXYq@ROZ}I}-ThVi=H|rD|L)%19r*74(f;YuGyhb6S#)-J`u%xH72iLW9-baw zZ&v&JXXxYW_w7yU|1(|TabV=0)8oJ_oWkSCDy`Gw$gcc_+lf5;rPv3eD)yVUg?5Q4#Wv@n6m>BazX>D|W&| zQzCUqMZ~{tnd=lJG~8XRZ!BQ!GO#cS_vJr2iGBm7LFe`6(J~j$nm9F z>96CAE2+}E&nEI186-4t3rRd^Tx#^-`7sMcjfke6A14@PkPKgd|BxK`(UL)ohsj^E4)E>KQTI1eq(2zJ5gr=b6)M<{~I42&FdCqVpcfR zFh6|to`f5q8N394?MLnN>n`bqE6itKRLPK`EbWlqv_r=uwZ7pSdt;r3+OHQD{j3r9 z8^825#5BuQ6gILHO%ZA2lQ=GAcyCgz!-?#QIgZ$F?HtV-L_lNy@ zxA5;plP^pEu*DSAcj@g3IkNrBre-DqD+QJ*k_vIm^-4PqFwE3H{^$7gV|679asySq zy?EXF+xRIbo5zBN#t{4IUuQj@^7C2K0h_SD`=a;W4L!8c-o|KeXHUM$o5%XAGWZ7}x~8P4Mml+Vr)|n-xRRniv&qw6NodN#J&!d!B^`q2eJWZRl6o}a<|&)KLep#~ zJ=HM{@s5t-n7(t$1-;6b3i1Df91n^tYwU>haVQd+xyt65(eKMX>Cf0_U7WMTXywh5 zng8Q1+uppQR=wk<@0S|kIZp(iTdYqx7cfnD?%lxWR<)P?%Fi92^|9n$ZcDJxRvs1I ziA$E7Jb&5GRR!l(xq9ZTG7{Eg-t#*b!Gb0)i$qR&0l%%s#(cZ?c7hduJ8IIx}i|;O~U=4h{M}h zH+I^dPLi}1J8oyTNN3{IH{lx5k>@^$ZnnDnrc&?r%_A9NTW0@!lQBOi>arQ@)|IUN z8Ih-@uI~#oa<=qdVmgs>!$GWcG>gYe;-wqnLbu#wqdr(=NWR z`~E84_N(}TM$r!qoYDfw8}wor{1}74({>y2ZqRdYXvVogZ}T$`9ogJhcTR2wAJxgV zwUf00dQ|5Y=mtHnt*frC25rzgwN?AFM%>{pS?_J&4SHRyt3gL~7Cb&R*L$k&-Q5+R zU)?=@eZ}2<4b0qfK099CKhzGj!-kF`n(P!s5cgyvP zn!LQcB3RyZ?XIn_&#s9-J@2fA+1uMY7;m3DyL z7Z(4X>bG}S;_Dlm?a!Z+{r>IUgZTRY{Qh!Ze|&o3Jm3Gk+~;54KB(^R_pkr>=hqL; z`{&Q^fB)|v!=tb94J^k@6B@5|g04{9^VB)?xn>4)x|z?4&{ns^4uxR53l|QHn%Q(r zl8F^ZyTfG?otwit9h&XM;!>UYlfZlUp6pNzlKqvuq%-5~3x9qk zx36=%lJA73@P|lMvi64D>_`r?p7&6Bf|HSY6K~{{%x5!=vqG0lbgL_5VTfp3`b6B< zD*UnVKNroXz3x+fGEG&NQf=ZdHF~j_-z3X2c}|;_YsTc*I>{v^;a>`liO)!zmC5gZ zB=YL494XCXqLEilPUVPf=t!F8|7F4RdH?qOPng0V{^Rn>K=&TU!@`^Q&2Z&T-oyF4 z*KAM0(I&UOGelM$*fznndE!3{&2Z7MY~8dibrX-a^xNG^5VK{e5?!%S>LxR<{)|;> zi&lHNCh^C=2wk$K;F{JO@qY|2&rh`rxallzyQV{9W8{;c54f4+RSyY<^f@kFbFt}W zs95|DZ`JM@joA-HXCDeJFAp#DSs!w0>#ik7O}b}a+tbk` zt+)2KodsLp-)|H27-D~X*4cQe?S|7DmroXtxli>xbUt!9NP5G>^-*evL`_V79Fg?j zqwtN}f6fc$6FTunA8c3NUQntYo|D?V^HrSt)${QOR($VHocG`L-6VgL4*faYw{PY5 z7sw^9UKA}|$2Qqhkjp`=zRPgy(n;UWrm9yKZ7b#GjWc9B&DHigbfe1U0=E^5mz{k2 zD#(B9-SaQk=5E{Veu2T}ly{%tkME(P+z&&yEM9+Y4=Z z=n5a_E)FX}J)@5=isHO(Mch%JX}INp`wFYcGaoEjCOvUI`?*!oVaJXfSkkjRsd4e? zJwoaRf`8sb7F2~@*s*mBtL*y1kWV28leRB@k?7ZWVI$+Rr~`-PJ{hz`|BT-E-{Fem z>Maj=6q_T%95fC|@8$70|0`I%?*mKwLw$e$6(QYOcXFbmsGhCH?1z>|*}A+RO*pd1>+ct%dYLR?mG(@zn;I`BoVIG}nNc~L zIq>tW=#6S-+cQOTi$2fxV_I(U@X^`AS;Cr6ZJt|RUU{x?)8@GkYSe9ZYM!r5+C1;u zk~9$yh5sjU9o6||p@3_K$TJ7@4SMKDbidpGvU-q9BEz?FRDdptjD4iF9ehcoclF)3Ro{0%n3_|5*!$k6S=Z$s+pe#=?tS;m zrtf<{n69gS?0x^&qq2SMC)U?BS2wWn9N5?WcfGxo00YD4vVGz&8k(#N8XqJ*U}P82 z<&sl)a7>ATbwa5FJI{hfE|>Dn92||ze*~L2B`mgoetUrBjur#EbHo1GcrgE9VAd08VDwqQ+}Y->Y2fCV!6+Efz!GQEz&*wC{~^8=4J=j$4a_@sGRxp#ysRpJH z9>#hf2PS!muZ%8x8v`? z{MJmo`>LPu$u7p#`xw|HHZZuv&tlYA(J;%<;5nE1HYR?DuWSNy8R}vjnEtF_n16O} zG6(+x2CfMV?d%MVycrHm%pMJF3KJRxWWYy&vM|{(aONm5e$qR@z_fwU|MOu6p@;)) zZZ?c{3uejlJt*ICT{pdM#tw#OX$?_^JdNxk3l0|jPb=ZD3t+6%I3O>3lFg{+ReC+1l)=_zA0d z>;jEU@(W%EnHjSfDLiHU^ZnYoG96}v3)h%dSThP5Twt8aqG`#s&V0O`K-`OI5k8M0{(fXA{r&jh>SykZ zTpt=bzt8o}wyP|$Ur_h0KblD~z~*u8`~IEEcb_PPCr`Vc^JaUUiF=)Vf{Gw}F)tgV zzI!gaLEhAFMUS8J|43I6{hluwz`z;6(0iQUAw5T2pl)Fh180Vc;)LXobQPhFIQO=i z@bJx>jr8}4;F0_1kR^=wd zz_WmXUx2~nSc_jp~ z$;YNQusSsH7}T#>&fxYuvtwD?)N71S)RUQ>w@ES-9I>brIZ+igqf!0?L*tJ|#Tz;Q zua*~twTc&1)@)FTMZhnVW{Whjo*YmG!W8^9* z`L}{WtUXS>gK2@pM79qM+zJz3r!}0-XqVuqc7ERfHoe+$S^f-hCYhNH@5Ls{{y*6z zzpZ_aY2TBMh|jGIyN{{m993g}z@VX!urXbQeL}h`XJm(Yurgb4m3h)xHb&tI?HAP< zJIpd-XLiL%YHC_n#s^QW7g@dv(&GJ zEfZyMIyw9H)7eKV=k#QNHt0Dm18vaz3f`diMs@C$%DGQd=ib;k_ukUEcYe;j`gHDt z|CaO4sm^;+Iq!Jtycav?9auW=&ChunVzWP3&OiHf-j~Yx>$ZlqZk_+<=KOy@=QBtx zV6s~9d*ytVss$Xg7I5uaz;kOsRqFg7k_(r!E)@uvZ*lvIEWTv8g>+ zz?-#rV(a2rsZ6O0xb1E&@jJy-wSdWG)dD7lIc~Q))MrJvURdmZYpGonTX58p@L3Ta zRgtM5me~Gc%lgF@yNW&0Yw10+WvVllZgb^~t>Titz+ZKXf9Zi`ey^70uUb&V6?-(4 z!~Yjs<*wyjRqX!%y*L}SRy3bdd#5_b%yi1qsZ24qmMmScV$Lo8x?4+}f30AbS~+Rf zQh@~t+#49U(sGuhF89;on!9UxWfa#;uBD5*7A)zCD~ZaSoiSyaR8-Z6C7q{MN^jtq zwqW`ESBpcr!kIqg=S__-d&=M|7IDaO!4%8+U(&i*wYulp>SfbP<|Lk$r#WzV1#Fx(Vfpfa)eXPGcoRy_uUen`bdBQgypG89{Htrq zLK%NnF)Xy$R9MQOc%V%0_nNZO`5gZ*Fmfx@Fuz{A^j8GCbVs5YLj&6y|5u!U6*jI3 z;1OBP(mr9`=>y9%q{3yj*I)R~_*a5S^g`I*6LtTt=5c<=mrW=VRLG9}o%w53nD(`> zl5d%;0!;R4m5K$$(g6&72TI?x=l4!)Ds7$5?49duy{Px=W`pljO*o7Mx+9HgLBG*9M#cgL&Ib$}2_??Y8F&LK zgeMf|F75aH8I~`R+{&KEcwvpcDTC;TtsDWZv&$JyYlUC;n$P@ud&B97rf5c6wJo<^ zMYjK5EWLqiXTZju=#A|MxUSvipL%+u-0iTL|7TaF{$}7PV2t$M^wX{7msGo~LejE! zCXVM}n@+EhpRnrBf!!Cv)A= ztJA>#*I{pe^uhk>H7C2+gMRNdT(;cpB$xH#e9>IW<9wYL7W z3jX`_pj0P=a6s$++YI})r?56m4L!l|uA70KJzPtBinJMn)Pro@_Wg>lw|Rcs%DCWI zTKMLPyJj&5>l1Kkzm^@9cxKHgG9yVC=cIB!O!^!~YwHKL27o*}?GhR+>x( zgZ;8iYzEu(dGgsNY_nhs|1MSFdyK(iJA=qH#*%Ac=50(2*$gfg43Ywes-urP?}=hQ zu*4U-g6LwJ?`r4ttLetM_?b*n^cBA#$(>!aL9^@5HJ%0XHitn*A zr|+Cm)ZSEb_3-9BQLb+m=U?DgXke8~I4F9WRXTuKT7j!$^@6pQOLs;sn_SAU&F0)+ z0aob;Tw*nANKV>a%5z||#nVO!N|mRYNJMa9@M z@ak;1P*uga_yA~|-bRBpjJ@m&y)H6lT@;UvVQygOKFhK110TbOi;e#km?j_KT;H&< z!r_dD?gA~}^HWu~tpC7Mq`(fD*z?fkan)tt$G~iJ_wxUhm*%uySZm6Vap6kQ1?$Bd z3>pHi%t>IqqjR-2bZJQJ)ib6HVIQuZXE1XAz&A&Mz1iSeN^C^>-OcAp8L}Ca10H0R zo}FJ_d!SMFhJ)>m=aDzsZ0B{%y>Wl%jh?-8`~KdzE_rj3?c6D`H_unzoUwP#ti3mn z-@G~R>}>dt2@89dmE5;=Q%7cjlhEx1apDeSo+5 zknNqY@H@xu&Iy})=Y8j$GrS4s{@%H`^UkH(8&_iQcGcd!arefpy>}=4zkBzd?ac?W z_omt2douUtv)X%aN=x>4MZH{m@40JkJlndvw<0tSF#K41YEtX{AG&KNurj`{jpS&U zb*VPHLyST8!_;?oBcu-SD}H$Jr=hC94t$xkk)OiX3apB~BJ-t=@+!3|r6+GX+nW(Q_^F4sIa zMY^GCeRYxGw%SADT6*WytfwCm5@57cXPBMd8VNI_iy7xF4C~v>&Gv%LCf>EK?udHY zv;W)D^u?>Xe>~BsU}Rz_dN4hIxqE%#_dMRT8iC^z53w^^mlub2JePlv8_-_x)NPV9 zM^rQ`!2q^0m2LDraQK^RC;qZ(iEUQ}0(=y@}|0B;xmw?LrdY21db#RdVeS ziUvESH!vO-fB8&&+rmTqi~Cxn3W|7+o#HvZ>Yu?o4Pi#Rbd|Z&yB@nuT(v4;E64k_ z?DgMYzsyWm;rlPZwBg=!$zQLzCu}YJ@&4=C2-ms^Z0Gq4zNZR)*yC5nc;S?qkbd2z z6KZ1pFJFW*xX3g5h&|R|{`6GoQ~mSG$IIHMd{5>2kj&=L^yT@db#L-Dg7@(s;1^VQ zlHU7St?e_z{)m=$)r!*^G!nk{JbmHyzHDmSw`Q?-0q1L<7BMV8sU|EC8xj9b*}pDk zfA`iMkCWx!r>$@I?MrpP`K0Q_$Ncy21q_;O_CK(DU%m4CgqPB>!9VgNBlT+XRlMyp z`oq%MZq@4J3Zq&J6{nmf2IMSfTBRngXiZLwD2OBMk16AUc+-5QRyI@c zQJwA+6kYpNA}>AhoT9<+5^>?u(lv6u3LKcYO+GlV2!|9nvPqYGaQw%Y&~L}3Jtf$u3hq+LOZT5$YY*%K!M}e!^{_RCGP$2*mRodhQl(ZH(%Hr>!kMG zVC*z_#^Glv{^48rvSZ)aGwXRe7!r7#Bzm9SJ94^&iT6!OgFMd23|75X3fYQMhpfvjG!fNeaUinKg&!D70{9g)eAgnX=XPeX;ec2aW7Af)rAlU+j7FsJ1m+riqy+lDkvFpbv&pTg4o)^3;JYT`- zxn257|N1PB1&nV+9IxK=Z_X=Rz&ZE4ljJYCcDpUMk8Un;*;ZoSxkzZPK!*c+!K6kG z2UiBsj)r+gQhp`dRG5ChV(@V|CZst}WT|qd13TaM&3rOx43aMn$ftUptCMn7&Zv6o zpDz-)_z=fJ&$Z8jW_I~5w=10;ICYiz%BZU|lT2rZUwzd#;nvlcHKJ?I3%-t+9UQj# zQ^)Ge*5^?-t^Bv|yE?CMYPQ|WsPMW+qN}Q6g`$3*lHUJMbaK;Htwj6E@WYqXH+0TD znbTvs^`0G5-?(T-M~cOYZGPJMCT-!8$n#pJTXJRBc3VY9otq=J^+V*_nCNV! ztNXqgZk(E%oDqHd=pC``ofqHcDKbahpC`KGV5oK>cUH_%KJlH~gWna|b~H4*;@O~A zdA2(4^|xy~3j^O*rC*BqF2uR-qv`pYm(jDo-4i$Xc2z&;)9v^Pf5Z>S34Um>3`$_m zW;w`tPPKV|a0BXfdqE6-jQ-#`yx*KM9vc=MY~~QwiaD`i;o)`xWv@9N84* ztSDS;&mk=%#_{0%edbmHuT@{n6%(77uW)=ivBB|RW9Z*2ku%huFl?`z#1s>-<6txP zIXtUg!-NA~^~^FSC++G?IqB;cT>JI<@=PuFCYdjiv$+I2JY$dLSsAMG?~cxh5O;onak#sy?VL)gdpdu6-%c5 z%BYiBvg*~UHQQdjTD{@ef34SRwp`14y>`d5Rj=3W`S$Ad`UAVVSQ(CpWxv^YLT&Y% zO=rwrzuA1jP5bSZD`DAhx86uw{dU_aw}WrDKWNi_x8upQ>~}jKRCT}G^=8}ace_6v z(_Xvl)3xmPdw)Dzt-tZhyVvjcGqCG?*z}h#=fgn`^_;weT;^{+92O{-{&++ryvE?L zSo)fe$6hC$`FLERJ!kE4rRh1JP9B}q^XZgM`kJEC2FKTYIBj%2XY+oO`)fX*`|#|{ z=kpGeUl(6+5$}C_!A*VbmdzgeZ@*mrtqYqy?_%zs;YI<{NKX`50H zufxswX6-|w`g6{%<4tpx{&q9mCU8B6-IKZHcdpp|FLc@F{zup8f!UI?Yxzxnd_F2z zF#mRwMEHlhIX9HHJUB1<=~ux~@j3N|$L?S4no#lZ+AZ)l%L3V#0n^|9dOaz;dxzJA z^oPtl%~q7`zOyM>uJT#u{DVI`laIVx^WxtIzqK#Zm*~0ipVEGNm^(Zq|L<4-H+gU0 zXn!?0#(k^#cdNZgi@oJmyB~S`c5iO3|NSFZ_;=OD4YPY~?${lfYrbiN@aa9DFI3Os z-FKl=OyWZWhX^xo54-NWwNH7PxWmOHGz>FC-xa-1j(_yXe8=ftUG8LuKyI$*%;#kr zD_VEo7d-c2H>245h;R$RC&#|o%iHKzT|0G1xa@@UvD6$M-5b%H{(n+u+nxKqP<>S} z|GEnnc@IC-T-tO;JkH^$R@leR$64|lFFohqow|_SHiMCO&-7iwa~HBI+Bn~Io4#9L z;5Db5j+l66LCZ(ib#3NP!XAZv+TFF+fyd;}p_fM-PDrj>%PLZG?d_%ohqW&x@UX;i z@;;irRv=EXS+qq==Jl3IIvcXfau&z&pJUk0`|NDdHs-~=pTrJ{-&x;cyCL?Gjix*E zqq8OP9~kABSGZ68GBK%L=Cw~+&58R|a9TJsn7TvTliy(Gnw@KVC%uo7=2e)*DwV*uCWGY>%W|!@ z#Ja?mBSk7NBf52@w;U+eH&_sSG}`iwlwb7b-}7#t*=jkvtSqUbdF{71+g(fgbXPrG ze0I~j*}Lw(UBCbN-D|fhvi1J$o1S{*>T>?R2^QbkeqI`;+B|8?gM&fc21hO& zTazRte%SG{L5KSt1Hs8Zj<6XUc7^}Z7v22h=#L`9p7fj$soN4sM*BAQiEn!>xBSL& z2eyd5W}he7ygN^L%oFE)b^M8%_0N+5eVe8%_j#%n{qt1BxlPlypLwcRU3oh2#bM=R zKF^G1|2&i7w|Um}GtbO+|NMWppl|b>=RVJ^ZvQ-2ac=Xx^K&|w7BDb2@NHSZ?)$&gnh zt*gS%z6#s@>*|KSt!vVCUoj~x(B5%w>$>u@uj79Ix_*Ff+lF@EH%ZdJZ=CSkwrTp= zH)+=_f@xl-}`ayd(@M)@9Td5zR$p4vG1quhbHMi4>(+PJGu=FEDSH7gzXBqdeAUmdd_q1 z9S7LA{bOuRe!$2cVa_GT@HNX=f%%4=11nEJ1CxpaQ!2YTlT*M~0iOe}Sf5w2OMRHi z%=6%7Lg6m20@;QQ|83vg`F0|lVf`d#jt8$hzOz4;j-Ryu_+1_C!aZIF@A;3K+kKMP zIN)XSX=+c29V7RJ9rgwj8n{yI-c54fb>IDb<&oR9y7m9LUjFX;arg1x+f~nhKM?=W z$f4k%A^yUFZI4kSPl&=#o{E3>w$?JRiacNx)HumxmC(RG<2RGy0lWV?uyU4(+z`!n$%Q%67U!y^E0fSUSJA@ z-@aW)xS{^bbAI`TdbWU4>4*l=2MzoQjr*VHRz%VgAS8xJDgBa6?=?#1bnz%m*Djs0qD5&>3(dxulrZACl`!@zr0S3tf z{Kv(aBqy}7D=b7LDBBmx-LwO6Sgto`?(UX`&@$f2xWyi?!+zt+X(#OLJ#0SuCct> zseWW!?9unHpaAigR4UJ0f4ZT;6F(@W5a0xIylxSOWqG57HUxK^vpAAzt z-{{jiSoLQ@y~z&77|$Ms%-%l-`qfv~N(pqNW=>ehF2?FGEn2fG+_RZ!1w-s~#+%`- z>>C)5o|qUKDU_}`NnetQD}mv!MYrCJNsn(Z#urZ4%V;rs$!`%U#JXX!O=qs1Wrc$z zgFuAPMGFS^%wErxy*JDlSU*f)Gno4LL<1Yc%+>5=LJbqN9!~xDp(0k1adX7%)beS+ zU$kd)PGC1^ix-@}YNn8O;dIdprThhTQOAWOR!%gzF;ORS#-D^KiUp0`os+abGKzAF z1^?%mJk@-%>B_3m5~f9mv^tToFS0V zl_t?4FTlW;(7HpiLr8#u*P(|+ilL5kPNwFZ#vd(*E6NvWh?IvnOC4`FnaP+@K3!-+ z?OI8O$rcP;2g(H#8189wHUFIW^MugNn|(Y7s_x#Lajl|GV*}%Qj!w7Cc@M6aOr1Gz z=FWK^ug$w0#%LK?DcexN{((`vfUzKb;;Bw1`3b%LGje$nnzeh5H@Ha5N+R1S9IlrVqz2(XF-1g?q2qFG} z9?lI*+@f-Au8ZzEUfs-Ix@lJ~dqMUR^Xi?Fl}+N6ObrZs%UicrweJ13da8I?-Hk2{ zg$nJ5B>~G@W*@KE(bbRp(|Npo6W146c~Ej8J^s#m@Hno zqigl*Q>#2~t8(N5TSKC^`gU*en!WXX+t#StTVEa98YjK&G26DJ=xq)jo6}}*^X}f3 zb$eU%>1}z^+vB;n7e#MR_TFALdwcro?KR$cDYv&5{@(s>8dGZkN7n+5)&!=8+v}Qk z=RxLhCu#58wSYY}f$99TowIglcfVdI63#KvdgmhRofQn+v#WP4zrAzLl^xH+*cSiZ zwP-cFWaF+ayK|N;-xYsw*Q04nRUdYhD{#$wz*+ubS4G3lt<`%@w(ZtlxclL?-HWQ( z6$1qp8SFaRz2{nX&Z+L16~FVR80;#aAh7(u!LF);y-%!jZdMobFfhK_z3=FcJT-w` zlYX;Z>=rnzy{k1~-;dYX&v)$kiWfvgDG(T9QF&0+q!aGPahV1FoXBQQi(S^uP-~S7|9s-kU@~)@N+iq#oAm< zj?>l_aL#Ht;a4F)O06aN3FpICpQzWYq(4l%|X z77V-_=Ki_Pz;{8Ev4J7w1w+~Ma-OWt`r{|N?_~4G)Rdba6Ad_)mXn+DhF^TcWbO<5 zoY~F+kH5s*>3mO$CFnsPh!I@Csxua3Y zLP*?TN_WO;_78J*m@$TQEYCY}STTS>cmqQjS4L`9qo~2Tjy3z2%P`jLJaydSpmgLx z*$qp+ugTkXhN&We>rVlvmjlP%o+C`M7gldzJhCR+Kj+MeoW}Te2KfmL%nA&yyBMX8 zw-u}qTrSnYd|+aWcqi9c2BrrmZT6gzn!EnCdS$0X(}8El4F51j9zS)>ZR7g?e;7|y zG8|ieeo;63-wj+lADphJ;bfLQ!ZMgJ$#2)E$};`TVqiVBCj0KC9o`#-H%!*Ja5#8x-iHmB|88Jp=smvp!u3W0*2cTX zx#rdjENE}rd!@63apj8c^%)&}1x<% znEo2*nT*x4uj-peA$ z5hus7^8pu&;!~XqN5b-+{{CHX`~Xvoo`CQK)_6Ov&I26Gicg*Dp1I9?mi2FM4`|aR z-x7yMixrq|DzNW6z#;v=fUCUk`H$IqZ%G#lec&lFcv-|?*XY0%d4O-qKb|KByL;mH z_5FJx$j+V#I$o9i<_D(56IhEGI3~++?*H)eiS_Pg8~Eo6^9cNTW7_`Y#=6b7_PsT`{q~;T z<_B``^sL`KS-0s~-8;3}?_TL`dUNlc-0ycE)@}S`_g*YJd(*u4zxKWVbMO6`wCpo- zADHYvu*830tN*|;{{z?l4?Ooj@csXAXx*%fdGC{=-;3056t4dmoBdI0|0A%c`ChJr{1*;&##7=;jeK)$rZaL2d^O&a8z}!(w&3CQV?Dp43WVc6ul5kK@i{!l z@}RQC{m)iiH;?hN8r&BYV0w76S1{n<4Kc+ut>7|GXa88Y&B9=^J*vv&Tx zgub%^>5G1 zG5B>bt8~%hdL}xN2#4{M~yc=D4on^;V zb&GPv34iipCagHYU;Ujy+M(SiYPv+jpNH)2iW?Z(I{M@OU#yQ_$(V4TG5Tc3^S;Z0 z=f6GYKeE;)cin%%=|AgJR@BvtGtSy2v`xSAPXMEcZ+i=m0KbreL*Rk~4UDXMKb#M| zcVgi9qxNI>hXsxee18NK3??u%+fPtX{xG@W0rP$VUI87B#sl}8*m(q0Dhe2!8#y@G z=7bn1Hn7xjaAzDiu(4r(hn@ZhXTeLL>-LuVEd^h<7r8a>DVLE$3v1%hw$=?>8@yMW zSeFDGSkLTSm-^6Xf|ODt3#U9+3x~pj1JU&k%ndFZ7CIhkFj#vpd>> zsosDgoAK#}q;p!lLJ__556awdXkrW6vtr$cc})H`pPY>N_x6S4G&FwSQ0%pDxzYlD zmERuBy^XvI8w?p3+*J%TnB_~n7}&L{7<9Q-Z~5e<ZN* zKD&%-;Zb$&3`3skHnB4~I5aMs6D7P#yjc4H1G~hG z&}*$yQYWtQv+yJ~g?fH*Z)B3l&{c706cO1n{gKlHGZtZk12-5&T|JtaWC|8A2QSt!x_jJklU}vqf@o2!O2-lp)qc6lU@slfXjJ#wkzC; zjn~*FTyN9e?_S(w%=G;-M89%U8y;xCJCzQ5q_PPx#dKc}JJhO7{ zxBt(V3q)$$zgx1^FPHt!-O(Ivoww`tw%PA?zQ0zT zzi;Nb-T4QY#pfK_&u;#w;D{}cb>T7T^gRW~m8Z|iKcT+;PtkVm<2A+S)?V9Fe8D-q zy6~d6x$Nol{^7M>?+2y-Exlo@YFl!7O$&#)NTH+;>qOj zy2@v>%l}orSp5HP>Fw3^bE{r&HkYk_yZil|>i37Y%hY^4oj<4M^JR0H+ON0Y&#wLc zc)N7n&)4;{>wbSWm#+W&`~Ix@|EvOO3?d1P$6Xzm)g}aSgd{MpKH{+QO-h!>=}8x5 zD)!!FZIh2WH7kWFz zu?u)q=iF%E)p1GXxW(YRrg;9}0Ch%v9#3Ybof{Vhw98K~>|*})kYVaqWe-KoUN)1R zh6^^d8%b&?P1I#78Zq;-;6z{qBb+~$}E`f zSlN?Yc1TC}?273E+6Sgg*wm=~|4};g=1Tw7PECz0upBz zO1zR}`t|Uza9r+e|AP%~4u>W+*{x74sdQiyQo6tx5!Cwe$*t#X0@Inkcm=s+EuZ`6 zU_aL%WB)%l9PGJL&orN6W#YQaA0_k3c|seD(Z3ZL_GW4-d3jefWnH;FlogbWzOG_s zb8uY6vXjAYlSuB$YCjIULo+`rWN7+XF86aiyilA;bfHIJMq*Uym5$biwG%U61#guM z6{)+tSpTB7UBBt!O$iDt@&XrGtpXTiy0-pbS}G!7&-W>?E@7*|jfn@?T7EL@ys;sK zW76sXQLR~GikI4Fm!1`zStZ~7%XIOFIj<5Ny_OVg{A8^-f#YCBx58b|Z`muEpR-Fp zxI7`{i6!4j;S9!G(dQ%*)Y)tUouZkiuv~buP$BYE(fiBc%NIPDyz`;|eg~(k{ja7f zrFb)VIx)%KisjfJ}U1&wXfQjCA9o?+);YP=?~;{a!}13S;2 zyLSv8>^E(8*zv9VnEOXIR#^eXI{70F(@YmIGrU_;l&7L4c#6$B`OhS-yFcTkjJ@&vowb=^oBK3xKa<%kdCsx!^PJb(>DJ}^ z=PS?MoA)!u*8cy_vPQ9zHyp7UJW8`pwufzbC2{w~+pCh7_-ei^Y1GK{j+VSU^W3Lp zmS>;x$Sq*xKtJw|}uHN4KBZRt82H220t9%vE3KI6l!~U}bjjJHR9`jpNF# zYqjSVcy4HJ;&16@l>hN@!|CwMbZ@>}7t8K!S^3w(KR){E&1>6!t-1U5YoYX=^J3q3 zq{ieG-?zHU_2=6z&Gx)UOe?|L#zRjrrZ+(eLy!h+^&bpV) z({DcDOn-PtvTtXZeN8>z^dC=@;y$j6m3dr#XU9*Idmoq8$~<9S^5cY{deP+R+)p*_ z6HZ3z?U=g%PLSdBpG>iFpUWTLDKojgb85xC;zM_3UX=d+c|kd(WHCScOV8=EFU+(% zy`0(R)$-_HS9S-Lu3pWp82!BJ`rdz~8`@*vB&%27JX=?`WqIt|?DXo}cmI{`IR5vw z&(+R5Z|lnUJdb@}{k;1AU%B#K>9Hjd;(H!(@mn2|->20X{wGA@{*T+1vY!;E|9Rq> z_wyqw3-T^aUj{!$Z}1GSqksgq8Qw)lyCnp#Nt&(kbL)<<>yq)@wB+Pujo_#yMr+Q_ zH12E*44gT^*<+Rk=o&?1rKvXdCpb)e_odQ+n|XoRlj$^CZc z)YjGE6YbC0)ZSXmB0fK6*BoDurAH!`=h|qgOzt=qYTU=xv(b2h_aeslI-M)Owr+TM zxb617+ST6_r*8RoO)2D$*}6kQ>0TUa0aF|n>vg_4V>Nks-{QTwR-Seq5*uG%-w-%U z^1|Zox3^;1erP+EYCbvPeK%}c^#z~l@}*BE-E>Zv6t#r;&62+#EhHpc*CuD*KPRg+ z)ppnYZ~rYWpLqCK&p%vxiOsc72essj;Wfc%EgEYd%Zr^}CH6 zN*=CC-Cr3JoS*kDjSD@r)jh_sRN%6!boCs=wwRD33*;GNWFEweGngqUG%`!hc+hRI zYg(9k?1QjFtvP?E#T9h#Ob=-5R1tR;_bQxrzfSF+_=5)1yCRP#2UH!s5|O@P$Gkr4 zO`#8i?ZT#>kTAAXpE*N???ZE8lc~@dnH!dpPiGfA+LFNk?~B31>WDPUo6`y&dI(6y z-k9hPo<&Z`1kED51Z_-|Yxs0EWzLkaDk1UoFB{sYvbgS$D2V#@LftXI|$eB;B znLYC^vKIT?{C~}rvw3Ic%e6cFj!aR0YM63ox!+w&0nuWeZ7-c-R7`{ATmKYh3h%?ptNA-)_HCr~Pinv^MW|J6{}HAY;Z&ll(n@B4j!zCmYr z4}45wJaCXhea(kMJm48#cOB3SZ_dY~5}+AgnesPwdeDk}gwrGh_K$vqS0Y-+sFtvz_<*of321^1Bt!Wxw35`u_jz z_cJyBb$>iiXZQQ@Fra(FmWNs0515bJY49~X?zsNOnLGT3!p~EY5odlJN{(sV%)4YV z-+{-H@s7>BQnxQSaZlcVuIj1LZwKeOx+#rTr;>kMKJ-qoe%=QO+dT=D+(|0>$HZ4B z>l}K!IXUNW#q#cp&R@1DzuCcmr~LlkXOg%5u1WZJFzh>FX2WW8tD&5eKeIUHaT3n-Bmz8`%=Vb|BnHLU+Eoc{E+ZT#;0A@z~* zyvJ4FS>C2P7IQqF$B}bkXS3~j`B~jD9I_UN_`UL~(pTL3x~YAG&X)up_Y({G$~~GT zvV0nOPu*qxe|Wi57w7xhiN^DV+9NjcD81qMx1;IjJO}mqISY&JUNlN5M5zCmdx0(B zk1Ruu2HS(13(EZ#9z2+!;mnYx!~L~8`R^SIQ#QMEZ2A>Va$h@+>dQDLD{Z(hx#s&y z!;A~b6<=bGo_o=+f8yiB5|c=_v(KCF9emFkRdZ0V>O<@0izg?9zi{MN+itJz$JZ6a z^H991pjF3V1FQ0z`~Pj8HEZ`Qt?)^?CA_cT_|x8p)AvtFQb^=HabjY?13s3n8cf3&`1m9*<9V^`F;7NGo_DsUVlEweW235=2X#q zIc^oboo44VnHVPb_dFIqvU8)(6T_J^tGeaYuWxKrD@mGv_yuq2GZ&+K8--69EKv5h z+AY3Dq?P^glJNgCHk4k{pJLX#&`x>fMx8&KGtG1!`oz0mzwT+!D9^Q&p% zAz?)v(|L?>rd1vd5FC#B*;m*GQWJSk;nU)_*1bkB8Z_zkgls0+7{@+T6 zjyPT0bkDDQuE5f?trfeZHhwLb^)%yU#?Om#7Z~PhY}Rw_QKNTT}!c?rQ8hl5v%?bO>W%UrsBllq&|t>M=5f1ZjuaxBQNdeY@J z5>BTKj%(%}ojh5l&vB2@L8Ggw`f3Juc4*y_J2dUM$Fvz`E1xwl++;ep+;`IyU8|Hg zdee3Vue&p8^O>hRb9bIj2)jM~u+OuNy*tlj^hM9QzGl1e=AZx11{535dA`TO>b7KV z(Z0>|DyKcS{k=1-_TT0OiDEAtrEg_w+-O{cIm0{S+?HkLXJ0~Rco&BTFo-BTz?k9n zeI0fC*ERSIZ`D}WfZY!|!@J{<__+^l)_d;%soQZx9Wui!ael`!PPvc0)qfr<@b5g~ z?)T|v$sW)Q@6J=<=ZYrH-}BUAx>L zj_*7_`P`Rf*0ztm>vvsQ?pNCEzV}tc`CZq($9`E`{r7d~eybbD{l3lZj(zb^)B51$ zb>C*pzbh{=q2Z1yL*p#v1q|#PelX}Yd}L&KaD{Qk4+gsl-d^d)js7+y3untP!_h68XTusQcg)3?65RSnz?j659tOaYuTqfZMPXQOxtp8Q7?Ef?H zI5Y;cE%?i*;K0Dg@$Y|d-+P9;3JaJI)p`ABt!H2tnfpb6<2-}bhK3Ib^*`RX|NFgC zzxEaXfp7l`s=5tpS3l>MIKa^Ood3T$!|Uh#56!DrZZGJ1Ud1KBD1L!)`8Ed807h|v z3V{Mf=^YI6KNz_c7+KqySQ8ld8_NGpV30~+U=?7xSkAyEz+|9N`A>n7e?gO)1tY%z zQ^j{i{soMR0gQeV8LcIlBquamXE6Nv!1zyq;m?Cw=NB!U6UxiO8JsH^1V1qRS-==L zgF*WTgKId0VMdGKh8Fe-jFJp_oDGb85=`0|41y2%Wj8dl9^e<>(Bu`-=&aGeA;5G? zy&-ucgJ(qfzXO$~6%|1?r6uUeR05z%0Oc(41lI@`juj{K5f@ zyDl;a3RG}xV3;CNz%5Y4exW_-M=j?9hJOkz_rn<{wKMz?sNof0;$UEuKEP1|i{4fl#ap>2G%!7{m2BvCo5Aq>1;6Ut7_A>p8z*L8})Bqws^Ktq}}d995o~9sfQs1iawqoxot3F)elHq_5i< z`@b`O`_X49!Q^MiDBxMWSb{Oob3)4t5z&NcTsIh)5*XZGh%8rcjTP_lORSLEIg#;u z=VS9p5&@I0cTCbcIcb+5gR*6Tr~s3~4+hi9$+92%l1@&}mSEy6n8o>_+xcaU{Dk(e z-x-&6&f+*QMf>NJs}>W2cQU*@!M|cAL(TPBv6&1jD*F~kcAI84Nfz|+Ea>Wp?9P8F zvc+@yk#7u~0gM%$vso?0WG^u02hL&SU~GFSa`ykq8QqzsGMqYnJ8P9swsM{TEnv{R zIYqZ~roqc<{)UdXH<}jw?7RCygzv)S|1W9^w$I|3S0ld%L|PlbY0AfzAm+28^u#4=iHRVDy$?a6Q2w*ih~_ zlaXfuBkP0m6s>uZp7U(13i78j%3IZm8dRR%(fH^%16wEKO^<5%fcXsDE7>+M$TPHx zFPJ2BV6LUc92JXd#sv(ABx`vdOlOd@y{m#zKck832P0bo zgP~L#=K^Vg4^8@+?HwHqJ6AAr8Z75(=$NN5XTl49$qAL`ZcX7-VECNQAjQCVSBhcf z%#MrO8`V132wo`VWmwl(&hYzIQ$a@iQmIw8Rr8FlR~x>}U&OxAPjM|vW`V$NhOg{8 z@0Lrm9at@5zLCeeFn{|D-UY2UeikqVtduwCn#n%*&w)w{i%rhqo234CGD%J-XTHF& z`bB}nhDOWZV(bQtybc>pW*3`PZ}Hl_rSs(mpWj;oPL~E+Zw;QkC8T<5Zu-`U-CGlu zZ;koAHS+n^1nX@<>f2JPw|S;-%hERG z-uvLc^u9;d`<_Jadse;gh4#KjYUInEH2LV z@i&{H&5=bL4(K>=n$0=#w(LOq_i~O8jJXyya?cOm?B;aVIkcMLpvjseF+F*8-TnCw z%lB&+$XnL&8ZdH37cwUtajH3b+JR$@z@c)1{h~H``!kMzoSyeQDxYz|Dvdb@%e!Z; z-&HFluz@vwbE;1@_x}lu!U|1d;T#{U*$wwFu65w>DmbZF!{rvk_S9^{<2FX21$k}m z`5FRD-Rjd`ZQJ?f*mi*lZMzH^T5V1&vf9(B!0Gp!!HVlge8Lc7}=K z8hMkX&OU1ES!2~Ix`5$f8N+c42CfT?>=ivixIwE(Y#G@v0G9j8J24>d|J`}bUK4j0)wXSbiz#V&qY`5FTYoC z>cak9nM2YAcOrET%`4cOx9-I(z8l+azu*n$*mZzulFp$&0vyZoUQLxdG`;S~>~$|K zVjd}#-(Spk@VNumazCzX9}cWK$JBQ3RqUQa^WGI4D`1!Yzy;cC2 z>o+hizwmnBx+B_W4p}~DXi#9kqsQ+4fVoj2qVB@mA_dl~|6X60d&6(e5T?L>{2X7n z0hhi3=exc)Mg{Loeb&!;xBHb|p;!Y`^8d=Kja@-yaq1w<+0wijV%JwtuU}{7>P#Kk3A8J$x+J z=)e7EllaeO^`9-~f4176du8qm&i|hsM#TKDQ+IrgnO_z6|LaBVZ(F6mrTCYn)qjh<{UPi8w#@tAqQ8GD z@GoY1a7O6Dha&r8f&5RO!x?!L7#~_4`+u#Ke?!ylQ-!VZKO{5?*cUK1TNZTRE12~| z(`v_eg?>g^gGvXvD_ee7dhVPV<5|!)Ysy;7UY}o#|8)xf-YoFt+LZioUe^C5{)Sz1 z-`Oy=NaD5nj#RuC>zkze&^>gD+cKeE#=4g6%X*Y zyH7meJELEInk<6=6PLsT@g#;8fj?YEAJiW>ve*AJ5_z)RvB92&pDS-dgTul@jDpH; z8Ve@IGjej7>vU8sIN;79Vb|vH;i1ZYP9d|FlnEOWnCsbv0%8P$o&+xOoT`U7Ts6ux zqlGnct6qmeV-_PzfUru3#R0V@Mou1?57!?aY;4ZyZ(E^oiE%#<6So0}Ktp^hBePhG zfWd+Tto1x*4hJNZn(Zes{&Bp~aA-aotEY0yfe98zm`@5@XKN_zbmG{-rtoWXq~d{w zDa?E}8eBga|L4s5C-osY<0^QDmw&nx!|SlMJc`mMEQxsV0}6Nj9_>G`{z0t{Pt_gwNj zz^XT+VeJQ{qSqRAb`Iy~GlltZn7o-Zi+?4@tL_(!A3uFQfBYzu%mp>(pOOm9pZcs4 zHn58nJaE$6{pHi?B}Op^v<;_D?%FBf-_js|TIK*Bqs7Iju60YYa$Ymal-it;XBO!Y zZrm3oVq~n;`-R=}k6FV11HNqkczh0YEa=fNYf<5z-1tt1xj5L*daIX@tonxnOM$sk z-b(m1{{jk6aTF$_OHLR zXf;!v(u(r)H|2gjFnM{cN1^$-Qx}6Lr=qb)VEw1h>Ps7UCwn)tYgss4YG3*#eFK+Y zvgZSyUD{KlCwV^$@_8c0@qK0C>{Aa6C+bZ&H=olbMqv$ukVn80z6YY24O|aIA22iX zG*r5?>T4)4ikAw1WZKCTu%MwuvEl#C`CKtP51w^MZIE=G=FF60dQ+(0e{BOw)Pxso=Ytj1yO0OqRul=?nVfo*(JDJ;U z%kNd2*OuRJ7OyLM&~E;(VrRE|UF9>|(0!FJ7H|Jh`EvF4d*-j!U$6VUY}509)gKOW z<;8OpkFWXr`}_ZT2DX3(W|aiSug`5i zsD7y82uWaGc-(>A?L&pYlmymVcSr8D4`mWp64+yp|99kX`%t1FlE~@k?j*d-pjaa$ zkz4$@llZX-`36%GdA;47rJqeGs=l1aAAZ7Fp6z3+gAEr`m9QY23Lmp|_9;f52b&kj zOblkuIN;i{!}h_;BN}ouZ!TneBJ$ltS83NKk>%?)FYTWCv5Q~j(Rnf9i99(ETrM_w zNlY|iFfBS2#CYStv}+Q3A01uge=h3)n~E_5+m*BZ!G|2!biP^4vSnix;_ze@P|)Ix z5S%Fc!-2tW!gk-X1O5Dm58SkyV8FVwjZyT5!}&g;2A%^&jCS5nSWS*HaBg`rMQ~<= z#Qz`8D|8kxIC370Z3t>)YFWbkN1}UA+UJ?yZ1R+NejdpEv$3)9ze<8?!IO5z8=^}V z>lEuq&$wC4_H*uHiv{w04Gm0BDz?5%D4ui5lDGPs@l4*5Ugh7f9_cs!oMIX%Xv*c_ zGvDBoTc<>k87GIQg!-dJS#2rIkA5ETyAwS9h{mc8j;o##(~eEHlY833wTdUmKtZFv zLu2vYnH*6jyCgULNS*ReB~IG4h~Z61gN|f{H^-hus~1lwHak_=Z@pgZVzw4L(_aY& zCa);{-eYGJlx`kK=4uFJ`Eod2`RQYy<%jtNc-gYAg-r1O$ufhTWs6Ja;+0Rj%p2G} z5?mY)o@BEsX0$naIw@24Je!$=#x5H~W6Mw;hXgPyM4vp1o^R}+^=$ayZSm(ejGs9C1tE{eky76gV`SpYsmq#IzAW>;k?G$qaAo1U2P-^d)Ai=hI=3urD@)|vEcSg;*SMy9 zU2}MPR`h$x>pQ=FURNob5&wVIg@a<>7IwzwxXMf4><#<2N%3w@y1&$|i`z;vvnR=l zv`BW!Cn#-o<=Dfg%fQO~f+3u5!`3xtZr)z1m&>}TX9Zx4dor?i)*&?7P@Wzwg9vFWcSE_rCJ^&wD@rectcA{$1nyIrlmJ6AsJQ z6*Pzc$^Vh=aKb(A)8yqfPj%nh9W%eDHS_wPcB}I?=j!*Bb&A)%@`(Q>Jx}iI9W4h3 z2ISdaPX<3m5Af_Sfirp+EXH|8Z?Foxj%o2L*crXg_GoTd0omWXYtoDT%L5jB&Gp&} zKcjc`_4NthGkUY;TBRN>@;JS1?d|R0GkUkLgPzg5ef|CYkTZHWJjA}g_uiSEo1dRw z;M^_eyK4*RjNa98XLoIVeSJgn>3P1px4pf+qxki`v%9yyzki^aTi$Qaj*pK|OxBKv z?eCrMw|Cdq*Ecq2-#@o^_xJY?4tM{Tj}JZ4U9a?^>%+wBdwS2BZJMz4<3IV@k9HX) zyONr1V+GwWD9n#lvU$kQTPIp^vZXfy$VkzIHi4j5TVc}@krc# z0sC~`q#vJKy9zhHc+@Ry=Xjrc?iT}vDy0_AsWTFtDjqi{*-c#*lIpZnxnK5MnJa(% zmCWaJZDj%Hw--Ar^_B@t3Yts5)heb=G5IB+Jl9snBUMay)Ayzw$#RW~ zyoU44r}M_wNGmtZWXxt>9DXM&liEa=;OcjnyV8h3^2wb@Q2 z7s@;e*<|%I;zII|m1%^IA?M0QO!4>hf6f2?1XF_;!>jbvozZ*d zIqd(%+Uwl3>qgxyku~)mFFAA%2ry*1*w&ZiH7a!kHpiq+7fL#ow}feG$bXL;*4*DR z%f8)yz@)oG<@H9^Qh#ZlPf;Sv#3j}UtCUPVdCq*V>b;DrAjSsK^z9~TsW0SXJG=$A zZ{HR7)vGr7CFAN%?)z1{T%-FJbIa6D-}Z$=H}`+XmD;N5ZoeJ$f+ldQd_T9r&6SDM zHq$vpL3yXNeA`6b@EHr4-mWlcX#7wm`sVbHE#_X|*ZNG9-om&dW6KdPErF?X)|^$- z{kh)x+Q-`JkYtVTL0&<58xp?XTEaiAWc|uBm!|igIcs=%)5%LugQjnMlc=#g((!Z2 z#*W;YrfHLYPKjUlZsx6@OXYTI&I;4JF!jET`sM?jUan=ub1$AbXa3RY+|3P>r#(6O zJpA3s|9-W2g9JxI)b&%>@Sf2d%^$$P z!t-DQ?lXGLPv1OOHEm<7>HG|L?^~BoYC96HYeTt_KpAlm>IX8U7J^a8gfQ2 zZ(d%$_MJyk-*=qwT~{=H_pOtL-*;Z<1)b4*_o(j|@EN_^Pv3n9I-{3&eZ_Im8NJ{4 zyeRFC7B6mK<4MqC-{Dj9->{Lv2 zuw6lch3|(uhuVb;d_E5tB;*Q`{%}9o&U4^E%+ChK9YKvuJ_}X~85Bm_{kg{(zJoDP z@&LQkg+?~#2TY<2hRkXTjodX47|ho>H1B0%WYuUm^#6ndv(^NLe|Ae5xjGnF{{8M} zELm{!Td4!9%7z9;nFmaqD?SFZ6g=g=F`ZFaW)8m9c8_~?h+w{5HjC2WUm8tp&-VCIZCz$hlm`1@WNqi^5(+0t(w ztx#aNEIvb@%i!qp`f!{+BQ1R8knOp|AQ@QF=Hr%}M;0rQ^(1{R?Y4Lm-x<>ed9TDc$O zunCAU|7&pIaQg5}_y1xh#S;t%L_hTNaZ+fgwr05Hzi?ZW`2+r`oBzm5 zDVS}VFO+H{{y>whps4fzM+Wu93mE=&6fguNFx0=PVvwx&Vzro8>M;H9YxaQM(+}3Z z-|el+e8a7T;nyFLjqDBb5-UEjP58qg5&ry$uWioKzm1I?JPI!J*^1bljxZ$sR%YPd zVA3eMpfN4FmQiulA7(p;V=9lM5B^sB{l+1l!H_xTAZtX~{>tm~6P0oH5&weX2pSb_tzUvJ?i+sXO#a|3hrQi3zwVo%>u8_<&fq^X{zsy}! zyrCu~gyG$De#Hr8e;T6yHPrtLU|6?ZmT`f}HgP413ydGPR)2k7eL%e?(vTs$je$LZ z;nFvLu82zRV~ze38Ce%(t@@Vx=YcxofpoG&dzAk z5iQ?P@#-6+$&9Mg;!2_iYQ$dfiv}?MyTA~;jgfakz07z1KhNuh85(#63fK)8zqz%9 z8diFJiVIz?GFPopB%|@#$5yXTB9c2Sx|tbwUoUx|8n4nJ${bLTzPU-)BI#0DwU9vh z@BeDq0s`^o9@YOD3YRa>>i?1J5K(_uT~t^hyW$&v{qbsz2mH^Mn{YkI-IAUVoY5s4 z&?xMX*ZU)fIe~#oqAf{6DTSj!Qh@1wDPy*UsKmEY$rB7W)N(gID-yJ@<`GaT^N`*7 zO#Qz=Td6psXhS@ELTu2%wAhb~(g*7DmoW-9l-8FsD26jEe9nKsqk!{5qRt6^P5~y) zf@-_-2>@BF{TL^&5Q>Zv52Jy!Edj7jPMf4zk0^mbYH3uOTxtG$e^eU_>mj!3;3k^1tQ zXlZyn$NvdczDwJA)v|d%H1jP`$qMiKSHKYW+=;`0(M3`zi@ngbvfM2)r?90Rj5tab)jgFfrbtN_m`VJ{hU%oqxO@@ojhXFYW? z&&*V8V)`07wb(M3Ic)mXi!~KDC#9I^oJ*Z?t3tH0V|w4q8SR}jWF=>I-JH?(GKQU@ zJV{Xm` z-6utV|C~Adt$VW-Yeb zwbs-<5zmqzVc z8gpxD+^?kxQp=L8mZelJ)O6siYGBvdune+8)JJOhH->-gNhoCCRLke^ z7G-Lfns9Obg@&74)v(X17dlYQ-OwcP|3OKC{x+7oztOZ?3?wuJcRtv8iR#6lR;P#vqZe|YKC~VT#kSU{pCsih7&mq8u%R&eLHf_rZJSx>|%XhK0jMD z$Z~dCcY^8c1qBxw&(5g1bW5y=TePBqQ#yfDSbNL9-~3($oPPv*{2g}$B?eX0a4gbsPZdDhu3T$KC5K?ihl;Z>F zV1>xlyR^~|fr|M~-%_{*7zNZ4`6e*bFKg##nEs%eL4E@RS3sGlL7)FehLy{6Wj8P=9$?tt zCi+o(zfWZH(@gQb;@kSg#s5a{`MZIOQD;k#;(@;()*W4afG38LdBZ;bH6_eq@p^x@ zYFs$f!aAKZBEomgPIiMn(K{h3In{A(hhC|r3kSsSU$!Yg@UT(NoRW!*(lUk4?a_sd z+<}ekWzrn`RCcZodl9E}ZT0ixO=*8bR$I@`I(ub@aezP z81^(yIA86vF+zZKACKrp9nrXDi{i{K7Vvlx<+k6`egx_M7zP&p*#4KIA`sV8j0hj1dnQLk_Y=fKLM6v3u8p|EE^1iAsHt zeYyQ=zP!TJX+9SndM_`l$(W{drDx^*Cz6*k1E+lAS$(sM_5W{?>OcHzFYrf9ST~PB zw7}tVkLs1a*30o{FEFjWY^l3s!q>SeQcCK7`E6#eTO`F1Fp+zm!RpAr{41xeh*75BQk zG_;a;-yQy*2i)%+xE4=1uQY*o&Ihh_rdQn}j%) zIB*?mxLx#s+x5b&b#EnEx^9H-fE_xIih+54Y*?@~{6YEWl z?3sThFIC=G?p=CX^+BNHgY&5mLK+{OSo$Dx;$1$uheCD_MdBWc)jgD$_fSesJeYln z+`oqka*vek9;w7VQsY}9J@1jE)>58(kM#aMGI$roEce(X?y*_l1FdiwH zD_a&S9^gNIM)c1C{v^5FXvc{BYof>Qh)F&u3Y^H8AigllEJ48FRMA9+nDA6tfyP)t zCT$CblDOSrj2C17E1t~>>=y349v&AbAI8YZ%P9KcMb5$Jt9LQ_9M8R~-od$HztFUE zLT)*-3XFmm^5T~z*v6)E7o3^DELXvdL3jZpYrtkkhk~CGIWO+CuoWZ;GQ18je0}x# zmL2ODvX&oYJHWvGr%3f!1M{{6r{`=FQf2tDmEqUcLrG%dHS1I~HdM8!y$C*5_%C#K z)4#SVH3n9N{v&P$LKnIpOB`GFtbcb~)qTD67ccg(CNMB@iwYj#4>CK%pHTeWFXN)v zJ4S{oDV{?=wlN5Xm2G~O|JQ8dm9#{*1N&~7R&h`GC^i41w2b)4dkojyCZ85B*G|t0 z2~6f_U=rNGsL}9Ft)@+3S)0!P?Fr(~yKi1gZB0KV{UKedq5DtYgl8FtcqUA2`j_IM z-qy=kFpVwO^9=vG@HWN|j3HtSSF1k?F8CO}{-Z3vc!u4xRWBH%1sKo$dcJb!jKjMb zZNIm^I5$W314CU?!3V$B=eo{D?yh>Va_;RFj9x(%)jM-E4)9;-iuc=ps=}=EFMFBL z_Vxu5N7+6wwr!1-*w0{lwpAuyocjaA_U8=$_I>o*IlsxvE$96=FQ)G%>l38)U&dwF z+3L@(l2@vE&wqDa*6j5k3o^T>oNU{B z&&G1ppn-8en-%Kp?^C8VVN0_vG<8Dn>=FsnTH-XGsGC(5-S zbVlz>W|oaw*=k@1iZ4L=-;nOS^yXZ}qBwGCm9JAIkq_TX<9Y z|8(nz2liaE*3*0Z&zYl9;D^x%U53BH8xAu5uFkgIA!gI($SGbUV8>_u<--FgA2%^PggHNXS)W<3TR&;g(Gfn!RU?ycKqT+2pNQ`Dc@@;c=7CJ|>raKDnCuN^kPDcs!-p z&uXR4CO@0cSBm}Zet-Gw@4zft65zyb`X#_cIP`x>pqq5*mp~8YsU<;P+FQQ_`50d< z3HG!8`XxBPS+w+uh2f8vAtAw`rJ-TbrC&oMlBbr2MP+aO8WvN0wKP1gI(2GDLbGUD zMABZK&k-q;L(3x5W|w}8%ve0NEGldD)^Abig-6Pw^LBsz7F}>ywERwLx9RtolFOmx zv1PYQzsFWQo?0GP^?K{~xSG#b%j4^QfBhccz%2H|UF3}Pj|Z2e0vZ-DDkU^5;4`Xt z#ZdQVaR$@;I+p_tA9&nmMDUnR$ZO)Y+M#!_Q`>JP@UCMzm&Pok5hda(+vjJrA=sAW@UkXk_qOUPM4%gK6?=w=45cSUy*8U{~=|V0?S4Pq%SP zB^M*pBnhY4%lKPo=Fij2dROt+ z;Dvf4gFuW$gFv3sf(9lQhlJ)czYQ%Jmbl2vGQU#QNNC_r(|W*Mukzry`EvCKod!)F z4^%Br%KDlyIB&9y?rZ zdC(@b`hlas1?R3F0ez-}-4A$ z1e~o@m6qtX8lL0qT_GeZB*+@R#FMRX+mXhY1DydXCpdmPA7zgD3lU@|IjGx>K?-lF=zLRzTl`IkGhr{grfJzmRs}pM!!9`_2ZI2b&u5-vv)LeYA4hhL(j=nc57Nl@VV} zmMm5Jdc{w`Dy!$zlFM`dU0H7aDl;H)ixr#Cf?Oscp`e|w7O+QlEVpFJ?ptumUufyo zm0`11g(u!@+!Ff0GkcT3Bt4U*yQ@k)BbqKm{@rqYz1P)sL65SdUwSQG!X@TjdsI7_ z`}OriO|k!*CfADw!b4Uw1sV1teTtYTOD;}S=iRqskzy)zoTv( z3){ANX>M+Ab@ZKQIIq@B%`3KTVOaY1naw%Z={~Zb`uTMlRGC&BVB&I*OnF_S*@_swhDBaRs_E9}j!NtSsYaoqY@VSnwPWCixf6V7Tyz0Kt*D(aCZy>-)yCeQtoqTwES zDtKAZ)U`FKI_Z(8qn{N`pBa;4FuOY@S*>{H*_t$yCc z?H@H(;&H?~gV?U|zX(2q$R+Cqn?XE<<(5zQ4C0!bn=>x2ilyfa;=B-+&u5k9-S}`Z zl;!Iy_e~SFzFWMzcv)ne#s6BxfAT*c+hu%NF!5>CHSPFQ*f3VT{-aae z-`zp8g=Pk+&iqN>*+P+uIrjg)d^jp>R}(&!SKu4Ng3vEF(-k9Ly52b4@$bThhvDgS z)S7txWgb52*1z>(VX$PJ;^I)vzJ;N6a`V16aVVuIw%LEGO;?;?b@m7U1b3~SPbRO~ zs_;-eBrGzOKiuMENSkiP@})rxH$E^iFeFTk=hvd~$@zZTyO zznNNiq~r3|2mL!4cz(RIj|_SBY9E);jeE7pubv#1Fo{@oSNyS?>$24X+XB+!^OpTQ zFSvZwtXAICOAkb5=0=6T>~ddmd}*+R+l>#w40p`gdJL>zT-~+h8#lxHmHR4P{E|#| ze-?emxL`9!dd806*>))xAKBka`nqAW@c*rv$9m1GT$=;cug>L9vMXfey)fyAe@$fb z$<@cxZdJGND{PPb7N(o=TIaS|$tV8Zr|z8G()@_=|K8xnxC z>a>%3yO;0F4VkB+bX#-QThG3b*)!8sn{NHzv9>ZS=ilmJeyP(og&S=<4cKPAo96qv zgyGAD(AiBp*DWqxH2LJW7tXCO{QVzZx~FscfVXM_|Fe0aX&ZNNz2I-1>Xf;=)JkoY zFvHi2R*Tp1sQ(T9RB>~G9Pgi{_xFU(-&R=@!mL&odiu;}t)|tFbi37Rc%N-~eSP<= zz!_;vx6kcpaQrJd>21(q%iP$9jRHD1LJY!Jes^^Z7l^tU5*0R0Y))4}{0{#KeW~+0 zOCNDLOF1`29a}wzqa!|**+DEpwN1I_A$RJkV80TJSrY=eJf2v4ef+;l{K%3gujM}# zM+vUJwC(Lzz7(sMk;2a9duBhppq{&0K~$RksN6R{t?-Jb-_v$X(bQVj9bK}B_2`B} zD@?sv^ecjeiZ89v3!B)py)gboUiV}@BI_aLL zqi%hgw&ToFz4DXZQU8+pRXLU!w0oXOlKMR3gwHag=_k*mS$&>)p=X)Na?i6_QJ-hs z&`Fb)YhYrj_&oao&vJ|7p67~YeV+5eXSvn&ljq8IeV+TFXSvOD&+}DX#tf_n4lsT@ z`Tu;~ug~)tcvm>EdtsYFWc!BlFS*Sr;5@+2)*#CA;pn3OD;gpVcQ)|FJXkB*z|AIC(6Bn8LB3R%i%Ftj3a840 z^;{2DhcF2+@%&L{5k8T^E;Zre$sf$DhqX_!a|KLcEKy+O`H;rUGvUSx9)`7Zau_!0 za5r*9gfIz49AG(N#mE_=z$EVAz@~J8k^R4dD}#K+0cN8Kletq87!^Al;#5C0Fx5Dy zGj=#|hzK;)8z?Xd8XRC0SkNGlabSb!+I384ZZ)tzxyGRJ<2WPFhkgc)h8_G34Sq$@ z3}5vgF#hpy@KXzDU_bIzUT{T2;eW&1j6WVP`gb6`Soe1WOO3)h$rlU-!VL}ItdEp2KCO(5` zHl78HvH}MJtv)dDm?SWF`ZCxXJ!oK$c_44E_KCU3sDb^&kG<>-2iQaw+~Z?^z@*4= zfN{>8JM0_|OwthziQE?+@rF#1fAIGJ+mEP5c7uffprc-y@0||kP?*N3_#l_j=>sF1 zjKchXC)Q48K6{(5WdUPB*a3U11N@u|4lw-L!N4Yx&>+Td$-t?g&-6p^-aM8B=B!?Z znUWhAITaKbQ~s5(7+h%Nl{mmCd4r*>nE5V8j`)5?jtvEhM;f?!7?^|}l(PSsedn2! z>Sjic2F7>Nj2xv6dpI+;*Bs3J+ACUj!_KAt@06&Mriyf9Px7$~6D_XqY z%*f8sFs0Qfl4IueJ=_~Cmj6G<&*G4ARzS;vx$JNR!}}u}QvR=f!Sw0XWLAZvj8ZG~ z?Mohgnrp-G@W$0*#&35r=lVEoZ{#(xFY}As*VdrHz2U=!^0)_cIUKh8#OM17{pn{7 zIUv8HbX~>uIl?XPU6|bOpJdPrxUb6nlu5Q@rG4JJ{Q^^VEV_T`m%WB}QOesRYpnjU zbG5yVWd3nWUW&nMZ}lgJf@O0~ij^Kmyh0Kk;O(c>Qe4 zvC=0@500PcHEI;7sQKc!vSHDGLk1q6gquEXh01>m8Nc!^U^+Oht8lsP=lM^z?fmZA zIP-5IL-qZ;XZNH#?ARY#GmY7GFGqp(1K|rF&i%?~WVJZ(;oiE*_n)~it$+UZKNC+L z11rx1hEK9yY@2c-7gSu{Df^$DMNXjM>ugiz3SB>zHS;Hb2@g5=b82{8c=7*&Y}dxp zKLSigpB708Fj_fAGA&@_^eF$wz{n}V#C*Kgi9KHMLA+2wBj1Ta$BpTK7BE`1F;p*W zsBz8WFQ{`8V``r!{JoWN3Trz5f#eU*;xDo{r@1%$I4&H0j9>Lc)&C8QkC(;Of7O*Q zXk;^I5In%o-w-j;R7QNO@IM8HiC^P?vnZ~aUi9D8CcZ-CNIL_Q0fSXon~z)A|D~yF z=54o@w^}`Hi_vH=I8hTkLzr(u&cCm^x?&8~9yL3cwWe6uWc=W_GGk<1Rx9|ueSRqe z*Z*TxmF0Cl-`aGK<(9hE96p@8W_oz?kM`t_*0LGGvobo`%cWy7TEi;3?oVo6Q_=2{ zXt`P96N5&|Idy7e?R&eBquOgPGE_gz*aedW99^ph5o`lXO{o-9jg+RZh~FIZ12hB%Pa+oR>~8keqB} zIoTv~vRUP1i~lnxTkV|Oo5Hj(fl2egrTmN7D{ zD!=8~A0Eiq@gtu5LUHiIg)e^cuRFliUcmA6C%^wHmbD-Fmo_YX^;1}ITG;al2Ehf% zE7Vdf8*AAXFrF2SeI8Nc-p0_X-o<|)V$JljKW$mZkH!fvVEEX|c*<@0{{wL=)Mac} zMLE6_l~suN_aP~&k$vd~M#2AH9Mc!DSUPe%_vBcYkmhlb;YvAUd>7Lfb;g_9Q}1tL ztkcNkxX|ePjDfG9L}&q{rh9~~dzd5xBXa|jdpP5z(mLC+YUbn1I2Kebej$8eru32L zi)9}a#yc+YQA>;YxM1f5;e#`Urx$SkU%)a=V4i0n;{ISqpNkz98(iN|>#I(WrW()kTKZ|E=li&lof;7?rn&?`>z0p1{y<&Mrz9n+}Jp8>+Jl5w7&_QmVz9!q*wlJV71sNJpZJy?A6v-i5b1DK?Tp}NQdqU zjcApy+~vU7#qpB=c*C@gmyD(b^MtfE%vKPPzOcY@A$!xxK$jQ0oU4NxQ^IQx?5+I&lGCbjYTU+-1(l-z0yg~**jBZ3 z=l==Z>lEg!x7-@yxIdzBE6YoM{onlF23)hGITi~rPg^Bik+8q9asSWT%oipIgZGK? zELpJ0dlG1dr{@Fz@&{GGB{LsLI1x4*AfLDWgYf84J^M89IR8^ zx%qcQ!vW6!3hdVe4vI5#{7*QX@nKK*!-K{)4A~RdY}e@OFW{Xcz?t#k5YvYv0WwE} zY>tM+91W{E8ZqZ+$eV79o0?G@_&JQ(j1so$MILi6-Dhmtowny#S^2)aKgSAWju+V+ zFNrx`R&%^!&he@_(k8QV>i!&WkU7z0bD|~YL|e^?jyWf~{_i=_bLT|gpA!>gPEN8p zIYs7##;Id7=A4|h=j5C_CpC8-Um$a8koAm zGPe3`XH>Khso%C`l53yeN5)rIh2;g9cI$ZY6qMdKVvI`{t-q=%dVqhkTBoBRQ~31) zR`HF_+;hMESUq`%u+)aC1637&=ImLM&cGs3{r1O^Mc$hih%A=ps^Re!{lArQk12y> zKs;B&q8Y3r0!7Wt8yF-TvgW7GXKmQNW;;WEdDhXdWj)aenyVPOPOP#nOtAT%7Bk}; z16xCt_+N(GtqcaS35*ZIbJdcZwBv3(PpLFr$NKHe{FKO*q1pa>*F`4gDTg!WnJ2Bu z3OjyBxU`g!X+yefKvJ1Cqy6-{X7>oL3k=>T!o4*ZXY5K*o_4wXM~iqva=;8m<>{%l zw=!lgWn?!Cty5w_gZKdkqc@(C z1`Hopie7QAR-912_ea=~JJAwX@7pJCU(;K#Lj8#2!SFMG!-9B4RF4%UcqCV7#d@R` zEPW-~Qkv2hx+J{gfYF{hEv^*nhYew`WLb5V^@Z<~sIF(r!j_BW*i4JG@7Q8^`ry9>40&w~ zUzXO^vqr_lGT6?2GEJ0e=G2JKvR+fxihfuo?0=o{NO&yQ492Hr=gp2sFUn5%H+h>u z1j8|@_Rs9iJpYeXRs3V<;7GptwnmoW!iMlbMS(c>4KJ$V8Vxj})70y|*$UVUUdWZd zH1Ih8)W)Nf^=iQ1XZ{=g3p?frG%-}&(roRE5%;c<(qoWF2>jwvv)iuy@-qKOjo0Nb zURf?o^?BC5TI02{b*|XaIVJ1X|9|Q`@5CFPKF`Z;Gbiud@uIr@yhX3g#f=kBx6VJB z{-NUCff-#d<9c)FbicLe{ZRLIW=e1~W12wr>Egytp}zNNjJN*$d(R;Mfyw>@OFYsE z!SYf(nG*%%KML7@6p8;RmLHHZ_13&SA7$=;l>7ft`{oBF`%fzIpVaCxk@+;4>)!(E0w-`a`WJ?pl_5q9NrxtKzzSJu5dmUfj&>{HJ@u zgI5jg^8ZRb8WdSVSY%B)e4}oBNIcRhs_*tn|DdPHf3}tt0*4l#y{&#h zWtqUv#*fGNSVjI>3pgFJXIsavza!zn*Z-M!c9y<>*8AhoojLzRYzzb@9c(+XVdDEa z1`dar86B8&GP)g}7xtdoJU?&4pZhJm*VsRBJ8pDjJiqtq`t$tAG(LdD8 zBk5G4aHhcNIJ+3bsrh_nD-@WgxJW(N_s1z>!T$LoQ3<=}u^BYTbJ{&nZ<-)u?X9%> zUu1*HcQ2EVE+XPq3QbQUf*njw8U;8oa*9YTXnwzD!#ShWev5YpxpxsbDvL1_mM)1^O_44&%iXC2U7V>DyIqHRGXpM5Qa zojulAPM;&JxaPx|;X9oI(OS`&zY7 z=QHSp;NRbV=O17euPHdh4LTuMIJ~Ctm~{D{!V}8VYl=>NlzmWi#`t?0DuZq^>O5h}iYy)~6P{D(2*-rKhL!1sy*%qmfAiJJ;OPRoP z7E_%1wPH_gdFiD4f05VU=~q@>T^*6_H22ijtZVBN4wv0IHFfok%^8>7RHDmP-P&HT zwo3QJwzXGwRDAwr)4<@^upsU2Bb$PR1C0#DU!}|hryCrs?XUm#Hu}fP=>1K(Yz}+) zH$G~aEu22J@xlB9wHzEu33rN(noe=O{=|FXTiT0rQAMxCc39_U>`)8hzaYfW$kkZG z{%ze$sj@@6{-;eBo%?BfDsqM^U< zLdiW=mv6f*xXZQwd|@yWc@T+#9aHD6DbD0n@V43moc zw^EfkclKXnIi~9?AKbK`eJ~`lIBJ7?c+2`i*LdH5u^#tIrDk25Uz;V^ahEZwX2yep z*`hZedaYj?5EK98XxOxt%b^w%dO07RnBG?)JM&uhA=dI63=C=RHyD2hhzAwjNWGra z%zl+MHFDccch$}lxjPomj1aX9sNC6lLVBK}ro_}XJ)tMhTDE#>UYHYhYo=0GcQl+Y=5)qgzA3KnU~jdKA*LSmMK1G^Zm`|^A5~=LAM&_e!1uo zZc}o}$NcS=%li7fU$2CO=YBmB6n}T4SX6nh(e*g}Ii|G&H-&f=sUhbWplk}u|e(s$#_1S*$Sv=zT zA3tu??*GN`zb50~5l*qlzc=5^{;}_f#=Fmd{(e~fzc5|%1|!>IK(Tq9ojO% zKx@N;!)zC3{_MTbre^KK;{VK1(&Tr2@fVOIFoRMZ38opNj}4T8&x)e4TeEYpQf&sS>`c|B} zG3bG3B zve*0?xI|L;Y@ZsMRvG4;+nBhb=Y{D;v&-$4CCPlckxgpBcHOsx>yF!GNlt#Xq2ZHJ zit|Lc`R$6htIyjun>e0c@hM_=--IdvjvTF%Vb2N^hC zUcTDBR5UFzH6miC*QH5v#p{l%tY-H8xnWw-*Ol_hs?m*|jYqT2EGsclzoxd#|A^U} z+5M?nX1vOgJMOKYA10C<$C@5C{oB`R8+$U7+;z@i z%C`q|Y9y*_8@|n&x=f^7ORHIZ(UpoTjZ0gWrW)^NzTPk?Z`sr(ap5~pJ&nA+TWXE= z@g3eq4CR~lxml^Xo|HEH9A`FnZO>AR=iz=Qc9d>-)$wRKt3r`Dv* zNxJ!8m$@+_HTm?d$VC;&-gfV1iXThe_$;g|LOb^-%U_GUq=eEWZU)ouJSlG6mg^5* z$uBt?V|mzxl{ql+fW3^uR1US7iN$4d1*OqX-#E=I;Pxw-y;S3l(X+xis=wRq6z_Oz znXg$T@nN;*?M-)#7hl@wFP1imQS7-$i&RQ*>iktp?`*VjmzcTHuA%+tjVy`y;629- zFRGu`Ikwf##PMsWrU!Rr)ZAXN*-!OMa*IqiG|EVqe%K-sU-NQz@Z+7+vf zS+QUJUq)lSjr~pc*~hBAKDl<=Jd=#HI$hk#*mULBv(RNfFWxq_npeK(Wp&uEtB3pF zubkg?J9hr&YnSbw_Vm|=lo%*pyf1fi|9;7$68X5xEces4+1t(6@63F3-}R@7_r6cY zGG)*CxqcZ>{`WEQeU0cDx!<~%>;8Y(>Hp`oq}(4(;s5VXuCM<5Tkfw_bN%TX^3~r@ z*QdVPU)$#W_)qEn|Nnk4H)tKUTXB(LS9-&1wT73=8}gg0&K|Rvu~3oO-bFoO07?VNs3)3~V#1{<$|f9c$`*+OUzmQSXL}u|;K>bB$s| z^EI~epY7HLH!7?c8YNFOR=V4IEjNF`}6sY<)}dhu?+~HP7 zFV>Vj?3_EJ>HMYY6U-e4L{gSTblee>eOwYTtG!HaX%F-9+@x*(nr0@hYEctAQVx9P zmspUQtJo9h*!R?zUP&%97}s!?%R?q-Ylw6wKnS%-Y$*y+tQm?flOFf`dzF!=(QPj5U?a0t$>m28=#8YqS!2 zSVX2pStjwnoEo+*tXH+$!gHF^G{5pfpP;Q8$G0$-A4|)#%=H!xWZM|1D(O7kj73Y1W`f?FE)`S&^Sa#sAGh z=G&G^Hp|>!K8MB8{@smku?FSW&F&LUdCa`Ea_!-j3#8_=HyYWlXkH;zyK>j6{PI<6 zey!r>SiOm>WS!OOZC=gWXRQtiUcKwqYO`mn4{%i;v|2OMea*37#(S#P9Gtc0)T`BJ zZmrq%Yt4nK)t9W+ZjM@ejcfIdS!>tqT6<^Ks(ZiIT1l*XWYzp6YMoBSx))kauXe4I zy0Pw^R^tb$^;{O~zi>5tt6KkQ`ubm8b$@QHzo8zQX5pH2r9VkIH0YrXTlEI9lqF8v zHgK;t5j?$N*)0A3tu_k^P8_Ft+4Pd7ERb` z=FFC6SgIAD{A%WsADiP<{I*m$W^Y}g>5`Ylm7$u-l@`rm{M#_@qi2+KM%1o&UN6Hh zEgCl(du5EXN>rx_FJ9{8zG$ap+UpzJ4jkFq7v^`wq{Uxjo7Eg z<}SeTNisN`*oScAAh+}aK}c`7SHZSyY|mA zj1t(r$$GcmV=sShBaW%TcmG#!_ur+Uv3jA!lMn^tJteF;j<+P4TK8OcNvl`2ns-Ee zRkU0E5wmt?n=8A6rJMKOwBB8rJ~@ZWf7b1c3X_duo4Yo$Es6ApCgpAqiVK$#BB54R+|~84f`|a$!dpP zT^#kH``|N?!#u^)u3tTPZS{_hiJKIBwn-!(QtvtR(D<;+lU%zc5z|*?ZJAssdpY9d zR|!MWSQj#}HVTVkKEXDk2z>q*6F$4*S!U;SD2&*@`~8R2RLCuh_g zn$dG|>*9Uqoc)*muFJPnR&+kGu4S{ypMYam=bEK-clsEe%04K3>1dzL9G{FMnwvJN z@Fc4#9KU(mDC?VoRsmo{c{ePu1cJA!a&O=LZa30wTz({>y9&L7Ww^&wRm!FsmhK;!Dqcb z&Diz${5GBoQ+yocoG)xzZS-jA4z6FGO{|M|wJ{hi$Y)<;_~^gNK@%U}z*lX)SB`F- zdU5IQg#Btc=c>(Yqpw)4z9@a7a^Xy)>=`pAFDy0OwuHyHf00d+qvqtoYQv9nVv2uF zJ-H;pRa0Vb&Mv<>L$`9)-kY7* zZ_d-LSRi|=%KX+6-STC%w=$>STJ^Sc&D~oe-*0W$Te8Xa_Li*MH=}Ovs4d%VyLQ*z z+Xd`*&i=Z6=&Vt4isws$L zwsQKntb4o0K6UHV%^E$2t)G2Yz;^Lj3VwcaJeYM(8WYIf;DZv_3?R35O+|^9rpQZC?^KOB^K@2gBZ*l_}4%vww z$*Vma_vYw7{^N3@r~BT_z4YeTzBi}%-kj1Cy~g+U*1I=H?!CSA?(MyEZy(OnM_yCB zfpHF_592lP_?$1k@j00T8xiAkpgU=x<8ubdr=a6=#l)^D&JLq-mHCe!b4 zoqva}iLTEnpVg}!tQ)&H|305V;r3^D_UXN45}OtNK4E4(|7wvnGoIS9^9U@^$=Q+F zAnPG)Zg+Br`u$ZrQXPB+L2nq?IX=A$7Do{g#?g zmgTr55tmK*KB#K;$cGv#~h9~#{z2}$%f7;JTY~Nc-5KSr6Sd`j*1KN z*4kb_P-=D7?MmH^uhWB5m{%yslrsmcn9avy@L^^kw_N(XXyzAU4Vl7T#|1?$&3ti> zfn&jq#5%XeijXqXg%6~d)^VmYGB5dhO}^7rHo>vysqBOVk4n}93?&w7t`Ga{`4}EF zI)8ZbFu+Z!RmGQ;kD)b1AUUTo8;`1(-pA=kDompUX`Pd>Z$oSmV zi1f8zuf>$V{dzs2UH99Kl|`F*$1a;57&+Dm*Q)08s6W!57-RHAZ)Ly_-`@%jJH6Rs=3VCFaY&pzox`Fb z(wF0m`17@Y0I5{L&%w_pD z!H%zBw(tKCheoMoPa@`a`~Iq#vExFdM~87`PXiOj?eFzs4+;8H>n1J@2c^Ru%# z+thwcy3%T>#@fUr$$0$Oj)*f=1~=QUY--*zW98GniXLUT?;BmGS>*OtPm%J{QE;Dl z#Zbk=ia}I%^N9lvYbG}Hq{u5@Z;+1o#MYeD$(6UDtzYKABzeO|5v!e(YGXc4nI^*| z61j*iB*`dJ_h5r|V5Ng}QBan~4#vF^Ma-HlEsW+CkG%vIoW2rWxmA3`bzmZW(x1^oB1aJ<}f`bxTIgXRLpg`e4$KYnClJL#-xm&*JPo0crnC}`%M z^klaAflkxyTTdqZn#{PmXO8OQEOi&Y?mx3+OT?c&jQ<0ed?M0$rt6F+smfIH&H}#j3dh1-+%`Gy$cSH&&yvmSYJ4e$isO$XH zYwMR9Uex?jqOhRy6f^38M8%9ww(6jr!6=9FH>Qi=A)IA|G}^8QvUoIH5#_zlWur%xp1*H zt#Z}BbxL;1o1GiiF3YrTy7*#N%POudPwb9pO}<{HvN3*Xu6LUBB$1Gy?SieUZWn(G zep)qUul0?W3_h6)6m7U>yfF>aj@68-cz31AIOnPV>5f}>T&}LYyZ3qCT5qB6bzhgb zf7L21=Q8Ygd0M8RIU0BVjGp}9s#@RFr!)R$ z?ci09SgI}N*&A==}_xNAFZ1$KKM6JN8p8CPm=U8y!Rp1@z? z9c+JVnzMqYd(v8;uzru}YMUceH~LHpamt=|u&zj3by-th`R!A4NPMC1Q-izR^@On5rx z9Ft0s<`tQqte$bsX zUspx{1>Z@t{oL1e)qh`ye3xJ{c))PN;6a_LwbhN4c})x@cY+Jrt+R^ueKWhH{;uM@ z^`dcYp75;qu#eOWyBcevwzTX{+tmCGs_|pQ}~f=(l5NnZNf&K>qJ@uNA)U zVxP@i?O%0=Jw8`X+3uy`f$xvYbLC$I{(JHBMa|}!3qD=?Uh{c%|JEnz`#-Baw)=n6 z==-Br<6osPcCV{2i9RBBjJ^TI0;`f|_OpvWkuU>6O9(O==b_ zE2pP5UCK;TV&#btny`_OE%UbepnZBu z;n`!#=Re!@FJ($AuT)*Zb}X^!sJdK_n9x+07XNGB#uY-QF9c;>i*?*`10QCX2c&B) zOrNr}S?XGfBD0UrbfF(tP4cc+s@`BIT+SxEpncwT`N$XfMqjPseh4K-G+bL!l=?#S zXGn7yBX-#fq4pd3T+iD= z7RIc9*rPGg*P^1ESG-r;La5Y3NJv6O#e(r>yFtW_%&3AM6T#;A45360AKrv+)r|#F zhFOoF^(i?9x>Yc|>=4?)A*0-uCAL77LDFlQa8Jkg&Lh_uBo;JHeZru3WrD;8=2@X# zKRtv@R|M~Sn4R`Qq(;?cr%T(WQlAUoLaWwk&o)e`}iBt)&QDr`2L_^X47e|i1e(q0u#VHQ)rCnoWE?h_i)CoD{C z3YuBLQP8k3B2X1VI`p!R4o?1q@=UV%_v}H zd1z$ogCE^i3z{s&+@3{fn%@+%TFUltQSi-ULLVwbZ%&z{k?F~>UBW-vD}^(rdfE)( zFzdS#Qk~{3wJ$m=XI2<$%GIuH&93bG_GPN#!PrR7BE7_!3jzZ#utuqRCi$gx3YYgT z;>e$_CUpIJcWGz&oF6j_!lzc18-#1l+Ids3-ZRE|gW-#cE*YT+>zA{FA9g8BWO@+l z7x&FG%b{`4#R4%2;oJW?npGChWW71F(0xv&_^j(NVXF$!%9Sr=+o-$WobwOfk2X z*1eiot6ugZaM|AL49)=yIcL>{B^#RmKf=%=)v$Dt<@@C;uC*`bVPL#;yJqlA&(6yld4;trd%cJYR5D%=xh@e$%o! zUaK=7n+O<}Ei#%aaA2`22jlT717mZ`$y{r)7pqQsT$K7;lj(=%cj1;JgMjo#A?uAI(Hq68H%iRjD7AZ|%U=Ov{=Q)eTPqj zR*d(p)JC&ue?^0Lh*_S$*fQa_jYz;2t&Q7qqS+i*3wi$zwK~?LcQHbUZI^6DsDh$* z^=-kR16yon_O|`rV!TZ#dFO1IjXu4%g?@k7;l;Fb$7+?$vxKHq?@XPrO;a$U^j5IO zqbyVD_`6T$vq()pb+Sow;;su!yL3ELv1@1oFvis3)ajO6qRi<qJ>`V-glsz@2$6xNa+mOXTi3aLa~*?n^$aMekHWB zI%>J~?t>!1!o~+Cgaz)LVm<5W-oh=ry_6)sge&pI98@kmC^6@tbj_wo5yC&pn2+uj z|KBYoe}#)h%Ovz2(wo;bj6x9DO*Q z|3sWIb9pHuoI7o&QegHLHQ}ggul~-)6N$d|Oovq#9=LIHr`*${YbAGj8Of+I9NY7M zwXjyhu?!zhi-p13HbO0_o_nQ_TPb=!p6u;eCA5-z!tVe(#WlSPOEVhh?D%xlpKa68 zmN!BZwGU2XHV~O3thRt-Th8ez0!OyzoNhf(a5poL-R$IJx1Gw2k^P%8HP-Bs3(Q&( zrnutiab>~G$KRwc+=$>=Bjlp8bIHbH{ut%wzlD4E?0cag&aGWTxzGF4>%{hlRd^~F>cusPUa8ZZw&Mz7# zxP?=u&R5i0w3cyiz?w6gX0(0H$=>y39?P2Z>pEip`v~vyK4!~vPQvEg+O`ChDbcZJ zCnr@J9%$LB`adw_nv3u!9vS~VS^EzsuUO-9t^35V}2%qClB@P3X4q?;5={h*7Nt*%lB^vDf3=PKRv~S_jv1tYk7B1 zTTJvdww#$5oujsfE%Wu3=+}o0mo8ml?QOE_qV%kz>SvBC?no;*BhY@)@&i|F>JrPp zJDELmn5-wLJF%-<-xPfDE9OPDWPTUJk4j0wo(@~ zPCe%w4^wa^15y9X?B57_D+aLjwaweJB>--Dta!=Js!e%}@Mc9Lq{d`;z} z+vY4W-g^%rrxJ1SiK+=a?6Y}T;>hTC?d0{PDvyrXe92-|vICz=H1CLI<9)ThgE#Mr zUwCM^UeR6RoUvC?%kwU4=0kBtb&q}SJ@K=9;Q#Ti^}Z`pCl$9IF`la{nXE1;5wM(3 zrD#KN2rDN_@_AX?N!Kr4|wg zA2K=pIaDMjaPVElv#kH~UUbcS;HUUBmv7Gm9^nPasyary7dG1+xuhMSV5KG?$rM=D zxtZB9V$X(+(|tBgpRTxX<++58eJ_kYteZ9YfqV;tQ3Io@)q|=>uUOxSSnb=lU;1I@ z?agYRq{aH|3NI~NwWWCeLsj2K#!4Z-ML}iXKfifvBeZtki086}@5AeyY%Y%4o%#1>v6sC0$r&WiXyt2I%ydvit3b7{o$-6zv1XNb)kS*u z*I8O`m~NC{x$K_nmVL~3%bde2?HCs@^Yef34SeL7!}V{&y93$$V*Go$9FkA6sHm6f ze4l8k<8;QP(e=SX*LkI~@09DWuY2+Qe^6pkm2<#{Z7V+7ZC`E4P}0Kc^m^+;O@{u* zPlS&=TX)6r%tr~CmxV{RZYv8~zo~e_$NL=%9XEVbYT}bF5&zs7r;r-{?0M9phkUB* z`5ad9IUEX3d|v$FjPV!W#xK*mKWcusRua#_wDF74K_x}j_jeDL)XW#LvoCw}J7!0) z(Yd_tnDx(|3!S=KwvL@&HRZjkwf@gyIltR-@2uTli<}pc^5@jtV3ct2U784<+w|aq+mcs2V-kzvj!W>f&#r!rBdg?txm-);R%ivT7Ni8M? z4u+=>$Y&NzS}BZ9)JJe-}X0O-(T02-5(?R?%!Lac=R2|`etLUgC6kDO zHk0C(4^BMBR|*cdtIXNp#OEwh=py8O<^+rDg}_2rvG9;1Q%>+qD0Gv`-ty5+rua&s zyBz3LB86s=A`hi*lTRm<-&-bns?D}}D5u2CgnUBA6h>dhd*IP*K^~kptQ%+9yx>2) zuV29#e#1K2X!b-kn|pUoZeD(VzC$yYY?iS)%A@K!iKcVt71=2OF73}@c2-!_x5#nSE&j7xP5y1dj{_ERwa%YV+V#qzMsFv z?kQ(Tn%^vc+(ajGWzw;3+3;-hyO$HQrr42&$C zJZ_6^_I_u0buE}z{>H@vEw3N6FFp0F(&5MPTf4sNuFqn0YJ0u@;H#(A3{1>tRxOtM zzr*lIOP}JIAKv*NmLkQy(~#+|oVPP(CBjG_KNW zhj~=Y`Kf9*LV^rF$OV7A+j5X+@k|330g;|<%L4yz{8U8C`co2`6JIg zAD=4q)cI~*z`3UP_!8zImOw>jpHn|3Pw*048B=7(8_?Ry!P0nu=XXSbbKY+c#iLUk zR}?5^GGu8kjMiCpje$jCiJDsP`MUvgSQ*baG;)eP5SL34iLy`+U^jW7!tD2IrIUb! zjDw><#LWKz52v~(CZsN6ib`1R8T;;OLwSOkS!U(bu3hJOCg%Rg^e<3TO%HLu)xx+Y zXu*TTsXu2H9OkuJP#{<3^Y?iJ=e5M`{%mKz{d(PP$T$05#WAJd6E_KaeO(iL@!FC@ zJQ^&85^SLh0*=}-djuX8;0S)H7PQo<;brdQMH3j;%9oyG$eR&3=gN$u+tl9MePO$* z6ZHL%`*MFV868#+#|i0;oLMVAUO(-sr0UGZD`4Xg$CPy@y8OQ3wwxVPCVg9e{Ny=< zFMi7Mp+;@$-$Swl7X6ZJ;ai;BaUdf=Lo$y+$m7C+#gYuCvKRmR(H40&Lag?{X8CrW zkjpN%xhYr9y!`NyG2qAl-xq7L!U9?EN#9S8oto2pMY|%*^1EQh*_X?jrhawu-VyZk zR>`L)iiuqZ6Qq<)Qd~wkQeKXCmcM)x=2muwZYnXRnK;`U%BJ8 z{P*YZ_dA92h29_AK4miJ8T0d*pA00q_Ih;t=?h0)OJDyt}L+hq1-SlUeUR{z zyFsL6K{LaRja&gAl>Z4dF!BVv?05RJSp4CRiM1>|iEM_Col_bZG}jm^GYW7pOEuUF zzsce7IWm7~n}A!Mr;w7J(?Lz;PiBsJF8%BY49ccO^B5Bjcz$1<61S@0WT@GaNz<>K zkd2i*U@}QiStQ}WltVujYb7{LF<;;yTJv&3>NEb{X6Yv;zNQ|UQ#NuXsBoByGx=D1 zJ)HUD^hB~#B$&40H+;lcDvn=SEAvyDslWc>%ylx2l&)um`hDF}pr+DhMW-N9Vd~`zg zhsLC`5`R5mvkSdnUicqB!@+iA=ET|sr<{ItKjmHU;ACHuNxfJDQ>DSIiEL|46x(E& z+~%)3yecZRZPJzozLd_bT@5|WqP84tWd@g8BN!O1Jyu6u-W9ZEl_(>lMz%}JhHJYv zZB=6Ac63pf>b2{jUG2CK2k-%#CLEFh@_D#_p-?m!r@ZgL3eM|NKodp~D6AxsE7u>wK z&x}L#;z6!y2d+qeVcH>L>A)8@;ex`N1$`2Zj9DMUZ=OywW4D>=AyD-&`bJ(^GjHYr zp&|noJHEtiroAV4!W?`w62x{fFE}9dN_ir~nQ4pxC)U?FM^60xs+_|(f~j3ch~cl6 zDZ9d@_ko&0thc3Fw*J|2ph);u!jC8ycBXyvD^6y|eTiGbWhs$W7x?ty$w|d~KB;un zPH#BZaVLRuGfQCy_oqj1jE?Nvdmy*I+VM>C!3yPfUzfQF7(^#|C2~3~>u8x-J%Q`8 z(P3vfedf+REbV49=2{=f5vjb9;3w6>_<3$e&E!4&Eaw%rv2Yrg7OqL|iV*w1IKeH? zt*K_excbafMjK8{(%i$s%`SdQGP9tHT`0XfX6H$JzlTnz_B180O4!rd+fjQl=d|hp zWA<;G9rTM1M7D3Rtj{;&XnWctXtm$y3TyPC1*)6UU4GA0k_d}(3ki7AEW@xhH|)eh zULzKE?I=YK34JFY17=he))*??lI$tj_UBHiW8a(9B0U!71~U(VX%mvf^&1y6TYPp8Yl!)H>&LOpq0GLj zSraq)6;A808Et#N{EpV@-AZfCIwsGSdM_(yuE-d-!9_04O?S41;(50ddtUPjez!Wg z`OAM^r|s+p?+g8`*bVxB?)YsT@3@q6^XhFKD<5klFisCU`CwYb{G+m|LEE{r+`5V6BB?)V;$z)(NbUG7S~;Eu;4=x^0kR|aa8Vkj;!aMMuh~{0GAEdi+NtfDLAkhoe;Tu zKJRVsiSlPve7kO6=PO_7yj$G(#TSMPv4W1~TNZ{jb?{C-ozg6mu%~jKN3-UFAB`I3 z9Pe_=Yjt10?eO{iY0CBZX@>kCpZ>e!H)p@copjsVH>6p%9xDV-Q1Vv z3hsX=U|sidZ+pl8gUo;4cfR>k_d)GnpXG|QMcGGwu^-&GP}o;cPH9e~_PXkH^Vp|N z3ljG1bx7EAz2XQp#=i3EeZWq*di)U#}WWQ1Q%3YAHqki+!`b+Nh!h)`Q+hTc- zGHh=37B!5k2@;q*tyW-Tl1KxS&W)z^YIVoTN>(i^n7Uk$Tf=8_D691Z&qrHXBHasx zJp^S>Fvv?Z$uDc{dn%wXqkMK)Q`$jo!UAdehOki6${g>2(1s>?JlZ zIo@#oY!-88spj`)rrBloY8zTA-Qy$zS(Y6$&_8VR?S%asiM}~T z3|kwSe78^FImy7c!L(knig{&^@Jhk6!P2}lC-QbO8cpaEz1c0FDR`z?^UsQjYC9)1 zD^5`KY}d9FJij?uU2?MTbB03IiMpJn=KpUBD6eGV(wuCk>0S8M$Z}%+m&Ym-y`O{WTkIU>Rk(_p2f?<`2 zo82Xb*iu2G2Tk28q68Y1^oA3hV^h$}Q(2k_*x>Iim%>C5F-NNJ( z+A-sXb2q2M^&d&e5%G*+(Ut}sd<&VcwMITRWm&FSw9j&m_XehvOc}<2sr^ihO~I@J z42--1Q{@&;WBiu&TD50O+8nWid2A0E2`)C9xf`z8ht0M&8EnUpA(te&PbJPO1y$fau6fzr&c?#^DcjWt0&shar zfz!M=7ld4zkQ_W!V*yjm%>0w;tv5QCiY{C_sXVBdZQ=jVJ$t_?9sJ+c)Yn>k@q7A_ z8ImbTl zv8jgqDFNwKNt;Di@)^!=>gY@nnyD7Ws_|;2gXAiXZMvs+=}r?}am#Xr_XUQ}W(i#E z%YJX}k$BdSaBb;@vXmLFaR(y>cJ5m0ePA{3!lEOR`6qVHR6jZA=&$+00_(L;Epk{= z=JTWNzWU-OFF}2?=oz7PLX67=yX<+lB}_h6aLFWkaYbU#_N47vbL^86{+QY@9$2UL za-FX^df~wf4GWgEt+>dZS61R9_?cjHmfdrVwT^xYj)6o z_myIWD>*0ji>;nG_f|>r<0aLco371VAD*>Ic;PbRfCyQMbsw*Yt^^R3}Sps&?7EIGxZmlhA6|lxoa|=)BHo4PMS=kHRv;`}=Gu1L8 zO`?L03gX*eZEc?7&2%iCVYk2&anB9gx3Uy6@QSgl=33NYopY&?p+|c1dCT?OC+CEE zPG0zHxn9&x>GVw2#D%k?1wM%F7E#>LS!JIUuz1n4O751(&b1yz1EqI*U_2o@&quI;OSqn&uWyYPF$G2hs zZ*JCSQT4qc*&GWueKpNLY8hjBV6DKx*+*s`;9o6p&UF8~3Wg~q)6SoqBi+4w4rln@ z?77+-g7^|zIczqH9Gv^9t(`wd;GOAK?F)MqZU$>89Gur7$njy`sV7@l4o+_LSaN0i za_?CawrWpKRxWUSGr8dEa_a@mdO6;09fIE#y+s-hKbq3PvXNh>W52CRM?(jb=AS99 zKW!Kr7+rJLsNG~(rNZ3KIki5r=VwM>vd^)KQwKBOOv(1?Ie4qRoa?yyO$Ppz`;sG% zALclgx8`_tj<%M;@sd5$j{SG^lt|dtGH0Uj0x$XByA(N3G~PL;vEX3;9#5Wx6SYy( zb1RQqFFjdhV>aoI`jnWH3wuuTIGj56YyPA&QY+V-T75@`W5I-VYo^bYJj}?js$V#8v(M>;drr)3Ilc4D%zG2hOqMy*mVP$RbJ6)X4E>*uN&VCi ze$c-y=)OWX$I2;{x7dQaplh0TIPv&uj*Oey%KwuMYk1Uvsa8X8D5eo(MCh@dzZW;!O^n7kK9XUn99x zZ`F5C=jsY3>OD(-XYcOz@QQi#=d_nR7YjW92|N;gcs6^*>)aa z%B@XMd$riSt9@O-6T^a?7iHMPUu*w+IzjK{zJ1Rn81Egh6O>(XPb@I)Sf2ait!_=x zf^Y85DPQj99^v}Pb!~&5V1gOTdiFakAM5X#ygs+?(YAM<#V)~pbyo!)Irr^*cbG3% zT*A#@`AeS0H*^1O%{H%*ICw8*dh=I50b%h3=F+!<6W^{p7Oz~sL^)2-^3ok9NB_w$ z?wIcLdbRJ}n|b!UWv`?P%cbky7+inPYna9ynEZ3zEd@p9tM_j0+E#W)O@L{AtzS{a z3DaeL{}HPKY7Q7Jn9l$n4;L8+&mo*Gu9OKy*?G;RLTl-x(UQukm zSRq48UB5&g(}pU+in>D?rv&oD-|8HHb>m%jx|%?@)o0HB@LlWJm>#-@d@_oa`zW@M zW8b|iya%K1)e5G~bB)oOzpt9X@xd;M!jyYOw<7#sF<;CMIq`hX^W+?+Z_Mwr*XMop zW^n0Id;Tx{5yxWY8T?+oeBVVCzfbb}&cpcOSMq~-Q&MMS3$|XHdqG;znxVBxzO&7q zAu6isRgsYd> z%@A~#DxUVfudP2h?EkmrvM+w<{r|&|VL$c1*CG3YBkQjU7XC4q_ouEsvhU6WzuN^b zukQ5F5EQUr5&f#w!Y{zg!lx2(VZi}K@1i>vH!eJ65Y{d-Sa9NjTc2u>kIj>aRL-uZ z-)RX;4t!K-R^?$;3Xn*7uun!?XT}mufx`lGEK8rIY}ojWfyJKpU32h>7lHGge61ob zW*-e+;a|-fVD;fxm*j7*pbD?8uN-8J!*n8z!n&@lXJfYEoi%Mi>$1S=rx^y9b0>(Z zawk=+3eRX34Pu)m@LK#?7jqteRKlGbg^yR|iuy0x$$v2Q%uK->y*&3#K{@WTF zl(^PUVQq7pP_ikFg;B^~tasak|Eag_Q;(==SZQWoJt*rX7I)`I;gge7_0#X|Q7e3Y zajF0ExVyhfn=Wk3e}3=o@9(c}WzSj>F|RTH!^^Ad)Az^J_{wTLIq&{)-{0ClKfk8m zzkmOKJ;T&Z`VLIoCINdH`9lgE*=BohaO6;)@?kZ*I%qUI{8WK6pZS^%&H~PN-a89= zn+Vzn`G*v`z6whD=qfR>*w9TXyCmC9y7b=5yoEW}dIcvo7<~$?I z8JPnoY(eNLqiDz( z`)6~4-&sAK8XliH%h+lvhf!k3$&iEu4%17a$sBBR)_O)}Kb~5CFKgq`b1k`_L!a#Y zC$)ixRa7Hk3gc0M9gVSb7C8K13!U0DJ+4D}+K*db{+yW}UY-BAW9vam^LW3$vT;wU7~RJ>Yn9~ufE&Os(VS%jho-8&6VAu zu^{m$lhsQW+5A>AH2Fr^*5hlsZlqoJse1n5 zZwPC>PTge@ht7L8J2?b2Cmtshe~|19hfTE!otw(`Q&j$-?*ERIHN zs-vFnTHK`AkiuXk(UhyPX%g!`?Urk^`!knpU}pDdEN#nb5HgftxmLP1je~;TKeXX^i`^?Klv-epvEKU*9OnD>9?fb~S zc|)LriScYl1A&%1cUChxH#8l(a-bnht7mFJM5;R;HA81|F49or@M8 z5LmLn>eI%&h^Rk(>%w02i#+k4b@_}u=l_&rZU1dnl(Pyh<-9a?3d56?1;#g$OS>#v zLLaHjeRYKMpu`P_)`NNsyIEWr*0CF_>lrcd9ynmbJN1l*QE^KH*Hq&><%T(OPaN5I zJa%xgY?1CbS&*A};8V!v{=TKIY%lJx^GPN!3I{mwDsA@Y`t`$1Bjh2Q_FEZ_lkH5J zYfR>c@G!70Sq@qP)51M&6A8WCDEU%Pc-Kp*xwkSz+vZjfT!uhmF3f1vO*>= z%wn3*92B)K?MWc>%?p})!e8mMY*9*Bt!5E+!{?F$&xwQEn_g+gbC!nh(q82ml5lwX zjSEah53=JgGU-06eYz^Lw3|8a0)xm3My~12huE)H1#NjH^lL)s3jdV!rPrDmj;)=# zCr#tsvG!X#L#=8UXJ_RVZJ4vhyFF>E&K@wx-@U?qS-RH>ZYyxyjf`Vs^537u01~M z`zRW5nxdK$->Ia@Sj0vJ>x`1U&aTJ>A)HIrUNY;+Pm;g2mU5F9T+N# zF&#L^=+dgGiQ7O|ZacpduDEc>PBiDS&{RPO{qqd9f4%0uUcVvnu%-`(n3Tiwq*)vS zHf3HvHl)@wtNqSdQDAfKCS0;zRW{Fj&4v2{HZNKn8lzbg?p5-hXlqE72zhWX zHS}z_e69RWu^@);xo-37{&;@9?`QQv;ijGO8V0!%ZI)$J2t-f z5HY0(fdb}BcCavO=xlW2o5VU*KKS?B8&^8a-yS|(%(!AlOKj+w4;T5Ij4mt;GBkSe zAVbhEV+QM|#2yb6x2d@^YVbNwPYf)ulXv)-&^nMl8%~RC5MI7LIhhlox}dW zoG42>@Qr$zxY%sz%UR|}jxm>MIUID}WIc7&m6g?O zOAm8#=rrEtb<8>akjI-P@tshqS?EC)u@nVmCdQHj8WCpK(%z)*Qro(QYfjylH}02$ zs-K74uws4cJCE<;BCgwf6Pvl%pBOY3pL3j;AHW;KaDyfE1w)Shv8%7oGky=6p;r8N z)rb5om$fwCY^{=-v&z3D>*vLZXWubw;W`bBghW8zBrrB?XSGvMjO6HCrN6^@@#GT)fZa zZhgTh;M%MI)y)SyWNU<3wcjuZF?)ZAe8Q#8^3WuhV@dD9#}=JFw@-wosSD*7G2X9`nrAT2L7kp7OPZ$(7NKc z$79!fzuOEMa_=vFI4J#ok9T4#%bzdmqV2XGnYr;IFYdML*9Bkg&;R$+V`~4uUvEAy zsr&um`2S14KV8qakFZ*^6gnNqz_S1`9k_r={KNq^D}#n&$p#j657=}doB0XIbRf?{ z4tEdmbfAXMLsr9tMzLKVS`EO{frq7Tfu{rc%1<1Y`}LvSfoGAx|8|cf5_}DQtV9;Ra8R96avW;0)DS^$B!--+Wi&d&@4oSkni#Qh;G)vD==wzO7h$l^wMfF{C zn6bkFp?n3yShb5Re6t(*)H+?1cCGUB?D1n*62SUQ@FELuKr>g9K})!r@sw*iN^C`v z43Zjxtd<`-G#Q=tQ|4pE%#%yU4f%%DY5z_`=WF-`EN(SHVogT)<2 z7Y_1hEO3z&c@^r_;-oQ~^>oRM?uFhi4t#Dij|4j@a`5~}7Z9~#2%ELpPa>j8>1>3L z(!Am&=Ti9GU7OdEkh&hvQ<$kb^acR=QN4Dq?f^ zt7!3EFvulHs6+klQYY3=-W9wD=Dre1ntEYsbJjHx#_*|5Va*l|oxBUU;?`z7nkFGD z?2^7FYH5T2L#`v1Pm7oM|4j_(UeT<&Ae6V{>q5?om6x^%1x_$d&E}Juz^w6S(*|#e zWkT%^msP%Pop;tmeXT~6i+!Uv*Y`Cmre}F=RAl+q9uUCHI!Wq?tVLO;Mu2WMt9H2T z|1;Oxc`9D&mm0KOoHdz|aYruy(Z{#No?Y!=;e6zfv?E4hRszGJ)OAjCc00F-Y*f_m z$>W}^efU}4_FWEN-w7P`jy=ftWXprK-sS%*W5m}PxoUeX<+qP)m0&x_n>@GQyj-JM z;moJ~|Ie;T`1||5g2zGcTXPjz8O}V2+$FT#cmk7Gv_^o>w*vWpb3Y`sdLGGbV%lq) zqWbs=_Yq!Z2@j1EiPaaku&Agoa_YG`b+^4v65S)fs(0-z-y{YWsofTw4tz!e4nBt& z86F;Y^gFa!NanD#%FmUy=A_qH{Q3d6y(&CeW#c)AYu%1Ft2`tTKNU^xDG#Y!%>5Umr_uCJO zeY5YK(KX?pBblwja`Enm|8^Dv$+oW*IxdU3X1@$uFlAC*=$he^Dyq6sas3l_XspTi(@BDZikbKqmW zH|0ATBfct$RX^bk_;e`rY}SV3(H%PMKe@}-6e!ExWXzA-U3~aY!jAI-T%I$2aUY)Z zPH$4xeJ$puXA=%IKi_rm{>^2!{AK%pt7h!GJ!>}i%83)c$(!x#s8I{J*81b?n>N{N zDdLarhc~3Fbo^~IUA};INyE3q`;u)z%eDW%uGXk1dab}|DdEw5a8|jzS=9GE&l_K9 z)}|=j_5l&D$$}iocFzl&msehGXDNSP%KRekyn4Q_dhN>N z?lspMSgt1-Tx)ii;C-kxMna&;a7*c2m--{qQ$<=CUOZ>eeo=R8naf=>$Jg8H|9o%Z zT$-YD!|AMgGwY6`i{dUmCz#FHoJF2DEnJ?@V(6&#tycd@Ugq(ZX@tqODYx2FH>$y1+XXcaiNFYgjN&~hZa<3NPV zrWM^v;tUtQ#yBceUI=e?O6f5-=wnu>xLwit$F1jGYn4$LYXMX8nQ5H{*D8C;Qucj! ze($c%q&Y#Mz2aDF@!b;@Z7+H|S9CUz3WhkQ+|x!!eCH0|qG=g@B{o@_;G4^n4OD>-fMBKM=8^M5GI<{v&wrlklUooE=6 zR{Y&ZNixN|tRj&u>hOvrMh8Zb1_t4uQE$zhCoj)BSUz=Dana=IQ`Qx+rZ4% z!1Vh;*R~f^^RG=2QRwUHn8w0B$>d4S`yK8jk<-g=x@fyO^D?kHR8Ft^IlXM=bmJ@2 z8zN`4dAc`M&gj}Xqi1GC!Oa;HBxg3voG~eK<}^$9DU~y4?VOo9W#*ipGZ)NsohLbK zN#v}GpEH)toV7}`b;ZtE>weCX=bXLCa(2qqSz9V+Z?~MaW9RIZD`)TdIeWp&*#|7= zOxK)qq;k%L%sD4^&S_sc=gi5Pn4fcEp3J#4(>bDYZg}L}FqgTvJf|MKo@w`pLFB*j zGzkX%n>XkB9hv*$rBkG(xQ2t_2g&(74;VNe_+H#O&vnVX7nM^xmFEAGoWED7=EuzW z+bkEDh}3W>%=^D{!G}%;_Ja9ryQT=<$}4YVk9#n!tP z+Wng5?=mCrON^IQ1}ozb+#X-j;-{ z154FDsA((I%ZRx!u_lB%OnY1!!rL|}e}(1W$VI#ijG+pQp$v??0*f~Y&i#93*-cRv zkD!1BER(bTJ0!)th)?Ho;SQ@RWm$6L`-HaR&KI5}IUHaX&Pd#`HO+u6rQ-2Y;hW7X ztiom~Gh3ZnAfUjwL29*-0i$8ol8b4zZcB3XBg&kQ=U-bkMbCuUYg(3dO8(vhOU0V_Uq0Nu(fU$|+|~aY+sb#%pCIr$p178`eJH@}Dz#xp)=x2B{V23>lwi zEpj?Mm9>3!+KQZMS$TS46ZXG~3q6p0Bx;Rk!J0sqWos9udLGb{d|np$wLIlV2d`y@ zpIQ4Nv9ty0&Z}>&`hSz*HP`%CQo(As#D%9f@0`W*SI?7e*urXm2b}^i*B7PT;|O@$Q9k6Q{nHDrRNh zFSP1>z}vei*~vCss@7gak&j4fwOUZw^yn}baM6YAhwYP8J|NlHgbA@w5+bUa+9dCYGKdD-{UwV&Ba{o7p zJrY%!M^C2@TW0qf zsO?_7J?DXxWce+LecFugv{%c-EQ#2-Mdsm7(W84xcC5ABz}y$MwkLfT%Nm!S?)}1@ zxvFgbY8&`}&f2hW*W}byDR#42GPIYjGh8CU#U*qh`cvjkiBlVkrPupKA6ig-$m0U{ z?+0N9VcRc7uiS4^^Q?NJf;huEqcDYzOc>X!KwQH%b zji<^R=SQ76f3=T#z08>wv6Xqv(cdhkVk{*l(^yhpII;g?)8z+t8#)e877{icjl~( zvJm)fu-5azsVA$Cg;<`n{z1)#_m_%=~Pj9cy z%-<)zlVz66I=90HTaxQ-VorT)KJz5&gVZVqg$bv=<%pb#*{8Ggz@EjmT8sh)jKUvQ zeeqdxBIbB!`ElD8hQ79m-z}!s%!&PSqslHN|E0|YONaC|kuEmuEJseAw^3mZT9D(Q zmb{wPMf^pxjRAAhv(0y^7R+s9`eeOj!z@NghShny7ymxf@XqI0sc3rf|FB8nBtAh9~N~?I;I%IB@rjNMWW!6+AW44-m6AkOaGi!RO-HZ zN|eER1M`K|&U3>~_bj^_GwsTxYv=BjFUjYa7oxkTswy*V?X_cD_aD|=^!oKR!&?j= znna6iuh+^hGMca;xA!{ts_WaduBZH(z513!v+s>hHyJb%=4ITS+qw3}1I^&R*hS6( z^9p}opL%w7Sud0B+?%4aqh)?yS82V~ox8Ai?oDTbo9(?fN_^)yb~7yUz4hOck?(?b zgyGF8wzo_FTCab|u(B$7r5#Ua#)F%sUwczRX{D@AqXsE|=QE*zkyF z-Gbo1j~`n;{vfnn*=~U<-<cFlWoVC~%dH!~&V zBqcsPjptj}a8OLM>+!ma-aJ_rz0#l@3*bVB z`U|i8zSQgOOiO(2e9r2U-jyV-8+q5hB?+&1XY2AV=v4;4&s5ij6v@4p7aeP!{Vmty zz*|d~41;NV4UZJZg}r5*u$$-phi8+#wWVC{^Syi6=X$K;!Yr>Ws~pCO?3-29);KI+ z7M_yf`QfTb3By(n=lbUx#Fwp4J(d3U-Y%Q}5}(tiq%z&lX?>RDH~n*bSwzr-4-W6+ zEq_0^H~A>?pFOp}^}(^Gcgv={KYqx%q>d#vw@WP|)F9V9eec_6pY&3C)a+;2%wUx{FkuE4oF_ZH76 z*7~2MaD9^SwEDQ?Uv*VF`>(x^N;{FVe&rqaEa#U__1C#=E_{x<`a#5iVY&!Q=mEu@ z+#m1Wb1v`yUMcTtaqNuKlQZ@16YWjvw%v%{$NS-)PO;sdA3}efns5AW5-mG7r^3wt zXVU+3)!TDs+dpb~_JQdj!{U6#BK_-lb2)a#IgOzh)>OM^g%`<5WZ5Ss4v1oc+v3 z!KXIN419jR)^h@j#*X#R{aYP#^|o3bVF_wco2HU@AYjE2M(qhE4OvcJlN6Y9gmizH zcCaciFI2k2elb_HNhB@ii{xbGpb4s9CoS1xrZ#1DU2xEbok(U{YHOcS(M@#-@;z=m5 z<~TOLy!6@K=s*7P$2gex^v#i3$aU5%-^TTf@V4S!kx6R|3==wEp7Ly1ArVl=@Ni+1 zZTQv=HYX=sikVpDYqYplNREY3dYYm2&Q9rq+sBnyDylgw=C(}sWObFw+0e6BCP#qp z4Ofv)r)tLP4XSc&96TFqD?FUoTbUGAZ@w-Npu_pRiRsai)*_EbY+sipoo8a3P!Xv8 zv+ffY=Wm}EtehHpPjW1`P6|G#%cWJt(YQ^ChI2WJuR63$<%HLkLkvBpWo#1R zLD%%XUTg^QZr)Ln$=W8JX7ONt29L_VtJIYWOCb#Q;O3!NU+Wh zNYH7JjNANw<>4uRH+|hzJee)2)6lxw=Snk=>hGsj$u5tce{(4b+nf1#nOMf6*L|5U zwsM@h)wEqWgppa6iQ()!uFY@y3U-UWl`dSOmI^yCUwi$9!qdVF{alA`G2!w)mW;&@BO~CSiG*{QSkk}6;CFI%l&*jz5HM0 z3+sHls#mMG?|b)h{q?%)cPn}JR)07w{(r%n zpo76a7uG+>)njP5>?nQrhnvN*z`o)o$+B-N+-;s2_E~p4e8qObS>LQX?~wEX-UfAJ zmV^VED{i;T$^P{4a0{ALddHDZm;HnT*QM&i(hgY?3z&ri8kpEQx+et~uxNTX@``vY z_F^*dj5roFZS#?ZuMB3MoTTE>v+DH|hA(crf|S~r_V4r%kVx=+dLd}$>6A2?iJCi| z9$d0e-*{Rt-DvyN{}ped&l{;Q&FLHC*vEX?d zYt)}aXq??EvS^mp!I>fp47M<`Jea{|$-um%fH^?y34@45hLd2ko8%F;-d8Wr^70w5 zG;DR{VxD!-qV&_L%C?Xt6>HQVKS(gBdDqJCtI}&~xS>hXBat=Kf`_YVLlaMA2AlT| zN71kY^Gen@q-%dzaqSZ4IfX5krb-zfoU|-?0aN0YS)7b7J?`#kzW??>pF{%_m%)Oo z7Il|a73pX`dN(s@?-#>Jxt*7J9GF{0XLzhIKe3WkM&SCpI|mG`A21608nj%rU}v;S zNJ_u(Qh&P#enFx`?7{-(EW-wN##6r$dk5Q&3wvCZ2L~vm*TsNuT9iY5~;k`Xi<2Qz&1DcaarfGj(2@2OVt?iD~|tm{k-gnpLfH>s>Mv#g%>7pYHM71`Ab!v zjU_Q!%;RaWfk47Bvu_(0T)4rvM1VzFkC9RK|J{QEDm)L*EW140*+W;PYy$JftnVdf zgg%z{D#z_@5G*lFbchjr^r$&OjEg<+U_!9~%QP=Pwmq`3`3tu22i;33DM}3QWa|lJ zW4U$0`~U-6)xu_(h#5@&c?@ZVX7OwpQ#V_CsJGkp80h`K!1B+7F~jC>rtp#nM^pqq zN2z|DeRR=I1&#>E{#)6N)3r*A+}+kh&&#|sQD{eRv)-45!nw|F)=}&l84XSPYnZ)) zcW$13ElAvZ5mP;ngVW_a0jooE9`%c?UH4Y;)f}@3P1u$!Q^P zR=mr?I&v zL>GD06x9FOYw;`Qm%EJ&gXD}@HEV~aHUE->@3A>@iCKQ)IrlA)FMq4~hTVzJzP?D* z*s#NiRh#dU^KSnql^N_e3>(c8%N1&VyFQ&)uKZw+*YESjX$4VEuj3{>4F2&@S269+ zA_nHR2MijVY2S9F|5KjX`|{r8s>vs+?H;CFsCnbR=~tAHjb3MVmh35=-;b{^UZd+S zvt?G&5j!SnG*?L}vCeT=k#N9%0W;5$1M*FZcV}vG{c$*Qlifn%;CpTbeilcOlKH|u zj$$sdoH>pXTV{**I7;oABf7>>X38ANGmdgRi{<_}p5+t|kakkiaZEZ|H1KpY&zdO6F2$r zbm^++@mnaK-oJ&d-m}M}@nK=2oS?yrrAU<2m6$)2VCqh8!=} zEYf0TV{BWaVOO2Sd^jlox6MAOgSIZAFo$wPt#6+H)UI} zf@O;N)O(kCCmm#5A|kkUF~^tH92ys7SsvJ4W2j_zXuBt*wPNFutWQ@} ztmi84&m+<}ol9Fm@QAy!3S&^4AFD+|W51z*BP%CU8AGVHLC2wTOOJ`n0yYK?2e};9 zG#m^NH7Zr?^32^ana8!J-9gBhVPij!@rMtBY~CMk90<|+{R3H z`Cwv0+^GetJtw(+b7T>=QCk(9ktKB`%T+<#fq5ZU>hhqaR;90e>y;SPr*^0{Hmt2Z z7of_2TuGEs<9EpgSuPHh2Mik?{xm(n&Mop=V(y8wUxNIn>#n?UzcB6e_dU zE>mZrzo#BNyTm6F$a>uJj({kWxQf9RCN7<`CJD?t(n2@=UU#SHszH43$3WBD|F1X* zZMoafGS69IqKlCB165JhO!Ea6C)Y`KNBbE1^99}ac>Cc;h@^^wf#1hnX^l?}LjA0s zEooOV+LzGEck}tm`6;`et~f5&z*F5WvSyF*t#_B+OjdvP+t2c0p3S?%hqqLIIwQP3 z_VWdIebJ~(;rV|9uEekZ`_0w%{NL{n_D`w(@nm}cW;6<{L(Fwt)Y)%`S#S|8@vIRLc%_!(rm1&UJ?BOIl zA+SYF;4y>G0$07ViM{zg4@6BhnAwiFw}c$J$>-M5F#o9ngX$bdK93HDIcpO*RC0m^ z&Yd_Ux#W463J=q{eWp$wx{Z^RWSGvC7)-ZJE8w(TJ(b;bXS2)`hCZD&hj;`wG-(-3 zYA%Xm;?z9AEb@Y3vg{IuISeO{AJJLJrF=z|tH_`^JWGJf;Xs2z?@UK$$Aw(lFP3nd zOE5%rZEBV=W;C2R^B}{E2NRTKuB`G{aZ=&Vfeg7V4VuLPtRYv6``yWPVWpY_R;`Wg#w{|hYNx3jTNE1?-V%R zJxN%kCC0KhD}dEpV>ai)4VMM^95UlG0Wxy7xtcW*iw}FlIgbk z;qNmqZ1ir)WlybkG5()4VQZL;VqS1`1Zz|SljV#Ez6A}AEKd|TxJ0JMb#`9kU~`DM z(3_WR{NwTquZNqD_pB>B8hVq9FOls)$O672tihbC942)3cK11D-Ew6!-eDv0j!l;# z^zFR|>&@T1jd3Vx`uEE`N%RgQk6%NW#uxGFRsu}IIvov++z$^5gl$OB%V1)6|H0C5 z`2mmKo%vVT8X`F9mWIv|R-TL8?@M(!==M^7&&1a;j z-Dx=K=Vq8|&%q+0pmipI|Gc&vUQql^fJNn0OKxSS*x&vP8^V zFn#uJM_~g;j^camQ;H9yvWGDqZ9b>K#?}+7;Qpnl`rT(vtK5SQw|6v~gnfoZG#N1n+(t7~`uPcvtJ>HC2@@7L+VlVw}J@-W*m*W6$d zH({4vlf%lj@VRKtkHf+{T7*AbzQuRMm?uFyRmlAUldHfXIo%CRiUA84ofuv%^*;N6 zk)5G#t=-QUzr9RT^(Q=4x>6+MBk)4$%kBH7;$|E7uKvus$blm-`~kx}nNJJMt6mga z8a_~ecfetNZKL|T-7G8Xn4TDMvTZ+H@Vm>dxU^r2bH;fa{xflp)E|p8=6P(sY}`>M z==GEB=Dgi3|L>k`C@y%g;m8Lj_6cvZf;T+uRqbHS<83Yx-q+aH{=3yZBkKp_sU|8H-LC6Zdh4xNSC1ORFy* zXSr!!>u}xizIpT_wqofcRV$xXNwZW-AF11{o_a*xk@t9VCY!o{d2WC~R^L*l^XZPF zWlU|CiaED2@i3%w?r>0j?)Y{Y^E>6@sV&)0N|;;^R2DC6bU4x|^`ud1N`h-Z+;cYO z|L#ro%bCJfFzpV_yxvymbRd8uAUXS)!*Vm`N9y7}K`c>i^%va}-l&xoE-c~ga1_1a z_&dF&Xd#p7^_aCzxt8TQSDsXIi7?n7&k|HA*6?IN#g%q@*~Z~t=L$c z7;3XUI#QnHsg@<}Kjv6`!m;#(>ZGQmY`WsvJENH^r9(U=MZ+NB zZh4BcLDq_jV0-h_V!;gNYd&%6_A@QgPqH&hZFii%!*QWS{EsxHloY10Z!NVqibP!) zw`D}2BvNmCnR-XwGHs2GEnkH0y?R(GBz41kxDjQ3*TceYO z(^(JSpfDEW0_NJOY04ZKt5!JfVP^?-==*-Wa#Mz*+p!?$4_W_MndLquPuWq)IfcdP zLYMd!W_5u?h0NZm*OE4FW9Bz&6&GP~YG6_7^q+KqrOZOS`O`#MHVZ>dar6Hligi65 z1vOb@(=ymUBzp*SZm!7X{1Re*LDAH#N;pL0>C-7vM+}23rzT1`%4~LMZR!+lk&2q> z*c&*NZ>Eg-2Q8Hrsf0{N_J%1wnU0Y^d&Q@us3%MbHJM%#Ih|K^I`5Z$^UUcrH>Zbw znc(qcdXwdhmYq=*kuy4G&gkNt(RFi1-^uAcKW9v`oarYrb4umR8JaHBX3m^*bEe6b zne!xPEm|4fZaHgN<*b(>vsUb!wT3fn)y-KOBxmpYI%`wp>ha%_ndCoaDb53jLoKrXF)SjGkPI7KB=iE!4j^Pz^uilK>GIOrz5lxN; zCcXk0p8pRRG#VJ?56$)8GWW$x2e)P^4TX6hB8NL73craoZmHZ z{z}dT(p%>Lja=}tlc7*!LHF``Oi?lt2@3?c=D$yx&lQ!zGi&})!Fhta79MY82#Z*_ zj&qUll!Y=;i+CO^RNA#blgm*hYmw@!g(ABaUD+A6eydyc%cv-q9^Md{_b(Tzb&2j2 zT@qyy?=;J?dS<*j3+s%hj*JSP9t`p)D;y7R@q4p{xmuDXWY*FRsjd9G7V+#Pl91rYB zmbbL{teVwwD*2n6Hpl-13k4WBCosfLv|^N5CDm!lu>5&+;Pz>6HCU`OT|L|CU5~S* zXJ)G(=zE#z)MSysoatyaAj?9bAFW=v9lhKTBC7uk-)DN z|2>w)Yw4xzTF7A~KBKEH`FcrBNb&q#Q4429MO~{tRk6ywtE90dsq9*Sw?RttwKC3M z4!OrYeuS}9N2S~h>k@2{inC&#AfWz%lX3acg&GRX@4l?5e^#S@VRh{DmD@|#>Xb6C zdDVF*Yq|I-hiZ1_zlMz;pBHep6bg7xaGl|x9#$!u!uVdw@krK!A6krmBv+o6TDUzq zZ{{oq2JH>zu4`GV>kB7#U&xxk{eSiU~~o-tAO=DyTGU^$iIv=`X7a+L+vD zt>bLzH4s?rF=fjeNd{Y~g^~=Mfv*=onz`xsgY`0nTT`cQ<&#`>c^b20Xa5SWZM@PQ z2PKN9TEqu@NO76LJS`%}WwyiRYwNdb>K;k{MnTZ%yB|$80Uj z>a7MFSol}$6e`K?^q!@XQX{dGKPs%u!i@QL`6`iwmK^Qexb`ibx8k|C#e45wxJY}R zu)wkl*1K3&Nw^j75}2{;=Js6?w|6yLXJw|9DE)WYeRo#9+>`ouk5gYx39?j3HDFlp zJbPpDLT2W&b?a*1rW$QGucvJ&|@N33`4ks~m40Idbgau^(R1Z?m+RSQZ=F1; zX|-VWCfWH}4c96jUgwIsxwrD>d&%3&?lNROU?{e|`Q7tQPppQ-gzG2vUgtGn8$WXw#e1!MqIv%-?=5aW?MrtTXE89E3tYJ#`(WFW2i$%4 zB>lu&H(e4bkP+o$ytU`-ZJkF#dF!88UjJ8nk0EZ(nS+J_XO9GMUv^G7^l0XzuH<{t z_a5oR$*ycFj(0$*-qAPd5duj_C zO;v2P_Rw1#!M7l+;@I)x*06VL)A=^VpL>)z?^)QoCwc2`>9!+ZGiLthD|c)Z-mB68HxQ6XgG3lC{t$5R`brp`{a-25yzI`+0` z>{_pV;w|S$bh%=WSko*vFLB^X#DA8)b(#>R+aY{9I+)eL&)w zUt<~5?0pqK*qGTOWL%H8-HuD+a$)3Bdu4Z=W&07sPPfd>8t>e*^H!!ihM6VgPhb1& z`Kyn1pP2J!NUFT~KJU}fe@lu4qPx$fd)1-=ii6sdc zp`V4MPe00gcW&25(UeqwvolY`%4Lr*O#b((za-{!%%{zrj^O*|ox~QT&e-GlE@!E0 zx{_2#x~0DO?`!`XpuJg zUKH5vyuadG1#@rO$LjMRzdx^U&w1B!-m%Z*yyXX7t0sod_^j`a#Z55==3#7w(+ ztux&^!N2UAo_WI5(=UXVq;AtWy=9przrJ|Dgj}KP8(06gKl1$FFVA|WHzhS`Gt%Y? zeUf1L`eEMhUdQ;!d0)f%{v5LZf5iUS<%l2t;w+cg-}P_{7?iwGjsVYkbF*16Dmyqz zFp2uPXt1ye_xLECTX3UrKC6OyUc`i=CeEf>=1Co2Itq?;s%A2$E4C=k@5(7atu>AgvABHN`izn7$#9gJM@yl+AEA&~e)20iR9==Sz(@s})2Uq8SbNsy}U5dtgR$qk?d* zwbu&ORT5h53T;mPN|MJHP1Lf=d1AQfXxMJS4T^yl4_g#9v{!Oia~3hP?eJOr-+@Dg z;qq%o&7W3^>^rqgP8{#%+WqQutx~fh`;0K{%iGNKjZ0h2)w?#Ywp_ky^6^?Xn}eRh zSzEt)?lfpgY;SVVRl2l}$w9YWUDttOy@Ri`!6L)l?oZbgw`dvc)aL%QPSMwzC+AdK z%Yw|1YXT9EuAHujU8TA6R6^Por`OpF6cs8al*d&v@9A<>wsy_sJf(3#i_`qp6)%I^ z{~lkDlz8Fmw7PlinQ04JPC0fc z{ulb6^kkEYnfj}^xIeYV)68O zPhPIx{;#Ta-toHXcg@%LRev~~{z*Dl1gd`9ykJ$JOqS6VI)tPhb{hN#IN^IYxqNX_-ic6yoym{KduC?I+?*st`78ONyV}X~YlNuVYHk2?} zX)v+w5@S)=pvDj&!Nh9vQ&Ox~R3mxr!MNX50iI)x`)_zxFAOy&4l(#0$T&rQSWxJMF^~MUg}Xj*Nz0sfL!_PI>FK zV;%W&xTnm%WVX$zH%DakhL&577gxB7u1g9G2!CiLwyC%GO^V_2rqZM8BAN!zLo^b* zE(#QG)(vzF;4!*2QH)z-l8Hu=&GO4yGZGl4aXPHkIk1dz#siIKStk=Zf8Ab|H<{gh z!GXYv53;fvVq7*C8tbPkSSw_FJ+OD`-B5XV&Px{*MVME-3lV9&YZMg{s=zByA$X`^ zv)Gf3im8j99$}UJCUzv5Wg)|=IZ5&DVGs8kb2uFja|l;p*>TKqfop?dXN#;(V{c9q zlhBXd@od)9r(F6k%Mj{b{P5x*$HQEi4nqGT1o)*}nB2@x#I!T8T%8ksf@LF*Z(Y!o zpZjJUycCiny5z$}sr;WCz5j0N?_Sfa@Iis2v{om0dclo*S~rW9PT6437Qo@69(7Ue zz=qY!dIW@fzAHqkb)4(jA}!ebt=Ra)+nn6pO?UJXId|)F^hp1T);xEBH^Al49L<1q zMrFrV=M$fk{_?zdW_6>mC9H9w=*R2?_GrQBQ$Lki3o)n#SurG-od{Ih+$^Ace%9@j z%ki0i<|?q1&CNcxz997Mt0Q5Itf40g*D$YPvw7aYboy`cq|&<hIb#3}>hXob_s~nOwoTf3_ zFdP!9+S4L!6UOfRz=D6uDduI*7_JA1e%bp?I8k)Tf_u$6hZpltl8|FDJ00rQ6g$b_ z?DcD{>>TplM+=f>EmoLlY~?Z0;r0>}Q)3BMs|CCG`exnPwWz`PaB5c3#9yV)thPKo z64Rveq;mn6dn@0v1@Bx$6F*tX}`~Y<=CAP9^k3WIL|~&)g`Jhe0VyK~_8uA02Ih#2 zep!#@UYh&Z&p4PKa^QXA@Z_NbKhMEaClv&B9G^2f3i~)7Rdf{1aXgjaDBk19G{sRW zMrIj}+30_hKj=e@2Jc8mHX56&KR{iL<_=zlznn528x|aF z<`8CM0lSna;|REPkOu;q7jXpw0|NsCBLgD?0|yJke^#W>p`wI2lLjLb0}lfOhX8{i z3&+LA1qYir_~mjMl#n79r+rpT>Ws_`tPBhsA`Jg|f3!bfI{hlE2+icTIs%nvXgVwN?tu=wD>=)(3_>QDB8_l=FboC^QAKP+@+?m+gZ zKiqg%u<;6vEsRXoIS~Sl_uK0^es~JZXpCpz{Hu_$<^n(CF#&!jyAL}aHXNADVdf$r zv4Gi$Lt0Ma!FGm+`x!*_3uhm z|NeG=aA4ZcETrACq2uFxw?0YpI3CVR%JwY6Y#uHbprr#iLB=zwGjf2#Jb>Xpr;J6y zg9B`=0uq1B8|OPU^NO*`9E?d9pIQ z$qNNVh8AYzn8)euaRt9bc1`ZvD|As$4tbf2-$Eq!Hqkz%5on2aK!H@C{3a%|| zhH+aWHYzmN@hgbrTwru0CditZG#S~zCL1&SmtE2DV7?>EUkQmh8y3uG;{30m@?yE- z;e#z~{}n?jCN6gC5tmM}`Jmv^!pN@gfKq!cz^gry{iAR7>`fR9MhP+%nmpdFf}1% z*$MDKcVYPd*BO*t8JGfFJT5#u*v!mqk|9u$(A>fxZRK#|M*?G0ouZ7@n})!q1C##? z*@)a=Ja~Zp506F3&wPd!J|UJr+(@g#aQXtL#2hAVMpkgnQDcw{nX!P;sg3uKh>yjI z2lw~$C^A{JXnagO(kU+gX>~`!A%=G3*j@t?7 zT3`{gJ*$9`*o6&^_nZ6qMMD0#PkO>y$NoPwq2K}IekOh=gBdp#IJYtUXF|H>0jE<} zfI|&bO#3qYPfeI`uz`u^Kby#n1&Ifl>o`QzeoUVb&)&+d@h@6{Y4I^WS?-i2D;_i+ zVq*WNcq70uu|t6Ehpk1!#e=Op%tBpPJT5AqujdK)!-Euj87A(Ba$|k8L@4V8~|OH1Zd zFdS59=93^QypJQ*KmU1uBrI^|fYd(%jzrZzr@-+As%E?y{_{>);MmN<`2U|P$Hhd( z{e1tKToNX1INZ$4WEk>dfuc(TBi9ej6%!69H}i`8PyKL%;pu*6HXFVR8y-4#uuBVY zOu5kL$iT^t?C^_l-|I8{|C_AfxZnVvA`9P+fQI|5EX-myHv||#Ss(6u)trh02OhMb zq~YsuH##x=|9d*Y!I8P~zf#MK2?@+C^}=Qe0xK3AXkb-T$k=m0-{CO3f+JhRhXe+v zMqZgkQxZNMYGY#k5$n+L(213WiJ6!*d><(ZNQY!3FdS~>(tL90U~KFmMR!-8d4J*wDZ)XvE=?a9M?E8S}eeM3p=T z8Lgpt%9r8)Uv7nk%q`4J>~{m)cl@S*WwD^eMb)7DFj*3j0K2g85fgo1}mUG+?&A1((Z9B!$X;4%=%Sio?I zkx|XT-~+?MR$f*in-?b*#51=DTKU~*a9q&V$KhlX5YTYHneo4Xj)6jBLIW|j-UQnU zDjfY7_tKmxg){Gare7hQxz?l8RzyHYgruW@P=( zdE>x(=Tl9bW(F0K9~qBMXVu#?B_ZMP(JAs`78Q~NT3_I<%mYS8MrHS(ZImSCM#hW!gD}i zk$Z=*p+$m2k;2Kzd;$f3P82<`ljI06_)#Im$|R|4yNaVhX}Kqx2GgI{FN6~rcwD(B z?eW}_aej%HWI_bz)>n=TLe^%9c5Kx=vOJpC=)%OW3wf3#N*nEXDVP@3?pe;6fM^`G-C_jSjcdows!r$%pBm$WA}Ua8`ZIm3fSPT^p&)j_SD^(B>g zVb)iTUv!qacc1+ox#?9~TWXD;#m}ub>NmCjTb(=m!7TanmMv5b57Z74 ziJPGk`Qtoqh2MgoNexVM)m6D#=ht5|OAc-J6LDB5A)B?5Pa zoV?#Hrm0INpNhP=VzRH6LPksNt_eJA+O$MgbF=bayS}{n*ea&ACwE?X8HU`Puw{D2 zXu@EMltvCVYNfD5NI0~0H47+vnXFJ^U}EPn$~x1*~fpZcre>#vT>x;o$||Tj`Z4G6}mJ1%G>DP#Harj-@f+lZV&JEYhSCy z*7FD#3o9wCF;@2H?qq(E@Mk(>6EpMojl2&UKE?C=Hq!p~=elD?V~Oshf80UFC$F|j zUbr(`iZ!Q0ih04koZs8R*miwa@r|3BTHP$nV5ao;{m!y>wJrQ(G<0viks7@4hBOqrR@9lGLJb6Mw& zhBlqT8~0L#H)LEBa1NL-KSCz&*+YAQ8#Au5@J*43a5ReA(wyt~%d}C7@4$|*NgN3Y zw^`V#t{&j!b+|Y?qBt<~sXedB&MPdAWoiKhf?rn%FnK$DMShyCUBVvbDcz9o~9%orrGo<+9TcbMLJ?b+`O>l_*lNWzY1G(SpGoIbAhr32FKK z@IQL6T|n6`L{XW6iA4j{*JEI1V_{@6nBuW%30Jp<#EO`cMN9cujAW+G=`>(yn`TjL z)u1^!&9mFG`IXCs%g>K6XtrwQp1i!`U<*fqUCb__R*&T@p)R4GlUH3`WU{d6=&CQP zgV*wG5Ynl>x`KHtTWeB>XD}Pv4))eVb5@GJzcMFH`JHv=q4)7~eC6NWR%dQZ?6A5m z731^6Fy%;zdN5DI&mwQxN$f$}ZruFr#m<-_5@Tc0(06H`qC*D5CXLr!`H9a%XEIKj zb!$<+^|igT9or* zH8Cx^u%K#BtpTIQs>7+j z8p3Pf4zR>NI?6CBTxjOfX&Qf-1eYiLyg)dCdt!UXxky(5lo_A9;=7~IK3I28U zKu%-QN{O!fZv$>Ds`D~Yb#H3gDz&67>B}{i&62EH%rcxy7Q0N?mNt=3WJLutOR?LO zEgCGoP7g0S%#xe3Y86MqgozH$C)i%IIP)w##Rh+FVYxy(+9Ns!eEqrj%Rq^N+{;=JAVsBmdCM`I;_j}R@!QIbRe$dlp?wk;0ZDKIe>XP~SUWu(wBiN|m zgQ3#N=}h`*A)b?!Pfsv#KjjdxS=~#7G3J?##^vP~dv!uNWTlK5x)ukRDjhhvC53@K zaI@dqs*jpy>mx&z0%jRM^yyVTY{bE3tmx0?yqPIB%5X{ZDyGFr6CBGI@yJ$iI>{xnzYLgeY|Knb@ZSC9=#`}9VQ(br_w4!3sWtOY^Pzj+pR>Jx%-!g;_t)z`nudxlwQCbq z8Y?wSA2c+uOj{(e!5}d zA>Q+IXB^~tdqClSmkygkg6I#`?H5`2-W-eS_${L7A|U*w;}*~TgUb&`ILGZc&!zt1 z!+Dk+h9@1EAHLnzpB!XXaEMdGq~Yl#Po|$K4DoI^=W}|@320$X)8k~Uk!wt0oDrlH zaD>xfO~66+1uoMLvI-eYP>-+PRhh`3eL$f_#YrW=F-i8$Z z&^$?hrsY8v=M5|qU4nkC{-(gTerBb*=eDM&2WB0eD7HG4|6RkOSl$(k2j|9!g}m9g zys4Fmjp^&Ho9pKpy1b74UtoD~qd>^PHBz=tjfW;Pn?BRtIz4jr;SC~|4hJ{$dwL6Q z-M93%0Nd+D3*PU0oV3AtPt8_Op}k+VwoGv5P~Icpv4HE3(7~_Ec7Hgc@bT(~L?6dH zbAn{bYYHG4h@X0%&Dc7z8dpIhiGtB|*Wl>BMxU(hwKV z&kJ~1c&<1Uac+L@&cm3+75ehQ;}#jo8m-iZ1B(JCYZbKRTu4}XX_?>rTT`;OYJ2b) zZORf|p|tAy=J>;XvKeA)qgk?!uPX7LzVgN_E!%6ZuM?hKo?ZR2X)m{_L*N8S=Jqe$ zL97XErlR~}uM%<|Fc<~A+r2X^;cjOO_q*H6g`CsR=LH(rSA zA$dOQ*pwoSoeYegI&HF`dGrtnM-2cyZ-k!g*Gmf&$75r6Q9>Odfy8M12 zpGby+Pw2<6*>(G0K1e8dw@~3>)Ar7C#hiDZCc^DJe2(%hJPb?4Iz@wy%K12Fyb$k{ z{bzizTWRG)r5L}9KcuT?(l&(6KK;llJpw&{#2ye6GY4TYQ;2YK?s8V}f6 zGgL7#KfL%d#aFT>z?C`3%VI(<$AJPT=1{#tCL#000Lhsf&R*~n<$Ez9A^$f+SIeX+ zyEsLhr~3XcSez=XqIi;b*`f3c8Da{BP696#FHCXbslCa-viX_`<4l7;#x6};K1Lnk z44B}N<2+TCdzGi~9fp;BJ~_*^SNU)(be<}m^7ku|0IknQ(9JJ$y+{#(q%wddRi zwK*bdPOm$xVG$z0^^|Smrj?WCZRzKfyMN)96OUn`)5&9&*EVdBNO+KUierlQiVY$s zH}nUH{b4@LB_^TXe9q>3jF_uExAny>MQX8Omy=l)Ot0!#EfcsVRuC(ex%i?@m{8L8 zu$SjdqH>w8pWA6GCT!^u7Fl=BZT(Yu zE_gdK1UkRF!)Fv)H|zP5?P4)6=ViBB^?q5qPob7+*FKXs*LM2-c3pa3&$#^!axM#H z+Q;a?;LO0l@gLNw1N|%h~g+n~1 z5+3Pbb3JRx8p8G8k3^v=# z$;jj_Yrep4j-lxD9)9!Y0~e|`gom$Bi1a#P*s{if+q2@1MfKE@PO?EROm}x> zhO;#;d8Tn?g5|S+v6<7KKH%hGvU!^M(9L>(wQ{+(3a|gxL6FY4l*@+tEeR*VQa=3>_z{jAG z#rVvJ^IoTBmS3>rStW3QS5%=@SYtv+#6p(GE0;Leav0u7V35c#y<*RlwnK>Py6%gI zwW6j9NogAA+%9ymY>tDL+(8Kl5`Ek4XuCLeoo{D}^o?tO)>Ekw|Ull6d96MAe z^qulmyD&N6-{h1&U(*j0DS3{h499cN)MdOO4Q`n8J zHna@}AWov!MBP(gJ2Kxq6)m%rXlXG`riGrp!NO`e^sM zLwc_jOxV=o=PYuu`QIVG$!Gm-lTDQ?4?OVjl;1glCnCM~tCRMzkgfBBl3#wkF7^4# zR@R6`e^vY$Kjb(HSOyd-bU{--*_QPAncU*(|W%r)ls?PqP+{vb9x z^~b&1%PW3x_W1i=<71bX``I-)d|&^Rna=ZmJnC(}xAvLZl6Aix_B@`My`<`Jz>zmw z&hs%poxgjY(V~sUf!1&T&sk7?tR(B*_YeFZ^>#fup}zhZ!+PTkr_UbpoVL5{g8D_8 zuV?@FM{2btF)e3wV(>!AkL;xiF(*Jn8%(1AoEn(ZF(0H6p!M4i9F>vw8 zc8&l8hMNvcPWLP8&zs{Z;COtRcHzF7o0l4WW=RRYxFK1z{NgmLI;PT$%PTG~m#fuF z<=Xlxcxf=V(uPx4HLk4@-rXg7^V91~%aSj9vCcMH8_lM&`5pI%Z@Nvj?9)yKM4Ib% zF*t;txN+;cE|a8I+A59%+dtgc-|5d+mJ@#LRLrSsOV#lIOg>Wylh+L$RRu?M%(Ii#m#T9-P{_-TA`IK=TO;K4&tYTW!cDp*1Pt zuJo?8W)~E1FYJ_YTxR~iKx3Na^|yDW%@~YS7}`DxtoB=R>1$(pP)6L)4poO_f=>Hb}$xCL`=(00ZYk$46O7Yv19KZQGpRdJ8?^3-M+5R`xEor*% zBDb{Vy=Avj*PkuBrnXo1n`e==Z~0yI%PrrJRJ}LPeHBzEZx#BW>Xhq`n+dY>6e3Kt z&s98bwAXumZ<2j<`BV0jy*p3l=HD~9IB#-p_KW6v-(L>MLqLU0-Ha{_{wR5ggH?{B zfq|JzL_$D;qd;Nd(QXNSy(0$}EI!^Z8>p~i#>OQlo7e>`7)}Z;X`ZN_94E72(^B7Q z9Be1T^{*P5B=U|N z>V0@lx63K#$=bTgpba%QA5Tz}llJ*h(ahvOb&j=hgTlebEj&rmDv1SFYhLy9CX}yH zVsvsh^)NclGCQiY?ILg0_PD)HKRh38xBr%A;Nq0Ga)FVBIj303m%cUO;r|mA5}n>D z-u7+^{k%8%>yyQxK7S`8lbrRzzP$4fc9gz)n)<+jVe8D}^H~u#p?R3&MugbFvP z1#>Y7#qXN!hm4=x4EVY(=t%?9?daRu%si7$99Yfj!BNQku=;xS4uP)sr+#qu7w-)| zG&y|T>!&lL&rW`55U|feXvSpQT~C%)&(nUfYCT`x^X1F+ssh){&a-^A@bKS|9b4Y# zRbLKgykGTpZ}Z>Xn-A8Xv;LTCx!UQak9GYafeW*ruHp2VbHQP~!5oIx_a&y;j(qAW zf(#eb&J;KauDk6m?s%3h;K*;|I{~Z$>IXjb-~BA@@wcLYNpa4C2G0ctZafipXg1*1 zbl$;Ob>jMgrYFAPqCOt?j3Nxr7H&`x`SZZ@fT~j?@}xiuQwF00LnumaXJzNH5MXF@ zW?^KKk!aYk=xDdNxYB_K4UNb9#GVf#oW>JvlTl7OjvsaXXKT`MA{2d(`jl6`GuNM)6*cFF1kjEu+n zN)0ZqX;_`LH!D@k?A?uRE}~^yw8htWZmGHYXsYFc><8E6rtuiC`u z$|iO8JzIM$J~!^2Gx^b%?t;V@Dx<*f&9%$6#ZE2Fo6V6B z9>ejy{Pcr8!k0|WZE8I7gvHb>;oPU4=_jt(vxzd)E;z(+bfx)!2S1CaU*7PXI3Ck6 zx00F1X}7!hg%38xUtgPAZLt5nw{#JY;su8Kg}&^9J}sIe3T(VGTW&P)$tpPb{0oU# zn8v%%fYGVOu|wWXtnCGVHAhCq#R%pk2ElT*P7S3_DYFxaA)0a=b2%-Ztw`<@dvjU9 zT{tHqFi6;>p{a}UO>&TnkJ-y7ZW@0sA7BX;$rNDzf8)vlUI8C=RxWFW4NE4cm~|fD z)38ubogNm)d8t9bAz+EOm4!*#%nGxX2EIT(3&%M{d>R?d&VMAAOFfs(O!urbTcysH z$z`%YD#j_`=+Z*JhKxm4Es~QL_gS%MvNH2pEtvA$vZ0AjtE*CI#fGl`r_yI-@jW@P zRzqXgi`5&x{kq^L@j^jowM4>)^Ug+JSG}3M|DTj#u$7K>?qU~K>$h7|U!~}7yJM$) zdE4Ej)wx?9K6*KkM^LeVd6yus;M3I)roCPw?eRmRLBuscU?QKZ!^g}$pNvjA2nwv+ zz$E0jWwr~$|4GsXlZ`|SU7S77FdVTnI&*;e@CU0u1unlC44Zj)R2mr%GJY^%KJLx* zXX7!kGaFi1J$M=&gm^ZbS#g?wdJltubIKfMR!@Zu=ecC-ebNMjL}VF6JVG{ZoaCu} zb_tKonhk|6o-1^kd3dAmKH>3XXYn>Wp2LVVuAXLHKm{d~Tl{oc=~ zOmCBayah9L}`1!0R2nb>(O3KSd|+PDNkLynJ}SR`Z&Zk)Kt z$i>3X9wOrz^nj5?!(vs;&Wns56IGMNWEvclPtVpil$oGe?9ed9qIsQ*Lc?>9Sx%GJ zxoo_=!j;8qaT(8pFA6~`BV+^CT7A&Ew#IjL)!(kED{nBQTsCrGnEae!LDqh@jOe1A zn3WnYT$RCgK_MjdcpX!Y-vg3?h&CKdl{_UHr$d7mHHg$^g=Lv2p znxp8QUAM02-NzPn>45M~rG~_ZyxWWa=Ool@ZD8Qo(%rxB*ODJ>5^LmhI~@Oh{?v2V zzWsciUDfZ+ncsu_)h!tmvYvdME?>nXVw33sGVa)MskAEqV}L>n^`v= zbc=jRg-5%#@pYb7|My#+Or^l*~-h zCX`%yDKI6*bESZDk<}CR`CL0^KKE%%5_>Vdk#E(DMLlM(UM!yAruA~kl(4LqOJ}65 zdbw;)*{he!7qn@;TCrqW)~l5(maSS9(Yo!`tJNEhIcdFKv*lXW>$Pjwt$MwVdFF%H z>kqJLzu9m^EIVgIo!aU*o6eZMezW<4oA%o+SHiO2ZoQGV`t7zmWv}0Ef6%7=ZpU@x zm82_GOoT?l0nn6-TSx~(D>Ivfv6X{>L&K4Ng};GkJT|4c@i5A? z#aMn|V4G~slHTbIN!{u#~0fr*m!F3KPH2@x4vZh zt_&&l`{5vT@FKIWd~82hEgWVRwsK2q z*ECFcc!-6KS1+c+XZNPZyCjT58WbKnwK8(cK+IC8WzJ8}G*pqTu0YuU!f+vW!9 z#j?$iRQTU_`{0UuTNbH0YfNA`%+4ukT(FiwKKUKnPW|(`l5*Qq9zJWGaEH{yu4~)xc8b)s->faQG+=1| z7jvLL{9nz48|*QOC5(|psV&M~g|PxtT5nsb$uP4Ys5rp_WSDYN<)Q zB@RJ(@^{=6-KMm1N;3)Ne7M38nbr{8(d+mi)h&7N$%Z-q{#=o%9l2t(w`>ZupYm^8mkTOMhd6}p0b zji**<+dQqHRjbqfADz6KD^XNCcr#Ph>$T2npRMv>Oh`~)Zy-ALf};qp_nT=um8uaG@)z;m-Z`;>38=m_$9NHl;HCk})>U+lm zw|B(7X4pIP*6ciwt#zmM7yf(Xop(UPM?f!__lEWB^}>pG3>I_kKOOAsuKGoXNut5J zz)$#2+rhmO&PzXT6|Ma9ajv3q!p8Li>z8~yCdztsLzTtrP9`p8b_U~+2E7&|Hi%p8K=re9Z4X0qMt+a)X_iO}>=gOo`Vmi_TpC z_S@}(1b9h<;sM4^1?R9MD>g0x zt%Ts=I5{cd=wt(bD~Ao6mL6f`@Hlp7rQlKb+5GGY4?M4^_%2}OU!ZX+*lpp>1W$JL9A!Yxf6`ExkQ5$m97uBx5+IVmStRH^S!T!IzxU3S#>7+lmgb?Jg%2AUyj{b; z-YDPjYLS-X?z*$a2U6-q(l7UA&dz;zcPnedHl906o*!oF5Wn8>&^YzV>0YaR*}Y5L z&n{nWoN?i!-l0QVLIIzuIyBzz7;GC!GG9#Qjip~1nrDBll`o`0v7-}YUuctRnN*P%d&MKoiH0@FXq zV+|}i58EG<**F#)OjgM=Kj;nTEhlYR?JNJjn2N`nn1Q^-=xis8f$gI;jW6?hesRPTnY#NwY{?s%) zpCb6cva!jttziKZkBZ6*cWbWyCI{Jq#1=R%<>uMqz`<-I;F!tt5M}Nn}v`50ahCUr`4=20*7Yu*Qhu!{n>Kn{3&kc znHLW6J2(WaVZNgInOX6-@kBjKP?}7o2@y zYmAtMv}`~Fam~zp;CZO#ZVdqijtL(Y9&cwBSdiebY01e+5*BqamH|&rPB)NDNO)By(OQQPtw4kpXWocR4+ z=h3=uPOgGK7FiGXv$KTzbvYb3&=F&Sh;%Hnkxyn8TKm6@$LGgRdn@qvQ@6^fzJ+%!qNvytOU}TAC~ef{J)Sl zY4^t&=MS?VaG2Nlvsr(CampY6hKf(`C1+jXi(Bu)BmCrgfaj;LVbg0l{#^~p-yGWTjB(`$>+dQ$6HFL&nT1nY9y3Tu9ZBvvmD$2H(W%Pii9NrPI>RKxX*-s5`X)VK znCNK7^2}Z%r1HR2Z=o%!{S7}OCN>El(R@B9aoWm@#Sx!YJe%d?_9V@fPv%L(+;Y8X z&!;he-1($a$ajgLtAxw12a6dWRB9|T(AuT3cxu_J%%$veW=>e<`Oo>)G%iD_SF0Ar zt-P>u>82-;w#@3aGY_qLy)JXssn_fG{Nj4EVF{P_n~i&wRDzZ{alTt=-M3oP`I*+O3EQS8?aJFCFA?y@`GD1jgVSVN3f{Tz zPrJas@7E{K;2w^U1P2zLj1C8O9+eLV*`>Y|J03E8mc58YDnp^MR7Y{aVh3@R5Bx0u z3^p*@bEX71GX7OqlYT&uL*gJadxnH@wDg2E3-{0XCG2o!=`8W&1s#dno^iZ996^DJ_Bw@_iim46Cv-(7uQ=4*OGj#W1Ssoq%0bb-N$!5y6N!Hsx@ zgN*D<3Jwhm6`L5?*(Bz8Y)m@H$Y@}2a@{pMOUWJj$R-;iQhb-Hptz*c*%Q zxBuPn`DhErvG%+M#y^#9uT)R#<$qUoW6@B{x%Pj8e)Jc<7tbZ?cU}#j>-6*Jbg}xy zk~hSiILs6hqIfH|UtoMT(M76C%6EZ6ppa36W4+|IEeoA~7#bXGv0i(kfmzupBq>Nh ztA#OK$mv0%yXCu%Lmhu7vR!dxTCn{>Z(eA`LwAioI|3T{W>qM4@t9soadxm{33Lf$ za*dkExpUiv8BuwjQPZ96IG!=)Y&)5j?zGJE$;^^@n$PD}JX`sEUd^|c&*wL=X}ws` zB9`@HVTT%8Bc3Y+CC>LS7%{ja;(WnDMh<=r7mEW64z~-)^KmqQTiAwH4vh*9hdUIM z`79a~7N49fDJb(pV$)L(9*yKhM;>fcJ;A7&`pTxWVd>c>#(+06Swcq}=d-8_#m>6u zcy_+mOs`Y{hOCB_5v#Y^6bP;kVRv`r5_nar#WkP3c-q`s!AjS+MjTuu+9AB|`ZiYg zTVJ)z*WcP3^zo9cgV=^En{~cQ9otouaOG&w{9nQ!#Ek-3nAi?)IU~aCRIkikESI(A z;fZyN+rQb)o%Q0?_O<2v++?lOGB2>3UGM8$`0>Q4-G*iXnX^(p%s5gQZ(C>7@V;|V z|6&e>2an&goss0p_xXO{)0=a`{!d&QY=0E=+tp{^@3pNAdjI5R`ICQgjBTG*N+%S| zFWW+t;232{sZ+L_u5 zQcna{$jgZ+xf=KiEDX^z+92QMq3ROodTZy3g`P5LCm7u2l%C$`;<%w6(yyns@^Sy~ zv*EEWj4ql_rv!_2I+j>3yr42Q#_i=L2F0rKZpev2oGS zW{v<*W>;or7f_Fr$t*a)$RiiLj74+OlM~DwA*W&_7cBK|W|TQ|=cQod+1dZtjy&<4 zqTI#oz{;h2s$_99gTr*Q+*b^mo-2b_Ms2lFaJaT!qE1f2`r7O3>p4#J$sU;Y_QK}i z)!TA2h2BNZR}H-NS8HOz-MtyPNvu9o*Ir_*;`Cp;@=L>`172tC^12)k9630-^OUT@ zgWP9}gnX<1oiYC6dCA9D-EHsXFB#XgVh`(`6?^t(=4`=x(>fJPuHV&^Ub0t)!I}Ai zta4mirHm!B$f@R6WqNz^0~k4HGxy)KSXOr9&`$P8Z@V5?zhYBZ%P-P<$R@Mn6~B7Q zgMS}ce%MQ&U(a80=rHrNz>wKaJU$a{vS>UwK9#}XXo^B2ugQc36`~ub3NZ2c9FPxY zvvW}lUz8g1pv~CVppjYbn7L3ohwc?Q7Jebt$HiQ=6{5}JMj;9;0xmZKnS~E%EOh?y z^m$OO?yim|KfzfyVx0LMm&}*|Z`jJvu@3l2PF5J@_kRPpO`R@55C8%nkU zUv$Gnidc)bz7BoIz*zdz@2K~Mr4P243huJi34MM4K+nu1feVI7Pu8Vqx9{yKeHwSp z=doH0Pt=MR=a>2hADd~lg`v|Rnmc!a;iI&h*8ca8=^8VnGVZPv{#v83?Zc!koyr*n zdo({jI3%01=8X{Z=f_(fpHuCPR*k-Wtu_6ue@}r@^Xs)UtJ^gSE#G~t6nx~;C|AWf?UE^zK1dMGR#nLVZIdpAo_2r$z6BOt?ZXs z#Qm-=V3zXJc+kMH;HpEj_u3ONZX9YQQ*wouS_pM0IPFmC`)fHts7bBr%XAmhuqO;D zJU==FnWTKh=lgRBo>ZAE5t8|Iii2Ls6~{n1hm>9)Hic!=Wlru`8p-AA@@!V-Jj>)+ z1-mq#&ndZr5?FtSy|bQ*xhOp+IQoV>K~$PA0*B#QvyB!-EW&9ht$Uk|!}R3!Ru4`VFkIgfw^B@`fl2S$)*{wjTW^2O zKFC=5bCs=v`JxBwQoq{S%9LhZY{`&xKDyd`&6CwF+VT6M%HF4~;FwYMZ_btV4cnKr zDxIjYDt&ooaqQ!mwVSrSnX^SuZpU0>hBwTOm9y`x5K3WwsG4qP*C18dd}mj^lKRWp zs|ufM7>7Trim@o!nbMDf$0pnLB`6>Hz;h+PF0S@LCr(y}!1^ZN34x_7|E9{b@hZ%?#=^%kL)?YmO(UX(JL`LZ3){XU@-cr(XI$`7 zcy;|iN4W0?c{Z`QAAxluOP?nNY0FIy>~T|dNp}6XQ{%}5<)kGSYt^M%16=GEH8i=Z zCAKj2yfs|$B;JQD;em@t;se#`?s5tZJ>fzX^QUDjdnrB1GcGgCDVgiUGp9T=SB1Hy z*lTf(1<297nyH<^kiiBV&5Rr}9vc!Ir6c3^R4g*+=4}>G?3&@RagnQ_S=^bM!rE=e z`!^=NRh-Ov;OJx#?-em8H>Gy6t=#52!*lbpGi z7*1_@-ncwqA)7_R0mkOl5u3B_R*AYctPecQC*i$KhiP5L<+xIT1#50I{(E$)*ZRSl zTf1YJ&)hwIJwA3%!}eEiPk+yUa5#cpcv-f%VG=vL*l*hhJ2nOFXkky-5F^R>Y%eoY z!8)Fu?-^DvmaS&fwc47@&Sdxd?webM(L0l~!;gKi-16@FM#=wWH4N_<7@gYX<7^)= zWIb&Vi_?B-*#1eDi-ZqTU6Z z4xypK5ho%id)I2rjQ3LOaGE$LQBt)m_)@4Ulf)4Y#w1<`frrx^$~;y3Y!^Oom}y-U z`lL%kr*z4z1*XT6PvL;WQZ8V)U<;FOi#9it|-WRiv2 zjVb;Kp;oC83*5e5T{K(DDr?1xeM?`h+UQt%XVr#jGN~^iBd$B1fu=aWy@HRp{?10K z@K-a{F&IKu1%nfef}2u*d2p z!=>`*`*Jbg8F+55P5xhC`=H_DhZBJ=`ts6yi`ic%?yi^H|M$=R5ApN&`&-vne)#iq z*}d#NtT&|7S)?iwYOZKnL^HhRWti2xa_zLcj2sDWiJ80#9&^&A=RH#_W=xS-lp*V+ zAzQpBQ{qvF{InN~nl;_b@3i-xl}KQob+bablfz=c0cr5#F>SlBF6D?;T8q^FqkDKNBI=DobbVxzZBZiao=OG%~&KU-s%y^|h1DavV! zjO*<6?0l9}qSx6pSD0Bl zIW6rL?aEx%k#|ZxSz^nsnM(y1Id*AP&aM1*bM5wPUh~%M5qf?rck8w?>3Ms_UTaq# zkwP7!50ZWZ9imr)4bh_(LuCv`$f+v9QQqW_X2^}?Gu#YHJfMl6MZG$mObRzTGW^A) z&9u5?I4`ozwB49wl5jvFv}d|u(%YGnTbG<|RN8dLlhat#e}+Tyo63sdCB1VUCu`Nd z-1y>%PW3b|U#V+&{T}MH?nviwb$J;psRTue5 zKvGrk;%PgVvoP`SNS&DHyL;Q)+dJ}&9U2)M-9&Vnx#j)#?D+V2A2W{y!|xrRpI?w< zu`o!e{QTm^=HTz?w!6Q-xV^jnzkI&UA4ZlZm-)^66PEt^!gq$f;D4PB+t05j%^fz( zt!8-ev)24B1DEdgxQ3gS1__PqEDZ`}zcsgCU|`B{x!-)^>v4rD2|4kFZFfv7650>1 zJtE)8o%`ZIt4du*QrGEB5$R4H-wVC$rB9s zrKQw)X#Gr@V41~oo8_zNg~d~XgDe+zNLU!ms*Ct$mM}f8Zso;no*vGJEW96%rOqk| zQ&*ZDb?9VL&XTv+ZqF&XCi!@l1j7vV%y*Uw>eDmWyi{9x*ZquIs1XvS$yTrB8N0Zz zD)Mg8#5|Q(D;TF#9auJZ*(sGJ%iXMESI;g|Se3PW{306HaOVokF#8XAd8oeSgT#I@{br^gj>5~&Ml=O|O*}BAk(s40j-xblxyHE= z-_2=ng|&~#NC)fhdy!>wENHE7@WVTwm2(`8Qgi2B^}3d!W0c2wu2Z`_J9b~i;X_ZO zKd*^8WO18Iw#VXX{1NlFEL)j(8a=d4Fut|njAdRzkG)m*G0mTko?TqAx5;nrt}Tw& zR@w{O@K$eelwX+LU#5G3J^AjO`YkFPHCu8TkLI5D)0=gq=-C|ZOKp5NEg1yuJOZ}+ z9BS{djb-)yFX6v$&(F^>sdRu zRCV^oW9w$EUUyPy*P5o)X{+CEx>{!aa?^vL*Kc<`nU?)-=Zj^l-|c#{?e#ln)RHNi z!3cS~mV%?8-M`e06H^!V)+@PyMzbCr(eZzF#_6L*SdYZUZ*LSQvo1P1Sya{T=4V4U zc{T${j;x=BOL#jqin&w{PkMHGhC%Q}7R%=g{kqGl`2KQ=q{;W|e*a_PHIe7aLcQ5p zb8i;$U2Q7fkkqTH5jvkON|Ix3=9H|6jfQ^51ddE`l;0L|vg5DR=d2!E_pf|^qXe{P z&MDg|2!-%98bNoGkL;8+W`F!4|@99*ZU}~`*iutT>I-XwYy@TU0W}#a?sM+ zRp_>Q<^SuRN?(68O+LV{a`5RMM`Je61x5ZF_8t&YN`A9QzhL^8dX5DL4zT?BYs|>3 zk?`R>BO^;g12fC6>kJJ{vIYkjc$7YzZ+ULo;J_fp^@P8TujoZAvqXbMgS}E;#erru zKXXMUv0sND*_%yTk=S#pa>b%v-CYrjN;NL+c+{)Qr*NQK;)BL*7LA$+i0~cr7 ztp%;I^KPzOE-vSBU_tZDuCB&KTboX-S{`C{>UHY^GmSSJb}`*vwc7IIti>BQnPo3z zIG}Y~+v`$Y=9-OH{i;K@-MN$*wf*iRwso7%_jc!$UG)+}D#6w;r85{Zn1M?$@EifS z^=+g0`H`!bG^jC^0cuRCCLO(T!Z2)&ZSkf68BfF0b8KaTSKayXk=13ALGGE0J2ovl zWow_%SK}DGRAIL1^F3E03-~Uu@%-Xp*;UvtyND%l-IYk=Yy5%%OP9516)X*1*)!jwtU#cJDXB#U|ES^k#g$uu8vke}e5Ea!D-UDlZz&w@HzL$^0Kx7@sU z&eq86{XG`-zt>h)?r^-!d{)AI1=mi8=cif!74S8*e0agMQ((Yb{;zdw(havl#lTKGg->_!iU%uM?M5{{!(mUAI`JvG(Dim#t~D|;J`ekqM=dxl9^)*qfEdfhkrkkmD)7>%osa# z`2>_54Np}(YLRh!rr4+V?nH8rn%9m*=2p`S3=^%SDwp&y|LJIIFHYo4og(x?rRZNXQ%Cx;mCsyq!hSw$EHvXdxIn<%-^;r;zRN&F>|jk7bS{MO5}O6e2cl%$A582@;?PfrLC{7O^%tJ zb2swak!w}C!AI6smuKtkk z`-7iLd{u{%BQwv0N39w=D#DrNFG(6tm0Ma&=(pMvee!)Yp2QteX|G8?DIDLOq`K4sd9;H`m&X3T)KxO)0vqp z8=N|m`aDx-Ry@;m=!%N#IJmgkt}3zDMXoEuGkuZefki!LRx77Chh?pt>YUn@I!R;M ztgODWd7jf&F5e`s^?Jp+b*!(~?$By`y>3a;vDZtteSL+zn{zEw0$N&e6yy+;iaD_% z(OEh&Zo*9GiD$ZX%w)3eWHg_dXS*?}Lq<~h+HrA7^Hm}`&IXQKyv$S6F zs~*X2AFtUg^=~>?8a|sR@4!B(ai)mc%q7JhYN{SZeP>rFEtgueswgvTEw{T}>5pWU z8*R!HjkY9y(}|jB&s|j-ZF1~JQ`x>tr5cy|4lb<@y`_7?Y{LVI!qZY>e-5`_I@etN#Ob>v2SX%W0mQ|5)^dtGP47 z+FD^1Mus0M51G^j+4I%@p5F@)du7vM)&dwJ|Klk)t*3e(egDwyrwn zUkffcs80DX(PKr4Mx#PHr;=m4(#$RHZLEq5l-T$TE*xMH@Gxi$6ZT*@$ii#zfPqa* zujBwDBTFYkH`76j#a{aNG$t@>u}L22=ixPIV6bO1P~rNo()rja%IpI_myKOz14EG2 zlk+qGd2l}I(y*BDl*Qm4$MWfX4L6@K2Jsah4=kv6` zu2h>4#-^3I!113(>U__yFQ3o1cY2kwJUOmwW!S7dt)&bro@u^Xkv{d+tJOVhTx_pb zuRQzed4rrHXz?p&{F|G?ix<21OP{-8uDim)>)qIguaP-TQ6+*Y(r`4gPnMAVQFt!M z$YHwJmlc!MLzV{VmX&CI&bl^9_T-E$mp`sP*=MhJOC$SR4v)3i!fkUUo!1=PT3or$ zRKtAz8HpJ7M_;|mmpg1~dHL^y;f1_M`&l;H+3ha=kg}oRf9k%RBE#f!M&gq_xOb^Y z3aNO_wpn}Y$&$lsj~~;USykp}{GVZhL+We=afV&--~TP|Vqs|9(LPyhN414`)8cCX zRdZ(_DZU-k!)@cIRQdYlPWD&-&n;eMy6VmGqyIPGyXEuh%OlSnAL=S6alc~OdH#9M z|613(pDsSXu2Q*xE#C3K!fo=2X?N1^G;%P#U=n}2b*Wb~o0Ertlbyy3C%Lqc9U@w3 zPaZZk+bN_3a;oNR3F29?qKQjuH^(F|E%Cb=C-jU@-MHvoy&*z~i6fzbiJ8X)oPrb> zS%i6x9B}Zl;(Th)tI)~B<}$0~z;s3y2Nfm_3xlPTe4G|Mn8MQ`w5c+ zbD0_}mrdmR^0L99?wIED>E-t_r`UJg<6Jf)+05$M9Cnee74|{CulyI5$O$}KHj}Gh z`O@6Ts?3G0RbH!Bv~;d|wQ5S-F|Ac@tc*zKq^xC%MNJh84!Us&axF;$Z6;#fIb){C z;zdU#QtAp$GH+b$ENB+@=4SE6l%o;VCsG6-Zd}T1q@ruLMOjdK1n=H@mUT_V?k17k27Sm)fE%u76_}>-%3{Z+wrxb5J*#L2QSFVWd1Wt2G;ULc+t7 ze9NVd-TB0xa(0el72n-SJe{YOD9&PIu=;xV+B)e&eX_N!|sQJQmh4W7hIOJi!aVeM93K!vC zmfa=F9_G6}1bF*btoSrLRO99hVNfDzV&*Zp;J_^G^MRiw#_NFU^hm!84pVs+G%&Jp z@RS~4WZc5JbP8X9=d(GXRtaiTa@jtdXAA(9HOy;Xs!eGT>vEW#EywU8=AX%{u8!2!3JGd@A0ix;l)Oxs~%X^)|Rs8*x&$r&e$H!eA-Q~b?D=VrnAdE#a+GxlU8pF1^^Ir`rL&yVgpeKy=? zbA(G2Bp0~y9=nmOyyENmD;&=5r?(Mb~n}Ji*6Ac1D6M>beBDo z>q@(Qu4m(Bh3K7X=l@;q-o7ZRvHtfomB)uyJlM&=^6NqMjf4ls)Lz@Q`9$hS3QaXW zow{ac!lQE{>VKIJux>dxm%(7RT1MJ}Racq4xz}(@8i?-E74fbE%;c?x|k0t;?#2#?wc3RCMV z&#dSYRGxM?!Z5)1(WMs&pbhd$bFO@lU)8^YrAb7hnMb@=gS%^kW14@@6c*`oCz*m5 zcgR;~rS+J)KR;@zUz>I(*?C3p9H+ozr<4{wyST(4`ESN7AHX-dX`x~{M(=RPtuwenKNtD?lOLHbXCls=-3OcuMRCU zeinH3mprGQBIAC)lda$HJ~a4z=UxjMZAe@1j z(?WquWDzLmDJTfEFv$j7V_@PbX=rDi_~SeSGtV=BFKsU5_Bv*OH`cQVX~mq_09qx% zS~O!ud-qJwl%$Lv!;?JIZGx`Ci$#c$d`OepzF#K-f13+$GA?FEb+8H6#E)5mAp3a6>f;L2RHG;Q%Zy^PAu2O?UNcl%{mmgcSu zxv%xFd`rpec6*PCt$RxonC~|!shN8wSsc8{_f~^FAog3-{IqS8{|7vsTUK&C=7_(R z{)Zno-rhO8Azdy|v_>dq%e>>#|6Yd+y^d@TuKIUWUBPp5$aS%D2001AM(%&j^PBlq zSHvW%>=00BWm-6?y{Xr8279HJJXFRP`20{8U)m?7E<-O1 z5?-%mJkZ6kZreZS)uqfVpylm+0gD{|sjPmp>E8itHv?QvpB8U zt~zJSk{^ru&&+bj%*!@#f9}_A_H7B*sV&c1LhSpDwoJ~7zP#E*dcs66(bd;yt~{~k zO-Ay|>+5;zek4eqeigZ;%xcolpWm{z`chx-n=25ysDEd~%U7Z<7vEgvG-R!Rs(rEV z(Si*JZr)1+^$*qz&ydwWqX z``u;V9=^N|QKsZ^+t<*Zz>NYPiQAW_lvXRAdLn_3k|V3(xPiARlk`(7}y z&-5^mVA=M>@p0hK!UL_kR$?bu+3mepANKa9GQ2>)*n1TdxJ|C@P;DtW#Hl{FXx&f6x!GAyK0M$iu~n&VpRZN?RP4xyZAL zXxL~Os&^mh*Y`4zFj~0e%RM!KBmCVFmOev)lv#I`$! zFE78yS^K8uPVp8^uZ5h?CzRaU(s)STb*0?h$p%xyR`E`e`eXPtJJ^wZ@2pa(Z42gZ z%I-h*!SDk6%pFmjzXV#QuZmz$`SnXxx%P*VZa7346+9&MCgi#rs5abb9mz-4l0?cRsWCsfN6T;LF&pYkX{ZT^kza@CfM&+b)ordcF5 ziuSqRYn7L}@vuW>+Kvev8p~EZ>eAWvVi5;&r)V=%B7+KpDmctR!*df97z8;4nKVGT zuAX7jgqy*O{5x%n<8*3H1TNr^E|xQt2nz6JW>YmOOW`$wsZI7DL#MtU= zCY*K(nZoL%u(2TMS-lX~|HqGJY|?6Ei1AoZbC@SfXr8=xfuq6stix+9_nQUp$~U>i z8awBmc4POu#-7h9JGc*ib=Et5-06gib>Un7*wr%jrl%j}#2GWQz-RGW46D)+cbv-cJpJgPpwAn=~UN6oE)i3fODViK=e zdI)gx-V#9?_FT{C!QjWJ#RNJt#2lWMLG|E5(3v4#b3lv6x+RR2o?KtQ_;|m9bC-FEZ^r{;JTKWUqVI6buZ9*-Aj(SgTgtys&Fm6w+XEcTjvYoq2> z@S+2+OIp|0Cmgm*6;NPcULJRJRjhZZa|eg`BPoyBn;beO$^LnHxBB4{hl$MgY-XO* z*FHS#YOdq3q5M4qCx3((hrmpgmSa`wQ|>sdc9dVw86Z=TuwbzO8@qvyLeGw^uNMdz zB-;f@K253L>Kb=!&CRq43__D4kBU49Y*IXC@vdNkkJt1&iro9>`Q2U0$jHo>aem!h zd6t(7`H%nW?c80MKarC!;P(mTTWqZCVPAvO6K73ezVpkI>%#Wzum2f#g&bsHPB3F& zlDR2%AnrnA$i2Adty378gbX&EWtI+UWrZsZfq5qr?3lXt8k zhdCqQdK1qi_2?FbwSo-_mMjl?JIy~nSXig1=D4U+W66v7ZXdIo=b3NG9*|`*PP+EM zoj)z@vQ+xl&4(t)G8#T%)+z}&GErJ(<)Xna?R85^EYtmut>8943VgC7?Kcqp;s7rXyUGQAH4cr;;Mo)YZD(X=;R|SJ+SHqVvsdM#fP;~uLBjzywt~%# z0!BT;hj3j%e;LrJR@W0ZU4~HB@&wMy6uvzTm5s}SL3XV$Xo6guH zS+4V*Sulmcky*uG;UFuU3ddEph%IkEoi;eG^ZAU)^_)I{dPOwd;`0t(t=ns&A=Prody5FI}848P*Gv@DY9@tGt{PA(;@=bp}-$-Bo_knxxdh7T5y!rlodoun1 z9)81$_qIbDr z!aioJ4~@0TjZFRKj89$r8U)TPVB2YS@cqsAP0~CIIku-Y^S#OwQek+=5q`qSdWJ!{ zMu;MJ`VL3&eF3G0N91|SXE^HJ5op)EqR9WhJ;On$<*o z!5QU`J0jdeb}Z;^P+ly@9%*TL>0-AR%ai*oo+sR#^m@`74v2()JkDtKY0|REi?t@q zXtdEVnlOV$^~IqX>>+mom||8eQFT3e>g36dQ`Dw3NQUe@6?SV=;rcsEn2aZ!GKhOW z?EuRiZR0|Z_bV3(*8f=|-F(t#|H+BWF#)Y2rR`n*1qw44B&Qf}ziDc@i(%Hb69-KA zr!$7uDDhwal4`c|q~Gq=i>&w0FpJwwZe+h`W6N5#A=PJ zO0W2(Cg?2+?|3OXFQIu?uP=+qhj!QFY0YvSj0|=H&irM&0@wdsz{`H4o%L2i`@~;s zm;Za==q&4QHmz@AiQS1z{%xzKE8j55vAFuuwYq?90oT+Ol_HJ2n+{ytwrlIM^wU{O z*(Nj@+AuQ2Ka~zSZ*X<($}LO&?a&G1k4<{psam;0efd(VMohOHT5ebucxS9CY0JzaBG>s0K! zclkTwPcSiypN=@@_jW_4s&tmI=+(*Fw&hHis(T|N`}XZ;Cfk*6tj#?BfPdky$6Vca zv-qtpGS_c8z&`1$Q$p?crfK*5S=?{DlGt|2;!U2|+LY6W#E%?E-Os0()# z!|w&G4EtVi_Z75?)hs;FyYg-9=csLh&#x^^tafT-J(JA1T)b#UYEDP>gw}`uw&w|_ z>9$_gVyllR-)VP4D?9o@zclmJ<)*nGBx({KZhptRs%oYFpHJS+yY@cd@%Pv~E3oR3 z$l|mAH=3P2)D_K;e09-TwyKF*H{^b?*4H>LYv|K#TR8o)g#5ws+GDHv`wraJ_%E`) z=i95I&#(K{e>~tmHDguf%~!#ClTCLwt4W8juHOG=+k(7s3FbNBvVltbZ*I&^-PkR? z_*MY_J09+PpKtv7!^E(tDs*B3Zk8b62s%#SOV(wYr;3+8G{J`T-4BRboQ@j!M$u-*xV3?t_L zyUpT1Zr^u3_UX#J5Aj#}$`9|}Y82P1by1+(@%Dmmv-jTr-YUi*dMhvY^=|*@jGJE^ zZVBs0-*xKST6uZ*eU<n$pE*ZVHD-Zqn(`;$OGyfiClc<^1nA zm9b|sUsz;u!Ps|ap!L4#QQEaRdhga=_o@4|yN_>si14aXe(g5pJq>IweMJegcZcq= z`~AGi^6S+_R+sI(LtkvuQ|_KPuVnYyaQ(8oKY~C1xi@M4+w)5f|GFZvHtW_i%bh0r zTUIk3i0?imliluHeY3Id=|b~Q*QZ{+UFFDU8M8a)`n&G+NoTE1^)`RoXg4=%;!3qD z!JdZ~BR(_E_VoX~Puw&+GIs6T-`lD#^cUPzN!c@oneE;kmMMR9Uf}1G z?iGg=t6Cme-S4?jzu@Eg`A6Rr|Np(@v)%t6$Ish+n*G1>k^TN(;yeF+yRP^5UH|@@ z5A^^4I(_}$kM*hlzSq0{`(59o(oo4{(ZCYXz*f<~F{6QNM+47|2EHE+0uqfv7L6h= zA_5)QiYGKm?P!#_(J1$$Q9+_f$)ZUmqDifyNn=Kn){Z8f8%=sYnhb6yKk7fsn7AK1qmxva(iWZLh1-iMAw*wv>prw2HQj8EsiR+9E3$SQ{9MMca!k+DjtZ%PQI{X0%uB zXs@}^UiYKDL87C{qN63Eqw+`Fo2eaLJ34x9boBlI(J?`ybCN~pl!(r06`eC?bk3U5 z(Xpd*-jB`&5?zZdx|T$AE#v5%ThX;@N7tGgUF&{yZP1WjDbc+pqI+9K_YRKk%`>|9 z-00r-qx--N&D|0`MBXJ?|v?zeM!!u;>pIQaxC~FeRzK zJ3`^#1qPEx{of^eyDJ!FCrpt2pmv~xF+iz{k8?t=1cL-ar$zyz)C2~3g9$S`7!)5c z{1adjc9^i!qF>g4@ux%&UnRpL3kKz%9g+zQ{Sl1(3XI1-F|z*upft;Zfj2-w$#W9x z%?{ZM484X7k_Q+>Hz)`PFv>5O%yWQ2Ab?S_fPq(GD%;Nvfej4Y4paUqF!Fw26f{t% zI>->k$;fZXASy6T=Ou$Y1EaWuLS`kSNz=5fm;5=FjDiii{|XrLix@;NFhs4K%(!x@ z!cRsv0j7J8_%#+x)t|}8YcSDi1B2iP#>p?Ir|e|l*)T)!z*GmzPPLnpSsw8-FJP#zWKeHrO8KcI zykRO|03){oqr?Qpd7Pc10!)$zwDl)3{3&3RJiveN5yPJgjFJj^K|u_h{}mV?Su)&D zWDqi7;B1)3$)L)d!0Qhegvrb?XoS;^Dh+i^*@y`PWfeZ6C z9%Q&RbFNDy!}U%n)&{1Dkqq~O7^i<^yydxo>jA&)2F81mObW^K*(S&;ZkY3E=X{~6 zSyhS*{0kVK%w%9c#DDrHgMpMf+XP0g1N{6C=KLv`y7Om;nxuFkW4qu13HE>`RTmlZ zITE7GT8qCqRLtiBaGHKTnY) zQ^I6n0j4x5CJhC~tXKS8QDV{!i}qzQ%=^i(dm#h&1AdE@;!+1Dh$>9xKQKWefT8QZ zBooI2hMuZ9%nEa>3K^wuF&b&EDD_%))M(PPnaepQEz(~pet)I-R7nOwfn`&6G6wEo z6t!fKez20iVYWTj61!7zvJ8wI8&*IC3NlPsTgl*KHSN6Q(%@YkE4b$0nz^*` z)WVKgGqh*U{T|66ov@(v*3>saj6IDDuUcse2{0v}rvj_10S*yCbLU^O|{ZmG~;hHNIKPk83TRu!4b0pq1@`F6W0eGrIb^ zxKzJ&GMRKUG5w#gthbSYX92@bp~-v!n{&0+m3giECAq3<;wla!M$_ACd$KeoH*ER5 zO5CS%o}aIGQ)Jsd0MiEvpUe z9~eJHZC!ARf6=Sitdo}MbTaJ>+L64IG1YP7zYE*)xwp@$SUq#oRM(XZCQS_MBN=56 zOsYK1aQxQB{*Hz1Ggtguz^He6#gzY!iyu_3lL=yAb69h87eoF>2IW`$f&u%a6y`Xc zW>9R{X(h$@xSQ!u0t1%-lZz#zw7?oUP9vtSKK^bdBhSfP2@L!f7^h_}Uy>xwaC_^f zY6gWY@$IvBb3R!5@4x{;0j4j%)>%qPXRj9jvtV*w_ntnBevX2f)r{@c2mARLCLCQP z&Mwf)t~9al1%psRv;HFnQO}uHne#MmPF9FktP*6{@`|DFrbO|o$zC~!Z6mj^&N315 zTw}IVoOgq?-p#|wlD&Bub6j>F74lT%DPW9Vwb}E`RIeC?LpK@N4H%gf7z$)2a3?ia zA7nW5QiAuv(S4STlV_?=_Y~7wv+>mGLyQXB-2U(G%)ixden;Y5CsRt7daF)vZv>A%-I`0 zJ=f=)y>qAg_MNj2a=P!!oO|-8D?g*-#hi1m_MCfj=iIwL=RWkD%hzD|5_A4r&G{d5 z&i~qT{?DEB|Nfk3kiEcUdx0hP0$c3`j=2}O_Fnjrqd7O`oWS4i$1)d1e7l5WFG~FF z6rX!hrngi2?nQ;Qv-xE&spMW%j=iL@_M-aSOFC~aYTvzNpnJ*C_OjN0-^*rmJLP@5 zG#7AGHL&Y^xLmP-Jyqbm%iebLy@&L3yO{&nc73?)_xFm<0(Px{DXV=QVm&-p0{<*-_vVetW9j9y|*Sf#gi?1?B8HAhXJS^IAk0t2NvD)_bTZdY2 zKRoQaD2%J)$(sAz0TVU&?*4t-u|;>Qz9oZ4*X^oUy=e!yj&6A1RlxCJFVEiqt}NYl zk-mEee=)x0Jj8No-pfmq@84V$bcylNrzNrm3R5NT=V=@-{77|yI`133eYEv1V-x30lXq7t3b>>bIK3F|@tk|WUB}5J_@vO{ z$<$9Lxuo~={GXtmR(W`(_d50kjC>6Y%3hLu7Z?hx7?r&grZh4YW^A?LTyC;bdv@Kk zl@ql^6J~zmy_mC4VoJo_pzhh+3JXj5uI!o1{c!Dbo;r^90}uA;z7URk*ebV7+~FyI z1H<9ErM55c-SkyRxydMRxl-c6qNzr&3SZ3O4p^4C^C5r2Qq~81<$I?1&w91$+^eJwsyWfI7{~Sy z%3!L5*K}z)t&Ev+>=PI>B;S>3+}77sEofqp;qPi?xcc`2W8i|vpVx7&W?=gI?@D6t zy$`28Snt*jh=0ob-~i`@$r+gpY!?(emFDsIE2QN;e)jNQv)@bE*x6^eqPayU&nM`-Ff&+cg@RFwReK!ua(d3eD>f<{sn%8239$P_dMrV zr309y6}ZacuFiaOV}k9cRTT`A?f?80V2#+o_4$7u=hS-PCx0*Z`d(f6@aEP=d&K-iP zeoG=QK6335`L0(6BJ$hR3a}u@tmR=+NINw@YH*TjPHY;%@ZcBFK1w36Ef_) ztlhHwgUG3fD=z|K6N=G4N=jA2g-s;)gc zHL-lo6CRR+j(Z zU@QOozq5Z7J~=tn*>vBXpT#dO{xzK*clX!VTUR#bKfgD3ZQ1*WNBhk==l!Yt^7!oX z^!xjMRek^XRKDH*{=dI>SPf(z+`s?7o`G4Uz=4U|$sM-JsF1&0`3 zAAaw|Be~hYna_GjmNUPz$VS7RZYCd(EOT%vbQKFt`FKPE5Z50eM#Edp#)9aQVsRR2$z z&+ljTppi9X(t$_;lNza_G1A9vr)y`+r3OX7BsM=^=@n6X5w&YWS0v_xZJC9LIHHw--7c@A_p3j z*_P~0XsDFeP-tS7>R7awC=QO#4Xka-f3$YwO%AN@;Uwc!PM*Alz+2V;Kl+U z&L1xon%ULd6kEJ44_$l4&7z^u!XhT2&?sQHqG7%VdxLpQk!nCfXuX0$R1*^y#{=fp ziBJD)FwHaeXpr|v)Jr(c`C}(T8Y|O^2aL>i77v)$DHkkAJ+eV8D!N|cfjZL)`GVZX zJDEBX7!NSB&k361XU4$Hqp+g9!99NM?D>rk&zPmRh~GHCIQM<+AOGco2EUl z7mEu#Xyglsy(!OI^i`vYNuavq(JuY(>MR!)-c@Lbtxk>B+adU1d1LRKdA}NUnLQ3L z?)&{u`onYf@RuodW;Yt-nYWcBG_&gMaJV|lR_n?yUbzjy>Bp@r7AU*fe|Nvdwa5HG z1B2O$n~K+B-)k@xm{%#3i=5zMcKLjdwe9-u-VMFV1;B{T#Q(V}4VgxXT%rdUSlasJxvzw7RNOXq2@6FtV%sB@b z0u-jReNe6mopVamnxT=qpr9)D4#%lK7aK3~Ixw>pbTW$+FfjT!6iF*8yPuI@2)9gO zoM{tvgHN@A>jr1px|t1Y?oDUVYP--^A+nfpNmE00W>9hc7xp`Hsk<0;5?Yi>I`4{| zJh40`u;7w6XU((YI*b1QWM;OmZaCfcRHy&L;X+wS2D!h6j83f&7^{os+qb!`VBf*Q zcqc|#L3YP}^I!Uh`!NN?3Txzr?P z&umR*uM`gvizkgt6%V{prSw>Sm`oS0NA!(eS+R|Cg5D(U4`C2F;r)NpOy48_q(!w^+ewveR`#bVNTiBMxs=1lI z)lpZXzA^Nc>O^SXymfSw*tXzFZ*%5{MBmoq+`iNHQeLIC%Kd-e+z*Pr>+6WVBP}bw z(>Ul|QT&yd=j%9jU9`PevQm2Sb-Sfu*I6%BExLMKx#&r%THs?P=?x8&&#^Lcp3n;w zcrfd$5(D#_()4$?4mE5#$eMp*BE#EH&L>-o0ha&px#?UJu`WecE*Mv%5Yd zy?Azn!61Q|`Sz1&GZ|s#vKfbz?)kF)3VbBcbL2?(e@n@?auXVPb5=HmopDhl{I5u3c>S*K&dEhT`;sB$O!0A4R1x%7Z3Y)Y)Fz$@7 zx+2ffz{0bEF+g|?^WOt6Wc3}^aEW}qbp90wmr4VJXS+Ak>^5N*g9{D*{YBT8_)Wi+ z#M#L5LvT&o{EkHe ztYoN;|2h9uOUzp?)=lhRlo{pYHl0*%_}YAt`-QmKZ~K|$jjvl1TD=?jxx!c*Z^S-` zyPPFu|83iG_l*&~LKChnThS&_V76AC>;6NAoRbs%uAlc~y`j$3c1Ez}^U1PuzMb|{ zZkAi5?whA7zi`3M+TH!v{VwLcmA8@=U~tQQa+}@Y#ZsXScCz-T8)8>IoiFzP!S}h` zg(n=pbv*qaqW|{koVe(qNcM$3n#{Y{zRg#p4m~V-wdZu z-?WGS^qyZccRM6Yo%i09pEymOTQR$mVR!I!*VEIkc$-YTJzt=KVN#F{TLA-mgAq%H zWap{|m0)>`ms@%lh^M_|7kt7#PkNuK*1prm>TZpDxeX4sc-g8q2`@2rWH(@l(Q1hM z$X=zX{{NDp$c2qUAMN@hjOKa^`xN!69$q5!MP$FS-SQRuygM37xy0ReF3(S%pTub& z^057XYvX~#tGEA;w%ILx;CT1KU!U3Q91h$GJP@^cE?bW=hsgm}5Brdn4kfc1%)J`8 zJQ&1&bL>)_G2yl0)Jcn&HVAXRXfIKkBJzaYBGFN;W@B;F0k2aAj4#@wH#{Ohu(MBa>RWKAFT&_zr&Q}Dr*vf0`* zCl4B&(fjH+&#P-zZ$`scVWr4}`^{7=$~+aSqGgXd&zN(0&ulN1oi`T0d~vug!olH= zO`n9qP6G#y8?qC28IF8G zR`Z64%=3O}8P?+VKitMHLd7(F=9<7wuC`|sPg*rx+Br+GOVYFFP|lj8%Bv5l+@9yX z@#v<@vrlFj_M~+EN?_P1eXQ=p+#;j-oB5=k=}&k@lU%bA0xAC|>sH%b35SadQ1jjtM;s`>V~)|2$^0YhS3++}qX+ zi76-F&u*yOv4Jt5p*m_))gNJ@Y}Yww^y(ie7tWp_3x5Z1-vl={Bne=5e^d~O1Io05s$PhSj8>@zxsH!^W zi+Pa~w{fgs5K%CD9@FrzW*e);6rLGVm3^mky|`#;=~yE;{avEm;>soOGzGm^Uo2s{ z6cxjecG^r;_tHwmxm+h^JmQ?`o!H>+Gf^P6=l}f7>1-7Y0*}_M3OJ+?dHJYA18c`D zJ`a6M*IB9}{I6dHPd?Gmr!8bH8gk4kWa5NCm$$l?o?MU_=l?VtRm zxfMCQzt%gWbeR)*a1Tzq4) z?NVXIJ`ST}oYT7w*+x4#<$M+Ru_2^x)>I!GhLWj!WlT1^Xv+WJIE`21O3B%<$qC_8 zS4wL9p2@?#>20N0LFgjQ+t;NoUc0kVd^*$g{M*t;a{{JcSn^*yTdbp+w{`D?Qd5S{ zmJL-$7OhQ`dwA0KbmXG#R~S;X=lfckKH~K9xO?Nv3Vx9b3?6n;gu|0{t%`>?|w4~;}OuDX~n>k;rC?ib|Wd;jp>_&=d{{vPH3`u6^$AMp%% z`x)o)GwLNwut;Dj)BYQqa3(i_{ay@5U&8sh30!=!-1`zPolW3PGva&q;7C9Mm)JuN zwM2HahpcXiEMX6s(jGFFB{H-<{5SRC-=&FvayxIFNR+*oDEBT={$HZPHy-JA_Z8>e zS89uw;Y(7DOH#{AQm;#r_hUTarK`0rNqb+C&bcJrdr5kJx}q~4>G35S$|W1=B^%p4 zKKJX<{}WEhW_iixb;%Zej~N1zE!QPm?@P8h_xQ}BWZQSi_WzO{o+a7wr8w!OINPP1 zkxFriOL5D4V*M|PCxFp&UW(`hM)!RwKIc+&|0VTGGKgMyqP>hEKu(wELSvv@vY#3! zx5d*dLaAZ@)&$i()$>c8DjK-*nc|S+n$_Vlsd_vF^+F-jNY@;k5W^v z>1cE`u*_gQC-kf&(jcKMS;XL};)zE8w8zPI$y^3c&BD^O-5O2(%r$1{aNS_Ep2nDN zHg&=!oy>Q_5^K3E;u=K*8WnduH+z?6%+@5c;+e5ny4JKtNr$G|3Dc!DmaI3`JkG|y?`k}IC*J$r8StWlC7!&odsE3HAPLSHnZ zNo567W#Y?bo|n0K$sJ}>JKLV$-|(XHUB~RS$D$W9GmIF?d%TJ-yv9yCffylggk zU3B^7ma{8qR~yY5RV0{L z9GdDcW>?;1pX~OSE#Ue8Q`0ge3r@S3^|c(4*0Rbqd6!f9uu*{_Z7pAeq6X8GZI7<- zJ-+Jp_?p{VHjgF-h9>ENMwuCmA{tGy0ga*!Z=`oHR`b0w3}fI3Xk_ikn4dNM;k?O@ z$^sYEB|qh3cvkkVJdfe!y+^O!J$lXe;^4ldi~AZ?BJQy{G)XfwFd!<$Gy|?Z1u%KK|Fy)A#z>wCT^;KD_*vZLarWiroiowkFYrf=bV(rF@yy z+Zy`*y)b55#^umZJK;&j&6lUUp7R~ZFVZSJZ}*YG<0HfMM~u&t#P?@OY=0!9xv8oQi=HTH23jmFNT1O64Uy%p>4@5Gv1WU&ha^3 za;5CCcll!ncU`~ZC6XRmTtAA9u1VQ0*U9ALjE;MD?$PIHzS0Q&tt4_+*1ws%8T^B>wGIK_Af7$*DZ-JFRw2*)z7Y&UtTqRP1*YLn(%Uud+c@Z%j^G_ zH}F?9%2zb$S2WvKwD?!F##glESG3nxbo5vJ@0?%JV9xk=14GaGir)JbeeWy!|5r@l zube1fIZ3~AvVG+g|H`THmDBPor`K2ZnM?63sF=0Ba`yhpIp-_q-mjduyn_2e|2+Px zh4NL4^s5%z|7!XFbEbRMviz#$^;IkSf7J$5tz2KVdVkfL^HojDtJc1+TK~Ul!}H2@ z{MDQEt2f*KZj`Uy5?{S7|M%+f-yPwMvI=i!?Qi_hS1^meK_Q|^YeM7f{f$~1Dwoe| zluTfleZ5g~2P5}_O700vhvqjPSpPGpEp2vwgA51Lp5>Kn9Qg|G%XhT@Soe%^|MDMf z3``muJ{+B2!M&nMbpgYPZIxUz3TC@A%1JP>U1*$jol#)|)Bii`E4e&`xz|^UFfg$> z)XtLs_lUpg%KRVi-&}u5`k={9PAD$Z?rQOGPE^ui5um- zD3BMCW04P9!y(wg*DWRO_Czp2*@HW@=}trvlZpqM3i64YgPR^DJ z)p^L6(tfT>WP-_wjY=A>Y&PEl89rT9a9YBz>^J39fV=N%zEF`fhKHNNS{WzkL>@ex z8qucKRO7o_{~52a<*J6#-TrL+v&+8oMk+2o)it@S{mhAtOY&|l^I`w5Wi#V~o=8;a zVWkIxEleUuqI4zhZB*(=TWus^Fk>Urin|KhC2J-mU3Z$GYIW}2)9NkX1mrY)xXZd1 zFbZf?GjHkHd*Nf8h^1O~>V_wk3A-76*eiE!cbss`DCGPDt-EKtw6=af>##eV^??jW z!Se(GE1hOh%K+zEnNPOPe}Z&pytsc`H{w^T>Vvx90v#a@k7XksH1S6&Bs6n86QAlP zrcmI_XL+Z=p@kUkKip{=Lq&mJW8u}Jn9JAXu-nF@ke|LpZ=y5)BOStybxf@ zT5(XuLyM=d*_kJ2Mp(ZBUqxb@blDF9W_>3?hvpNJDTjCr<|woXs2{j+&DScU^8X?} z^*IGfOil(5WxY5uGS%j!85uGJsTl|^pK0)6!@Lm32S3$?{I#w$%#_qkVd7U;Xq>`h zz;SR6tH&INlnCcJ2I>ut87G%cw>5~sr-PG(`_oD@}8F`bK=nU*so@A|rOG#+xQ z^V=05s?MOeVzxRKcIA?%Tt~!2R?WMx?!&9)!nfF$PS?1S+Bic!B6}k1=UKn> zgbZG2M)>S|btQXq)J@Hng>0(|o*zhj%EdA_F}K?3h*-kwcd>m=T6s}V(|p!V7kRV0 zc^dB(&i_ZIw+AIGD-d1yY69n^ur(9rci$^%Uf0B`peyWi#gswV;L0DN!_~Gjg^sq( zXD%F7j}|Ss!0NbAb^g6BnRjca#${~{S+q+q>ipM3ET7M@cFtlxAYx~-MSxfNLF#pd zPR{0pX|q_HnORvMqz3rj{%Yc!HD|(+D?46!Z41$vf8p)zMYXjuCzEDphpw*_S7{b7 zFxexxC-?p4SGg-s#V$EGe^tN{PLVq)jKWT9#5V}IX`PwAC@cKPhWfvzeLI}k&zyFC z%oWPJV714L0}ay`FaGlOMB?H-J3}-$0$FBr@g^AbE!cE7U{S&CJyp)r!~*k$)LZ95Yj~|L}zC0IW*>%+aOY*d^73|vzJqvO~e41EqFK=6LfSbd3M-pE`S<{u3 zel0wK^1tsa7dAY*lFMnr{M9kbRLe{C81iZshrUVKU@7I3TBev3HDTFOO|_3jy(jN{ zRah?k!f~Ub_!n`}tsUxWEQ#%3S8^~%U7v9MsGqu;#N-tLi}$ML=yEn)>3Dc2Nk?Rb z11sZ!2XP-}h%FFR)b(mn5RQG~vNogte{~y6yi(!@7OoGIm}Iy(e@Qr*?|yi8(xOWt zir)np)Km`oKa0LGV@}xa4#r;2l{GQB9HA{wTG!+yJWf{7T&(&_gQaBA>7_3lULJM! zd^OW1SS0Ar5oX0d{)#pig5OOLI<-Y$y~2j ztCY@|mZUS=;OR5Fs@4|81qUpnihNI|Ns29!ni?DQe_=*I-m?XZ4{@@sty#SyYuAKt zx{g|Ve@&ig$0_XYm@9v)*)ee<6W8=xOBru7zqTv8u+QQk!<<>dZ+x$PQnR@FIo0V( zXN;@}1J7AkZ%>8go3BiW(2(g2Gz*%@l7C?X+Z~0Cjt3j0rln2i-ub3Lgrh0kZ-u|< z7cmFM2~KTG9F{&?VYWj5r2^Bf<1FWUm+!i^SFh~yrv-1WrR~16c7m(9P=n}_&%2-5 z>Q`M}eedf#^*wKD^=lq$-~YMEe9M=;`gNaI-~T($f5o4_`VGuF4_@CYKfq>Z&?J1v zmDPC1K|Z;HMtPlwoWVbK3$8P0GhUPUJa5Ngxqk)~f4LtCPM&c{T+i@-S8&Y(;nh2i z>YdZ?j@NlCx!(Mk$v(qA&NugDKJPeQy>VlIvrbAW-_8?meXsla*E~^Wt~}}Yu5j{v zom46J%2Q!^g;UqBNfk=3JRSG0VETTYXI$--XVS_FW}aV@#k`I?4&0FDJoHy#_k)is4tOhW?PB`; zzlljhgPUztUFR=}xy(029gm1+&2N*ceQ~VUggrnbdr^YJtJw;>71bJKH`hMa$gtK3 zw3c33U_NQJL+bRar=n$o%qOePmsANq8KdFDn%wwNZ{3pfx%YQ&U39?xc5wK+SKFf) z_KBNwHa_6n*l^H!$}v)L3RWpSg|?yMg|eeWlAzd=_A`;S*XEeD(yKsHO zhO`^I*41XHu>YNIUGKf4&GEgW*dm_P49>pw9Uoq5{C`qk_jAGC=X1Bj{aW|>#@(5s zS37J@+}j~@K*1p|gHyO7&ad`r=mWW@N23=noxgAe*YX*j4_+KmWMyQ^QD9eMxT-17 z`0Ti?C-;S#+Yfx_|KHo+izb;=dluRhe`4z}+|vzF&qdJUofd9?spr^`Z9(uO9&o(@rdOOo)xRdopb9 z0+D439yI}i2JS0ruDv;M?py@J!UG3>hqnCUVJPwB5?B%IVeu&L03Uye|N0n({gFwh zWR<)s&WL?Iw#sG7{kJ?{{#;@FdhS`}^=zMoZfCl!dAWQ%S=C>i;@#3UtwijYA*05I zqg;19h0Z7)_^>GUhlo&(GII#y?p}FkiHExr{wsO8ToRke_9=1R*#@O=v0a*WF6%@b z1!Pp5!jAVxCcERwDoYf-?l`=ip~5pEHinVW)|5qv!%^r$vU9@h^@W`(D;^gcs<`&Fc}t0j zaHP7vIKX?xL4c<*D9DAuficvHk@d|1cdPD2k<>u;CF7lp_FTbd9Sp1`*(k%=QvZdnpj zT9V$fWTCWVyJaa#X>oqbQiGnQrY)-!U6xk1tkGpzdfWeJ+d9=U`8YDBsbv+ZW!Wvu zTK6nz+p=sqwVY$m63(gR{#%-R?^)clWqJ1`^S?ct^H;54-^>EO=d(nY7tWI`QhPo_ zSG~AyX0hG#X|Bsl;v`GMo=-U%$tO1RgTZr?I(1W_nPCSQ<#W)l-jppPUh8QT_SC+oP}gP3SI# z6FytHo!4~5v9`{NXy;rb(ks=`>-5;}$qT7-E4RPRaI}~&AIZupvoiCG{{qRC^B2V> zb7?KSw`yV9b&r`ZoqwDxdE30cE-QRPz@rsceO-ijW;~D9SrnS{%1LLkKuc!?w1g-sHF5k{Fym@om z?o79I!|ODLwu$E)7;=ttEpS+V>ecRZ&l!)b-g8e&H6c9CaT@asv5j7>Goscwn)Ge@ z&~WT*_fk0>9^QuYLhQ4frmeVhK;fUrk+kexI}VCvwjF%aaBS17c~vn-=XSP%HczkKVRp}lBM!=D1Z7@>Dp?;M!7>UAuG z_G7J=ZZ>*Pmg&e=yp{9vIuPZ0uta1>W?m`lJJBTH%AISsYE544l0UWW?3obh*Na-V z8MXR}9pPHbzeQ}FtHL^;6{}eVj=3+$o^Ph-F+p8^kG}kZH){{Q|NSg0Dn(u>_VS%a zYcm3pN=s+!NG;l+#d1ijWyyjp51zG7JG`_LU*F?eXRpb7EyevuTWh++OM#HJJ{4WUr;zP;AJ zn1fM3#p{UE`_2P*mRw6=QPeivmM6f^tIVkvA+a%R#evnIUcQ^>mE!bqt=Gn;HK$_V z9`%|SwMgZlNU+QLBd(SQ`80k+UXt)p%w{<>i$VRFu+(v(z?-X&uzfsQ)%iKwTovt!jCcG=rhTSy$b8$c>hbODd6h`y%&!_s1dz5P2 zY`V7R$$Fj;-*a=?oJ3?5fSg`XTy-hYQr|A=wlN9UAyx?N7`QLqyW?VQqGD*mb;9A(6w%U1hRhFH z5rzy`5*U~_bh>z})WoWn+0FFcZ!+0+8Rw5RzQ1*zohm7FQ{&zg-*%$kZ+GMj-o}2B zt^FHH0^XNcshj4>nFRk{7HnTy;I=hHSUoiSYhj#enD+9p{LMn<5$#_~`b;CE zmq*Uu8npUr$}yAZ{WGHXn@(H0HAXu!=DKOuUDLQ?$=LT_C%oJm|9bkSINx~oZOyU= z%=W*R|HCLhrSZd~)FxI2CY|Yt(bpLRn*O*M%4d{l%KyJnVzD%MUvcVU7MT>YCzl%k zKKdB6`1%ev9@YoNbtfGJ{wd~O-i)d>Q*}FyKXP>H!ls}UK(#+mcG3#e|y$?7uWzrtZ(erXyo|TJ;{Usf)!!OA?=qB}fB4MAIica#xAQzQ zPv1Y--fVBt;%*`GVteUIhRNT|pDZ&wr}(}6BrDSbhup+jd<|?(I(p>{Yzx*hR9_2d z*}kKD|Bh0Bi@KkBZN+P~qE|FIElr!jU>V7g8PVQv+>l|-!a9SC_0Io>H%}VMja^os zYk#}$b-F@B<`$3H2X;)2-`NuUqvC*LZ~OMb^v#OQjzS%lODksiZ}1dQVdVca;Z4K= z-W8St3XIL+Cj@TX=CfcA+WoYc<-o(bqm{3vWYsknCR#2O|5bC^qV{6N^vMP@_TS-| zaE~E`QE11+S^AE8Rr!Je4dU9g`G$GxYkH2XT;80iz+GCx!6fmk z#9Qk3O2)Fq4%rTkyS&Ur7#P=tn=d+ly*2yWqWWU-#^=p58u;EEXkOr8wTgj_hrw5( zWA^$6-Ff+fCA+xnJ(v|5-KBSLuU}qYvwMd<$AYc#3pU;_p0RsN|L z$7}~ikp{*Xn`7xU$Lt+ABK{mL-g7+r5A%oSW6})Ar`w!bzUS2XKc~bUIBXn_HrE{8 zZF5GP;l$>eW9RprdvA08`=0av_nbMt=j`=A7jD~}m9D)QUUOXd?|JjRm#uBjx$nKQ z`H$_KrmOLLug-5^46(fy|JPQcL8WA`zylk5arIW?t{GE{ivhPtP#4SFpKR z&~jACF|hfXztqb`nraBiN{>4Bj>JdD1QAcqM_{7Df7bja(*(;S?{lW z$fkd8&(0(FP6U<<_)XmP`StEp_w{>ac7K2W*D2?}|J()EKR%z~?QyR?SmpBl#bf4s z1|_xWbsL|)Zr}TGN9{?;8)ZT@zeOX&>_z{WM>77DeDNT8xu(WUHeS07#e_$m84;Dt zaW59eOJ>awZ||zjcoZYp)gj(0JMYD#2#!@9;?27IGLmC9pFA;K<1!Qud8~OmU+kOyZddT!)9-e3?BssGhxw%U`#sVP3*PVh^X>Kf{S53n9}cjH=X^NG zp}ywBAs+KL9}Ww+|H^jD0tRQ>Xb-!Ft`Rw!MqKEq0FPD6xb4o4;xa)qs5>o!B1hR+SBK|C75Buq4 z&>r^4>fUd+a<<>yd@B!hr=-SV-|u%Tp4V=?TlM|_+wZq({_FmD5X0{G<6(z$ZpEV> zbG#JO)PA4aUEm_-*d0)EN-5^!p^HjC9_-_Pw*1c_VeS1J_Hle`p1+abz+6ylAP9T{3Dqs`p=D5X%WuyY-?LyGdJ*-&2Z!0 zwZ7q9u`*BN3a1~jI(<^r_m6gDxTv)CvDp?ZP>{XREY>nHC3MRo?&AusvnIZ;QE^_N zII+#GTk!+ev(1bO)e9VW`yST2FL7jjSvn93*FCP*1%iHQor_QMhnrK|C`d< z-m=>6xFON!dBLe^L+3w{nfy5gr))|jf&>E_-~I5#KQ6^Bi3bN}GT zzpG%QvdV@Q(UR3n{SwDo^#Ye9MP%sw<_MeGIC*87^s1jdS-#nEjy$!06@%s%uu3_U ztP1%Xe_Cf&=%%STv%^=cIq$W?PO2%e*V-p7%{^I{P9R<_A*y> zf%yr?)wjH#p|W+QeO%YAMY95U()O&XE0&6j{AN-TBzjm}%_mXhmU+sX+O;*AX?{`} zVac1FGu2zCot^gU@czgfioAT^v!@>K$gvPHX!y5rCks8m%YkuGNlPEnc*Rzqm(IUk( z>nCTQt19ok+YXEbEBg1X8|Sud+kW!ih2QpF*U!Ey+x`3QhraE5 zp8LM9y8Zj!k8|7ieLwrYZoRc2s{Fe#bF>xdx_$2Mh}QJ5RX#eVR1;&l8RR{yR^FpZhdz_n)T*{X5U3`+c5u`_D5~ zeFi4B1E1&p{`1^{f7gX}zb}iV|Gx0>-*svFxi8DC|Go_9-*siVT*=)0FRvob@4B}A z+}Cy0e_to?@4j)|@7t!?f8S*I@4mTq-M4MK|Gq8g-+kw~-}has=e{jFzx&?zbKm#< z{`)@exb*{e{~w2>|9$8Xw|OLf{>L%ve;;GRZJwz6|2!3K_kPm;Jw|vg3Gz?Et`z@EcVf0EyD;77!Ez6q z=e3+S>ZM)l_&OS-RT%^{YSl^_7&#i{0~okC8pTi4FfU->Y+zJ7!tiQgL-_Ru36F*@ zmD(@Un@(eY{09cM3yk-l^K&d_kf>w0fXQIM)n5AxD1A`$6MJxv|crD zll{>0Rh&V}fPrxW!}3oI@&%2{jU@gFF!Ddp`e(o(`JqMjKn-_+mZU)~*MS4t4|=;J81CHY4m&O)HG$z@ zg2=xIEj$N6JEi|!V7%GEWTMEZ!O+3XP%qHHaOzS2)$dJVEcKENefO_3NHZ`>Y^eM5 z{|2MR1V(9vu17bzc3PDExX$Quvzb|d@tin=;DiRAf)0TV9se#&IU~-%ozNI7&Y*C; z@XmAwZiQC12mDe3jA~DMzezBui7+TOFbN#s4=d?bw`36bzz{6S#HYZRCOP%bh3>x` z^=nTEF&~(qCc-GUvr#RDAz&sW{{#N23@5%1Q+WgWdo|m*4H*6u)b;$B7+*OtN3!qF zgL=k-Nv}^#TE4q$+ra1@!C)ydC9%EP*1W^xCBLd?9mfWS zR~%D=Uh;=zPK|lq%Q}I9;}L(%%c)&E+y5PCuvsZ6olrOXB|o2oFq=ZXyu#cKnbTEg zOlRCM{m=gkUEB*8Kd8@*_nh%gV{Yfp8UH>oKCloG*PO=lVD{xqC;0~xeG;ASot*j4 zTsuxZPB{0a{n9sv7k15MZsbiW$=X{wL zeJr=;sG0P1Y1SVX?BiBo__Lt?XXng6mD3-s=u>2t?Ft;l})pA7QsjL)CX zvx!)C#6rS7vfj3ffpx+BnK$PLXH91gSC5FQ=Lo3fVVE6qsz>obv%Enax7D(Zmb25P zDl=!5if?EUmsoUYWo_iadSL~|o=C>vn~X zYq+LsJ5H!O*}CmViyY@VHIwy8KWdpb^lBT{aR@M}{9t&eKEsu3J^zNbeM#M)#2NS> z%xZYae=~AT!u4v_4A;vU?P3)T6aQ;$P7-I3FK6(a*e55vQ(l~yR`aqrg(ovBr8=1K2blgu(>cWtt+-e|ph+v=*V)w_3DSMA)rd*A8Gy}x%Kimp6pz2{hU?Skq(r%vxa zxqHt!?mcIJ@44i?$GW@tTJ_!=v-jTGz4y-Tz4x+NBzoVo>U}R}?|Zd- zU&!~Jmw)e@9lqy-_5Nb@{a>p0=cVuev3q~|^8J5)@82b{m%-*h$@BdzH3uTo4{+=` zuwDHC&z}PupC1sgIjFCFuzxz!!T=5_8LkrVg957#6o-SiMh6^H?P0HS;C!?DkmvJ* z$97dJLhXOo>%ZsluDGDucrnAK1zj zxE2+#+xM_L#~caMX?CA;By0N-qd#m3ibqXqjwaO{;f<-BVsN;8g1|h3!&M5$!hMeM zX&r31%~0ZVTxCW%v%?{yJrW5yhgu&TbLBbyZg(k<0;A9c#(XWt*8kq+b~=aWO%RX| zW4dji>a{F}bd zbjKd%0LCMy7$$Zti*G*}Hl@?Bac6-zS4NJ&p8^iA1uQKJ0`oqc)Cpj7xX!@&pg1|A zWYfp^-c^kILA1^*S<3hyA;yY)GxeaE%w5pq^ zQNR`3bM(XnuH1_gJ1$zjIlJjm-Qya@r#a_h+s}zlXy#@(<7IQW{|(og16=J69LN9H za2&hC^;h9!;-2$=&lF#Hvn)bGWMW6F)PY|2$~lj+8Q2Vjo2+I^Tv*NZmtn$*Vy6=m zPfcRnx@N6F!yKuA3m&x{aYmcu-|m?p&3M-1lHjh>GuO0)9l2I;{HS^imvq9J7Kh7U zeJ-~=;F3OY%pWd4uwl5@&LAJq&i-KIdd(i@)>(ol)3; z;ZgW$Gpj3M(Yj?bHq8qv+3a&obV7%!Y=zCAc{BeoW>-vE$y-)%mZ{dRadVT< z)v)aD+!GU){@1L+&kWLd0Eb(iVN2dyj^ys zi{VJ`x?{Brd;$!-3Rh)186H~S6+Hk#>CrxbbuDrW-pZCZ1yW z<$GL4oAHmqU3Lfgsk&D=6m-RJH=l?+EmU`dM{aulY6kJVl4*Z#)h=f;zQeDufw7GD z{;UK1k@t@4FmOA#_i+Y1yW6pDQC7Rh%yn!Rgf8=*Z97>k{o^6~+6SfYiiI22U#XhJ zwc)`<9>&whi`REB73{pi{&z~y|9MA4&Xrf@9Ey9x`1bhyU}OC|GkegPmV>&M}t2myHv+we1A5@0^+9bEIj_n~Zcmod<82 z7#SlSUN#zVZJr>U9drMB-Qgyi6H}M7XYOHKe}MD71J`1McS;7FMGv@t^stM}c&EMR z-J7_|#|Joz0zMQugf%*FE&0I5cAnGW!ux;w_A$tRnEIW&F5$zq39O49xZD{yiVko# zY+w`o|6X|g+r01G%?uppKd=NI;VYWJrex2+^xxpKN&IKC`p*{gKU?kpY;*s!+52K) z|6PmoDwrAASDLWRk@<2o{gYSy9-sSP4*&iV@PBuZ{ny>mU&HEmN6i1adH2_t|GVP; ze_bv8EvbH2O8mFQ+WVCEf6KZ5E${v}x$k?6?7x@9e=n>5UU9$ZoyC_7{{89qzt8*q zz2W@MCi@?4(LdVWZ|a!;BYoG89{<^W|9|Y1_&F)ReoFjL@AKbh=(p`)s^Cf-MOR1}?45{8l^{E<9x1&&X(!^I*C|!+u6KhhN13 z3CA{q`J6L5@|dL!V-XkRBPaifO8QT;qGn*amvM+JvAb}@B_3WEdkO)MUw zG8#V=kF>G#O05I$Vc${s^xV?u>v;#6xxeUj2sq}q^c9OAs?8`k)Yd2Kx*8!b^gYgV?+yW^NmTz<7;NVdD;QZi#!~doOZ0Yp} z5(Le-SQRDWI4&}@wlT3!({eC;;Cz~gLuG+Xz=7vMy#LfbOm0YEe7pPM(SG&#dp5Tj z>b|cl*P8M0HVdm=O@x5bq3}7Zh96G9HFRoT%O#+;P}c#5xh`57j%+HYI@N9^MJ>>o!sEipTBr6oeI?w&wH3%u%0CG1hSvoKfSJUUh00lhT0$^UtIeT~MgAOSmA<-0@~p1A~YU zhcoYHZH?9Zg3dvn$IWIOa1r61mB+y8r0`mSg{#7$k@MZ1kNytKyjza578N<{{q{uY z|E0Bu{%q_}U^iRgz~r?%`t@SQJw_`|HLcUpIIYWlVL=e58b`2h%Lx|^CgZ=3m*oW{ zZ#-ZQ5^T6U|M?S_m45cTRtp+AT$DWmlDbW=={Xfo@?G!pIBLsUH+^eC#=2RluNSvi zd`;h#C|FQ@Izf4p4oeqT)B@#D{-mi+mlIsyFuXgm*EC#|-QkieiN{0_K<>BhiO|=Yyi`0rWXf}HEnS9h-Q@YNYG+-yKQukgO4Hm5Vu^yg9g4I ziW+v&S98CH3jF2Fb6j`AOF{YhkBg-ZXO?gsFszTOo?+kEZMG|->3Z~o`0evjy&HK% zck!n#(OUBV&3$1e~#^$@^7{Mjyc=18{&>zCum%{b=3QEht~2x z>(29^`m&bU;`+)@M}x!fuDc~u@b{}`^OB0Mm(@=6m)$A8UR!!Hbo<;7cbmoQDw3-G zZB;LKhSya-du_C@^2Ormc0Zmk-~O-a4dXhy>UX=p&)f5Mzj%GkCuy$zHD4}2|5x+% zcKQ6uZ}+Fy*WG!%{D0k_?rrw<|9-QV)cQ*@Nqy;ofKS^M>{qf&Xpe>+9StXIvx57zeSwO3HN+KKohXWkP3>plkB=UZA zcb0zkq1NI`BLBtX&hl&@s~tp=1UI_7D61J(d4wbhPd)CU?p9bHFeOQ}GTcEs?PFEQ zl_YW2W3Kvb8zT}#k|qDUxEU||=$0Wecd5v98&f`mnwXEF!8}O~OcVR$eT`1AA1GP# zcLzslLz9EM8PhBQAI_}$SqFOlh0nYjnqab$JAMWeb7S3{j2Pu2?ma&uCR}BB!yvD~ zkrm^zMnHsvxntthDJBON%I30n7bY^Od@O4GUg1&7cR?o5>B2NN1;2pjrzhR@3lfOj z>z=~#A($cP&>35?B~8;Lrx`OhG$j6i$U6Ik_X(rFAyyMDg%wX&AJCG%p#3CvbNIb0 zCpQE=TJ5;(lDw?O0fwkb#fV2Iv^Vkey9zZm)Umit=WR@`*)E0f##xP^C% z27?lF#2Ge;CeckTY8w1AeA4?qG5GJ(Fx|nl(%x)A1N$7;Y(9>z+9M?bytyvE_UZKY20=9^VG*v4xANxaBh{F zgSUPc16STM{YCU~iDgo+c+JD2P7_eYl-xehxp83<^G6nZnMn=LClU~|`wui~PHhrya_Z~R*rLKbj-=-PfM zi-DKr-5HMB&Gt=SC-w9$VF+x#UK{XgDx=E*zfF&loy32gW(e4(q*|)MqLa0*%I=iS z^T{1;>MpvGSJ$SndwQkcVqVXCW=DUlVP^H7o`>Ig9M1fkvDxuN>$5UOkoH zNd_|umj)JO&P}a4TYgkX^Wy9L%@3xWQ0s#ajN|g{Kkx!ywa=lr{-s6^yi)AAAb1wLO zmdRkQwV=$>HpdE zo?Yio{+T8`&+GiDI|~yh+C1d3m;3hd38VAGtuxO=PB?6Exo=u$+J+OCO0u10O||cq zH3j@HouQc+dne~zRoQ;KZ9%_hxp{09xOvNLSMwi{nT_+ZTnq}9yBaf0o_hHAfrEMJ zb+3-)UwY%7!|gw%;l0Jg#fxpZU-kqX-n;yB*Yf~|?YFiu{@`Pm(JTnwytaDB z$qQy%jqNNSulecxo;aUdLY1VCpTf<|1e0k`*?S zIyfyGlnfhIG3Z=7p~l+56}P4@-lSoL!|cTg#|}QQ|1NcGW^*TtfL{M^(|BXan$3Kh zv=4kpSo|@eA+fPx+st`)tvH@4aPzF#xQ=mxz)ANNla|%~>AF+Uz{xSO{>(bg42AN(70v}LjlA8%L_fq*Vyw|c%0j~yz2-z+X0z~!V~KX1t+*L zK9%6=+9S-9(ZE*G@8YAN;yowfq5jTp<;9XaKP9?8hoV~hUscygEwQk9&4~`7;E;R5cbTjN& z!~bI${|g4r8v8M4 zFym#k{=PFxN!1NA114=eEikX5e}l&E11mIRZZfPl=ydElqouTMfpu@iMpwoFi{=}a zA`Ak&AEf;sdtTyEzHHJv@tZ+{RwvBp<(m+zG)OP)uZZT+ks z$=OqIaXpuT#p5eVPh2*8-a5!r+4ubd!`x2W89!X--e};>n0|51Ih9vCuk7%s6X;x} z(0$Hh*QSH~n%R8RafHR??sB_}cLk&C<(^7wP zB|bW@R&=T`%bauTxxY63lP2!u-m1jqx?=UKfRkqe1fQPg*S)w;B~ZlloN(+#!O}p9 zP!7@FiyLzSrAn2g_Xet-3Y0Zfl6xDd_$yH1tD>T8kgQga@>WF^-yq4TAhliv_1a5p zT|t_$3R-I~G4Be}nYvi=Zjj-nApNiM2E4)lB!i9iF4VKVEMCj~zi`sB4h9|5uNPy;%Y!9Kq7zIU%I{t`0xO-O*Ke4uRTd(F_`*!d>DSLK{S!|pB$tqoP}4UNnV zidq}0u{ShkZcyyqQ0=#&@n?e)c*FEmuU(P}OBM}I@x5jg82j}IvQCtzR!iO3zmEPUpdiToIyP{dKw?!3ioV_bF ziBYz|{lCDXySKJRpH7Xw^YkwNt=K2LijoZsyDlH$7rNI}(BP=Fe*4}=MUDo=2u9J2 z+mBtN-;_o_-Fizgp!p;(0}n?N_o}A%z0sYEcE9OIOBV%%fuX5(LZeJXBbO7SB3qMmLA2yD zYpH4X@9j)Ha4GTswx#$}RC&P2wxB_jp~-Atl$u<$dRnwb-on}s@uCt; zDh>@w2h`*O?rY6s6xq?p=63s}Y%-@zfCxRb6ac>g;e=+a?x?+MSlCkhqMGUh#7vo>wr-e-b-PgmeqtdW7^iRcc-j$`*?pWXVe6~K@)sUhxOJ5NSC-=w&zHXok6inLIF}+wT z`eM`BG_gsHauqMRCNZikX#IaRsif`pq5=lV07jM>jCWqVNS9PSVbVC^T6*0&g-o+d zF{jM6p_!XfGsR{#s!V7)Iki#MgXw{5qe=lY%Z0}FyxUw20;SA7o$C~mN|4BlS+rf^|f z?8Ug;y{7XRU*z1_w&3WxTyCYXLWfm=jHKr<-OYX?)A33H}~?M*To3`$os&T|4}aglV1L3yZkSH`CsGmzvbnB zugm|@m;ZBK{;zfUzvbS)I?zyiBmdvK{Qv**8TbnrYrycyWYrC&TLj*%(K2}`m%<9e4p9ln^Ye(>d!CGZ1^I1 zf>D^GP?MvnHlj)E#Aoh&28Do-2 z@B5H?10nB*jwc*-9u2$b^VGsszd)9x?e z&Uh+1qcMB_2U!Lt=lKPe_v6C$f3P-Z%sO76aUdzYzeM_bDQ9|F`TP&k2`>WcOT_EZoFZuJF~ z$3IxRH>6B2@RDxu`u~Id{WH_$#XQ#;c_bQI)GOJ|8{5sk`PG*xIF$CqGjiYfdb6*B z?L?zwK_$C8qfHxA)Q(R*BKT998Ln$e(; z(7?N#A^m&-8%NVY^REgGOuXUDj@Jt`IGPg96=>{eEI9sEi{o+J`R`2TjXs{gS@%a8 zlviH(Uo*e|Lc1H_qF!F)8GFQxPFsme)a#f22qcu|0)rUY#9YS zC!YQ?r>agF>3d9(DV?!Vd2mpzSdlC-E{zy9}Wd|gWVhuHq=|7}c<(+gHVyZ4{( z(T4b6G1LCCUa!6Py_REpgF@Xug$+NMj{p4|#;9P?DBQp(v7uhY;vXYB2aACp%Z>B` z`DS7DGMPK=P9786gw*9kI36n<>vm-ec%yNVWpWR5t3l#}k53LNsJnErBr&GC%QE|# zZJF@oNY``&^-~RsE^RK{9)5Rja2B87RFmml#KYk7(6!a0!RRkX%Tgx=j)W+U2F7O> zl$-?=B#MMPyH>H~U%Hd7c>I(CZ~B}E3n#BUAj}i+;q7YYT;~buZ6B6iXgGdqe*^D- zr3Dwv*?ClI18ZXUdWWH?EnkSa&uQz#|u;@+H&Bare%;ROdpu2q)>lsVFTYET;v^d^UU&3(C+UBd{1Sb)BnO#rvUTLW} z=p3C_?s0gwu~+l5Q#B@K&C5?O=R243`rsq=RcH3?+V`+>Z)NEu)?I)`*nxwN0;Ai{oOI0rDAEPdt6Q4#ZZ^#tTiSN)9rSqPY7_CY7yUQ zKJnTGUUd<{MS)umeu#J0p0lB)VSk{?1OXA&f{7EYpKcB5v*hT!XUDPTNPr#G8y@A8!EW)>9ZbZUg(y&^QoQ8wJ#xlNqz2VwN`u+k_C0PNksX6I66~g zMMEHipeys7n+KAr{YxbsD&UeeD*RI?-Q)|wRlQW~7HhG#H@ICbEWxTV|iYvMc>6uiIl{nFXDb}ij1 z{n|I{N>!+!$BLdKGYvul|L6KLS6yl7`?D$Zt}F8y1(vCf9EFD`8GPWryfoTwkC71b zQ_1@?W~9wB;b~90ZrHPG)#bo9&43OFX!=5Ddf@LCc#gC3Er|IX>06^n_9_CqK`N@EvEaT+m=Vad2kK z1|hj0{*DGWPE6A|z@WEIg>&z0ah^G?GP;Xg*m)8f_}UJ&C|(Klw4M;lP?lh`RUv_W z-k&Gx!Oac&WkF@#C!bvMQfZc5Vq_}D!N4lP(8^=Q`+utNopxo#Os&&Xj4FFfx_EYc z-5D*kdB!mtHKWNZeNq<*Semc6VYFEDY-Z5rS=U&6jMuL`n_IMb_Psf2X8SeI6;9eb z=b6uK%gZazm2NVg`7lS_=CS7a%0-jsecPjMclPJGnopbO|1)`J_h0iuqfp7jcpVKV z;Z*?=e~T9K)n?c;b6xBVDw!*^R>RHs)!FW>EsN!*s=3>1U7C2$Xo-rgrdMs{g(;i1 zEX^`p>7A?z8RU#}I?!B)D$%{L>ke^p4MTIou^v+2R} zwXRmWm97d)O%GkaDzx@m$?CYR>0#5uPbTh7iBt_*5Xsyb#=7dNZu0-DqGv9>GTAwA zLlkGp{pirBGly9e*Q;N2NvH2zhSxYn=I%egG3N93rPfJDu5 z6OX_XoGlBED^+}J@s#i=Xk8s+`tAseQaL{`}?n66s?cttS(QQEWF}j(Z5pF?OI|oCc4YA z<-7axHtk;eR*m1#K}n3qY4?mze&rFdzPvF4n+?B5_P4Y6oKove+Oa#r!DidsH~)PX zL@Ldk)6k;k70|faTCh-ahYOG0!e)La@tVZ;L(lgXd~({veAi<~OR9>W;EW6Iahl#r zJDfgphP<)c^+tDZlJ+M*o}9Zrsy8ytkEQi}pQyaI;?$nwF6(Q`7H>}4$#3L&jI*=H+BThd+i&woBzzw9|vOW9D5&semebG#JX6QPxlZW3zzz2hB@(-r;t**D$SJlbBp(7`+AAC;aAh%QW zgNBPnn}hGAz7>)=&iv3Z?#rFG5#qvoXSqp<|9ot9W=@5nguRE8(%(xWyZw#{y_8w- zdrztqf6P@@^YtRdwx3&`zg}b0Uv?znfp*-c4M76Szxma@6I1-#c_WCe;IoQSvvH%W z>e7EFPSx7AyylUzOiY{hBF()x>6}@`jZgYVV*ltUX1sX4FZ#P(%-yne0(UbxEe~G0 zHt)-1XF2F?Hj0d|erW7FOHCag%kwmL-e%=)x54rvQseByg>dH%Gu zFUvoP*xI*n7HnIbcf_JuX2$JD)Az{Gi*9Sy3msj>#X}RU@yKzx~e>apprx z8zx+jxE3=*mgVdLhg~aMI~f_7)D(mYqQx4ewEHgjq4TGX9+y2t)pAT}x+&R8|f#8#~q2CU+a9x@2 z_+^3i!$~?n<=%Zsh+?=ujUn+Yr=rN4{|DAG+S zr=+oIp+=vQ)U-vK(-gIqB}uGP(yp4Sy>C(2(M38@qPo`>h1^xrvzn^+Zc)(JMfy^r z2G_1e{gt{Zcp^q|vGzt$JK5xyg-PxKhva31xjr zFgWmIqkxl|3ir(WpG=fgdk%2;BpaR-mx?`d&ybaMO-t9-c{^>y_igRT5?$hSVCIvj z*F9G_FeJ7(sT{N@i@U)gw?}4H)SHOIK1Z50{5szp5ONUne|6!1U{aO_ldQuNjt`E8 zD-Rs6j5+pZUSQ6Jf+rk2OOhwN;9zE8WnyW(6v6iD$IJx=3SJXfSSK9iGjMp}8+Kzs zoR85G_ct?kpFQfez^mqrga5fyZ#7XF9@Rjxr$%qZe>0w+>2ZJF+3?Jilon5yZJF1i zd)OB~xGTZP<{8so{qzB6>sg*|)52l$@;cMyFO5c}?7>@(Fs zsi&1!6WB|+@9mAunsC=8g@?Dl`|TgJ}3v)>k1ci(D7$m!;@tzh0#7_= z9PoV+GmCR>^0|4=Yg)ozoeFMPcI~B0VOZ;{Bd3=MuofRv^|yK`;Pc#$g)uWqf=xuZ zS#1JW#`7|#6iE|}*02nwm+pLj=Cwv?FwODwNO{p_rg7w@L&r3Y5221Jf97$v1PWxV z=$s|$`Jy4n&7*r?29xJ;UXQr=$O8;2XL4kdeW_(Y|O!L+_%i0&6B55HeXg zS+7x;>1c-4%F<~oI7J*7B^svXX-+SDIgRU}X3NSMeJf|At(-njbJntzj5Z9)22Hc} zY0heMm~GTF^HAn2F^AdLR?dC5lJUXH*?F1sKE0gzOVeIN%RYxe`9t%9c`N7G9hjx| zYT74eWs6sfHoav2p*ek1CWl1B5fCuau+2F0H&tjwSr z?$9R^Hn49!ge0b>Gx>&qV&h*wnhW+#97JAL~+M0Eh zm(@xl^wd`E>n#3DpKbBlw&vz$LFbIGr?#za+-UP~5onX%6<)S&+wPwJdbnwW?bBPb zK06Ff@CttU6uZWx#s6TNviH0m0mH|qV`YyoJ+sp|{qzjy@Er>x)l<%|2%hY#u&b2) zfQJ9-dA`-po3HH3BR^~e5@GBUA}jAa`BT3i^ZRa*X(@3WIo^f ze&3yj(mS$Sg8z%0-)r-hS9ag_e-3|CDnEa)Z4c7T&4OtfS8GI)((boPwRuRi%Nl)K*sQe7L%dV< zQ>l`t#3~JCPn~Tyrm_7!yFxis_|}f;ts3t%R03Z9yb;!Hz_&8R-&ykIRaTZRk;jul zte(uB9II>a)G0dbrK*!+NP8-)zS_^HGuv4$o&|c$>U=g^OmssUYt%jV=W~TW2CGbS zXzF-AUm*5n%G@%htQSsyR=j-7n#)%CVljuULRu$h$;k;zC-CiTWSgw_vrR6BCk)X9|<+}e`+AHVkZD&}s@zgG@uxh>XMy9}Lt6r~CbP0H!Dk~!J z%K0?k>g0f~q-!A?7FfN`3E(U#c#|RS;&3_iTGZ@74px-~4lLX{3X>W6uegM5eyFwb z;+~9y((m^MJzA|F`|aK9_bz|`X>V|5 zPqF3OnKH>gFXHJm;kL?WWx3}p_s{QsRrw-4_rbxJE0(YO^=i%bcfVe50ADnFJ@5D1 z9naVOez)iQJL}g9AD9n(I3k|^=i>?W^?yE{F@OK(^96VPzhAC|=l}hBBYl1Cr@)-| zf4@Iy*Z=q9$@Khx-|x;}|L@nE?eG8n{@^~p?$4L&`Tzg^cy9ma&#&+A|Nm#;S@7wH z`T-`X4-FhT@{GbK4zO8$XuQ+BfJNQIky&p;lf;YFlR#+3q?(A_$Xx4-Z zWt)Xu;T;D>HhpL{@L9l}>~UD?TYj6_nT34!+YihB`q2LPwW7fPc8?>kIX8BA_#}Pq zTX96qs<7U_XORfI#Zj%OiJf6*l6W_+II6d4LU$ZbGUq{!V@6dIdeVH7Sud_QW@a~` zH?K#T>7mAPE4B%JWoH)u`>^7;-L?MyI-VuJ88lBg&Fi1g=CkC-1q~+Gus|074UPQQ z)SEm{ov%`|a1wi=-sD#%R2^Bt_$_XOC&M0xDSbNg_%kjrOQjr~^mO+j$$+0W25Fb% zbvxqKq7R%Fi+RY}J7*q8#shxoi1Tc=0?Z6G3T<){##0YRFSTFxvsIZvVaCJF2TlGm zo>mQdKW%5ve3PRN=j0v;&AuR=X4rqT*;r@u%=0;mI5vG~QNHnc-nSYju?v>}Z8P&O z%g^1>#QS5Vt@NCO^2smMK5yVS_sjD$tJwxeu_F)oqu7LJ+s#N(*7rKo_={mN*WPDt zo$Ws5Mwe&$=Q#2oJ8)^*t}X203L0Pj{PeH?vVe8bl$VBzRVQDhZkqb7`o#yA0@nH? zA`E632gQ#Sv}RmlSf2dDiMwZ|pR`RQn|sC*-V6ukk~E`L$rVjuw}1LgN%LP6>9IQM zvF5?Vx`Q(m_o^9X-t^5myuX81r2naF$y(p$KU|U^R^?zLHR1Etqz=u% z{;0hj|I;3Fga@p${4W{pnYNxa=|ooi;_{YEt;OsoeDjLDEA2An92dDetW$}8(63xG zWyX4!cXqA|A`;5JELp!qFSd5WmF#ujHmT}nXRa@gib5Es zIx}P2g+6obxT+JRxvHl=&tm5JGjH0gyFyyR%=R5^PEjwb2@qZ^G4KBB4{Y%VZav&7 zwrxGDPWNp0kgj{8{}1R^Ed8!zbLXXC<=JQ2Ym&?}o71j+KXjzJDbTX!tl^z2@@G`# zru>|JMz~+>%>Bc~-I1>XB~M3OI#c|?{=C8kh3jEe2`Nhp9;%0UD1F(r;O`1eZOyx0 zeG3cGv^!kqM96svp>U(XNS)HC^v6^ZdHr1#^&?!5zoYr97u5X5?;n^ zdVlrwy%%09?moCmF3YWRd6u_b*v!fQTb`ci{NCE3uK#qZ$<8wiJwJ(ce0_W*F3mr@ z;#1e`?JH!KSAH$vvv=Y8aLDfaL6hSOU-b7C%;P-&NptqQl2y^Q6T;8``qi`U>$=4k zUMKL&Uq3wW>&D4-x1aPzEj$WOXbH3b?7F&2L3${V4L*( z_dJ4b(wp%9xBKFNrc=@XKFv742fRtI`rqdTpiO%Izb?%NZ_;}ee!lAL_Js@@4h`T< zdbj_5+i`yHyYlnD@BRMw{Q&>I5AFVc9!dZIal(J!Tjl@;9+?L}FZA#GvfTggtLXo~ zZk*ruZTtDZ@2da*e!#!~|BvJT|31y0|BKb(0K>KO|Gw@1|Mx@x{y)$C|NpxE|KE@E z`~Q7E|Nr0b|Nj{z8kjihUw%)+e$i}1qgX|wgoQ8b4AhHeUo=QZG-=Fef?hOR5w8BD z$;hJFB%;~uMU!DgGwh<-G|L~&kc(y&BwE}c7tL}sb6jW%kZ29kXz{RU1zj|2(G)rZ ze9oUGxXy5j|nQ;Omvp|zSM{Dg5 z##)bdbBT7@1-klyjP@Oj`~?gO9ZZV1Fyjhi3^j3JUUt!`c%J-7gp( zTJ|#^;Q#l6UsPe1uc5D`02BL3{`23ba5PN+qdEI_$bx?Y3{nbx_dRE8ILxly!MLwt z#u3Rzd&w!x1#=|?7$l}(HHcRzkqYeWtAn24oi3}7^E&R_Pmhw;+mJ%vHZ!-j;|io z7b@p7H_QuYV%Ta`U9P!|XTtp7naeJ#2r~*W@fuXGidc|0lW|M<^2;HN*MBgyc6JDF zU|>>M+@Q7MvdA)duBnO(>cV!e(3M>K_~mrw2dgGpF;0HPKQ)SBde=%GkFL2@bN>}E z{FD6xz~C)4@6Evu?rz5VDhAOB)4reVoaHf_=K%x1 z^u~Ku%MNEP)Qp<>FM%QN6~C}Sr^YS@)&m_p5BeuW^`zaHw#8!BzltUQ3>cpMXIkCz zYpv|5jwq*fb(wwAA6kSHCd#hr3XtmlBd~Z@#>!8#R&wuP_-MtTVd<-MlacXI$6u?t z|7R`wcBpPeJ z5tzuKuzfnm7Ji3`QUcq%GTX|!7}xHY$6BCmw6T4T1e0Pzr^W7#3oNI-J;a~CvG31= z&K)xtq#Al#qPv5nxBn67di!gsl*D@9*$sspopYmteSYrx|D#)=fYg7pmzV4g zSzWj4N5B5B{{6dm^BL?hxxM~qZ{qA^rU|JvtO9NxnLJZObgeXgEAHj z3r`&k-*HgE=FlgtLn<|gc4i#X*mJ08#UY(PhcaIrGO#(Eq;c4!=5S=jVT(P7vws}0 z`E%Ix&0zwKN?nZG-A%ts69tx?i>x} zIT|E$OpD`KQp~Zaj3a4tjs^D|%er$c`OL9And9j?$4l-srNkVsuvuR)=XlMV$<=p` zH~eWRmpRdr(^emIq9doF`Tvc7fKMGwy9F{hWUVP7-nG&9F(Q`OV*5o}xk zfc6xYe_-3%b9&dDGyjgCzWwC1EXSE;f7oUO2rMmNpVo8o#F{g==G33bIpG|^vHTAk zry|$&nvxY`x2cr5^LE?jWo{dESZfJ^(Tr?{M~JNo*{>mAo$w=-1hPH(iG z_Ii3>@=V6Y*{k^`T-vx|Zdc}wjoa(aw==cL*89mYh$l>YlG$VR_AF1uWzh%S%o`Z~ z-fUc&bK&m;t~CW5j(@MU7jSr8;QBk^!it#df1j&&Otm-n#mxLapjx!$IWiT}Xrm6p>dSF8|kU{rc} zko5rr--ix?1%0o}*KEICCv;&N_qp52mebGQ)_wiseoE2J58p3&&$*f$JLjqFTHVN7 z^Y=EqhHWm)&*l7B%Q;=(@!x{`KhM_vo+~2V$6)hr+ER_XQG4rz7BD0{WJt-JbmGSY z{)Q*7pPyo!(C25^yTA8g-bo>kQw$|e^$K#2&RR4kpl!R`Wjoxc8YE z-zA-Sk6&+peEaMj{f%`Ws-`G=t>ymL!s+)^>|`DLh5jjP`(AWx3Guth|Nq0DdkY_I z*zxoW`)O$%#-~<{`X3qN<~D9Qwypv%ly7#!Oo2l$J z!s5U>n;6#@zS#MTL7;%)UI!E3hdF(A^%Lej`hTKV zVFlCjJN$ADta)dj%@Sah+rU_!aLVIuo!cr#sS6Cp>!#;w_I-_JloDVRJit&slYz5g z!GRg?{4$?=^SuzL-0-!V>D@oZR~(GD_tw35SCcY{L5N{))sGho-<%P*V0ik6ZP{gh zqjfBe1|RclIsZ1W25xveqv!nmH>W-x@05FAr+B_jS-)P@zCJeY1Xq3I@rIL+daSO> z-JktmfYtN@|DuMoKY5ns-n{Vo_-XC@My3Va({;}M{UEsRz_Ts6!t)wVt~&FX_wDC1 z^O|O!W3uxV5LwT%^8pvj#xFV!ul(kp`{UE{%;77`MwZ0}9~%w0c25xY?!O;@{=(fq zpSk&Y?e>DU8y<4tN|)zQn!r2f16NTmyUhP@3G&|q*EiW3@ar1&`qu&7SO)4P2LU z^U_oA8HUQMrbJ$T<~zr-lyAw-%g_B6IJRoNvApskaEYg{Sm>=QFN0Tb&U9NEb@kQx zmEl|So^}~!hi^zc8uZ8N+MCENHnz)BZ;NFxuw|=HD~-PX?i8D#p3V=0hBYy*!gf5< z7;YrC9h6r|`0)D!bMpc|tvgHC2pnW;WYRPF@jAe{gNaq{!)%#}_nR*)7PqlUSYUF5 zOVTcELPNlz|INpymhw;W2`G5p#wjGQ<^VtQLB^fJZ56zY_gk8FPIfn6QS+drNlHZH z$h-5q%3rdD1%H?O`}_OdSbmcg4F_ynxEP;FxoOD!WV^|1)Dfm2-^h4^D{0*Vb}I>m zCVmr*ga!tsiUkLl_XInb)f;#)m~yQ9?XZRAjIIFQJRe>+u*$_KIMu5N@E-Vb$ZA0|>!c7PPj;0>-3q_ik`kEDX>l+z zv2k5EF#kW3O2&ak7LJEZjCL^!&5S%64UKGa5_t_wdLPsq1VkJjGS|&Iqrk!$;Gw{> zgU92*=|&}mvyH5Nr~fxJaz8yR?Cr>ZB8ZWV?ZD**VburfJGrG34!G7!Jy2)nQQ~Oa zr?fJw;H0YY9L~^YAq@v6&NU_pes-0oH`_EjMKmz7sd*$Yvz(GSzw_~*nVT+I{N2g0 zy^*KmK?A$pj=uS~wpeR4MgJ9i(D+g2#)6=_HKEhwSuV-&`zx5rGBg)1W%<&~u4H3$ zGh}O2=t+(#C8CSe*?$?E)U|zfk71o;QgXD};Ys^^0ilddXD)f^tcbCAYQk_w#hE*S zxoz|9a|<}O$-H2+{Ogz)!~VmwqOtX=EQf#7omK@F1x_c0W87M2r?0L}EnaY+^dln;0J)6Rgt=|A3_qql zTmH;fHR09*W)+VHdETObJ6F{jyEJ4hmUXi7vU+;tf#nksrp*gh{k<7%r{{R?#(WO0 z1-Iri7s;+juRo%${K2 zWS%_>5AuFnzN196qM?zYi045AH$(KCS4>`~|G2W49LO!Ye%m?0sou*)&c9K}pkd?r zlVY-Om-PGZYv5@0F8``vZo8m?FED09v%rM;4e}d`!>4A1iJtY`pye@T53Al8O$L`5 zDTmVq;cJ_hyfoGLA&|jhv{S*B!G&|ec}5@6|BFRU6EVaDaJ3U)uii*^ZHZ}-$$9(ZS$ChwRM=Yh=eh(K@!&kS2g$LSQ3>W#bZ7}BA z7})RSILl{FaD(2vzzNPTlYQP<9CuU`Qg01OQLaDc;qF#sK4D6V>RTsI?=&Ob8CO!& zj~?>$Z!6MXAd;%N(#b1$*(S{uA*tHEhrGg%32B#3N!6Xq261|EgL z8HZKU40|hmQq+QHo=!x3CzVFGipp2Wo1@Au1eyH+X z|9GTdNtN;3*K1Ci-TdiyaIWyY&s#dIUsj%Px*v*?G96Awb}f^2nzHW9cco7LdlD%ia-}U#vLU9irm1m7nLZ15;(_;G zO(uVMmbe#(ift@W6W5SqU<}^dS94Bc_Y)RlD~=ZrD(@^lc7*NI{|;sWhagFXh8H)w zYgjAa2+fsno58na_2Jdw5!UWu6K7mi60ex;C~%>lVP~njQln#?SK5s&OVtjn=1ta1@v*+vY|^k@^u$dTkp&Flp3jUpH)uW6 zQiy-fa4d*3;6N8&Kw}E)0%pb^4UA?5j5VeSjDI^E+5*@bSzHcybG&e1N=p}Dvr%Kb zkT|c#cGHSQdI_tGtv;-0Yii(L;J_%ak!7#9A!PMOQ;$QAml?DI7?$s~IFj(+d*g<4 zRRT*QU&b(MG~~Q7ZV+&32rgcFjajDQ$n!U6625#o&BjoxEA@`O;q)oP2PWACTP9Atvu8G(v09_bffnXl{$s>)nonDXs7+m%~athZG%?I=8Or)ueoeP*{5zD&OE%Q){HLxPpVG49aW z#tS~w?Vk~`v`mzXuaatvT*F!BNbC#|y6qwLur`mS$ z+O}fe0}IxK>1X^-X6n(q(zHwWX4R#lrKXdDSBA6gPLJ)`zmNCv;a>~Gt~$Pc{3CCs z`!`>W7jZt1G^=$5!_x|Egr5}1byl;ql;>qnjaX)V*r9WJ^T*#-yDVfA9CrU|R6h8H znem-e$CTI1LiK3FT)ysw&@&92hBGsdvNKl)NPouldcj9H3GwZmb{RM^ttR1#Z z2jsIp>aicNy{pV|Pk8@Ms|g)18JH#Z-PtA0@MXq@;C=Z~QY${QKYlq)Gjf%tBjaoB z-JB8(uRn5EMenXln6T%Umeb9HuGvDLt8KnmTQo=Pzp1@5W#+EO5)2(DCzx4IJ$%}J z)nWZ79~`BEwKy%DnlFebOxz^IIO+eH?rrQAi|QH}&SyJZs6HUw?ci!`JJ-8|=S%y= zu0Ey-wvQVpb0~DM-Cz*Wa1>j)_TFr5wGtWeC#z2zcSy2q&oA0JF>zI$fw)Xc-y@47 z@<(Kj%$&DHd-FSI=WA87*dLg`H<-$GV6roh#jfT8B&Q}WQH?qCJ!LvvVg%V%^m-rZuzMs} z^KjzU3;q8$S>~^7SSr=PcSDQq1p|+V;rjT$?A{ zdy#=B>-G$@Xa?hIfhOVU>>QF=mFtRU>+5b2o2exsa8TT{MaJUc$%_%kxunE07)|D} z2sS1h+1KSI@`aDvc~h7OhmZBVR%2uC&C?q8aI|geI4ISz^03wN145>!8aNJ4oZ;Lv z!Li{W$ApvFJR(1)v;Am@o7`~0Ya>60%E1p(hn^f?@o>$ADOL*FJ+B)Wj&9&%X<&FN z&~vtDuZZ*HrV~C2oDFANJlEBnc|C!lbc(NnqHlje!@&lR-K#qO?_JYzYNcp==8E1A z4x1(Xx-1%a3>bb3Yd(qLcq+sFY|gnkJ63Mq;rC{ZYV9nBp4Wb{uatQh&a|BH{`kc& z>X6a=C&8n|aPT==~=B4%ONu0FOIhCj9}X-H(?_0VS( zSR)}hgF}#k_rUBopZZ(`<;$$MPZSh)(Z1l}B=xOFY{l)FS1*{J5ID5mk#U!g)ajKC zyf@CnEf*zX&r7<>UwhepO+ao&@d=;o zgBCnH#0y;&OI0MNPVWCH?j+2>x?#@Q%GsKX((H+XubQW6$_BOnxUgxW`58vh{~tQH zZ9nL=z2*}00=@OS78@e)TvshL-aJ}i*sT{S^$*(6~≥O?D=bW-uml&yI-^A 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�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%0A>8Nh*|7981R9*c}reAA0E|JEr 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�K=kMhDUrG7%n<(QRC5QV32BP;K?v#`agjo z(7-I> zu#3Y%EBR+(<5U*~76Au`QrQpvkK7nod3L;Hkh$>SGneSzKMk)KOeQo;ESkdjr-Ok_ zYC<%l1;ZYm8%x-JOl;s;ps;4C>;Y!30}LV>3Cy$KFti93H1cjKQ`=qZz@*1;rCwwK z6RW}sCb5Le^)(Jh`B$iNJvqI&{zC?X@QH@Rjc$=MkBGE!d@!_ETM)VT6~})j#cLNC zgcjVbH;_2e|MfVdoWV5?8wUofBnJ+O2mL%Blo=YMPsZ@_+^bJ$kQc}>a2Ni-@9_IE zv+Gy)W_AMu-Z$5Z&hqNp2P{}@n;go__FzlaTd&DXe-^J`_h4Wat8RE>m9SW5s>2#? zgT{luF3C%DtccqCrk~NlfoaaY{F%v@5+2(rFf&IVV7~V1GBLCFfv84OHq zy29bdr(P0#Vag=e5Mf!iVCjr=nKw&X8n`@a7%crtc2DD-Jj3V!Bi{jr<%@0KPQ7q* zjn03@O>?Dff|z&QdCD!(kbgYa(s}b&`R;p5W;ho#nw>7(qF?@V^_#!^HM6&EspFgT z^uU}3_9x$-=^O6wR+`1gs_w+vsy@K0OPTLOZCN{8e#98$1KeF03s&mVI&D^DK zCbxN~F-+L|qF;wyk0EWV<*xm!Zs+pfxw~)aZ~KnCFH>87T`e!yT+VF3xbd0`qhHwu zW-9^4-lT@TY!ybOiUkb30XwP~6HL8#t^T;ld(O?jttW5Aa=)A^VR2Rae>=0&g+^J! zhDA>QGMJrm-p$tkCBHkoXv^I@B`)H>H#Ymf-OE^0^5Al{Dx*N=m+H8j>6ck1{kx|9 z?d{8pZ+=TpdYJ6+el>AFiU;31v^FXoLSB7}ge8CTlTm=8>WBAi->GCQAMn=I0P5%Xor?&S8H8byK%~P%fr)D+t|xWJaS^&+NH!JDm2kMvYEKsYeoe{>8-cj_z%xii^<%{;I z99;`M%I3XjpL)gJSJ}NdBF8+@&vYV#^oGFaDH2mWIz>LWxjzg%S=PNuvD;)~*8vZy zLlHgu3wuu7kUq7eXXnM9a}m-PBziX+_FnlRb*-Xz^~By=GolmqNl7pA5LaHu6rO<_#qHAvg7(PHqnki{k z3xD#sMy8|LbMWV?NH?*9vO{}#;F-8lP6rSP+v8EgW9K4!Dl zZcksFSC+;(84`|G%4O_i5I?ZgYGI3is_>yHOgS%wW)v(l-pHuGkUMeZqP$%~1zp9@H5ng? zFkR8ffuQAFRY%&pK`1o|CRFtsVgN3Q5HtHVa zpSFO->wr+!E1@X`!oJgEI1R#dttvEEr}JN!@3(xL)PWT42mHO_pd&&$zw#@FGD>Yv zk1kJ0Yt57mU?@@D5V@Nnb?Y|wh-SCyEh{?P*#j6IPYDZsSh{G{WLc?odK($n6>!Ad z-fCR9?ndec_SgJ|&oTl+mlUOi_bgXt3W(=et*r5Z@lI%)QF-z4a7LXQ9lQaIL0_~a zo|y@(&JwsVkD0mX;FIis8)EGr@^=XDTox|8;Iz{=#6JXsy6J{qquzEy4*Xu)E#@u!dACr-0gnG0xPE(ch@BAt$@<=n;xJJ&wg`TxQGSZR)RANXbWSf(62;9_}5|04e%0S=vohyEYf`XYRK zrlW7f>BF{rg!E!89D2kqeqp<_a<8r+$KnS<^1u1xXKz%Tb0lQrk-rZZ12-JBK6ALe zfmQAU|Dp#%RSgIFTaE^N^w!)Zc;lp9Hk!yt(mu$@pGsUv3KO%=j_Lg7Z55CCn z_lLi6!C~z}&h%G|_y6A%dOY*^x33L%ug*&;U7+=2-h|gxrcd`&%k0m*DU`K~QI3Hr zqJUGoac>zL)1o{4Ur!6AHXQglvyE{BgTa;#9l_JW0+F%|&awh2cS;$T?mQXXUAG|m zn2Oc`orhd1ReX;8^&XM7{zBSq8?MF2>;h%I9UiUa)Y_5OqkHwC4PrKS9e1xTHUD6)CXK z*}xXrps+-N>Bs?2*SFk4F>IO>E=IO)V86g17`B4P!H;o4oIqq|-&2OuI@8WQ;df@> zaNR3ZRKV(M#AR2&@8ri^WAlm^o2u#y_!0PsZ-@?EkDuKl%f$@I>vqB!D?*<`@5BH)P?*IPFFP6aK zmcS&-$7ZpBRr3Oii^1dn2CS;*gk=3#4%>QdI(z(|0^@`i9lpDTl9 z_t$#Ze4Qa-dAxYR3W<4-6Br&9e&COKz#9F4MRXtQPhXaV1*~Elp6WbgNpxU&bM{f< z0~WsrtYQyX{(pFw^X^$$9?Pq}LgIeUjQE~N{}Oen3wMv}3j5C%T6NNpdqS{|>h`sz zPlfKy3)&~-vf$oyJA=Xt59183`V~BW^OiO4!JRk*(}vc6`yNPU`jN2JbT?k zu?depy%ma5cr5kr&acb&4FX?mt$A_y+U4WRlvitp7~N$st6-FP@Wd?krOv*WaSLA0 zKKE>q+;g!Hui_k7iss$dHoU{U`Ej%W+oFH`m+n4Q zf1!8r@_8QNPcr#i*1VbhJ>uLu{)W8!m-n%X1$>BN_>h>#l(>P_{vW@Z0DH;K$K`P! zHvi*)_V1PEg%7$5S%VLJys7u1Q1QLcUq9AkLbkFDJgVnknXd2j+NI}pS@Ob*3;#6> zEg0DR3`}SHJv83OdOnw>(14X$n9b`st6#u#H9oe(z8}Qye-xhoPIuwwGiTqvw|fyU zxMbn>1wtD>2`q>^Aoj4uEkdv?O!C9Lmz_022f`Tce~2#lU~%A``T0kF2kz*-6A}}6 zcj0a7YdJQ_cl1?t1S~YQHHOKyNovwcU!-=;#7QauOqJMZHht~(b+XWMUth1lHR{nQ3A_kSlP{ywmN?SA_|@rr+r#Rr7U|C8VN=Zt*!(f@xU9{#!Xe%96a z2^Z`CM$Y+r>%aW%)A#?*|NHlWyx0}{e+P5^6$k!%asKS1{r{pC{(BMsZ@-VQ(sto3 zYbC$$m-x9}xSUhqKMS8q1Zda(|39J*6DB0IFf)tu6hvH1a;g(&6+5tCVnQ3Aoa;Yl zfx;)A{i^?cJsOHsm>BhR`7$a5oLZWht*m?u7@sn5aw}xSL~MT4#=!h@=9JDN=QG_h z{&f)-t}JAlAGVe6%7m>?!`j(o8ca_dU~uB#;$y3La3S7*3U`&=iGqzUj`I5a40KW0 zuGhlFWNi|$QlTkOL{KAb!iV*ZOsp-;8XjB^nGf;`i|BY*J#cE`^Zf7W5%BP!U*jxg z@tQRa2klqM3Yq*nuHf{)Y2p7}O?@vc54N5Y^$He=P-KZcJ|SK>CBee^F7qLEu`4o+ zNB46(vuhkMcyRcAhoDDy-+}iBS>_)q{(jVVgCpxrNf`&Z*)?D7dA6(ayg0DlA?$`- z@&W4)|12MD=C1nvhPg|qLZ^vAt)fAG4%?vv8@R7n32b4I&`4lD$~0ju(<70l?!CKM zq6!+@6g(7|*#FFYz))|uV#A`{I=2>>vB}(c&A=?h@xXo?56{B}PKl-k43GFK7A$0! z_#nW_?v%jLxGiCI)&gFU436cj3PSJXIsBR%rQTnO7>|0iy_0 zgYfC=KVC90@-j8+SZy`4PH3FKQuRNZss7y&hjXu44iz!+$nAI_@4~!d0YlRymj#XL zk-yx}GCooIGM|ZOMuSCt$W+4?wi=fPU5+0zFV3(9R2*PnG0HgI$h2ZhPy>6`-~5FV zs#<0ZtQOhd=5r`a@!ej})bW6+-s!_9c~&Xi1M?f%I2@UHcmh^8v+_+iJHPEuC3h3E zmvly;x+=$kh6#EJFXZb4J_s;!W`23zz{Igx55Wba_)y;G{t%*s>Y%~ZZLNaVq3j#UiN^({+I#WG8LP;X$% z3cDiDdF9sWhs-}F1~I9L#-4o4e`oCjH;$lh|J4n)F%_7zEaquAaJTp9SDmZ~NeyGA z`3FN!gicG!V+he)ud2Z~@AAEmk0+>ei8M20{85i=6kBm%KI4y#!AuO6yZp@cwR@K?CKq+BM!+pIM4J+ArelSF{vnu7L=o)r=etmzhIPz+5z~ta2wjFXS znh%JqDm*=p^IXG*rw4RrDRtfPeZ};n^&h+5de)-C1^j-~r+n_^$+Nx0rE@@@Tfw*d z-$lkBp$xgK2jv>L%tU_hvMH|rc)V}dBb!~U3`Ki{JZhQ5|Ic8|VW|7BAZUN!>AGXC z2Fx{uKHfGi6IR{y*FCgR?gh)F23C^<#$VA>R;>EUutK6kxo2fj!4lA$QTdZ5E|x*)EMnQtAK_TlA%jn zxDN(8E<4S(g=LaRs7HY4hjUUlg*(_Yvi5XLUsYL}+Q9mvVXBntLZ$*<@9mxpt^v;k zTP#yTnKK;@vo$cW@ElOtY_-sDSFxq&S5fBrr!sMZ0xPd9G0tt5ZMep9yn)X!^?w47 zg+uD|r`OwL!xW~}=G>pdsdv^dESt-cA%)S~Wo_cOqS-DEKU}JBoqPJVMZ@4^ayJfF(qrFl6x5N$`Fwp_A>VeUgATrN-cgsGu5T<|doAKd>kY?qudJ@; zuKAr*a(z$I=D@zU7qk{y8{HOaS1X8J6dhLWRK7Di<8xR3jtxuB{^Il9_eFGm-BaoN ze}9Q@c+~v9hOy?*PoWw6nBT2$@?6zu(W(&iQM2{ENs;{QqZRb5n}^|S zJm05jypJ0Ak651DIP2w9^$$Vvw+yMez^Fu zEL^gfKbFDu`L=XsONTB2BL?0*Y$}uYZCu&>XY=J9-Zxl8PBe-!EMWZc%3!Nwh_70} z+rZQYErA4vZR|#k*I6DgaYrb|@_uVP6=SF*)Np`F!eP-=h7QKwMhE6&whLHo7@Ycc zCIzVpOxd%k$MLF^`TxEAzh;H(s@~i*`>cb&_E~xQ2QDnmx>h8#Pljhf_9N-)Z4NG1 zK3@Ch%=M3T`LsNKHSgmqrh@^rp6t%O?#PNyh`lMc3`~M`}&i=P{YK?=<(uiF7L{{DTf>GzgWgp z?o@fiD`<_;*)I7c`RTJKR7FI%&RcSHk`>bimBw@Raj$OJJlj8i%Tb<+4Et(FM*A9} z!N+9MeLHfRo@)`(N0rihEMHG zHRjEXOzI`ugHE5jn|f%;j5!^S`$MvpCMix3rhfdB551e(7eNYwb=Fim7)gLLK;jtF(3Q|c!+Y&S^R z+S<_TV%VK=T7dmR13$x-1Cmp97S8>o*}(Cm!ROGy8^JvuuN+wo#7{UhtWU7?blf*B zL-c>*&O~Jio)ZneDrT((o3)SZ6Th=BdC^1$gZ27zq`rS-=gH|{4w!47WN)gXF)vEF z>3|UP0*>&XlKl&NZ|$_UaoKIFv-Rh}{U<#|wiL?e=}bM4!EY-ygVAHLwAR7K8MA_g zX2n$=;?z(pQ=D}|^F(`viV$Iz6gX zCpB1oUBzp=v-jj-j~f>}R$dnOOYG+0SjF%|b*I6;Seu65g{wDsHN*JUzQ4TF!C>b6#TF{{O0? zZH^+xZ57Uo9cBq;}dsy*Eg6ZjhI@(X=H&I%k7)RWGTPb}HTsGT;q1{JTldm2=w*!|AV< z8pa0yH_N@OwU%RHgSW_!gKJ)F(x9d-rgmlhgO`@)@NUNkIgZJ3U7SmT7N$-L99Ewqj}7;1ZT^43esW7fKBZ3!#!y*~Lxkl!ZX z3t2O=c^E2N{m(I8I9ansB2spKq-W{d@cPh27rz9^*BXjCUu;^ssopoDHB=-gHln@u zhO%~W$J~gntz7>**GBZ5y9_PyES8aXxhW=U$~ z^xnwC*2tM_Bg2+P&ORIIcrb+5G&PJ`h8@29j)cU_s8*-x<6F7Kvqc__|Z}E-Z8XLVWH+p+*^p4)> zopYmit&QHjH+olY#NxZrhN@Bf{@xDfjW{5CXW`Z8L$)!hU!#x2#;BRb9IL&PC>nEO z?wzH@F{k$4>DU@`=5CC9aLl>CF%7J-7pEF6GG&ysh}D&dJ+^gCuPrBcM6;s@^Ub+; zO*S-L4!PSNYOJuim*a;?(50CFjT0EPXWVU`ao2SNgU?yhJ9|0uST-$Cn0(E5v+IP| z<`2v&9!&NU95-EcChj=4J(1x)E4SiB4u!~j?^@-TDR7rH%3t~&d(xIevg2;k2k^e> z9rx=durzrvn|Q=d{4g=ZQ^P^kw8Mf^L_o3MK=E_xy~5vLy_O6gXh@q;fr+5#R_U0or%tn`pC!7C zDjPPuvC@0D>Cnds#g`kH9wg2G(DV2L!++Lk!g}ktV}DM#_;8WHj6Um!kB&(k;hea9 z|KG&U46(4uoU%bXk}h-TO2jo;G}f)?P|@fxiD2$uF=?vI+`7bum7n|af2zv0i0)@z z)-lOfBBgjiX?_0AL zh3)*?IBqcrY`<{Zbi;$j56v#?I_f^Kx=my3Wk}F!vdZ#lIJhX~yz{afk2{_f%~6pw z4*t8eO(C^FQ`kpx`PDT-A9CkjIh>Yp(=>JRhCTsJo&(Rg9^Kn}c4iBQRrl2?{1=`o zWnxfai`+&Xf#>Dy;?H(E&8v8mnZV;yEmCX*wgelOd4K&tDVeqpy9WsZRJrukr!6`PruSVWM9@edHptd&&FL!Ua606 zn#5(kytT29EnwEIzy{ydi?+AjnPWL`p`@(X#xBkdv8BJC%S~ha-ze7Mc!MF_;>}?* zj<6T)!Ui4p%Q~3W85~ibw?%T-`j;I{0;`!{8S!{9eB8xQwpb%>nxNLNzN-tZnHwZo z0vfK`ybocNEK|(?oR@dbB_`GT{#DEC{bzX-*@)+B>BnG7GIB|ht`7lw5-{?|TVq>LPWY^9{zf|flSMdFq6LS{gW`R;mu#=c zH7ex!{wRL5?c-B1jwg4yV!u6a`XKJgA2!3cSxAFJP`;ybMalAw9DhnWoDVKzzF6Y= zX{+D<$Q;+p^%}Y5(fAa3uiUyt? z4Sf%GSA5^Y9k7|}MP_FFCB0bV0DBJ32c_CSzE=Kh&`B~s^s`}J@}@NF@^d?-_IZl` zf25>1e`Wu-76!MUlbv_Iu3dkq?Q46Me1}9pQ&Rv_r3Z(kLdBn*ecUTL*(A0UJ>Ocq ze=h$429X`I@l5isvewRx>#UMq*=HZMS2v}_p*Y6=SG~p0<_PAg@s&cW_gizR1`YV#HNyxMp8rMZwC- ze+@co?G+SvZehOU*R@MZouzu=`-hdC_ZvG{96FlQ7BD+WbVPTwr%hmXP~gzM(6p?4 zvel`XTq_t3Y@Gl6gb8EA^yP&;Cod|r*7wG(X5d^P$g_f>)qFE-^pI_ovjW{-7ye+hskPcq>{TO>gFD`~N7N#VvwSROA2R zsSm!echobn3+ZHhP&m}W{#VptLBK-$dZQ*6p%WR+DvdcycshQ3P(Cq1$$gSeCUn<+ z%*hLir#b|`D;{7t-r`fQTmI?I%g^c;7C1%kQ<+t)acPOybfa9YEh{chV_r0Gj?q&U zzEx4%japBIDmL+mF5}|~c&OvUA+BejP_cmd4j1n~*%bzj3tBcZ=~nF2y3i>s6Srjn zOU;F**5;^R|Fb$je>F^*7pE-ja-s1c<6LHAABh7C4%v%{^Rc`TSny^)1G~=lSZ#*~ zx9ivc|9Sh7%YtbJ7F!}NoSA$gxA~vWk_9<`Do&m}Zfv0NJO9ufR`wT{S`8E*v9qTx zb#GB{e84KgBwe$@;J19ALCfY}*_%{dxs($3q=f!I-zoSdnnP;ld)Eb=(JdQWSwFQF z&K8W5`Efp}&hUkc#4bg1#nvsRE)N;RLoOU-dc<0Aiz!&Pz>z`d%-8emxtt;mCCoA( z4#tR2_6N9)0~>3mMlLi<`!Y_pw@-QgjFI~VA&FJTcM4u_6L3% zvnD4#5ofD!Hf3~cO$=K0UnVu*@&P}W?1g2)9VHVp7CC566FtcO^x}{6tQ;Qg4)Zqg zKH*P@YM2-irdiQAU(lDS{cxSTl|`z{cGI*Y)1p|+=Ll}sj0_M?)bN}!V_}hNT8i>K zGY6J^p3k2#UOT$-ZWs58X*2y)oF2^Wdj8kAbz0nukQWP?1P|VBw0OYYmVe;ySO26# z&&2OceDPQPj)+Ue#`!C5y18nl=2|Whj=UH5;3oT~uaeqpnI}d>v9JF5b9vLxh2QR$++kNh9`l_B`2aznmN_QFW6bpD)B zE~`w&S2tOg8nm+JUtV@p#Nwjc_gkg&*On#KPXBB2a8j&<+I3ddeai}Zwf{}b@3vQ| zd=wLY?(_U+@qIhJ+l&8MzF6q|cIS(wv+JsgmMq?9^?GeI$K=-=PxDn>*?hasx^?U0 zeb(<6uexXb;b8F6-5-uh=UcovuD##p(;4gkHC{)Y?QOqa3a+>Pd@Xr@?Z;ci|83vj zYqtOQ=F#MOyVuVa@2`9LYV&`)=kE^N@4x)%a{c}l-yZM(U;l3Z=l}H&zpx)*taEK( zv3aoVPx}GZZ~B!SKLv6Ugc#Vxo-hbD++{fOS-6}rmEqRUGYnP@j3G09REv^0Ik#VG z;896!Weib>6H=JKC&6IDec_G+-v@>pvJT7+nFm(jGjE4(Q-brw-7Wr zF6`+j=FqaPrQsx7gO3VV%go}y=OJGr6z~32W--0UAkLm}UQTNPGrz!jW;uaIE2Rg_ ztUm%c*d{R6=_D}nTO45Ix^P~Ar?Abgrs0^(t_Bv71D3)84a^divW*G}%$zS8Z2t!~ zN{KzNmYU(sIBABjpVY>zMLHhp`U@B&l@>6Fdo(b8_|m|qkM* zTMp^XmNaN^JT1!D_0O4!?MIe0cL0OlrM6bi3hAK0PB)j z>O7o6jGP_|n1y#NP*+cA=<|56i1$M~-)t#{dKLwie-j#*?6zIoU9yB()K>YX%!dX> zodryCnh)LoPHem}>C)DJ0S;_@2l~$}5cQXLU~p#@VASASGMQzkrYQG;hzl+WI_w*s zvs!&HtmAQD;_Nsf;+b`at;cMIrO52GIPW`Gmh^Q0|KcOe`jVf`X3BO^hXc$frZuv> z5N7&E(o1Lil6EU`NOdo80)LJN;ZgI|C^eEo)t@&%n7a~bwTl{wAW`|rit z|8~)qijQ&)8D}_NWOpNz!vkhcjYk@ibK+Hl+7%?L8ki&=@H2OmcJf7>b}$lgY3`V|guisX z=Q8CROe+Evyx(MVuw=X3SomN8|I)Mex14UQJX2&GlakNGbKtV=^T-WsAC6DUJbcWH zOC=@XUAB;90t2tagNx$x4pbU?AJl6*z!3S`fW^sH+~fX_+3r5Q*~eE*W-?&7Vzn{X z@sT7GqeP^Fhnz>jbRRGOf6acXIcpjCE@X;0G;}v_e&?_uI(C&|!p_=W5BF<20 z8P43){&$i=HsSpl_1g?a|Cr}G%0HX0cj5Geg}?obx){`)85Z}{f7&1za3F-^^n`3F ztJQ)6YkAIVJmZrwOZl(XVXsth(68z_BjbceOlks`_w2vR;Qhs&)rnz|j=Nix=A};Y z_8dvpo-NFLf()k=4&42}GUdTFHaABWu?w-=BP^MN8Pizi9B!D|{())LCC>C0LB9&l z-Z`A9>HVCY={RG}0ZZl#2Jzo#8J3vsVAPVjUbmB@(P{k)=KT3w@0s(f>Kz<>Gxr@h z^ED`2%JIOBuXeoI+8=6``iuX%>;KN!eCPc}u1|W59PhcTCp9WeyME))*-i6~%uze- z{^YO!{=UeVV**QM%1i=0BKVk2u{_pD@Y8rOi>E=Aw}CO>`YeM7ZK-~&c{e8HE?`zt zc>KZVrisG6(gmzd32bc&oN}$qVGo#!9z0S}c=A?^CokcdOCNU?3!j<7(yC%;uQSSJkJEGEG+Qw+s3hwF=(Nf61!mDLh(&5 z;&lrp=DA4pEtKqXk({?s%E?7!-9qW6MCpACWhN!coLeZnC{gy_Lb**3SG-*)-{d0o z@1X?SB856dMX^U>a!N`;3zhVgm`xWcD+#E$DKYvgsWMGajZ^xcvPkU_zj~R{-x?*2 zL;V_kO24Np(p<#n~oL>LnxIND?hYe3AUUBn( z#~$gFJXNDp_)WXjzePdkc!H%CPZD6{d(jfi)Dxfv&{NRfE<(A3Du(>hZUwz4(};(O!1GsqV%9iWMC}86DGBsL#{rYFpH`PD69s zitc$zJ;zpPozv)@#NT;u#hWK9`gZa4f6MrFLt{eT`F_5aFGW^P+}1xq?f=U!3YwEI zottF$vej+nlq`*@VVUi5n$wzAl;pkqVX<=hwf<>sneQ7kXJ&QJoR|5pXXUJWezUe^ zmK@6LEn7Lq>HO?#FGDYB&P(H;{VsFDtCjQ9G#9XGO&8Ld_-*Avt(ObbvSuo2Ep~dj z$WH6sos~=Y`WJ`2;&#^t)7~1kdTX5awzSpT^0c>?t==Bh&iv=@*AH63XD+stFw9!px@(>G?rp1g@6+CM zZ1tXV+Iuhk*PhoR{!KA#ve@bsEv&P8`FHs-JYK1_zpc~iulBDD4+TyBom@w|6=Z)` zh|M=!{c;BDk$wK`5kiN>bc(JV{qM16fmFX!tj^yS!Gl3LFBk+FC!RPdcK)|wz+q1R z!{1m~`7Z2q&^YdP?6h9MnNzDi7&LrUypVAs=bRH;nC^w60=lR2`i{?YVJ!%AZ9Q_i z*8S`;zpH*(7wcRz=d`e_VPj%BaIvjLG4#NJZGJb}{I3`mI9u+N z+YB~#~csl5BE~f1TmOIU!UZ0e9zi9vB%O^TuH6yxbrRU z>c`Hfv2Q)+ynA;h$9u`iZ_7@!7lc(V6slMg$=;*$_7^9Y#iY)L_cu@7W=&~u-m=b% z^T67OOY8{_Dy=^A_PmP+h7Lsom0kz@4soS{a|3=F~$3UTK@BQg3KAS zSr&76zRkXojBB+_8<-8|vwfe+(6O<&#K3Vv2lE{vk11@-C5CE$*UNMsGd_7SUFLw? zL^ko$fpcDLlyP0{taJbJ!AsvO^d%O#u*cltTH7kLxR#|=0?Pn>n&1j`Lq&B@10GB`>~d^A>{5j^_dJz^d+ zJHO;~mP7OOD^_&x%8))8cHmVm&$Y-xL-Eaq%CWo3^5Pa;o~4*$8XW3rv+z8d|7Ewf zo|?BaHCg{!wdid>;&C(gqr|7=71}KSkL==V?aD3waOFP-(_CXK@$>qh`EPE^`JN-h zaQ4G9g-s~}AOChlo{;me{Mhd(Z{qaZ_)kbfTcoB#y&s#a?yn7#S{A))m2(hXc%FTM z!&RR(bK45y3i@Ne%$~dKt&J})SH}kL{U$!gxA>el@x8vq_r8fw|HrJA-#d~4i$)A;YDG0vu;zmJ5l ze{*6mOMHJMLEh}1pb3x|Y!c@#syvve3Em%50 z%6Z#Ty|?c_nS^&_uYYy^_gR^sKkK&4^0=wQa)WX8-#z!4GCl@enZvxL)vW#qlf~t= zr}`rc0@-UEjHZ_P>p1M+$f2n_xx?h09QTm|c81TE4!Pc|e#@9UnuI)MYb`!}jOmP! zKxWU&mz_ncuaxj?-xC_clJkA>uJb;UEc(hWcd9nl`8(KXA7MJOaX%kJw}iz0%8OZh zTNhY5v~4-ca-eM0E`M|BBNh5B&i3tD+YNSo3rn+bc;MS`Cyz&nucg}DV%~;=6{q|! z-wLZ(>3#aoNwtX|T2Ieze0j2jVQt;%z$ct8TTaa`d(kOwxPAGp{^Moy8r&z%T5(`= zw>|p=^&=I_eGV74KUpf*dxEw9vKwoS!@G^E{#|KpzTCE7tA$1Qig{s_suJo08)2CEXSQCS)%niN_4d7tADkT+z?-^L82&Tp ztY`FK@MDw#Z|YvmA>*-O!NFz@VXc@G8x|gJ7f|+^)5_ddGUE6wo!{<_6K z*!=cv`S9q3qrCq)o6nz~T~M^|_xt#QF1gWcukh>g!ZwqyiH|xp7R~VTmE3pWQj=2W2Pgh`2Z_W0{U6H5ByGOE zSZt(w!u_&jyh!GfiRpW;9=7*h$D-_E)Rl3#z&z-xim$-F1y85x-LeRl|9>PQNPeTm zhDAPR9Usq&>j%vYYPPj`kv6rWHAB6_|AWZknTkh0_0O+4Hsz>ju$AR<&w?z8`4dY1 zT{*lseBaRnU4{)W1GSRoNoM#k7|gmf`M=G^C!+3Cva(kCSKM0Rs~=*)7RP(!zfiNn z#(&Bj%<{RvE{Vp=EL`QeK<(%Gxn*KIR{6$XxNyil{DOmX?Buqe{Y!VAOF2Ag;v$wT zv8;U#r&`6ts@_a6GvI3A5C748eaVitCE4DY|30r;sLgmFmp@X*>YZPD#Y<=IS7P7v z_V~Yu7FzNDhBn*2-)vXcyT6n(Gw^1RsM&B-MN{WkJ@5aJ6C|Eb#OEmh7l zMdH*MS6wgR)hW%pZq94@;QlSHapNiLQl}~1CO=n-Fs#?}*7vdgTr+8Jy2Gn4m)%w7 z3SIQK_fqmTJRx-$bQSySwx8QpnuIdkNceU$WA@qLo0;9aZ=6j&&b#JH(RJJHcPsX@ zl;10R|NrdvJ9YnU%N{g+_NjO%E8q8HUzdJe&f`9FuF5B&etwlLfqyo?cs8f}-OuL> z+Cf*bPtW`Ha>ep>zg{s;dN7e&u5d!T`{Wf(UGCfeCm(yQ$hxEIW#AdbKOc^W=l}V5 z0(2Go8T0plK3{Ox|NG@icz*5Ylob1u-|k#rck27y{Q0%tuKU*?Wwu(e;NPz|+u#5D z{o%O&|36=@=l}ov64H_t*2caK9tvp%$F_#|%pXUNbWW5A#+_`qIIgMnw&gqG>be%!@qo4Tbw zc6j(K5}JPE2xwDxK+htPDQ5wbq37ZNrfuu((iA4xy*ywqpy0sc(jdc2h;&dG`Q{RnJW6bu;IN<N6iW<@^8KC;5vu4jfh! zuGVu*W&9`6z{tg*6v?HKpy-j#eX)z-Y}1MaC)j@kGTzZHjhai)J8 zG@0B^2Th3iqT=s*fLUSzV}D7*MDY#lFY)~hoXGNknQz6~RO?IzW|aj@)hy{9+!s8y z%nMr~)v<|f!byGse(zYmY0KPyPV{HaXxJ|wxq6D}uMq8SmbE`vIXgt6uCUZF{GTR~ z%E@NJb%C{G>;9>4Z!jnbob6{>z{DZ2DfRDZw=k`n%BFft%>EZfg`_>W_<2r){VJPD zI>A$0*()5_XGNcK&UavZ-m*phtc;M!N2L~g>tNWx9iPzdazlf| zktOOxgVyTTTMSGNu<{Eu2Ce$Kl6i*#PwTAwIe8+?iX4t%{I!gAE}I#p?rGb*L}m4x zn6DQ8|Dj^pW$6ZXj^9V0_xA;W@KQyC@o zSNBcb6*kMRbcW>q0*^JeI=ndwC*=hX1Sy~F&;F-8U9sa^r2V3>b>$ARydG964`V(n zbcr%;N^@W}Xz-Yq@L;c!BjdgHRKM?HpSI|=>~>{7)lm8El0095{;v4!BI{SR?MrG+ z*qGd+6tt(zh2#4|2(t)|MNoszAwxD|GtX;|Ley2 zec!g9|NE}`|L+I<`~Uwq?*H%8?EimW^zU=#Kk)C{?*D&3^zZ-k-2eZt+yDRlIKThj z_w)b%{r>--L85`lqT&5@r^atp95WiYb~NzZXyE(NAn>E=rFo-BM59#j zgV5g%jO7YKyArr`8>=r(@fK2GpGr!gar7a2?2h=-ByxLZ>B%Fb6~2Z4F^fgEmD)uEicg z%N2ww7+SRsb}23lzs(wM`IO<00%OBdK}G>4p$0~&2Vpyp1o0p6sQk*$cz}QRwoaWZ zPVxZ^oKvdh9>#GfglHHrOzvP}oEcVjLYC)2nDR!3{0!mT4B=FUuBQ<~^Av<0?dVf2 zWR$WD4B?!tmzBB)PAG+z-MTCouYafCV>yNV&BOQuQe55CnG0GiT;t_8W>%f3JFG5TqadW3DRkfq z|8oi9a?j8p^(m20J5);r90;HZX8E zRGMv^sE8lx{7pkLZl7t(5F6=ieE&2&}%6;qv}oz8PBl(WqieLC6uGWRxmkZ0~eQ+$H24CF~<1wDds6^aU(2Rh$ZmoJ)SRCw^pnT~@c} zr=au(M!i>p*;YdB4PmJlmpm(9p7>3$g<-jTAxBI%o2^z6lxUU zP-66%`bx0RYNf-(MHRO=x_=4I$XZd^z^cr+YThm3E(0Nj4UDT3gw|f*UtiUwuyDRc zBg6M?t27l;ww=mYQnhmFFaBOjp<|IkUypd`EosemY1MhMCW>|KhsD z$Ah1xw@MhRzL$Pl5-vM~Da~VPu=^@G$GI;w)~jvYps$^)ae+a5sYBrZlZ^`%mdhXH z`0Bu=;K>4}n4(Jmd=k*JF^#?fR4LR(rxn?K+xo`E()g}zt1{@6wRNW`A)){bQCoryDy~}GN6ElNmRKe~Yzjq&5 z&g#n`9Gt*-Rh+?GJm98@(1nux*T0rTwWg%Bw{R3N?tQ}I_CYZC0jrn*oAJZF%Kr^n zRT5eJKJZ5e)J|bZV!Xg8?~o-bus~trIu3^QJR2B1Bp6q<%>JLi66etL{{c(%1AdDG zUjJ4LvAz*x&0$Jl5Ms??d?YQzex@d5M@G@fIdv|reAjxde|d|~TKb`{G9?$t_-kWENT(qv5A#%gefC2_-%{}Y&k0}iFy zu=)k;vwS0%^oKvrfhD8okZBL|J?lNe3s|EWjyUTGspPQSp1t?{^&_ee69hKoF%~fT zuiE(fe^xSs)IPUmi|XjrQ>jB4olPnR_i&3w0VRI z1%%=z9M8--B=5+gU2{swkoD3VR+Bq5>tZq$7KV#n^|$bFke}ea$z?&8`Am7n1Bng? zTpqAyKihvy=1lIMBgeSUX8t*+@bEN~%szuTOqpknybudmA2N&aDq|08-s3y|4^H|o zEpdGDMle`_{ff=8qi>ET8tk#yaNwEtNd?0rZ{G;sea+&+urGn(i07NVf-z^5Vh>$# z_dY6;a=3|URSVKod%ei^dP(f{vfAqvbFWwJyJ*kH&XzEQrsG2l~}QV`4d&5&Rh#oydE!E=6EYIjPohO z+#^{osSFc6%FbNN^j-QW>Zq?$pO^W(n4?b~M7DZ8a&@RanH8rtZ&L`v&Ld$tQ^Rst z1LRa6<%o7GDuhH|4e>fQS1Hf;>d!XWfG}Q#WD7qZ^M4F~cRZ;}d6;@NVqQwBd_u@H zj&P|D3>>Z1n_TV(lx|+C!kB00oum3lLV-c;zn-_X)(l%g#y&Ylm2W{gFP!o`-P6^R zGp{)PJi@SxN0#358{AaRbstP+_<1Dd!jACe z>7D-#DrE~2bNr%KnI``;V4NtKr)0*E|5Na%ODyxOdskgcXK#7MZV=q_5xP~B3p=C6=> z?d9H^41yEV9_dB;=P__S2wSC+|I9S(%9Dged1+gpZsGdB;X%*ZQ27N6-mF5h2BFqj zX#x$O*UAOR7gos$emeS(fAYHE_GyfV+yb<--Pg1Qr*B`#Tb^tDU&tkW!7^PTP66L# z|K3SCL_YqN8GgQa`E@6^{HKbJH-#6x{&r)M)k-f7flrcRLQ9Wy?PUpMOh`#us<&(F zYBR+YUG>XB?Acr9J>(AY;0s_dKQ?PwWSkw2Pen!^9%MX7K0rhw~5i zv+*+-a1>s6XwNIp<`D2;y+bP_kCDw8gN+UhEUXeTE+6<^8JPdGsl2dw*tox0Qb@+( z!-0eQ85D&id@eXLwDI%vXT9LKnCQvItkNT*wdLS(23CayB?b-$4{|cg#2Ku3c*vjg zzfeNUhR=;IY>BOlDheJf2xDVZk}&z8=+Mse|BhUPw8BBZc7X|eFABDPXk_~n(6RLA zw9McW6Xn}3WPLc;z$ni3<<1X%C)Q@B4^k%>K0jdRWn-@~2oO(;VCLE($HO@J;D0|h zR<4+ihDygq4puKIn*|FFG_mym4b9MXeEDWBbDegF!Nd5&^#WoKd^YS{a{p}q|K2wz z=9E0G|ED5yfkC3NkeB&>mYD+MGJEE0U#0%1+x~Q(u9opYt3%?$az6aRZY_^Gmgpv!=XZxcp@ozcF{H^LcqK`CSfczpzLg zFsa{jeieP)h|zuSpEqOVL7|MZac}W*5Gg>|Fr4%J8^#<6INPWEd3r^@px)^T-9sYZww#rv3`v% zdS05aqgXKFO@x(V@`C$n#$KT-cAR)3%J}n5Z&=fQGihz}IBAC)H*`hyl28n=W zp+Z^>i_2ZDj2Oep{_k43GL-wL0>{QYujehBw+R_-Jh)V}nO#Nucb1)VUU;lqYg57Q zt<7>`57>oHL~BoP5Dse;xa9V!aRc9rz7PLyR4Xv;k(=;w0qdcvtj$xkA9yi53-)$zdG(-?MK;Qz zT&22v1=C%wU+TXcMw`#{kNVpcY(Wgp%_=V%8n?vS`EI|r?*H4yMuQ!9 zGtY9}IHC36^1YwWIE5zIF@6!^s4?VS!XDMc{-!OZ?6&9uCx)$CWEEIiy%@gFX=~)p zbYS2(aE{gJlGt>Kw>n;)oz7;D{#(>DoK+B!>1SM0%vkSppoKHy0HcBVBvv^WUG9Jb z3s?qG|Lr)y^rz8K>eJH3e?J`Zxh^yaI3=hGd^qmVlP2^*ve$Lh#RDvQDTh)j zUsyAB@OQF&U|@UFrtend5q-|>AOnvAlZK6gv(;vUdY!Ze?y8HA-L5q@T}frQb~Blg zEnrgVOaq2b!d*^xK1nc2Fmy^mWc{!> zKIS&+PcRH|0F>RR?aD2Lr%;||z zU-+MV{lqA_K_;bSTZ6#XH@chw4s2yJ8jt?h?VfqFBtB(Hq|?g@o0vEY9N6WB3|$Qw z`de%`8vDM@jx$qXnwvSvepi}E^M$wadG;?)jZ%wp~6`Nb@401(O_?dfX|R%*k`cNO@udW6Y06t5OF|-p&^s>Rc1U>YjyL zHFh#Rk!slBz+ul`;^E0#6!b-Ki8=cVjtguKv+5NX{yTFzG}=3C6J*yZP~tXxB9hP9 zID6qaXTBMoVRpAp@)T@SS=r0O%DvTXYRdyH(T;@yY(@vRT~S#jz{%BSb7(qG#_5(_ zQ_t86PV!%F$-p{G^h`vhtM$5%Pt=SS-3dD|W9GBJ6Kg6n873^SUc5HpMA^N<;L?uE zvdjjBjB=A2*dCcQHpMhle~NCnbY+Ih?+^!FjZ>#R6ecJvNoz0omvFCe!IjJ3EH>El z2PFMA_$2m3_PICfJKn~V?Q$Fr=?V)>KlHCT%plCxrXBe4qP@t3@NW!lt_IyXO(GK* z^tbx2`_B6QT+ro)w-I4Bwp%sA!&zC|4v4;K9!_cz7#px zJE>^No+VN$INU`3Ddf)rPX=rL~5VWg6n@&15C9}x}O(kdb#W&#%oKM`g9avVjRe5ita&6Iai-Qd@f$5WK{~5Ae zTe&jkXvYG^sOY<|r(I`Da60c-ms4>{mDAqofbF-EK6ws9_1%B}=9lL>Xn$~X_@DQ0 ztN+$%ER{T`93&<;DKxmpJh~_J;}hQ^liRUYEMMdlQ;lXyvWEC7T{q@fre0SxMfi08 z%|Rx(w#*V9+&nwSwcYCg?e9Ng`ZP%)MkDizPjCtvuUUhly zzmgU1v9E%cS6y9OSGp!W_I330s_T3Im2PN{eUq$See-Nx*_P$8Z?n^@Z{Ph_w&VER z*R{*4@4l@o-}5~7ef9I|`+xtHA7GFB(5znbkhi|#h`QbTvZ>XN8E54kcaQrtd3nuK z-TKNi>2aTDKd*Ui`@ix+d)$}B>a{O@>#MFT{}&^6gON*YV~6Pi2ezny+H0<6M&*$S zyv-iY!I%Cgw8mf9_icUI&g}(z?iJ5W5ZqgSNueqK72^uguNnby+rK zucF8q?{Y{n%z$yDK)_Y0T88x7YSW}tZ%$!&WBv8Ydb@3Ei;fg6Z1~Ckv026J0)P7r z2K#~z#|e#ziP{_)+Po49p`WL63Yf)lHcYm%713Db^m3Zk&uOMN_Sbi?crqMVHer9W zhC{p{e^lm5&L0dyH#?pQ&E&6`)9TSZTS_kPAcIIj1OExGW`;F94vm~C4Kizlk2o|= zWInXgfJ_S{Mh)B*4FA_vI6SD};B?{KnsIoZgPOMy!yb#33oi7`Eoi8{-PyZ?fj42I zok_#o9g4PBdbc(oTDF2=b>_6!uf@0@9H@VBNbMu*!VOJvlAP=t4nDj%ZMLPrn+f(p zfzx>lxLY%(7e>vSdU^uqY3`h+NsS8?m>aw619;|W^S*Rwn3J)T`9XW*g`<`syLWf= zaCf*Q-`tt~(52mhDXx-r;Sbh0L4H<_Wp{Tp@J+D)`C9XIq}JL?nujbKjwVc6CZHHA z)yW;OYGqd^`-_G`M~=FPtR|+zNrng71)7%KXb7FrmO5h< zql&}B*OGfRHj7VY=aF-P|nj>hgO3@^R;7hITKAb7I- z$np9F&&`+J*$NuiKFmw5npQheEM3OY$3~}aMqBg+mIY5*lV%)fztI>wamn+K#y=8`CoC>>W;pu6Yf|CK=|>iO9Gouq z>bM$b15bi(X66ZoC+uN6PDejr^%dw!`oXStNm7-h+bg%%l^^k!j|0^3KLrE8BS$hXi4I@P@ll+D-hsaz|7mbn_HEI(UgO~HSqtR zb_Ug!jEKO~yII2?u*M~{&R-Jv|IO+83G6a+_elS3m-`y1%E9vgOJJNq2X||;QtfW> z(vH0)9B~SlmQ6T6zq_aBhTlX+XURF2;x{*(xY4F|(tlyV<^O6w8JJ5uehD+&RJq)d z9Ng~FtZv)kP|y+5(PMf;^wk!%$rBo$H3iRAn%J)7`KW<`;{re93{NeQnM;2tFYA!C zTD!&kZ@WSRPuz}xwF{SU&0r{MS(IWD;(U~0bxd zQj^6kJ@K|2i48saZ>Bo0v^xSXF{J++}O^Ge3%t4*qlcxE)*JkcQD6Dp^4<(0Ev=h`idCp;Y{FgItk zY3*%GGvKgX(bBwPO=72S^v@M_j*ZfP#HFQX2^MwtbaYSJv8DOaWCJG7<_3neHIZ(Y zn_Pd~oO}0XzCw#&MaTTFOco50&%5@qF7RKcs!;FHUO#~)vxj;2-+lEPZd)w4UH^l* z`i05eGgr5#ng}!K{w?V!b7&O#VX{>=MoF4MGNQ%t0BdPQgHi{Ja|E+@;hpWtn~pt= zxxjmOT{e&9m!sP&mW2FV?CzyJRb$^=MhV@FrnWgvnirZhGsKh+G$yXRD{_PB;@0W^ zZC5qu8gu^K)i6!y9=}GHY)=C}N7uyH40@5gvoAFnT!?MH!KCz~T{j{o=OE+jzp+Zm z40R^G_6L1SH!fg%aFjb@*0!UQ8dN#h92shjIMhGftv6sa*m3vvU-sK~@BiJ(aBK73 z_64!+8yeIMg1HNt>J2W}&tSS~+HtG2o8@7^Etgxfgper1xe<4`YX z64}5Y754D|?XOKra*xIMn7MB@NzQ8$5qMOe@rY%atyo`=QrV;8NCtN%Nz<;Li+|d? zb5xslbT@HN3{h;--OymM!27N4W7cDhx&}<@9!UlZPOF)9e9}&qt>aL>!K9GK#odvp zo{(VwZ`v#EjdKI9J*nX^>73Lw<79;0LsqutU!q(}3+~>&%4D!1Udbb=eM5Zng(iy+ zPc=J){=Q9KxPgItMpI(k(|pAZg)j=t=j^RjoX%ig^&d(XM-z4u0Y=EpqMa>*liC96YmvEyWob&ISpKH(%gr zk-g`1X;S`1jlBYQk{ub=E$EQ6lxS_3_IBs&(x+l&MdH_{DhgiGJUz`>XG(+CKU4Ns zee#VtCj}a~G+rb+GV=UX%gYEFU~eg<9?3>A@hohij=;z>1!pI|1jmOceZo$8UDigp+L%JqDYOr#cDFzBP09 zfre8S=U8@}=W>|1KtuLt=rz_IoNU~mCO%+~db`X==q&4=zU5A{Ex+ zQ_6Dx%@wMXIyx9KJ{e@4bos>~GP7ImP>%8>L0%8Zz~}~^hI_}(adWQVXh{%kSlAab zaoJlRh8YcNlUDSeIK(H*9`SMF{JAGMZ6C5T{$P0gb04g#6h9c!TR8hu?5uUlNenlNq;4CYNtw9sD@);>dBq1?8Z@WRk4cSd zj&NTa*xj}0-JTx}tclZtlXE9aG?dF+*EZv*yTDu2xLy9F`o)`3|3m9Ll$v_7n(R-{ zX<$w0D69PDk=SrQK#p~Vpr?KckBxilujNLwN}fbt*Xp0LY(_Vi0#|WugVaQ)_=BMf zZ*cUU>Ymh58opTcyAZeQtA2sWn#a1=NPS#z&Ba_;cYj%Y#fJ9`1uYG%HEZ_Xn!x&`^kc+~xgO`I39VKu=f2F#xP5|V zJ;UN@Jv!!{m6ME9ZWYX$=Hz{7y5^Y#PWMAz^EU7u-m0F#{XNL|$Fx$Z)erI?dQ|Nd z_>?@iTR*7t+XtD8k$t{Nd56?L3m8-$sF?TQLjMDg$s1NM6nt4zxBIy7BDK;^-v1BS zjx$UAy*TgZ0}0OQ0Sp@|8CWA`o|*OeTm{#YfJ)Ps46NZ3XK?o&EvUO*!SS0>VylB3 zli_568+^Ab|2? zQa_v<9?iFBWB(_cao|CGTfLB=RYt>s`}RB>DrPf&=r=I8@W^?|I9xjDRxfIp@FzWB zk<)Z(RW1vSi}G#E|8nl}7!)-eVOD0{!NVZDIA995`nxS18#S4DgakQKUMZ-YWY82e zabWzS#6;rq z;@UG5ew)3!vy9&%V`cPr22P%u|9W|UEIvIsHQhM>-5<*@FD@+)UZ3~Z>f4)J+l$}7 z`)mE zE(;{wi=x(ONb*Hjq#R5YyK`evt6EFrVG)CW&X4=eyq36f{a<8yssYu#oztX#1CSMf5j=qJC@S7|(Q zh*>VQ%%F9C^|6Q2f=8@4+h+W4NnEwPwlXn$iAI*=V)wWkoW~|!nD=Vp+*ev$ZfoL9 z1an*0d~4N9+4yhq)BVvu#M*Xjd-r<9DzQD0FYVPb4z69*`$&Lo>9f>V!IoQ~6Z z*!|z^)lruNVlR(u=9Pcdaxn6rwnk5_+e`I%%jITiUI`34yV8EM;>kpB zJIkk2qw6f6&CK3s`Fw6QXj6B$oz=^wv+Jy0ty~S-)V=$k)til{?X2H!y|9^k~w`XA1IKX(%pP_%^ zJ;ohMjr<`GnD}ZGSd=vmat3W^65FG|X1wAcR|*4Do8^N~OcxsXZ9X`P|2WR7@<8Gl z=K@C29Tzx_6{bE>e84QTi-E;K!iHzg250dT2ZGw#o8+oqFo|*~eGqUs!v8m*eV6qC zZcmoR2c8YCHV_e~Il`y`{c-UtF;~v4YlIuI>a}^kt8LRlZ1Z7O&t6^ZuTeFDe z-;aJS4}~uKKkUrfl1J-Pgp}kuJ6MbyJULc;l3$&Z%5L?8(ZVU^e`7(`V$%It+~3vU@sstq+KXr+w5a zFzI4pDsW1vV(AmGIKZh})X041Q>*@#M7BFWoj6S%F!5MCWSLUY;8DcFTCj%0cJXtE zhcXAu9=jY={;kAd{!O*VVB^Zvo=ZL=JC-rp{Zvl)pfQoh!a;fRMm4TQC--sHd~Okn zIN@RZ^SG58+wvur4I%$$G?pzYSpC01G2iZj;3^}BRh2&S%AuU+7z{48Y5td4=Fav@ zu>XO{LZL#jsrE1GPZqrfF>eLd0XddHQi9PF8=4hGg8*rEPM zk@N0}gM1xNneNsuh?YKa=ec6Tv}=wGEJ9Vs9{0GkzWo{}y>aqITeam(v4x3wT`zZ6 zhqbSIZu#hahL;l4b3KN4qJqZF#~aw|ISwAmW@xo`iF)uexleADgM#IohGS_O%+hl6 z{@XJ-MDXrStz>p^&{k(T&dHKcaNOHaPWwWm?B^ZF?fw}~Xx4e6D7^E8n_W@=OewKf zKOfgIRv1lLtn*Yec;~6GeMZwZuX(Eb`g7ga-$pYI>pZ*q^a(?no$;*8Yo3{I-g!2! z&Unsao#&R9cb+TTXFTuon&-Bkcb?Bw<6_&P^TJVh*M&AalSRU7U$`3Yy4Y8@#g5zH znP>2>OVjq5EHhsF(zkfm<$3>1Rygav3Y@&_%4x?%D_1ioKY8MB()Q7WO|0PBeTxkn zBsmOI*HmRrW_!AEh2bgF&^d{xTozp3^Y1v5hNZ+EmIXH)I~zixZX9f7VPMiY@z$rS zw}F#E)k6JWQNH5$Vn)|P%Wev1eseSWz{p-wq$C;p(SDBY#NHoLb+cN|Fxkk=e8&~O zgKJq&pxOZjp7aF@JTJSMcIh6k@L!_5a?KZpwAjlhZBlmX_h=qS{dLRK)m%ulPv&$y0%43|HlTo;j(S{I1_6L2bj@XDR;$KSlM)+~D3|I3u`jG9$-R zCcy*QDRM#6&zzA_GMUTCwvXwmV_(gT?`=B@ejl2hXuR~;g3PMTvJd+6mkAvd{NbQ3 zDf!;+QPml>#vXgMhQcc!QnjKBPXviPn0#(9Ffk>A3tbgJHW??5UEp<~EL5 zUUR*sDz~%-uFkq^71HFiF5&R4Jy`_}{u?tcx79>#XuP$(z*0(9V*9)3O-$-H?skhc zA7H3qZdN_*u;JkUz*SBuY zTc*W$Fnu=jBtMG8bMNfwxzsY=HJjlpmWz42={*!4W53|4&`3Kt=e@vHe z5s%AaY*{|tk1fqWijDEl;&6r!e~YIDF#dB9<1(zAdXQb%vAG-S&sI6Mk?%W5i_<)0|bVMs{J zjZmN6WTZBQWy{;2{Jru&o?q=dD)`^(Km|`t*wd+zRu;G07Tap6^*GjDi)OGsr^c8d z-^0e3%g9uAfMpw(b+-78JI=^D^-{OWeeJ6w0p`Sm7XfVxMI-{|g zw{GRc#S^lwXh)}}ZJ6Y+a-&Kd1G@}E!D{9jYYlJncfT?x#$Gas$}|r>J&{HDN6ECK zqTUlO9+RFMy0(7(+iM?B><+!N@#NiwJd3vOK5FvxluG^?T~GNVw{L9J+9x8$Hq|g< zYB_+S}4{PT8NPSGhZF zzoi{FYB_i&{qxo}&gZw)N?hjq$-LxFLG8C#Z{}W&=#T6T?0P>Qw20^Zc-W!7?#H7Z z_I=UJ93Dr^o&Ndd{d_v(b8fja^6jAfOj1m}465MF^OsY`Bj7;;gM@@hhQUJAjbvsC z4;T(MHYqrZ*)%jJ9-9o>NLD$Cv58SB`ImISq@|$SL4#5R9Mn!Q^OSJOdYLR`o?-Ww zRpZo_m6zLjCLDA5uw_-~Lf$1!F;!bvhffW3<}>)YbxmX!pA(zy>1}Hd&tuuSYi_og z_Q9!lu7s-3Yg;Zf9@sW$|ezHqiCl<4eanU^5>ZHjE6@;r-&t&bDM z98%siN?fgy^;3w?k`(K>9~FM8?Lf7_tRuIoCHXw2lpMP@gXA%hk?Zn zgu}OvFl>DQsx-P>CJ2Bk4Qn~oAExW>Zb{)(+k0AErUvCIx6tPR_p~Wo-1Lm+@24Q@$eS^oEbiOGM6MPF1@2u(g6YUWU^ zW0@ddI=9sAZjWNoj-Z^`nxct07LMB{XEI(n{?IME8kcg*0i#3JEq}z5vM?TO_YOsPW(vRh(}6S4}FqCaFbk%EFuFs-0m?GgMRL zx4lT6kTaF$kRr3QzBQbITD%^5vPNeeFer{6siHq+v+SwKh5+3Pdy zb?rKsl9wN}(#Y0(y8OlTZM*^Z7@7aM-p^dM_VqP;W)6n;UG4{u&tmwzc8g+keP*O^ z#){sjGrZ3H3KQXCZC{epwQ26t4TXH->`RZlIeR3hcw2&M^;522uN+N+M;=`#e1&T++9M7 zS64?k^67dQufD!stemTdYulU9rnJjRy)XOM+}iG$erWFLD=K$)yYcS`_1w7n)Rel9 z&+clMEV_3#?R*oj$R-!N1~hv)pgy)EWpQTsi)b-&e_E-w#!yiX>r3 z_Wpe_mli$g-qBw5|NYJ{zn**$&Gb6P^K&=DB|X8M`jzF&if&F=8@_*r?SbDLM9zv& zwdZItjVonRxiK#>@$E7B!rup{MK|$1V~KU2yOJ#?^_TDU`}KEf-DN9S`!r_O&SngM zn0RNU#KXEnOEVsNU!GebSE0KtLfW@YR5hto^_R!J6mQ;_izZm=&A48oqp2=au;yZ0 za_Q-}?2D3)@Jc*s^xG#E<8{o?U53f;(X^%AlT>e93iz=1>*Ej~w$w|lkza491-zG4 zkPc?%T=smfb3gm)uP!j@qFFL3Rfw*8?)@utWp}_W{2Q2oQ3)zdlIqNQDFZ$sw zS?_H*#~Tnyc0MBmLm}fkCe)sQ+>iDHj?J6`N;q6{jFVSF;fJ`w!qecZ&NBs$vT(|4 zz4&wBvvV7$Cm^eJMg8Jpk9sLzP-A8}$R(#rGK1%N>1W)nI;a)8Ag=q{mB@$9EF64c zmtszDd+W_3Z1jdjYa#fm^H+CIZ(o0Ze*-hOoX?I84-eNGmqeV|;mmeqf~v;d&TDI* zo;DQpnbBRo$#2~P=WaRQU0YsWUJ<-H?(D9uudgpxJ~2;rA@iGS)*Ekqjgj2^{xrM9 zlP^21pNDO-ova;yZqLro&o3jDxsK1CJlB8!zQ4bJe13iZ{Qmv_|1+>@JZNAMTQR@pLuROB#iXY!jZrUMJuXx| zxT-q6DYduMk)2ydVK$ROo5rIKm1!A|IyIK9c+{n{?Zu;RgJT+xdrW3o+-BPL^jL6{ zxXzCVHa;f}#i~Ps?ZHiwQqL26eav1ynH=Dz`E*K%TSM}sW0|fp6Q1z?lx8Yw{odG= zTq?jgHDlSzXR~s)-Av2%{Io;WpRXeGW?I@q7d3xzod?h6H?V2FSWtg$*}`dJVG54k zW#?F)HBC~zahWw;DC_0Y8ELCBI}6_J2%TrS@Ir*XGi*OazFf9u+pAZrgUbHT zQe9HyyWzqLcG(R#`#+>cG|u0^ru}Bakwp@Y;gMyb(--ZKQ(;`PBJGCb#w%gjZ&!2w zSoL6~Jcnf#gM;4cRKL}6+3$9~*mbjUxvkx_$=l05iDa&l_nG}}?~iBPsoRd-Jhpm8 zFPp;q*3`eN-XG*pzmvowStq4=z?my2j4dTBVBH~!^fezg*4#2HWR<9p)H}K|YR$)! z8p=Iy+D@Hf)i1E{4EuOg^LkG4*4KS6_5I`xe%($pn(p)Ytc&>HtaH&jXERQm@Xc%8 z#Q^uWuTDkThB312Q@pU{LQwhJuS;V;W|%V1XzkYWKfw0u;q@Hpvme&*?$Z&zpz=9o z{p?M;+Z5ZKxBq|p{aB4s*0kuT|1Qs18?L#%uSom%cE>S~@V`1Yb7rnxda|NCU_(21 zi|@{=~K&Q|B0J$;Pra_3Biv!FQ@%}yW{z~-|zOkm$RCZZ~cey znlWci>8cL#t{f&$21XZaE){`quYN||{JtUV-@hljKKy+B$>+_>&Np8}KHYX?di!L< zk`&Wsa4`YMK%m>GJZJM)Q)+T~cV8Vn3jvKs* z=C1b%kBv2^H@eQT%aDMoQi`HOfioukU`ksP%|ZROEJyfc?FO|d?~VpnPD^U>SV z?amUd=%cpo|5Oy+Bo{0z*s@S{?Wbw$ixOn+Hw*CStcZJjc&Wkq8=~!{Clz>qEIEG6 z;!IlB`l(S9&s^dua*wq7JgdKW!Hp+H+D$(?e5M>(W|4i(Gc|O9AIrz3KZ=abm2Fx( z<8QWuquWoNe%14Hf3!p?FXsHer0K|+SwDGRoc(5bC|&Rg!$zO0!mOtPdR_)~i#7x> z@J?S7d0ouPrJBCf>(26#10A9#v%m`hDT2wv@T8C$2G6$&jA)TAv;gQ z%S$a>ukYF+w7K!ZAZWn){(ifA=0kYe_51h^vR}G z4okEcr=@;cm5?jyI92HC%3H>3^qb~JT|X7JWn0FYzjt1RYz+zBzDR>n>cr}}@2A4| z?mM?mv*|Vasav7Dn9N)&-mXp(o!xMFmdA#crK^&Cr`$Lib#47*Rl$^}Q8$v(rnyYr z`zBTTccehCsQN_HoJ?nxTi3E0wk*wkl62x!Yt|*_%_ct?(@uO_RljJew$}qU-Q)8X6OHS93t7d>`Aj<4Wp8et#&K!J zxh>1g&%X4Fj=ZGYR1~gjo9aLN*Oe81rZdkT$TCilx+38xmRtK<;$&RZ)g9-yuCqOx zrUv}>Eu zy3d=A`@Zvi|4@N>M(dWNK5g}DtWT%SXmu3Yn5#SefMEQAPcr8|@Vh(wW=|_P3}#8TfC3{Kh1=16JJ*QcyV-XC&+7#9&62l1^v|9C_esJ(%L@;63!4OF zPk*#?R+72+SzhH4kEcAt)RPJgLPr!{h1fZB?^yHo-O-sBioaFe5I_GzZ?@wDR>=eV zwc{pCkB)qicfVq){Q1vjG72{omw&srJAi31d)c@&$P_H{+M#ox2MZ%z8RjY zop(R(&xL0ku?g(O0j2*pl%%NV+DtE>JU#VYxM@*Xwz0U<*YL)8ceYpq2Jhwdzr{=b zKCca*#=&%v^+Fp*g?SnC1y=TsqO0lxCJ6;+*&9DxFa4C>Xcked+E6ZYqw!msfb@>k zlnV{-#ai^$OXI#ZaG5t>a4$B$(eh+jZEt(a=5NtU#A^L2S|d8L_#*`U2Od(>LbKX3g!y{(F)uv(yf<%jkLiH^Jo zrjj4&DHZMQVoLfSSbA=B^!@+QF+rkpl11l~h|Xyhoik>1&f3vA=SJteADs&#I=fet z6h?F{%jn9zz~=sesav9J&Wp6k-|E(?*RP4_-d54QV@CI`9o>6wbnpAoeL$k;kVVgt zh@N8=Jtuy2hhJb@exv8y4K~#eEGIpB{296zZYzzQ(V<|}d*?>)y&t^~B>Em%^zHq@ za^^+PiyJ+wJop%502u7{&y1HMi$*_ zi~2ccPT<-(f#+oZzaJ9{Z?IJt^fOoXa+Xq2NotwB8vixBKV~Eu+?;IpbMliL?IxB}E}iI8kIcD| zF+r7SiqFj{ewsbS1%1wzlZ7*%U zzrY-;z;;1$?j=ix~>$! zlzw1dNoR+RWzXl8v%Xa>+}SbTVB!2b6|9_$b9Q*JF=VK-T9W2nYtF`%~&F_VRr3@ zxn8e&ia2}3c6A$D%}84@&D^S5_rlV+S4&-2E&g_L@&5~~;RP)2A6Qugmz=a%!Y;UM zYsHeSn)C8K7O6?i)6VJ&e${PKz*eBO_^sxQ_bVrPujmNTVza%)+_h^(*Z*IvNnH!2 zs@ReZ*j#TiP4=q3`(hc}$L0D8%LPs@dGK;cEyJq7TRqjMx?e_?2}pHs4qg25WjF6F z=Jc**TfA2HS1pv%Vq4|OeC^n>6DwA-9$Y!^*MucftJG&LUs|y$Y*kaA*V0C<=noy! zT6gsxy0L&+VDWaYmCTB4w>_D!Pg^NJtNZY){-afk9(1lLT(M^NiTRNWM=wizzWO-F z8kU1A_5V-clDWO{QRIf&4GUJ>TD>}JV$?&46`|3g8cN42SWGUg(_KCN?=PX(Ki5mn z?!J4ozkJo^2Q&MgzwGv3uU`cAI&)U6B)w(x!_qNUM^B-l+ zbe_p&@37SUz?SVRHhkoo&zrriA-j9}kKV~Ux~09hD{*eRw_?M^Q=yZbwlzxcv_DzW z!98*1hOIMyu8W+x^t0B^dA}#ONAG+o!Cme>ZPsenv9$vQj&8gXYcFyNfT6S6ISk0LebIzQyQQ4v+uzCB!vun;A>{)tv4a3nr zXKu_nduz|x(|fjYGH$)pK{Ls`*WT__QK*t=g+;~{!!-w$J`5CdoN@=USO)Zz$1H6$o3-B+Y4S5 zTNe0Sl-heyX75?C+QUcgTvW2Xq++YW61zoKr~hxvC7rvM^kN0%b1x`wx}^U<_OjX8 zi-vPAskvP?zkAtk?gdNU^TvNKyTo4MmOZmNe1FA>%WikC_{}~0Zp{^~zgI$HuilZl z!awI?*xsu#xo14?u38zbzQZI_sHIi>AexO_wuCLn<2ed zC-mN&6MJP=ZnT=yjk&hBUY&7G_noexd!^a;*4{I>7)b=BSVI# zf{Kg{OxJA~9#l&5DBQaH=GOFulEzmrsB>SsCL=7Q5Fd7lA?>>mr-My)Bf~-$0f7RY zwBrmv{(ly_VSATpBE!kMVvlqcpXc6@*7Dz7duvPd%}>X#oIk6Pu!uoGSXiUswy_~2 zSAoE7S;l)tZh{|#&d(M2zV{rPAj8eQq3m|TFW27FcXa1axcg3a&sOcrt9gZ4{RFrS zA~+u0=4DXixxny!?%jhI8DvxiG$sf!A7qeRz@QZ(S z6BsLT)?|ZP;RzPAw!vD{VDa=dG**sFZ$451rg{t)YrT-VH2{$lsI#~YcRpy$&_+0S$@ucSy(*=D0@k=r=iW>w6Pi5p@ zXeu6{E})<#$)F!r_tc%0QB!fj|I4>|4ltN2nllSNn`Zm$`mq*`S*)C;SJlyUwQWT zQVxGAO?h(a-5GX=e<5)KmwauW3ci+1(2iDR)E0cSi&uc-AFqlyMbyp;OIB=plPculw=EjG_Bb}nsZjF!P8#w*`7xuiE zxXfdcf%TFY%MDLY&oI_c-z&M$Ab1{&WXG2ql?@jgB@UEzO!#&GKRc_en9GDW#&52z z`>&<4p=$SQhCAG18Vhtxn2vMJ-TSImtH|)0foH zz)<`2qO$j_Cuc6Hp5ZofpIq0$;99|ApmAmqr-25C*;OTHj%H3hD~?7c;a~X+7-XbR z>~Ff0?V!*kA;Q7P$3BT`@p3bfHw<4@jL$Fp5LWWhRpQ{}1r7(L8V($4*IDdo_vh8; zH~TJpyR3DrUO?rOhnR6tk*C`1l24v0i>FMo5$Qf1;M!io)9F5=W)9DZKQ6m-PKc`? z$UUNYIi%Rv?6!`-_k9&6IuV=(!%U*B29$mgxR{VD0`>)^cC*J*95?}nB zl{2D2I7}kA@o4C^&}Mt5io`DMyv>Ofitnx`^;!D~KhEa9cO!X{cbVb+BIjx4DU+ki zEDa}c#@)O*J$u{E#nINK7WX_7_x?;@AiC(=l16#Io$0yFVOAMajO}(s&YwPQ*RvV& zW&bnx74y%l%AUTO?d!`ms?Vx&Chfg8BW=^=xEncj7sGz%*54GnnY-h4TzCSfQJ!_y z?%#gl?`Hn|R$Y+E=x(({fHVA0(%PY(`$-OX>b2ibjJ94P4PMF?|+IfIE&jB z9bV{HRd}UU_wUEf;PSm!t|~u!UwX^m^{47g{_DHS?s?`$rQSXIuC~HEIeliu6a8~L z-S<8hv#ESxA7>Z-z<0TM)tlw9`$^HlINg|k9FmZE;ULwc*zk980;_Jt z!Joa2Y|=dsB=#x%cLr|lIZCLO-gnxjZ%LU1=LSC z8?*(s$393B&!2I$F`?6j=Z0vF-r=QZ_dH?t{ljyO&te8+z@o0|ElNpUn_Z>0EM!gF zv(VKoeJXQD;KZ&kii*1}+H{N@bhMouyo9c=dmEYEcY4#~k8+co?AnT2)eL-gN}M?8 zzv9qDnLEtpPUbTwmpss$YTeKvR_T3yV&T-Z7M1QB7M`DZ6c`v!Bwf~8uwb+8dBuf$ z+AIz*@)gD?PtlU_VKusNg7^QlgbBOvIP0Ec*u|;yzR{)1!<4WYS4mlA{w7cfemn9%8)k~G`! z#t}8Oz-|t!1x&I6-fj{JlXl-}o56hIK>ZY#b3I#97?@@-_%N9=s5>oS6z@3T#n#Z+ z>*v5=V0u~Sa;JCfrpps!BRFhMt2XXgn!uioSiZa$J#aoM&{kQ zxN*;t^Q(+p`a}O53yWRdxGqYtSM-ESg!;k@{y7CgJI}i0WePC&Oj{sk=+UD5Pnf~_ zud^z%#G4eo4NH_%CwVGtI%&xNn^D#9!5o#d2N?M-H1KJN@17K!qAnB_=~R}a$a_O4 zePUG51w9rvWk4qe_|I~_wq zOoTfOI()PYgqoi$Va|vYil3S?l~G{8GReVTEu|}zrM;f{mp?BM2<9(#Ji_VUuFUj$Gdcd@+v#0y*Bp#O4t6&%&FB)8JfLoOu}vsrG8=neQC{Qfvpxx1 zW~)lPo^tqiKVMI|X>DSVMdA<9EemdHPcYp$>CDbxwkIy@8Yg|8Q!J5e9Uj)e%b>;+ zcqR7end^Z|?oP_&{j2P9ex2gqFY6aqF68tLU|#U(hO(+c(95Wh65*Y`s#6r-+zztS)QG&uA9Ka-V$!jfNB?XP*h z={c@&ldxiVBqI3sNb5A?MRTXE`W4Heu~MUNty+WhnjL1_Ods+!9Glja`lF0H=3kLi z&gXWc^2#%o5yjGcpLj0uI&eQAJiICm{f06P_bytu<;gsKH@iM4 zK=-`gzV(`~FNoD-aqd6wei=&FqbLUUobN*hBF2~+k@BXj; z`oObVvBsx?$>-dUYxf_CsXcFFOX4rT%N^RZ{r;Zki{GCx`%-uOtX|23X8usStOGyR zs@K1st^ZOq=-;oYf7QN<&inoKzrpVnQnx%!1wt5)M@%Qomlpl>xfA7EcxK;jwSyR-`{Nw|Y-Umt+ z=?JN)u-6)NI*{tC_iwRcKv*y7W$cB-}# zyMsZS)?v>8)|uJO?sHl^Z!mkAIJ56|naORY+~dsSE=B){xcyDZ1ew*NSby z3t5bgr<_rXwm4k2#QpzWp+jDGnr#)fo=-kjBO{y9bIfdxTi_pemy^ypJVvbsjG|0T zYrU9UESj!WGr3P-TRX|dR$xPsiL-Z!WoSTaWX9p{n&X}tjb<6{OM2{Tcs$SDbXR)g zJ~_qlMxdvri)$wbt6fI>iOFpC9WCw^OtY@EC|58^b+Fh^XluK|Y@@|knbTOc#@(7n z#ph0Qe9Q6svt0VG?0s46bvE)q#*|Ylmw4UZ=ymD0U6(;4p97Pb2lH{4=1o4k)hn8+ z3eK1$G;cBKIhA~9#^EiwLPyPPocTWaFz0yfpChxv$MK+y&lHzQ4KBJ(H66?m%%^>t z9W9!5ESOYJG#Og7-*RD=ym01!>@4p|Ulv`SBXeMh^K@yS$vJ03IuD3HIegfG`Lsxz zu|<=%M03stACrcKhga-*m~v>Lj-PbX;m0YHuBaTmn&Nlp%#^mDn^vE3JMx3oM50A| z$9e4yOeQ~Ag%ungrl?;~@#lMW`tunD#@?M@Q~aOH$R8H*I13l@Xr7bS~10>(+%wB^Sk81A|%)={24|Jm-+l%`*}bhi2Sn_IR^M$e}et;Jn!e z7md9;JuTes1|7?J6~t%NULfI{n9*$VAxL4>od22yOd6F!G73$M3QRH^6at>O^QWGh z9(_Tg@@((wKxfH@swu}+&#w^zFUMYtpHndK;!D`xSZoK9Q@2Qs9I|~1+c-*9g+KL;to7_;a=U9^J zdfCjPz1OzApSK|(bW(XJW1eh-+28i*bDMM}LY05CPwTyGxi(n+0Hf%Qn-?TKCzV*b zODwD2dbsB5b&1@oMIXacwWc(%23KSspE>!4nFDJy!^CACEPP#!@mr2`h90+dh}eE5 z#Cs{@q^}X1McaHXN0=;aOy_Mc*udoF8R>uZnr6mjHG{?}t&uVZ7|oAH2Gw4#-_Vf# zSEka`p-OfcgGIw4+grB2QSZ7Bg-`YOu8fjay0KE$Da4^Q(W;BVp=HKy7Z&Sd30quB z=NS1eZA|eE-eSw%t9sL@H7uj`?#p1tU8TXfXYXoWxSh51^6aU>|AV$NC>Mn5yqfdm z$#F3UmZM+SRhQm5ZhPs3>Cqo4CmlE5X`Fg%&g6)488uFaeujdUjb}}gjz&1wn)hr~ zy#6*~i|%cQTME~8FPm6g=KZx`W@+S|*6`j3cNuRqmHdr|UDUSA((%Wfw7Z^sg%q?XyXAM<;aIOBr?OCPad0+3(F=bR=5Uifjbk_3T z3ZHvVLm#kSyUbP>7uFjWK9@%#C#vDBn(x)4xm?j%yu7j5E%1aKE^{?-#p_0b~BrWEHiE`yM?$D|l&v?z+UH zBv!wx8t1OFr^!oRJ^Xp$MJ}Ez=cOB%17iH-)J*IGn4T)C_MNJVO>s+$n_Tl`aqkn+ zkHIf{uAUcWV7&3fFYJ!-wLrhU3NCzSA{`zCmpuvzOZ8~GV{ww__u?n3D+407*+kBJ z8ujdnzgwW_Q3WyE3r9?!#=E617JZi3b}vb^%3{LI_&-f_;5?SZm6lerEVY!cAw%uF-;?>)s!!FHrL&eDs6LjS z6eg0qF6qDE-jwBMQ%dHgHrAz;UAvfeMScOVkCej$mb4dbbr~Js(mL<0D0uh$;O>mm z(JOntt!w;uuSw7EvdJAW-KUexGT*PVnG*N3E|BJheo_lW2dC`7r#)f^_d(yI4 z>mXOV6AG^R6F! z_wwAi7d;Md7KG(|DoFL-x9h*DXO?xf(A9g{efu&eoq4L-m|nLe>(;urX>me#+%oUV z^>gUu7Uc;)m}YoVGTTiq<8j}y{pUJv%nP6QEmK`AdtvaT7j-XQ{u4V^_wszkr$-FPS zWCZvNOZ{5fbJL$bdHHPF^Z#rgPV6j5JXhdb`TkyB(9dl$k;~ErpMP9w_mR{6{g=2x zG4rAsjeaR@`9iTp($`bi&VQ6`|0MMNqo(`&|ECm-LXQ<4`C)s0$D{TC()rKJ?B)Bs zNv)VU?VUV(k%@n?So`ZkQ!Y-=n84gosOn#E_NHcA*mGmKVw?OO+0Sz9-WS{N|Fp18 z`EzEDq`$C~__p(gnb+z{_7+<{omyfS{_%9+XZN(vhYh4H*1ta9xBG(O`%Tvh1GjHJ zYxXr>ZWq^inb7{EF!P@8YvdwaKUjXB^KV_MOTm}u`~x@LKCX_-I`{J{8{fBs0*du^ zxi0^|q&$CeD(~Cf;FQ?=rE%+v;{O+CFXv$TG*9LDVr9+1>N~22OBv5NGV)bV;tVfK zd;U?V;mQo-&;R;zim%ILmERNCY!Sa|eg0>^wCa!Lie;x#IsV_i!L6csQNv-q?+1@F zGaG~_%ZR*x66%Xv6ZS$0jwzryicuyfY{0U~X2(_Wa-4(h=QRj9t5ot>yw6dk$I=lQ2rj|NrFU%eem>k}GwDCj)Az$==$rt!z-+ZTA>a5enr+t9^1 zfnoXeO;Huw^KP{~?6NHU%+OoUeK&m)cXY!$DV?V~8khwd=2~>t3ox+mU}$q;TstTWHY>U|YI-db^NRNQUxSvgQUtEGilu7RmlSOY9 z8om_f4YlvRb&Pk5%bOhn3Thk+HXUs3l{N6G;1E8#pQq5;LSjNg?o(#Lf3hc}6Lufr z6J##Q*s#HQk=g7MC+-EWX6^N#9L2LFaM83k_e@^Azqz@yt=?n}`-A$~XQs#38a`-t zVBj=paAM(*DLBX?Xu5#Gg*&Byfr*pnLPG^pzza!Vp(R`9cxi`Th|Oj;C^%@ZD|OY) z&!_ehQ=smOO`hEEbgw!|2TlK#D?m#JbvPQa$n+Uu2o|>%Kac z|4$HLOquv_>Hq1;brW4A65ABnZm@P+9t)q5bZTk?bLpann+$vb+YYb`y2?$TH_rP1wwMnEL%?S+|qH_1Tjrzq2u(hX7(NtOHRr>|GMK&MLHXiLYG7*Pz8Vf8uj3k5y--4lpv_=(-us=VG$3Quc>b z1B>}Fv7C*^k1fktb~f$P8w4FsISvU{|1T ziIVT0-|GleKGS6|w1-`@=WFYd7X0wwwcoVUcmKLl`NUc!&FUFP9;Ps>n`idt1hXN7 zTk4buMb3n?g%?&b99z^P_QHcfD^+Cy(9Y$A5c$`qpH$)nt8Yc974RdoH$? zw(k;)XU<@;6Px>iSHaM83->c8F>S7i`T-p$``N!YNdM1K5;o6j3}{K($`F$hx{|4r zq3wDj*WZ&J8Ws+0HcHbNT^8iXukei2`*3)XuEKwl{NPFJRUS;($*Le|#WkbmP4Y_L zT7!f)Cq&9?7s;8}Pg^|WjiORhlT0ry*v7-X~(lz&c^_9(l-xZ;bx>O!fj z_I3g*LjEphl3j4j-%G$CjWN}H`kf=HQ=Qs;OBPfHGWM`cVmY?jwIESJn}_jkW~IYsR4PFG(FI9{4t+8L7V{6Dmlq2bmwdB)BzhafNM zH(BAk$`qFd{L+Ycmt;KU-cd^x)iv&#T}G3xe5};_&FwC`*}XqVOrQP1qlBj>4XcW_ zZQgWHGs9W?*2PsWYnIvS1`8hG_1<@U^SaVpZlQ-A>*rnDqIpO$&vW(dyG<#}O8)8= zOrLb~)TA?vvJz(|cWA2})$89V(R$DAvqt38AaO6x>c?sEt7GpKm2cnr_g%%6Z~YPh zfxDlzW^(UZ74kvq>$V-K+2xN{3*BeizVFc5chb@h!hfduq!qhNPJYi}Jo9YNezs+i zW#?~4{xp(U{)SaQSoWbe>*26%TWSlU9Dm+pGoG>U+uHX9{5p?L7<|`fIJe?|gYxYe zC+kGE#7P;R6zYlNDqgj_@M%G&Va=`S8YzcN&b>0eE-iTC>b1Snb=n!M4NMD*zBAlD zqFI|9&?Ns>^hkQ4a?j>D;{T(*9Jz2*xiPXv{OS(Y9$|;Z!+pzFygD_5ZDs&#@8i=A z;Z5Hdcup8(W=o5Ha5FfxQ9-Tsp>edLI}4NcrnM6bXDniqI6o)LHMGWPaal23+RyhbJ0L6D%Q-7}LR#pu@UJ;hg%u1x zw4&B31b*M7v#8}q*1cu5YQDwE`+S9L$I-Q0Z?`F!UtMwEdTo?w?UM;!toxq+ zQt=4>|0FEnP)bMFvizmGx$$40yel?~E?vIzcjkeXU#FSp&2zUERuid6Ns1R-{&Cxt z3F`|A88toA*9CoAw25OGv!F*>&b>GLDz`ms)>zWN-)^GztTn5eI3rK42-zxB>UJPp zP@>|O%%n*TZVXl`8=tylojkd>B#lXsL)^tKVB#eACt6%LTK_PaT}=urvR}2pjm2(( z0Oy?=XThIomohCTaXF+h7Mk*23OS^~p74Y@=danCtdpu?n~s^t~9fk1LpEY+tUkxSyu@;H}SQl_gW#boHeiw#zJ# z;=YAa{E7@MCSjR1tZss*<}VSN#G#O4(Zqh# z&EV{Ll|L*^ZyG~XPpg;2SgQ(7{BV2G+PL{L6PB!;bdEPDMkO|Ni;#FKtIG$u%Z-62 z*ZT1D&3{tR{wqdcw)N=6TwX}Q8`q2Am?#0u*hmuMKCV1SL$**))<5kb)-V^I26fd(d+*=XA zvx3PyfkFIWQpuKMdO?Y82?r7%T;RKTbJdg96*UV>S`srCo+{Vy>P|TEcICnig)WIb zN=pwU?)u98?_#1%kd)l94!Jr8<*u3XlWyE^{4Z71DWz!k;G>NKqw9hbJ2~J9GlCP3clvHqlogaVO7l7R3f84yLfTjHU{Q-)kP=cVu8KY!3)}=%#aE z;sUpCxkrTCj=077nYlUqT*%eJk>J&kEaevPzlFWr$3gl`{NKo1R@Y*!E*+1!a`gWV zmXsZCwrbaG*%ryZO?J0@z}mve7H~%8TTfJiPnyEga}!$9R;1W>w$Iz(#-0^-WaDu= zR-OfkmkbkyJXHb{Gh&ODa4>tgE9AOp7qV2u9ML!!$8+qz$AX;67JvI)D(*8nG&pPA zP3>iQAKCGp)q%|S`o^6 z;Iu1)o0rXzc8T-BYdGFaY+-p4dYiRJhRcU1=92o2L^YR=#GKCX1`qqKOQZL7&OEU6 z!9*#0r5W*CZ>{DGTf;idOEGLs?9{M}Az_K${&z$CV#9d47B5uL7Jg0wi!W=87<$YG(8iEs=U2R;&yH(Z?n>i@DEeGGI}>oo&WMe_|&^s+h_LG zK3iTT$FHE3&?P80=Zr?_)|>rTCHxA5G9E-Uzm%9Lra5n~cn#;uNq+xtwbw>;ynMm< zFP7=rZKiv-W1KWoKfdU1y~dcpFj+UVTyo`%I&nWm%^CAv)|jrGoTOQ&xS;r`YE7Kx zY`Okv$0TPpy`1qZ(`CxanfG4KteP?F($&f5G|!%iw*E0(k;R($qXjcq?P=RI2) zC8{|;PkM=#<^r}?wSroOXJ_}GdRlVy_WP43XJt-X{OtHry_YjSiA)SzwRD+QU+Su5 zYCOx!UPaes&1qXz^XPf}UG)(S zS!-`8SC>gn?UQ0YwQ`OI!|5I&S;09`2UwX{xGry*G;Pb|D4UMMl2<%Lvwa=4+h0bk z@BF{;<(BXa%j~_UvbUVmDhSNp`tABwrZ_vPwrx=>*M;?mZ{R8SP-VJuG(F;eWPqz@ zN89ul{J$tLK}LNdAla3zS(_9`{cRoT-(((oo`OC<<@v+FIbdwD(~ijCaFbDSr29TZy(7y zHbuL6jqA?3qi3BO9H+6fDIA!1g|F%0>VBo%nndaKvTHLWb#E`y&DUHz%P9Ahk@##D zw}W9TyPj%Xt!up+mXz@z(0r`K6Hxn+LUx_M>pwH$yO7)4x2oW-{#S?<{0~wtjDu*pS_wk z)9uKg}<$C%LjNUwW~YO|HvR_kNW~?K-J7d!^SiU30&g_u*OgyIFpd zKIygpTX!*ReNFDWSz`JpE3`h{x_$2*t1a7_$MdFt-?eI6hi|0OMG!_&*{hzGz^zd%D?g~i_85Z|KeQs&4!S3H^V~`}o7g1vGapswb-S38z&VN zC|eitI)9q8F6WAqSfAJ@-uO>V4>!(?GHgDZuU`F$so7|@*CyfMqF-U!TE$_lkw{? z>G?S(?4P#Y(><;AK8~SC{f8m%i};dRgsG7ct6Xp`>S7k@y2ss+pcbn z4PPJiyi~^Bz}~s!W0T1?hHVM_X5o*wy7_NT-uNZr^Ou!OWx>wd#Kix9OOgK;CtjRX zZL-{`tnQ3yy7lJt_Rk^T-@gc(&hzGg{(=Mg6|Nz(x8KZ>d9`kLQO zQ!@|R|2d?zpixxEyf~UsYE2{038TE@$K>+A<&|$s$zH?w%9HKFyB}Ve3wCTZoczsw zVMCF*xzv*KDr0jgm+yVEbd@KEg~V)U|Gq83Hs^kE4GMthe_Di?Kb3&tdV*-NGQ* zv!K?bRg2-cpAXZrBcXG@a86=7=5~vVVQ)*6sRMIKhnK83ax&IKcA4(u1MO z%hYnd_LqtE2I9qDDLOw-aj>z7DD)T}@N~80ocy^n-t6oF#%bCN)2CbY@H-V-m!~Q+ z%zW=I^1|gnij!{71NDjn>VH}SPE@IFiD&A$yzZgcizlqytCszmGN)*T5BnakVxIHv z50>mVJ)Xewa#Os+W^IN|t8Xo?*B9?UxMY0~OU>`?=KifcGwO_Am3#kOF1&qCjir=F z^UB$~nwI~sYOWSxdf@zUORV%8jv5h{{S^*Jcd{2uX<*Md`c~m(!U3-deEcU^5{eso z=HGu)$EeD-$qz7!QK0-t!s9Qo?zIke%Pj#set3C%Ad1;Ickpj9$MoaDze}JpXKVZ4?SO7T~hfj z{5sm&E;3<7#}SsA&TA$PJ6>2ar?9YexXi!bSGu9~xbrWj?Txn4*G`!Ge_rFWSLR0k zn6bo4>cfHE>JElGnsp1Dd*(CWGhXX^rm3E*Rm-PIYX#HapSx%0Iz7_f z7q;S78|%;0t=*T~gEL(I+rHT0!TPase)3)~h5*)(N6$a+T-))tbjseF{P{{3dbZ#0 zVorIG!e<*@QfXBL~UEZ)hq!&$zE@ngTE&YmAaH*C(y_xsK{7<%=5-IcZr zyZu+ccl;e#w?33Dn0LbUtbLa}me0TM^Sq+<#d6{3J3ZL}HNGybuO{2RuKs*;`QAA- zzvCAk*uHp+OG(=uZGIMJ7dB3DqZH5xZqDWnIR0#e9SBy z5(WwlOd|g5f4U1k913RR*3etQ@bJG=(<&h`2Zd^e=B9doPvl)kzQkTC?DDgQ0ohw!o>KJRc>T8};U2aXVno z=Cjf}-uo&e8w=k&wi$7yOe`$-SuGk26b~>wV4vq@U;lT0<(JTA{)~dX1$BRa|NLx! zM&iIRp^s-b*Wc*Q+{I*kc)^0xA}nXOUi6AyvEYh8P={iBOfbW?GXXQsa+wIdU@*T^ z63ywtEwUz~!KLFc=P`-YBP&-f`?Xu}xJ>DtiT~aA{o|gn(JW1|!1Y9j&Lr2Kuoi{c)a zE{^{pb6}#GK=J3Z7H@4tuZFxlQoPaZR>2bw`_#EpPj)c%UOMDtd}qR8Ch@x*OCr3a zo_A*)dE*?BzfIDe*Dvsc@RdlH)TwREH;hBC=DglA?RMVYzhZX^j>>*-5R0)DzgvC+`QjCC`LtC$@wf(~YQCFPa6+6^LD~OnjJpa_dR%1cBp$h( zm^)^H|ls1=>k z@cT))ldw}?o!0L7ee=4{}vCn?R>tN$%a))ze#L*>7CpZZmkaztezDHckgKgex<{Ms4&Glb$@-sAo z_?|LJe~5h2SR|-aA<)pk8PZyFfA#^EsN)U%dJ}7SJ}eaFj69*uX%_)TXBk!X)zc5w znzW$R(gn~z03RQbV+ZmL5If?Wx4tVD*9&_&wFe*D6q#+ zZ4%!D1_djJLa~*}vdh~gd1BbixWC-9=i~BbV=0*`zkW%1Pr3?n~(B2jkg^d>?{0dhlE=>wPd-CX2 z2K`law~k9)Na*0xN#I=KEgW$)Dt!08-Zd;29|d1NX<0rqPq$q3O+x1M@L8X(w)U&M zNs_caS2SsQT~*Da$E$XGEmZQ~S;Gr!c9vutYM@>#%i;2S5qufwF5YztVG zOv*O)+pdXJogMa5R;A2n=YooBlMZq|`Nkl5+3>MY^?W~=W8|JV37Ybt<(KaT>81iJJ?*_D)}wo;7hS#RnKtT=~B=nxMf3Y zeMaK({|62>$G&Ef4ZnWOY93SX+=gaGi-_ad6AN4aC#EQ;M;z`ra+m8UcWCdl0H&LJ z7PuNs*w1X@P$*C^q0wl;O_o)DXD>}*VEgd<@zXF#_Wv6;imiwil^14Y7u#X9K>JdQ zwE2wVc5)28KV$CJOB`fj&e*g>^HQu7{{a^MW1GBBW(D$9&)oA#`?LKU?=<7ZGrP0- z-gTc=7yrS=!2Dw6<_Yn;TUWigdBVk@sb9K>`M>gs3U2qgAL~T-u>O5j!N7Ix(1g-A zPc+N7pW1bAOAxPgoZGc;r$0(Nu>RlZAtbHQl;85m?xFWod9{rlo7Zis(`I~tySE^`cf=r z4*UC}_@CuYIrGA;^zJ3UW_xhlH;Uzj$A3AeVsAm`Xp3F_W=4EI&$F$icPN=$`?|Z- zCZFTI#Z5P}rpa5QpVrTC*zbF6=fSOSuFO@Rwn2Dm9Pb>1#7kx+}dYe zuB(}$mykTQ>Ds@)$2;u{9{tX<(&e~kZXUloIrY`I?D8GYCtv^btbN(%)6KUI&k_Io zLiPH$O;H=-7M!$wI5fe|NVYa|C(R%&(FKU zbAH~GzxJ8g{_lzW|9@82{=Rwa|IaVi>s=D_FMoV5&%o`u?Lq%Ordi(&qEp9!r0 z{SBO?4teOzNQH%FPgB~;1LG?v##k4nKk3b&3+ALn6=3RGLNv3k|VNHK4Un8_JdLd z1|0=K9U}(DU!01&G?}y(IL|7STey-*X+hJbAg%Kg&k7|lsx7e5N>IQ3*wgE==Ch4w z|0Q1Wd0E&q!Gp^$t#lS3~ptp~`UVv#DlLF&~cEkUU#!ri6trs)f zF?96^HZHlyr~E)Qk(1f#K%dg**kjI8ss}7&4@ikC99j3s_}yb>HRddhMasX_Pb|wZ^F@{-U4;DxiGw2!FG=9|m^f{LAgx1!z-1!0WoQ9uIO+2G^V7AI7Ev5yG z+ca$cACz|e!tC~z!D90nwg$uhxtp|FR_{|g6Ne*Ea z^02XM(6&oB8}><_#bI+xuuJD=W-DgqYl04r!Fpcn`aZR>?vfF%hGn4 zVr{yb`JV8L4MFa;&zY7T?_W}=wbrSQ!T8UV52u+7t@Z_~hpIDqUFmqVS}Ih;?9L^@ ztIV#vDMrgTxlYz|`5&nNbA^|-lh4``eV56aS_-9N4>b86xEn6eF?06PSCZ;C@ob;$ zYS3VLJBP7|-9A=R=INu3T_!6h8Et6$Qu<%!Bg2tL(#Jz&ww~AGQaW=bq;%UN1s zdf}k?PNr`~9BUstFFavVzTE$Jh{BAk(##*3tPXpdhPdAiHPTban)WF6)>N$VkNn?F5rND_7Do#@P@Q2x(RCNr#7%)vrBK`BuwV&WCm=^|mvA4eHaHovLB52z%u6nqCr$PF{MmXbyZ>b2MxTq03&a#dM40EfGbZI; z**96OSX5_G(-pM_xzsPQGA^ZiCh}-IbZ&cLHL+0Pe^IcNm4lJKm^AZ)pzBUzwuJ|( zKJq0$m3(h#Fq?%Z5 zCemL6#5PVd(uy!-|0|9F0gx3=YPHjk!%&F{()S*p2po% z_nN$pWMQy4CD3?{i8Kbi^XN@!Yg4w&fu3h4#4QrPQKNOsJ_QR%AAEJ0B^2v)U%Y+#u z7s!WikmtN6dHO?v`pt=*EcF)O-<|s4VQ}5SZs)rh86BcW(*GK&`AD`%|Kxjru_<%s zk1=@SY?%cma~oHhBT8u!)Ns@rBSt(=*hG|P7JRPkv%{MVjyeCE7|GacGy&U{%pOLgm~O)sYL1plkCEWD~VS9{Ua zP{HEf3A10{oGQ3$PI$^(ZuQA$XHHkOn)iO@66>mIud|mbino8;x%8^xviO_xwF{T= z&RXUjwOEmPda%_(7lgB| z7B+Uw`5n2LZ~O8>sWty4mo1i@n}2J`$AHEATRk>dtr4zTY5aKgLMd;FU##2LIE5Dc z2K`H@v;|@2|DJ1)G0Y&1URUN&3rqrh3TjpH1moR?Zar)NIRH@CE ztF~F*USli0OMLqFm9uB9l}^=2U!fVgyZg30_v{__qt`vW5F!w@@9*rXwYL|ynr`pj zc~I772fNIw$+wq^-`?jq=g>o|W&f>bAAJ4$V0*!qt87?tdq;=}avnA1H#s7`7SCs3Eif_C8Gv@fi zm=o52kJsCtD4sj3clL4VY5UGfO*<>K@xAqB*S$C5qmCL^pE|jF2Y>k{-oOLC)+0+89L9f3uq&wton@z@t_CHU~A&9!oCx$g$gjz&$qEOSSF z-n54W+sf}u%&uEzcvoO+;cDl)XRd}XTI=rE*WIh0_j2~lC#-jiKK2!^o%Lwy-RpOE zKi~ZEgtP3t{=0ix=iX$j*?c;d>wMkizq5`A&b=!f`%*Z%aPGhTr~j-_{d05so`wHI z?=}DLf1>*D>gwHh73c9AUcVSAeMIu!?VZK5r%M_9mplFb_v!1hFR$12*8Q8D-1JWK z_Q&lz-yD{?|Je2Z=4f`_Yi|Gc=B~S-=@Kpd@!glT6Ers!+*p4&WI^kjZ1=CJ_X|Vh z1thPfvzlj_pyTSK}eP1Fk{J5&ZHTMtWW47{127hPlUlIM`r+(a7 z#Ru-JD#kPIFto7~RsabJ9?&WQW=h^!ITkqW4-XDGk-!-`r|0i?8A4zNJ%z}T6 zj}C!0+W)cUc-YL+D6SO~z+k9!gn?ZVHvjFxyzWagV8V<7ankwJ%l zfe)wgvNJPH{%4j=kSJ_)Ze!t93OR8>@mz;Uwv=s`$;wMJm}lwLa+$6=yO68oolL|A zr4}A;R<5N{R~C!uiu!uUdVX7TYirK!T~n)ySNOF>)c*6nvGt+z2KnU@icQQpUf^n2@_4;PgfxwZ-_M{UcHJn&=w<>m4B_u^K{kN8vJZ6cYb<0QQLdY=>Vr^yB+qvd;90(cSo6} zg#wCAm#h4g6*We-KKlv-3Sn<&QkDB+-Ew9iB7&RdPwSOn#)9xkk8 z7ffv7(4{-;!*sF5Z`J2_`OSEK&tCJw3f2rYhO|2(>VAzfEq->KsqR8fC6Vl< znW1yzLNpH~wlDho<9x#Vsnc&%YVoNl^s+B~z|y=wbNV7yzXc`-61KDcxSJ5obMm&M znnmCZvHMFUmvt|2?aWxz_0KppR5a$Q@U+>P8iCU#d>t3n?C<29QGR>v_vA{;uq)3T z^RLa2Sg`8mPPv4xnb(pd_g}3Ptb09mN5;BcM%Q1YGP$mbY>SSnnm9@2*@=~v3sx3r z@Fg`xPOLUQ^kZJvx}fd_%d~!6T~xoxh*Rx9pW3R{@ zZ+q;r`>9^J}Vwm*d-RfX3DM! z+oac&9&R$3mb7K5=%y3vc{2mKQZmJjl2=ZR`IMc!blIkpir0B$j#<6;kqq4zR-qXF zK8gM2L>u|uEsI`VVto_qRL!HG;;ZIcdUdj{ZUDP)GKcBKuzKFI8)BJrzFvzg+P3gU z*7~_7H}@C$++8BS*|+@e^If&6$D`bG%kS6OcYa-exp43H>-F+|6-ABAZ}YBas`FJo zdHKs{;mxAYx|Ppnskd%_>TJKS^2MUlSAIT75B9q9V%2)Sp#Lw$=h>OR-gG`r@LE{a zyNab--s}B-6tlN(=fgex>vz8H73Z`1c*_0V?w1GE_t$)>H|PKJe3y6s-urpkZ})s% zna*!__t@*+Z(kqImpA)*eENUeU+==#+WlPN|E2D4ZZO}=2Tsu^?f!oIzkc(_qi3uC zF)v_z-)4U%M8Ev4^#W$`R`ZuEZ!4cwFJN6PV!u3aL!3%a~sc7t=TJ12i9$xZep`kyK|;%a+c6cmk>4mnJ1mo#e`;E{GX@LQ=6SZ!QCUzj8(ZDn%qPdvt4#<$=smxA zjI1vi-~PYpH+f#k+^f6O9YcTZbK6z2*r4>j-SNoFeM(n4RnKPnl;1j)^UI`0gmpzg z`z+i3cY-S)SU-1~c`Ib%xl5A+w!Y*{&Aieb7Fy}0`ZR3u^DFb}MAztCJtZ^WDr_Co z)kQyiwBILqTw1(qYFR;Urq}aTVHNv&mlqw?e!a`fZ-3jm6@^zB{T@7f&G(_Y&SUE9{ltdlQkef#piZ50Po-xkaazjIRV`?hHZ71P_B8lHOPF~qty zc&_Wd`zEV=_no)v%J*~M`~RV$F0LQjj`O64(#&QUx?T7hw5OXMlLyh4vlXQt}3N* zpf8_tuEow{@#pIMyyk4@qwg6xDkwZ#`s4pjB}tA03{nP+ECvir-FXa)9`mHJuqZIk zku#WCy8Cf&jn(<7+e+AeteME*qi}Ig+-LV&XBc=EI56=#aIOw*e#LF6aJ4+rjCm8! z%S-dFo>RzVSj!po(lR}TL875y``)=P`?geG5SYQ>WiX*JKb?X3|BnOSLT7VCCPdx7 zXt$|P^{-&IHv`l8wuX`&hbHDVTdT}wGj`o|R%n{ck-O}T-{;(}No9FH@o3lAj;+)G z2vw#(xG3?^Y?*6NK>&ZkRT0O}hqRR#%d)GP{!H7^_5Anai4DJ$m=hRQnLc_FFaKL! z;D7ts-+Nzh_RPBSR?K+U{x>(5Msgmwyza+j>$;4kDN?HcRsXYd8U1Z!PH4ExezIhi z^q&W_p6t0WF|p#f{n?LI=F>Ahwtd_gW?N?9tnltQul1!D??gEEOFg@y_vzf5ci$Dv z%agyyWj*-+?-Rr9pC6t%WuB4DH$32M*YIDas7GD?=aI|TKFn}mu=n(vZ|Ahvp7Rs8 zT{MNyC*tw#FN?2hH<-Td-mzi z3;P$b-Q)lN<8=MMFYEXJec%88&+GdCf8Ou^|DV5sQKNy`qk%P};p=pU@UMw#V*dr% zUo`M@Gzw}o3VSq)W;F88X!!7@W}0(hNMWNqN0XvPld?yX>Wg~G9c8JP%Y+h}bYC>- zb2J-jG=FSwI{#20VrvtxMYHvZX4@Cd_8s-67K-xQ8Z0>k9XeV(SG0I@G&y;wn$M`J z*{j1@oHt6sFla8&8+=qR4Su<#pW z+>biW0!E$#4BP^plTNfdA8g~=kli(-o$~;L@Pr2W0}Ol##oQAZL>n09yl9vGps_XM zf48VYcj%1{8^(6WC2gD^7+o(i_E<2ks9>B|CMeL*$>GpdB*9qnouBbSkN1x5G>+=b z9Sl3l1P;$=mu+C2(NVnOMQ+s&h6_LVFRy61x+7|mNE;VJ?$*Wa9WxjLOc**V7+!Sr zUa(;J(b1B(BeH)*b2vv&^6}oH6$~5;7$g~bH6}3d1~67e^rau`cUaLUbD}S72E#px za@Lb=H&-y8z9z`Y$tbY0U-)OUXlBc_h*oX|)yf&2QUZBWf{daU7*sSTO8o4!C~OsM zU?@30G5ZE155weM!^y%4lQ*SJvfI(Ee6=-V27`IKp#91=!4JLfX9zMbV2Cc8VljP+ z?Tdb`8~>-ouVCPrP#iR~jq?Em_XS4T#x4#4#^{~7u_v3a-l!AjY?FP!ur6_0rUs*% z=d_(-jN%Q9Pa-B+{H$D6-jc-8vua~+)d~iQ3yjMurcUf+5_gzkzOvhrvomwXRJDoS z#TASje@sbg6XH#1U#vEr=_Lclfmv%`&f=Xg%eZ4w;?6d~4>R>T8RQGvvvx9YO_<2H zpr^i~{jlep!F}q!jQMABXqk&OeVD_n*eJ5tlxU#d2(I>PI|g>-s_iBRi{mQ!#V#WXLHHPhDVz7zjw|T^O*l@vd7Pt^Z!dW z{`Q>B;I)ADS3Q%|yqB5(7jU0iP+c)2=teuQ)uNLWZEjH9zZ0xnz^wpwQ<)RALTB8pwwtcnOo@ulGGi2i=QkB_F5X6wKTkIY2>P<(YKZaM9q)kT9&A_ zEZJ*WYSuD;uGX|GCF!S@<-S^$&$Ybp*W!JLN;*}Rmv=3%T(!JfYDwIzfJsM|H)^eD z_F9qrYWYIe6|JjQbe~$$tF@$k)`C8*m6N?zwsNg}5xH{as+F^EEt`I7;T*123$<27 zb1mK3vT||Ps+Fr&)xL_ExNFsVuGQ;bt?Uy2zj|xd>XlZj9Tp~TKec+V)ykb&%lB!m zIecpQfhyM{U29HqEk8DE#i>_o&Py#zU9or_*V?ODYeT)3$5*YreQK@OFXLOc);`o) z=kRN-rscZlUF$S6dJoQ8_f~7&g^i8RUak9lYVC)p<;T6&|BPCDMr-M>SL;vBTKlJS z{eQ0whfb|m3faJUd-YPS^<1wvY|vV}Us+T3#|F{r)!(uv{>|DbExk(c_68a5O*5-D zIErjijb5poy-D-*%6V3dT#g&HxHnHawMp;x8pG_()w}ANHWv#Yn9Xe%d49^ zVfLO(@!2mnyJ!mt3T*L}Ua>`j(e?FabMbCZ=^3I2w)m~yT5(GtaQ6Q#O&Nmm)mtNX zZ%zHZjCUbJ&~65qa>jV;Eot1_bF3!wFWmNU8e{xw;a;Kb)vK2p-(-w?y*WEmP@rKu z$La0u-b<|n1o8Fucl*g4mFY4YlAncw+$yx6?$ z<(zrlyS%NoXa3$QX~-!4f$^MYx615YTVF5Ue7bI9wzya$qo{*GT;tYl+IyT5-gDDhWV5utb-N(PgWZx0d-kr}`*gP+ z)51L`b_>cc*uxyXZ?5zPQ31D(2m$$T8c%-jI~=`-H&CGXxFEa0K9`IgKf8B4jbe;B z{$G$UQs9s99+sHhN4>jQAMTYFWMJI0ny+TZ7LDBzfsD6q2y*>l???k~h zd!9uIG9K9XhR_;qkh#u*<=V%!ejCfYIcywwbpPoCg?o;3c`uj!aCEn~ zF#Cl$O*%*Fqff|GpXl5pA|!K&XU@@yF&i`f)Wq*O=6Uc?{G8)6*PLXPW{iDvG^^); z_r*iuI^N%-PyMg@zx7i0i7cK;fr7i6Y)+)goPL}oYJTHn)|-O?8?zotA91odvbSd4 z<~gQ5KTl>|JaOE+#mVQ?sWWSr$8@XP1m((1PA_B#)j4&ZeNS%o*=t>AUmvOytl1Or zQmkXep5v$MPWYUAI%myupVQ}T7z4#5cS`PCr*rNm&&e-mR&Uche=Fwn@!JPaeLS0c zNBCvW8FPz6OuegEa=Xv|Ic6KkDAZ6ARG7-cd!DbhTi$I;p!UTnTsx%ZoE7u!p7)1w z`x^zG1-m`goGmHr7I6~iTYKrz98--c=PtEhw$!b)(LLw*_pCi zW%qwNw^94*f$D3ex!22kuUD?UUVZj@?c3}1yf+$kZ#4Vfs6Bf%?9(}pzlEKjuF1vj zcbj|lh5F4X+nZB!Z%*&MIn%e!?d%n~z1O}@9nL*w>RAt=sdfx--z)v!vfI)h$Hb=uLT5sgg_(1jC`h>%$a!y=+;mXE0FN|fD zS#H4gyqoKKy{G8iS|oDw-reqLdH42}K2+M8U0!g1N9)&5tam==KRi;wX*|PW=O>n9 zRsZ=~@66a7xQ_iumz;0$@}Si#f>%TC)Y*`HdY`+1Y|eRD5H; zFtYG$;d1TW3cpjw>cSP5dL;{kJv+Y7WIv_%e_htzJwHFcxV+jt-_*K`;qLy^;u3b& zR~Lq8JKsRQQzzp=BZu0G2TeR?FCH{AN0r?#5@x&Fz|43?{eEHB#B{|}0pvS%qFYZ^ zN{Fy6%=Cz2__6T@L%!Ct6_5LDzP(WH64rRG6maOIL|BDWSjFRhAMl+z&m@+#>MRmn zJkji;$I>uwx0g?+C$woktMk-4?l3XplnFyoNL>5l83o5QpU+W_dlo#^U|FZYr0c#n zROf>4)cIE!`E+X4CHL^we4$q_7Eh?NOzlofo3Vm{J>W&~;)!LiUM{zkS~X{BX4DtV z-fX_GuItST>0?jRx7(F`+bH7v!AxhJdDyetZ}uJJ-PO=% z&QR$%>GRqASD_9DEk)aerBvRYxF7cCEo0X?v6swGd7rK}iNC)2`Hl4b(w}>!d-9yb z?(hE7c}4&D-=7Ut_a~oVQ~k4L`qSTi|Na!7^4|C|ym$ShkM)<{*U#9wc*b6@Z~K`x zovD0n-SPWN+5tAZGle`0t~1P7X3rjFkn}3MVfzR5{oGXoaj&d{9oZ~)^3S>u%aOvs z#6!li97YV=C$u-}pINw3;f5^VHJ^5eJE3g@DnC z*nRDWo5d}|zRWob9d2-Fo8SA`fBCdB^NSnq4r(4u9X?Bx#5Eb6SJr&%d zE=`)!GeJSMQrs?Nsa#^*>ep!gJ7WYrDfNhheZoT8L z^Hqbv&d+H2_9Ll!<(4NR|81IfP$X5abhBr2(6Y?aK4}`$B@aJ7xoLV$^)hvP&9mM? zn={WpS;}0-d^V#jc;e}lv{fo56AGi2&3R^%Zgq8~^v$i4=iHvN+>Tr1|KTd5H}l?~ zd2YuZVxFyb8u_C2E8kN(!DOvy7gt5#o3j9<)<#s`}JkH1MjMUcCRaoq`s~& z*ImKW-RT?kZ#hdq@2ZgHURPH|eO(oCc2(H+Q&-njeO;ZvyE@{y*R@TvzOD%qZ3%jw zamJ60V{Jk2>X_$V*LU6ex~`&kbyVfmGs~7uv9IJ^lfdqMLr@TTp%7iKf*S@@VJnGx#1-xrB+-HZL`P9C#?(Lgk&(pWA?V7eJ{e_eG zIflr~|6grdKd1CfR{ZJP_qw+6c5>t?-7>glCh&b#r$j^1^w}MUZ-vdxIjZIr`da>e z)c0KzeObzJtCQ5eKoS+InVLj!;)+lTAw9--MQlI zTPMcuyZ?TPuYYQ)Uw)MPo*qxao)BJx@{84>?j;8Wzx8Ixr&)L3aQe!SKEt(A&?fZH zBeSWy|E}+F&$(vCapS;q-3>*SI-#uP-&bv3u|7n&u|r5@`i@+I13Er;?wH9a918uq zAuW2>{l61_obce&R$^ImM{h;N(TLK*uC=c^m@OXh+|zATZb*F0JiX!g<_jNNCFfk* z>b7a4Yn#KQ>t~*=IT!x_z~%>86+2HqTH#g6kg_bxK(43brdR1n3o!?!B$?+z5kL2( z*`;QcM&C8F{&g`vY#qak-wBqBD|VK)y_);k=4!$QaY3dZ?|S#^WH|LpUaX1Lnz)_w zW!PrRou^Abg+0%?=aU_IV9vBv%NJT@#Z}KduGY7?|9HczqiqFO^GmmG>i?6bpFQ>3 z);^gRbHbNW+XyM*s zC}8-akNew`uD;5SOuv%8^GeS>{CAzJd{?}Co8t+E@>TUw4qsPA|9us4e%H0_=f1A1 z{`)$CfA@{!e&06D{`)3_{rB0a=e{kutnfCS{nviyd-Kf>{C!t(e)qla=f3azefM>C z{_Y3G^E&ny$G$UIZe9N-@5eFge;+6G?|HI%-Ir{{krvd&xZ-|w#S&C{JQ!3-}eLO_q=VNUv>Po z-P7A|YClXr|L57^+8-Cr|2=J~|8_1XvL>#N@POFwUD3py1jzSqH&=)TVFw=#Eiz;^hR+Dwz%*0yb;w*C&~qWgwIPaODEEvF$CN zzP0$zXsKXt4OvmjV^Ps3z!0^gHReWZQ#ucON9k$RGM5t>ZX7K^Ct9*rl$IY$VV+Q) ztPoT5qA`lSY1#DFV-s3qceM5;@Gw5$XcBJ=Pfv(-XuEv8>c3(~WP6L(j<&x48Kt== zQYzT9ecal^ud|g^R7Q3bSG{Pp_}<=R-W2W5JdL9)bw*lOMQP8Ejx`ab<=gU~wU(Pa zXk!lO3cb#DczJunjP{xnolzVex$0e&?p3g(S4AkjIXS>y}aY$^p3>x0;X#nXJ#}onnm|kq@Gpl-r~`B zv!ZJ5kM4Uj`WHy_#hbS!y7%l6FEdFLN&4ed2Cf(XGNkp=%UVZXg_cGR-tWFh^xinH+E7EN<mrh7k?=|01 zDo|N@?8D3jlCu_CR_JR^GtBI(xH)n1&RGZ2V-ziFA|t1)IO%pdwQb_bSvxeVkGalR zf3mFeX1QBs+fK_lOW3FEwXCofo0cX%XS?T|bDUMXE9XpGKBv%pcEZm&H)d9rrO&z4 zIdwnhoNJPe>i=iVeZpCJ?CQ+Bojg|}=f+u?1`t`~p5x!CU242N3_e;Y0d&?-CjHTAh>`yo^Alb%bxPR+CLs%O11 zH$iGylGU=5sAXxhO2VXKB6cmy=~|k}wIJ~o$JDI{7T$9Gi~~=Rhz6Ynln~wuSBiAR<-st4)S_iER=Q&H>Qt(<@9)Vfbr>%T;;pYduPE7$sOyVn1?wfTRm8w-ueo43Q zW4*Q3ddHV8#RlueBD=TG+|{15I_9?FYF@$hQ5vh5KI~W|wX(r$$I@NG5(PVAv)0*n zujG5+KSfbyP8EYd1Ji<60+Z9?G%iRpwK42U+{pK^ZN=(c)4O-pt=hTdmZ*W#x<>A8 zGp+o}y_5wKcFoS(Iq%o5OTTsrFv#E8>L+HsYpr%d{p{V9yJRb(oNr~X*i*f8)&J<- zb60Kub8*Y#ki9FU9iu8_F1!}dJs5kcT1Gc;Z}umFJApexuZXVmmgGAS%XmRrxL~FA z#~m_7dp>Mzm0*xR^;+Q9?wC)j_wdbGF19&lhLPj`O#%juns*q5tAFjCv+7{f4}nu( z<;t?QJh{E;ur=csX%%4>rN7#{9hn$;HpB{C*vTxwz@vP4ZM3WRCRv3`3_Etmthbh& zZsoVQdcTd2WYmwthc2pe*6cae%BZb$_*Cm*!!?f2mM#}lW{AGXp#4PH_>RCyMH#*i z+B^Y=%3mFv-Nl&tMrdud4FADn7reXG-)z*3+57+Y{-4&F|D_$xc^Lm%GnnoX(AlFF zpmTItwCkP!2lp_2+x0(t7rOzY$is~tYhuh7#azig8Sv-$p~hW}I=c@|lnI@)D^lkm zuK|PbgTqlp3?hXLyM#|h-C#J;c<}b>W3ztk+cx{a%&6nftQoZ*`rR>*OoR@sI1_R*##!LPF8hr!{XA#Oat`Y5iJg2p zHmc^-jhbWWhXjNT4z9Vg^T_Rk+vcpC+P&w+n$x>w_N1;}x!7AyV?*qroRjifV>X?Z zZT_<>?BJo0J0~9N9J##b>{*{9D^A<5bUJo7=VaZPle%Y8!qzxlnssi)8G$!@WG!+G zRd|&rHy+E3IX_G5(2GBEvrnD=_uqyw&*H+Bm}(_?|hHdZu~L*;`eo_Was?@l5P`>&(X{7hH50rXNh1>2o%K_vrf5vAc41PTX^G zkJdqLkqgHekH_2<$owd@YtDfr+iTM*WuB=rIPE>(;k~JB_BE!q%e8whH`<(-*mHEI zlFU>a$0>g|{C z-J84j$C~>p|8DL)B|Ll1Ba^zV>aRp~Ufc1_ny4{RpfQFOmwR6J?oqMbGt27dHF^mdanEbzUR>dMnz!$H)4k{Q|MOn7 z*FEo)+tq#Ud2ig7ez_MD&uy99_oBt`c~{-bthkp=r(YJndwGcaRe|2Czdo;)&U;z) z?!}6@XUp#0S$ps0s(H^gu6w#Z?$K7hXWRK6@63C>Ic~YM+?zvoZ;r&hIac@PMBJO^ zdC#}pd+oRHb=T`Rk$i8TYQNog?oC79TX(;=8f)I3|Mzb-aMd*71%{`W1`c!&0>o;#-tePFWxz_f5mh&yw9!8F6w z?shlZ*Pf8Qp#1LL>wPPk6h3VTU`q}7q?-T5ou6GR ze^qP8NB#ewgwMYeum2p@|JgwQv(L2qt52!f%5CMze>p#j!?piQTKzZocn;tHD-Aw= zac}q<`2Lfw0FPPDs)^BeT7P^Cum38&{_BV8E8AA7a`_!k{Qq)x7Soi1FX{Q8RQCT! zb>KX|cV)&!PTd6`(>^d4{{K|GfqUZrm9u13Chb`{L3{lBW~Sx(FEb~q^7{6BuxZm!=xr`9^YZ`+ptZD%~|v=W}){BPO%KUXq* zmCR?K@t@f~fjuUHO=|w1n1uh*`~n>kCJ6-z2N+m*r7Q#r8XOy%c?4u^I1(Q@vh(mI z1#~=AU=rbxDQF20Sm-3eW|$!0dGV?D48zoOOCm2n^PQ8v&gS9G%g_B6xSu=Ya^S#= zKsL?7do~XaG#;GItKhSgEAfy+D@!le11TYG$CaTgo%VXJ{8_O-P;w`80S zFxptcz-;9H<<--q=N)rx?Bl-Jep)m!K`*Ay=3$T$gJX-Z0LzRA8yUM8*%c;;Dlk6s z77=Dr*ia$P)WyK)#4aFkKq>VMk5>J*=-c1^=iV{S)M2}I`}<@uBW49vJ;U#(7ad#N zHMh!ibr@R=YsFcs2|q(NnR3tVtv>Vi`?i9|(;Ab_r{3S|zT?l2Y`68Y-#>i)7x3rF z2T^xnewG{gB@D+I12P_{a&EHL*!ov*xA)?HwwMnM%nV=NTX1P_5m1|3ciCXY!X&SC z7K`;373^p);PTdazUuZW?ZvCC19g~FjTF~yIj}jfDLF$&`Pj^)0{y*TZ_Pe$YkXmG z{=Oq;JXDO=tdlxmddfv+f$=Sc8l%;mNy#6UG)v8Ru#;6^q}b;+i$wEw-pfxq*%tLV z9n@S>vE{6n@OtmSg^QOg<~uLGdeY~mZ!=?Y!`X# zWqZ8Vz<5W-!-;Hs0t$=uWN*A#dWnzg<+|l89$IJEl(jgVn$=W_!=eoDc>2c7-_f*e z(Y#YRTY79(O*r>-#%^IN+vq2scLnAy64Z54eX)!``PQkzJ-hOZ4)e^vlFT3ES%2bj z=YA3Co)~M9&B|?m=eVp;nGsgK$>c=LXql8?Q@!Tbp4v$?;bXXR9v%`UT*q~JDF#y!=5dtG@dNeGxF6j%>Qrn z$^F2N6JPdDkB{i-_MBtwVXUcHl)$veuzFHPlZ*tzmHD0DN-`FyGesn<>VN%zQELCFSxyRWEKS>VEjOkvx}nwm`N$2|G)p;!3p?0KvuC>>`|;?V{;ve?r&~m}uI&E8 zBwkZ+h`F3a>(rB$uS?rIRkvlG&@55OUVHh?FSE}4>{Hj10)48FzMR6kI(N3l!ikfl zx$Z9GPx2P13rNb$7O1g8btCUEXP{zwOAoUi;_H`WMbCpIi>FXK>Fmy_fLh`ufW^&M%Ix zzS%q1cJ5mDcV=pW*LxFF;%Bf%&kJSyIMKhcwSFApPW5PtQf6jN^qBngs($2osXgAxov9n@Hipa|qTjlReoD{ohGT-~{2lZ|pNxnPLfm3AzuOe4q z{xX-vry|na&D?@I!#nP)C9atKC^pEs?8+0huN~^TU47jKyE+xTGxzT96r8AH9HLcr zaJx~;YPIwqjj|^W98>wvr8oQL6>jrz3pU0ZpITWitMVG(^!EDqDXI2Iyhbh)`?~{& zteY03b4->z;(ozcR7k=p=1-ciOU`R{{}sz*Z)~2fE^&pgtm2>(|E+*#w@rB zYu-_bmkW4uUsxWzb*xiJv?$yG^p#!=Os7JgvFvLOjku7y zI#zatY^l_>pFLk$uhuvw?|td!zGCbDtjXEh>+a3E5_PO_onmPgf4&lf*Mf7pp&Sm} zKW;S{dpxvWcDp@x$E{$uk1c`MU2>L){AQW^$z|G0&($f%MGl_Id$G*(=$jz>UB_jA zU7TxS$f&AtKumK1Bkz$6M&2VH&0-c#yH@?3$CY)tReVO`0`Axsrf0RUeSfiLmdRaR zZspg}hFkS_WbV=_te+5Rk!Kdk7ocbOHmQlzrJOh0;jsP&?PFqF7#K4mSQ?iyFlOe7 zYFv+5T$$V%&|iEZ(_?P_t|8{*>esW{HfCTT?7fq$-1NvPT zB=YXh(9J6Y-CADJXrfU#AvMWU$^XU)cfHNR{%8CGewmvabrkg+cVHIz8yFpW)IunN9=z8Jl~*pUxl4_^4*fl7wjU77m4#8b}PTO z&W~lzEFGInuh)JTrpqzt8Si=Nr!IAJ8&7HMbnZ-F?(bK`{Y>ArRS64Md^h{Of#JzH z5sQf9QP<|zC9a)qGtFX})nvPcFa3FbGjki471-YOb`d=F_UDmLoRcNYA7*HJ$+*l= zifub7QeeO!OY%KxPA+M+Y*mEzfS(&b}ji{?#Eq0_wxMB z9!~Z(@=4OS2(759vlSLyBCRH7(Bz|Y;Z_Crn;T#GHdcPDElLZ&aP#dH<0X|sf%66C zEJ%q^&fA!K!~KopL!R6z&bxPB$dxM-s+{;kP|5C#$J17xYcHhux@rZuA26`{Uf8j_ zP@#E+^ud>63wB**S75Gew|T6%U*IE$dy3JUM6pxH%1_Df_+(*yv#a6vX-C$3lN-Ms zS#bT%wc=!l&-wpd)0_SWsvTTA-E)KbqpHI5J2LG`=6H2U$S(KTvonuH_yhy{?+J~9 zc6lrl%o|_7FEih>Jb_6$VeXqryE7ho#WhV`V|lH&bT{+Ec}W@nf7`OZU3RAa|E6-I zoYNXY9dBd;1mP zP1mXq_b4?>u5q;T(X!s6Z*%7GpT%8&AM5-}vfO!i>!Al~O9jo2G120{ zgn|aU8O?hx%sjaJ&>@LA+}*0yO%}ewbIcl;xo>Rd*V(`PHoJ+5v&o&!{n89c+ABXL zZ(l0V;G;Z0d``3b0{ciFX}&YtPVaKHxpMgH!^7^vPL*$Zl|6c=9$-lGae4fD>!-qn zPYO+0wU%$$e7yR|@!7Yx{$KLwc-@(!U6GsWxKBtg+THl4v&rN{vyX>J#j&pB6JIu% zJm@^mx@*ag)yFN@c+|8k4YpSA-Q!vxBHtjhbwZ5dB%j_ZAEr-r@MMqhm}X+$zC@~{ z$J1)oiCJ4t&OUO&=a%QZCmvfqdRQE`{eRhZp^MuhlatLZ9%(ghywP5JcUVl!R@U6L z%xr zZ8@{%%IVtA2cLX8dgrrqWYg61GQFE@ywbj~{a>o%d+*Sh83p@~t(miBw*9IpM>o## zSwG3Q*TnDP#WM;#r|v#E`&Z-C1(CC?KHghv^iSS2c&x~Lt;c6}jo+?4mM`b{t-W*f zcF*oRe@>fS@{4$Q?w!wxInHO6zA?C7bog$N-_Pu`T_OG~JC}XC~EbzH_H!1L*46nHEX_r6eB=2^I z&hA`(B=Gd73;b^b@3CA`IO=-qioLMurL#xQc?4bhopMQy)lZ}&@c+LQ&*?|DgczGv zsRYPfy`=j#Nb&26y<5)q+guV4x~OV&*?6r7SLg}u-b_4q$v8(9j*jvpL9j;2f36!52VsrMi;@!(iqFW7JFTGiERcz@g@xLLl zsh$jPx883!?Oy6q<$$TwW_FCT8Ysp_P zrK~-h`quSl&($JX&+|6nC8}34-!?nvhI?IJ@mVBHU~ahT-)n-r*H>|x)_(OWI(y~+ z=HI=_3^!E&Uif3<)7%@J-5lN;d&6UCXuEGf$I@AaXE%Q7GYo9>PrkgW(BQpP<{ktc!JXTruX~ev%H|9@0 zw}5xXLSDher57gn+**1!qVKIAPeag(P~UkCN1K;KCH3B1J@xFGwNuwl?O1p8%KEvA zOL=eqmAJj3Fe<<{TGZ?I%CncY#opS!^~{c?Q+Kj*rrF-IXN}%FHL|ZZa5HO6c*yOF zxjlzW?^NAASj~D_=MvX3R;Tr@QG0r0Ha?BspBsESwrA#wOE~&LwDxhjoCXlcFminiwAm7p5VQ= z_wF&>*cmezrhmA%^;4(Y2~jy+?y`B_%X`X2pzuvWdd)-A09!kYpnmWM%a@9CpaItN!QJG|qlyan_B+6S72yqh`%pJs3z zJIT}5$i;Smq5X&KY>Nl~R!7W?Nci8C@qppyBsPHtEwNt40FILjI*xa6to$g{Yr#;< zIl*1v{t*kFrYsgcfs|t$DW7wvuY4hLZ%%`&9AE5{{tW`X%o7;)|6*WwU||2jzCu%V2Dg%*gk>F->-K=2E)IAGRR~?O4FgmW7u0N5-BWza9N1;x`zRGvWcIUXRNp!G$dBmXjxKr_x z*uz_IeG_I(U|2Gtq0K|I^FcDl%m)8JsrF?Y|1IzFOnWhD{maLuqW3>Odf2eIzvWTl zZ;vO7Jb2GHOudjXeNjWhm5Sc>g5LFxX{RRe`0I7$tjkQD=E|ADz%!#E z`{!{99roxixhZ+1L*!@#f77Ff`zjLi53co>=;RXUM6;94T?H!##VyJbk>#^vH~TY5%)_G;scHSotDl?z5LX z4>Ci-c;-~{w@JNHo{+jvLVLf2_{UBC(-`)!B&FJSaAi;9xp(I6<1mhCdK2W&st9mAIs74e&$VouFHgl^%(V@hc7`EdsIliT zpETRUWX1#OUTPeMd9%(ORBV*vXmWdS$2XZRkHaf&>Y|2cmTtXQ3ObLUNI7rzw)0-b z)oHpb?j;^;cP4$?SE(POyjvUgXhP&hOg6j9_T&fx8MrolWuWi@Q&-uJHcY+ zl{K^RsmiJ5OrBTKaT*itdREtbjGWgJnZc0a*6{uu!*U7Ev#AZA+8RFdHT?fl$JO5O z?Baw%!?yQQ>r*1vq;Nb`-Cwps{6Rj$^w%D@r8REynItqjYI1Ht_!XCF&yy*ES#paPaFCuq>*b^Vl zmyUi<77Au(_f7aTtw;L1_%yRm_wu+h?!BD)t!SNG$D?N*YwkT^`{5U%cWgypR$-xq zoZq*j8L9hTtZeI=nWw}YeL_f&U)tY|agJD_N8U^0=Y>2P#a9}hw>G@~Q8#sU#*8Tn z9n1pahXwS1oG3ncW`_H5GMya;v-|@)7BEieebK?D$Pu!gU#vcBw%|*jlT%#kde*iT-#2^Rqfk2a zJ%^P&gTHyvUaLf&7f+Plzxp_@A$c7S&kWYN-s0hUJ##o67RhTYeYb4-&D3uZ%3HG; zg3o0tPv=Nom$oRTSHtk-KZRN0oV{zG^%sdh&fb@+-}wIcjE1ilvJ1s}H2lQ(`aM7P z>1plpt|i?BHpd-?7)c({ws-^Y{?rLbp2FQdASp=cYR~; zUy!dADQNd8%=^^ieBNEuIjqUp$k@^&rRbAz;XyJp zo1CkXgQC!qhYvfj@WqPB1*Zpytaw%DsrF0d~p;M0@i zQwkL~lvqDtaNVDm`beiIIQ`~4Mxp=Qs;9nw3}oY*@lNsc5v^yd{7+0t=wj65VvjvM zDeleXFXuaZDwTdzmMxeo&yu=abcVo&2X}TpT=j;-DCO?%V&_j`H?J!--RBKZY43C} zPn?zZM#65-FNQWY2j&C^+iQiFPEHW?$Y^MIxmZ5HIQ`hFQthA~H}7`VEFlB7`NC1_ z-40xTp3P*Mblkz-#7R1;Y>puV8_$bx52CAox``JYS{QQ2OaHCO!6asr??UZ6UBhEb z-7DB5l2=r24Q3Gd(7q_ixv^Y?rOYwxUXX-~b6cwWlO;F%XTNF6=gUufm}P%>q?7h1w*2XZ#N@P~%-59&>za$$=?co|!4rgW9%C zVBje@GKrb@&G+f<)oRP1d+z=daJj_)&jHp&rZ*Fh`NZt`&>|_h_1i=iK8Fnv9ySXX z+;tc5IN;DNv_pa+=BK2lW1w(BhzPT!N5d-?$&`d}=1sHIm#&t)^D3TI{*64kNEfii&tl4Uz$z@}iFn#&0k6Z3DPV>8PAjx#q zty%TY{VE&g3Ro0~iTB>{Uf{s?FQI*J?-R!WIn$EEcim-oS{`KKtNEEaqv&s$kY`%l z4)J({jW?%H(P@=f6wA{(V-AD(bQ1>#;ff#cSTuMHxW!w7rvE>_AdSZ=cv{;w8`*N1 zd9SB-voCn1yZpr@_K5Rxo+U5c)dO*4zkRTIChjj zXzv8~#jk1~FmTCtOgWeyDt(k;)f3THt(6PB6fQ2eTRiPKqu;kJ@+-S1YB#Rr+3?cg zeodjT$%fuN2@3oA(>Y{7&JrhQKg zy-Qq^%=a>+|8@>RrcXU0ma87`wLH?YYnYta)x} zJSC6jT``~dAajdYr5~Z}64tT$Fv!le*@jTFNVofxPn`j0h(!X^d?8M*5LF@R z%A+1OyJdHMD?d7^bBTJ*=i58f9w}*VWvgbtBAu{IL}>AZMsEHyK`iRu^QvOPO8Z+f4i<@q{%g4yKBkFHY)8eRw3b}?a zE-v(HFr7F@*OE7BS?s`*_%E~$!_bn+j{Jdz+(@#r1 zxcFSW#g069W}|Y_^U#xKe(hj2U-Jv?DeLupf;m=%OxJ9?Qp`}9)o^H&_#~-K@7lJ_ z={%Fuw!;0Y$~1+i9^0oq*5&*9#E$1hST}>Rd{8P&;UvjLt4_2s8E#xvcWZH2V3z7C zoqOx*&d*|COZzy*mcuEmdQB+XykA-eo*MNvY`*ft=W@dzkCWElGbipSOPcqlP$kvt z&dG4WZxiQAtW^H;|5dos_opkw6}lB-ZnV0DgsBJlO<7Q9sL|?Dq1IX!z@RJGkoYoX zqTs|825LrUyBLatkLsvxnh<(1HMJ#jeb4gEZa>ddPI`6or^-q@XUp8=rcFz(?qPF` zFgnqsH7R2MzfdQIJxf}>!kuo_b#Cn4%bDc2c=^@1=T^p2xy5MOyj)y`u= z>l-m8<&Z;}Ri3?HwrDmVG|*Q`e$YMn@U?!Hsb>Xd7M5q*OU|pSUw)onqIY%!Z%@|u zRaI9Lsy39yu8mgN&cd{5U&2hzr1pQVtqWEO&y9S(pii~t&Xai+g_hc{I>aTO ziKv$>H1s=p@6NU}>(2?aq`eke7(d~Z-u|Am=VRq(eYSXcZmq>My>-QH8Fx<1V)=DN ze$j_xMt5E=j=Om9>f2I5=e=e@=YMf6T>GduK2}*|-mhz(S4wAJPgjh;|LMlbzR#?S z@2Z9#*W3Dg--I30HQrAV^}Z-tRIVre?@G1&iJP1Ml^Y+9`p|4|bxC@D-2V=px}Cl9 zUmh#le?9sC&f96}91nSo%Zsn?Rhql}{By_shfPe#1uTO{#Hu8$1}L9!ytUu=%BY;>`KXRpFal9~=r zFN;<1Qra8%NM_lB(`yqVRTiCFs#IN>q}{gYQ|}|iq{z$<3dde5iA+k+-GrAZ}5`!$nU6m8U8wn`tTE&Q*|QQ=ZScK<$~5#ith2 zPj@Z$DYZ;h=6aN%YxRG+`J;I2cSo$9;*3ruZ_!oT-g>ns?9X(<0}*7S*c@bj*^yBN}*mRAhRVuznGi_PE6i=hnih}|R8e&$dMq4l~;_xn=aD}<9z0yX0iYJ5jwkJyaRHV&VE-Ucmo=kCc zRDGJ6?0upk=9Q|*3Pz1RUFl6tacTdbdTwidccU>=jms~kGvU_4EKgsVN>)Romh&ph zlI#?nCpE4zr=A=TTCSe4brP z-nxt4p|iNfSUu8`75B9iq&@YMRd4*Z#DAMBL(LUonGPMr(+x6Sy1ZQCPZ~GgILNr5 zVMWcP2H)O24^IBL;pt{LK~mF2YU;&TTe{1Z1kQip={|@1WR1q5jO#Ua_hfFU*WJt5 zuHzHsrxv{|KJ{O^ht_ft0gc9IOB+8eb!9oAGUI>ZUJ>q_5~tp+P@7`sHI0R#O{>$Q z&|{v4i{!>5kCq5}Pk7#K*DkbGcuAqao+aF_FMRj^W&L(yMQ%{Mw853_cV0+2E}Q;F zWA)ExqG5}?-!wF?W1J|IStRu$Y?lkef~Ng4rx*LI$eh*q*h6!PAN!JNN0#c`TmFH^ zHRt8Y1K!ims9mvWSZZ+m<(bK`8(Qi+RxK?#{_>7*&C!?lCORhHik*AwrT0sXNvEXe z*FEu$XlVS`Bqfk(p5rXPq)DM-S>r2JpDAbLZnSKep;qdl)#|l;YMfU@0>hRUp1l!M zxKe!%>|EJy!Yyoh(kWu)Je9um5_ccXmeu=SaesR(H|N538R5Vy|1;NRO|Sp8(v@LV zXjNCClY_j(VR4=};uX&39t zrgdBAjJ(qaSq0{=dAZ^Y-$?=A_${k0 z`q#8Vq4|PT^8q29gH_EeJdKC$txopy$JzUz%_F&w(?;qwka&SIg3b7cYa~y;1n~CiUMM zmK+^Tp9)kV+Ny|YVkxRfkt=kkatn^{2T zVpsk}xx7PauNNM25f5Q&GtF<0c>kHp?aQw4rzi6MJku_ydVk)m!Cax)yyCF(mxKSS zoaJo}{g+~9QOP!4svo_DvH!`B9?;CVXT`09kF+D)vJ8Oe?fx*SP58?{W z2PbVfFz>&CkwO#GdV>-LC+Rl_<<2mQh8K#yXKYB_D3)(1UcOOW{kll>Mv3>1(h7&v z7s(22ZxN_>&xF?;>@C0DNRwA<2DvzGN* z23#V#s`ZTQ+ONG!+V>dUR8ww$+hwF@T(oHJ^60Yl|Fk~nOT3lf+5BUb5!a26&KAKn zLano=aO<}7q_eu+KQTeAd(!Ls{2D_mB0F!C4oF|7yh%FN<&R7xOsS-q+ouaLu~d*dbbeCa+9V>*kPuo0v9ly14I? z;bzuXi}ae0%rL&Jm7t+#uievbb3FY6i?C3u>&;+?_AL&V6&$%69OrLxe$#BIZF2CH ziotf)SC3Yv9NKIhZqixB%AUIAWti})IyXD{RuA^l`@AL=vyCQfJMv!5($nK zFL;-}VV%qV&BoC$eW#x`t?rmT=hA_fCOlWy2DFzlO)3fezhd96Bl5phxYjsIYJ8E9 zV77XZnx?eb)!^tS5!Q(5r)xsCx&|K!>u78L@uk?H-8fQ0w%ar*T_I9@OW=IdsBmGv zH3y|WFj_5e{&~nbRlh0avr6pY{}NTx@^&iSo8`6HdFHW$k)Kz*bqP~9%bqV0XuK`* z`FigIj%oT#so~rBB%CEf%&c^pau+l6X%uOHiVoktt?=>dq}e6Or?+|EaF)E&AhLi_ z-sd~-orc`t@)GSP&I@Vx6W$lre?PQmThZ}vk=OE=JsR`%okTqvN`II2R+v{DH&5#R z?kT@D{tdIt3x`<00|EgD_)a)fp5ET@+^AN+yn)}MN&N@UyByE!+mp2|THJqpRo&if zy#2vSvnh!?9(e}&--tC{-C4ljz&dA1@b04?yIPeVH?*W1v`YUFX5?^J(N-u_Mpqph(YuCNE`Eu&(qt+S+eI?Xs>m$RL9!q1GT zh5~J)$=WTm%zwINxwxC{=ze_vZq#OB?bFVCjm zhtqe>S^Ve7{ChjUuWU`1t?%o}a{qc_##0yVxI%5YM+<~(gk||GYEG@*v9#afp!y%X zIX2ES_RLzpYy0T~6Yu|?@ObBSYn#dGdrz|e^{}_hS-jhBnoG?om2=PD$hSXj<&rI1 z@M_jG6Xy`?`NBI_>D!#E-xa*tYL0t#LAS*UWt%zLKbNOl9n#-(oBQ5{&##{5|NmE* zUMF8{_o994bLXNL)9uXr{~evZ?^U6c z_r3qWbHGir)W?Tk_lO z@89b8XM68|)@=W?-TqGh_RrJz|BB!LqyFEo{q^jn|CvZoSf$WnX>>rg%(LRyOnKC^+QM#LHqN z@Ik2cz*IJWEtN{)bicXQmEXSn{JiYsEY0vuB0qv0e5UHOUW?%n$qHE+yfSR-tc{sY zi;VjZO^G!4pM7X~_{nLe)nd7~7!&#PbZ?lx3}0rNyJ~B-cz)bok(*8h*;kfcUurV< zo#`HPher(UlAmoBL^#~vuRBp|?oIJSH#*qm*dN@HDDh{}2vE52Gs5`Fxp~3zb$>oE zWQwq>=(EN|>OGvimR-i7!Esv-Pq*PGK3>BHW|33LY{wfI8VkGFS?;jJSuJmPeMH^s z`itMYJ|AV;GoRN%s`>DZC7GA!@?1BH*fmqJkMCXrQ|2Fu1OD?D++>``Wbk(LoFwT3 zjGO^K8W`E_67+UlI#u1lczIv*iDT~B}j6-b#4F*nMWF!jpf2T1A zZu{1@sn_VB-fVrB=PoS|{%_~8yP&`K-M?%NO-A3t4X)ix2O2z8)g>AjImA*JTDfEr zj9mX2tqM?PoLA!E(kAz3gY!*`nC{(-hmLGu>=Br-fbq+MX1zU}`>vecry%($;MnAQ z+&b$H8eD#3^W|96g6*7I3qCPlW|BDJ#AcB3py7YU=QU?9Dx`8SPCM}?S3zWzGvi6N zb2fsFoC+SF7_=VW308e-6mf#leR7IMgL-(3!7jzbvkJ^w^Mx}MUzmknUSS-a%h3F) z?e*Elu<+Vdm;bQ+NND1D;k~LcLw46B#&c%24m&MYclWBYu=QlRd-=*tjF~3aZFGP! z;Kj6So|df(n)o$DCwiNzo^W8|6bthDe<$hd9xcPOKh~^WA;r9RqTbn0S8|$;M9ezS z$W;|u&U88G)z^y*+OiDIJ;6_Pp2Uc!ZA;Mz-4}eeAX(})gIsZ}&bb)TGY(G#r&eoR zXXMJ=a%m$|M#4+3cdZEyv7v4z;Sx}PN8Z~m*8Qu|9Hs*b|Tdu6% z?MIQch})%eEkeyvf(;8AhGXSb1b(C zF|o_l2IVfAZ?h|WsdJua>!F3GZ!}g3_I568=>1pp|K-}=Wg!eM2U7~qOktQm`B~3T zGqYQqt6rP@ZqJdY>fK4Dhw>YZ=H0TzB$O%-+ykFM_R-riRQieB^$h_+}hXD zz`dp;S;pV@;Krs`lMXmvyRx@nt(SsB z4#(vm*CxeTY(o~nuG#YEJbmYl2m zV7&NZim*}j&OUqRSqwK-5@z`^rWc;bYg=fudQ???iW5R8BGpQ}TViOk6!E@j&Lu zW+@gs1;(_pPww-Lc{8PgY#&|xRK;|XpIKtd_Gi7%W=NiOVBPR-N}thX#{*Kq|5*hj zPcv9vjMHU(Cepe2L{lYOSI8R?fAtwE39Z>CuU#0uYfV-&uHlMg@;pD`Q|7A83*W^t zOR5HMXk&`^({$}Vncnhk;MLc(>UMqG<3F4k8=Hx%|^~|E%vu7O0t-Z)NvBSiCT4T&fpS8wbRWYxn zADMi&;%Hd&E2dlf+Klgp1vaT`*+k|&KauJ1vnFAohv==+%)ZXQISKQ(1X(uf8Ej-V zohA63P&3)I2iN02c_G_Itht!{`S7+$l_$wBc)NrLYLP}@Ov7jTa z{C5mjI$det`I0k1@Z@CY7q`P>dVeU1&hwZgedD?&_m0B}#|&oLEO{dS{n#nZw2p$P z;IwXuEy|kR!uev_PbF+;m{^6aX)f$KU3qAYne2!=TkrzKt zRUZ4?t>P5Vb^oX0gtePHzjQ15eD+k*efDL!6z?<6l{e#*r+TbZWlHbcP<3TW8}BmN zwNGUGZ=V0pvPHg-IpM#P^6`1hZeI?uWoPeit5lkH%y?sWP39K^&RYlX$*lSQ#vypJ z$63X98%5mDzUf#Xb=7v8k-KS_Ip(- zU8D5ySipg;ci$fTB(D1JZRy_59KrifcAtMct@POMTle~ccU~3@ESqL6H^+=y{g$Ks z=fldEUQ7y~?d-Dr#L=~H-xsd_@mSVA_-J`vVb6Qc`Wz@Q|s^EyU)pg)Anw@i?R42-p4)N*Y{J^*>*&-0rbwqR)^1)88J{)c@W1`t+=`hpnHpf0=xWU)p0svdih?_qN|n)qi#^ zWbc&kW;L7q>mNo&-+Q{;z3Ow|`I?UR_udQ5|NBM%{e(l8zx=+la?@q;|BsJJmnBWe z|H-)EZk54({pafW7Bd&!FZ=R3_SZlDha2>-$+-Pa@w3{Ldsk;|z&yY2msI}r_5Dg( z54ugK=>3Ns@82bz`zz3Ky(|BOK$6arb)RPSef!e?_n+TIzIFfqYxVxoyI|4c{9Q(X z)w@7PWrE_Qq$Lsh$}`U0C@8p^$NyEQfYn*=k&?iV8-722X?NA>SH>Z|L0l&^Lc*eh3~#j_z;|t z&l$Xq(@~(9=iN)Se&*Z{T$=v2hnzFFO}IOOpEo~e!Fdn=>MCy;psNtS0!si*S{6D)p^+Fd^Y3aeYrxey~%$Cf?kUS zzS7#jlKFx0_e5p=rhi`kdn9t@rwcw-6FlVdmS0iuDVq^Lw-L*0CbMgW^FlVNW*0CQ z7p$LmIP!;&lKLc}!u2*0o0bXjtN%PAdGYYIIzfrI{t}Kw>J#-pzUn^b7sOy6Wc$r4 zR$-#`f{7dM>7QWJI$*ZgnCpqwIU)6PK|JM6#!_sI#R0muPVBBr4L@+`on<+B|{|P&qUSY8aVY3Nw{+`GX72+(-#NNZe?z6!4f3STX_W02&MjA%zBPtz{OYD?8Zo_uYtGcb(^I3` zr^ePUiYWdZadL8G;1tDW3mlIZ`+8n+nC;;BdaKRd%Pdc>+MJ!@AZX%tdoyQ-Ny6tV zibf&U##e0YLs-vDb~4@?t6!#m&(v!9qEN$Y$)Ul?{FAh-z9oBaGjbNQ_6% zn=|K!WtkdhEx(pEH`qdQvfuh?+5L;Nc8ldGnq(Ua#~r?wv$r_M^>Xm}X}R4dxzU#+ zZinTaeU$zDTHeYS?5S9J@$}sC>E&mymDh{sG>2F0oL13&J*!{5a%ouQ^y`_kuUAeLt6F?L zWBK%|)@xPkucvPguP&Wdy?cAwe({>bu$trLsi&{k_=?qD{+@h&dTr{rqV=Cs?{Ck& zJH0O7tZt%7`up#dpTFB>POCqEt&UkD<8QbVXGA$~MYiCMZ1x$A?N=J*Z#XJPG(Df( zq%DytT9Iowqmuc%$N%zr%NuFEA$5!qEx*I1{vZF;%>Scha(MIOZLR&^nv!=|#qY?; zl&H?FC@8GRNT1Q}7}jq5y}j6?^}kR{dqh>UMe4*K>7EhwGj~)?t*BdQQM2?$%Swy1 zT8VDq;O^}bCTlHP_ErS%j_7f2?m4}~@SH?%u5izFi|*SudLzSnA6w`@o6-0FP~ZCq z-A@tyPeuBE-$?!`G2yh+1ood=TsJ3le(e{oj1ZhTap|Fn@{(Fgk(271CTZ_f*OQ#g zxV@u&a;v$dpQ+@OLZ>P2GgZ89PH||H`mg%OkDk-<)N+eb(lk zZPt<5<}+qz3(r1WsdQ}S9Np`)PnNZxpJ{S-=3LoBo&7)NmR_Fwyi#}D&3XP}^FRMI zd|f%O+`Q+frFucRrv$UQ@6Iy$Rx{70m|5ymOq42SMV85{mNzU4jd<*$@N264&r;1> z+h=T6k9w-9SHx#bo2SU;;OrV}nqx4I**FOF8~GTWu>A{8dqF<)_O{S~Px@!_kbQ)HtLMQin1W=MrxIlgl8;c1gwEdMt@UzE6V zQnt9~mm}t3PhOroyzc$1;7f;>^}205nQEvcwMC$6zUd;*87ty;B`ZtKUJ}S^apdXV zB^rz5k1jnbQU2Hcl#umlhI1RGyqA3bbYhb7)W<(3%O2KRRHft~wOaePVWG71mb7h^ zzYT92t+0&R!FsEwUwY@|h@I1C_g;?7O}w#l@3x)GqxJv%oV#)M+^xUo?3_Jkul1aR zvu7W*o_+H6tn_Ufm!kJxpS}0??Y;M<_dSl@_k8xg*SGh*m)`$5djI#?`+rC8d;Zve z-%LHOwgcRE4)Dty6plG4KIfqHorCf+hm>Ousn0p2edmzA%wgl0!=f?=IEC`ElMXw_ z9C4p>#QV;X|Nb&ZgJX_Z+o&ZcwHi7fO^!L1KId5Won!eiha&$lFP1)1c;|S%%!%fh z6YX;jl-?1k{G-wlb8`Bele6!fl)iIfd0>I?qm!%eoLVn)I@RXXaiL;CrPKRm&K!<8 zBU^L2cZcC|nX{K;&aT~a##Zv!b(wRIW6tI7Im^1|_;Z=_3-96SL5x4jJf9t1R(PqXydzDxeZnm#Ky8O=Ri!vK(_nr@bb^BmT zjs(Mv^|rTX795|x_x9mAT}B_Tf3!)tby6v8>Eus;ZYVO{J{)s(=U;^*GVMmX_nzMg zQF$=c(S6|u*}Fe$uOIxAt-^Hc)JhCFp)xx0@g z-*eSHeH)vX&+qsN%*{M=SO1%gpWEV#&y%0_$K77PujRj{?ER~$t)^}7wPPPM+h0Cv zwT2`9!$i5)g7ZI0%fDr=xp@2TNA>xiX4*XczVDN9{O86$pC*=kwwM2szUT9Q^Do}_ zzj)SsIsfNN`24ThHec`W`I;R6P5jT-=gHsl<-d2=d?=p(-GBc#_C4R5?|(0k|Ixkw zQ~mvSljDC*pZ|0A{h#yYe=UyxwS4}s)%Sm`m;b#v{`cp9CvMjM-Y@^>aQvU+^Z%T_ z|L45@FU9|tF3$gZ`~Khi^8X&k|C=8FcW?c__wxTg$N&Fc|DQ35shN#M+Q33XL9wfu zmEEa=!*J1&P7$;_bxurF@t&olxnY503zwu&Nre&kPMz#qdpeDm`*mqbZxY#1@ZdC? zb+;7wPMxJ{)%*TPZcz+e7P2~OZyme{x{=P{j(U+dg+k^isVo@F!TBN+x zF0_hswKcR0r|d}V?2t5JtQNQW5Xzu5DI=+;R`Sb)c8;3T1Km7JG?M#EE^0jL<&?e9 z&|&?nBPB+%OX5H{^P3lmrJjD7)18(0K1^lc+hZX&HBRs9!Y+w4O@|J-LqAeyc`ej< z#-cId<)fMIt300vdf)PR#u!j{B6)7zqbWhNg=HL;F-UYwy)(b-(v(Xbv5B0|y?DRw z%vh?bcx&o}%1x$omd#j|k^>)YnK93pOpQlOYEq|po>y5+JeZNJSc6nK@pVhdrLw`?>;IiEPahp~P zMIU6-Ik0-UXimYTuVx8)hhKC_8&3Tyw`If8he>lb_Q#Z#u0I;dw&3+T#n~30)~cCX z=R|0&w^2M~XV`FPyS{ZN(?;XVH4D!A7w#$EEU$h?^#T*u7wrwM&DI8&yo^tj9Lkyc zr(~hKH|xhMpVrEwbG6_kGT$B^~PL?gwbKzTNUzjDJD?lMX+>ojH9>|Gqm& zl&rDbr{Vrq_`a_{dzk<=0!W_4};fv&8HFUp=gz+S>uf{(x!Ujd>iO$_JnHwa`}u5sf8DQF>(AHyK3OjR_2m`o zdA|=BHRsp=`}O{O{r`Xe`42E~6*REuEMVmCIKZN|q2^n512c=c{idt@#iz69v+8#o z%mU**)UB6uMnBkMgZQTm@)`ai)vr?s6B1Q3c%#VW-b@~;qlNR#kd+ZZz6)0}C zj^*@Sv6DfkrG4Jvg@XMZM^v;5YyTK`FfU_2qQR%ha6@Sk)B291I$4F?F>e-$@BT4K zUCh7BLB@eW;KMBaRfWAdIg4GT?^hgMzJP&|Q9@bry+#A0%mapW7Y4Qf1`l?Qq6sZJ zOBDG#Pq=6mP3*~8qAbsO(!;B0(v&qzRJl1l9FiLPs{bfTGBYtK{$yeO#qgg&2kXho z!dfvWHY_~cE}-l+#{dfVFD+Y26_n(MuN-QC?4pI_ZQy?y=t{SD0A zay~mYJUrYXtQ~h|$HvFUCn$T*^VzxS>FF89+4s)u-2D9f0_Scy-(6c?US5H5vht4N z*Z0ou-v0jnfo5)bzdbuXK0Yy7JO13Bou8jySnNIDZ||eAWv;!(H3F^$JPCf70^_gX+tR({;?z^3_Rf{R$@lZhT`E1yh)oUH7o`E*K1 zSmx8I5os%*PKzmno~%40Wm@L5nHkGgKAV-Z?d7xC;FFb0u4O)-Tk&k=^LaJjUOt}> zK3Ta%EbGO>4z*P;7WJ5cPF8l)%5047xp+v_J*My&;~db*$_v`GUaeR%E$h|F70bXU zD_5~Co7wmOVr%w#yN^y{JJLQnDXsnX>h<~qY}z^-Oi~h?d6zu+)wFC?>BU29kI8AT z<(<;s z*7m3?{ov}ybEZeFc`_+{-p!{owBvH0uPEoTe6dhI&gz-X^1iBLYrM~WOSzBvsI=*ie0Nb>&-klZa_!zH zDk<-)zF!jN{de+Ozkj*hv&Vh=-UY9ZH#~P>cfQ%5kK*?KU)OHFzvtAl{m(x7S5JR< z@4rE~J>$O9g4x;&82KygG7A$MtGO32uNSr8^qbK1Dfl|8;f-y+xd9P(KP~tWbHHBU zmw$`0%EAj71x`tveyv`~iQEsL@8LQW&=zm)#QVRR)kH>XZH@JlM1JQV`g#_E9Zt9J z2>5TcQO@&e_bh1?6U>lgJ!H@pFeT#TvWQjRA_e0;e3In0>~NJgyV#k)^Z3XeSGRy( z#q{($5mK8^98=vFkW|pKSmvtfF{`3eJ(Voi<;rikn-y&|imOqUVdvcNexhJ~r_T+g z=o=pHy96gRE_t$lnfZ}sS?$RWl^Df--te?{dY|1`vqw#RE=~?UEZMc*{__IYF%A%ByQWp&J8`PhF6aoygHd1dnzT(c&_Che>UA|o8~;* zab^0h1AnzA$6Q!ueA9y|^k0$D!InrJ){o4EUfqR#%`yi*dHiRxUZOO2&68!^w|@Ga zJQ6&wAm;$f2?dY9w8pkx?P({@onSRGXkcV``oxO;mS0nq@vNAP2B-2w_G&rq`AL_T zA6etUWW40jB8jUn+^lb1Y;t?H;6Bev57AQrle6-2Iz3{H3L`GE9WYsT`;Dq^W>)Zt z2VZ7e-_6uHlW?ga^z0PJ;6(4_!Ykd&iWf)4Xa(D!GP5>NSfw=gfQpot=aZ{%QsX}> zaMmSUS(hZTMn-hC`@%>o>x2huGIAYwys|DiZOYFVy0a=)@&N;TP3hb!S?zekbas|M zq8kE3wWHZ1PygQQP{=wbJ9hW22$6!sWfOCkYi}3ddrFCcQJi;8!pf{mLjNOpR(HtC zbM810y4&m;qlQ7QcIeFOC+t4=*VewxPL#UAdFAqwxxLqQ9al&1(%ZJ>;L!t_(lc)@ ztt#5oRlK$+S^Ks`PFUZuJ7PxO=5~5V5;pd*=9LLRis%Kv{EiE;!>|i^;_rp;=E~zzl zE<0HqsuhvV?hQ}mo@~CVNnwp`|AF0!D?`e+zD{ovof9iG{l^gvzSUjAH=>?ORql-C z&1rl4Ga%7(OZA%7FG^j{JWy6Xz~=BSL;kkq6Fsq;r}Xx1@JNmconptA@q#(lyS(Pg z{cjTg&xD^dDtznRr04wUbV1wOB;(t`YTkjN1-_pjbXh%p(0Ai_*s-E%H*W@7GYg-u zY|~GZHn_TK-n)?f(F;DDQRvV-z5P^W-{(c$(dmEP)*elmR+4s|E7oe-t3HV((hDp9 zJPYWSI-2eA)ez&#gW83RLx>-NwEj-WP`>*E5iOK7}6dC`$<5zzsIMM9G z3UArXj@zyJ-kZKQ)1PtBV|&r{>GO)`?hh(YHUE9{xK7U2&EXUEzLxYq*89G*dhff$ z@-sJFrydTGO;*xsH_grzc3N7@Um%D3=bAIo> zoI~PqpJy+xcrN+5vY~a~b3TjR^^vu|)-W}&q)4oN%D%to#%0^*o8x`n{x7Y*^LpN% zrSJcGO+H)oNc{efcK5miC+`(Mx0m0rXZpYYTNoJf9v=SkVx7IwH`RY%Gt}b^PsfLU z-aY^K`}wA9q0{YtzW2@E@xA8noA!#^$1Co-*MAJHUp$?QeR=)U<)#11YZ+$L&M|LT z5Z>_Rdvx#8>ZYTt%a)h@7EfAxy;e}7;nwt`n>B#a3z+ zkH6LDR1|y8NcHZJ@U_tOmuS~8XqWV8)6Gbjs@{D1|FQPk7wt?ln(v6W7j<-SROB8^ zYx{gTXs%n!jP&A(9W9f06wgTR6szc%F{5LZSY_t54)LdH)f|nBJ-Spgy1JUXR_^FZ z=7_(@o|?9y%B{RoDWco!d%J^JN4sIiJ0=689=}RQ}KGQr|gQO|rFnDpy%S z`=v|%(wtpCSM(clPKx#DG?+Zu>g41A%L$)3f>S#T^j?-JR`hzF?ECv;!i>+lW)G(L zOHQ>}IZ2##iVJ5`KgZt~u*{=7P4D40l&{b7##@D7W~@xlpQWis-F2@viy&yB2OcrupUO zLY`j>`LyOJWo0v7(ER5)T`g8 zUoCFnSi+*U#O>Om#X(EXRV?wC)#G)_idAcoMU+-RR%eja(%TVB7ZfiQc(r8trlr<5 zmd1HaPSCPUS~Z_>!s3-l%hq--TX1v1xyZ!=x3r61O)Pn}(3oqHwYb6kXn7@=dw>qUE5BrT=r_^F0Waur1aN+ zU%h?SlAT#gzeF19cdeN;D{6|C#I&weevxaKC(pYewRWQE+Qz`OhpX1AZ(e(6RrozF zi3g|FB%NHX;k@q5&6Uin)>(G0ReQYdz^_%yrPe*LSZ$KL{PB9 z^^Y>wuHZ0dv!2|sYgzNywh#i)pWwu(mwcVfF6U#=BCR zq8`uPQS;3wo7b4_4)Ikj z`zgFkqrz!_)E3d>lI#<<yq7?Pr!?G24*AzF_5VwJm&CR)yd8FW)R#{a~we zmd6sSZN(2HI~ul~I=VhodS&=)rj7@aF{?MY9orb}vRyr$J@vF?rvsD2@2H~H+iG8J z(VM`r(2%{gdPiFcL+9^&JqNZ$g$!j1>#L=AMs+PSd(D)_z_YlJ&CznJ@2Q=sx0dR1 z?{c5*VDGR)^7fL1?p+HP?dm?pSXQvEtB|4k0P6DbouX4!#y*-mv)8 zZqX%9!h8xrJP!mlWEeCQcNX40WHWJ|ZV!WX>S1j`1`UO1o(2Z198o)+XrnU>5)4Ol z1rNJj6=Y|K;0O?6WH_2}^oVjJUrzjc*gDOvH3D0 z$8(NOSR)XjbJ{xa43FX2g*L~v4xVw`c$|yz#F8H2J2pb+)|_EuJXZNellkDGm3y}H zG#=Kh5qPJ=pz$Ya-lCJd1?M>n&#^Q{a%?fWC*TYbJCb6y0Mip z>i_LajYm&?<2f!74Z9eh3(+^Jv`S`CteNY z5i~e^HRCCR``%;jJXac*o{L>`xnj?;fRBNn;>Z*q?H>I$4WbMD)o z;N;p%;d?KJ*PdK_M z$%aPgER;yN)v7CS?heDuoiTw|52y7ow6#UHEj#zR<_4?cRh7M`R_;7CXU=sVvrHa= z9o)G$*&T0&&b_JfzxFcEvdh=c3N&23Em(Ncr-yNS?oFebdG%i}vmU(qJTr2&%#Jm0 zZ=C5pXRUji{ovJxv&UFA-bj?W_;l{Egqn+M_ugj;+;&Un%Fn-d`)mXndKgk?-73N-YbE$O{#E&Jfn9)VlFf~$3oE$t1H@)J0B=eFUx8xd>HeAi`I z$1CJ;_DGyA!=E*Gm=bQe@42+^?cK9`g9~-fT_dyZC){$kxD6l8I(x_E-d&~E2MgxjI8k$KQ|uMygU{~9JYy+*Y`O07oU@NP4xazR zdra!@g{yCFfBkcH_M8WeeFD7e1fH)s74ct>p-krH`<~O9e;+>Bb0h5D6@IyA;(h09 z^8}LqKAov2IJ-{Z$(d^jxrf*T?-ibVB4PJHd*ju`yr(|)zG5|e=6;VMZQt39d9T{| zUi0lc;^}r$_svswLxy{EFD3Jx>#BS0V*7NCUXaSUHyj^drrcww-gj}K*oA_7_fN<@ zh}kF5UH3|9>XTLHgfwz)N!~kTUib7|+^a)t1m*-b+PxLY|81vsSF}yu@=kbjH|30v+e;H+WL2}-y z341@Ztb09e-JLc2KIihiF0MNqv-i1V{1fT62vu{cDZ%^xf&98g;qvtx8{g<5hFPa~3N6r2+W#5;S z*l$|;huxn&tJVM1apq1$&VvZ~hf(q0^!z_Gw0_{Le`#v}`dRFU$^JiW_TROe|Dn}} zVY1o%$EBZz_kW6cbGJhOwNL)bo_pT|?tjkN|N3FyuZ?~m!{g8C)_>0AzhnIW*Ts8p z^5(yv8ZV&ech6k@L-_w68L{u!F1{_Y|Ma^~D7Yrjq3@k|+^@v{pHlz7n_>T=QU2?h z|M{;M@&75x{iSsN{mgftR=xik@BjH~|F=8xfA`h@%)bBk*1r#L_#Z9s|FY@4V9D7J zY>WboQammS4h(ILygYg_8WR+p+j!WlB3>jYFn4eZs@N(#B!9^i1WQI>g$;EvyRIU5-79E#>V!K*xFg2(3xUj((j zCp5oVAuD@;fl=hy=6;8H0S*k!vhy#Wv}#U~$=Lq-SN6tD0SAS9}mQ-GBE}OUC)t5-i2%wx3w_(bJdrtYDz0x|5HYMy2s2z4=0| zFBz6=O?}zm9Xa=^=Y%J_gBXOHQ}b?ZjEUuBbd>Dgd}P7C?8(ld=7+!B6t-Pj93gP_ z%odvuZlAv?6=X|K3dpK8{nEf7AYj2@_BWLGK_hF*wg)GK%ziv*5LB?(F-u_DjE9@* z4*f{%>+kyUsIO3_;6W=}M8)F?!C{rD)1sGcim!K_bS`yP_O_pCbBeE7K5?qmEl*$2 zELN4VsJm|ca~WT!n;FYym;K6I(cT=HGTETx|F5hyo3HK4SQ0up7|5J3v_g|&JnA<8SMg+WcwObQ+2#K#Uo4(pSM_T3_J37xHeau+ez*Jk zzv>T%#p`Q6oi_hp^W}1QeeJi~<^OAcJf2=(_v`ic|8;*pU$3wK_xt<*dIq+D24>;Uuyw zpjA61kvsN>lfSUR!I` z!$rd_uq${;lIYqBSDm!L?&v2;;(LF%8ngxWB&#G#o~>{*Sr*uvosukl_lKLsvB19K zCCRdHE8K0K1@>1zNtXZn!`*=`XhO3}iXv~Nhl^U!#O{<7W!aw|9&SOCCND`*)vfgO zNeh}h`$>wr?N85uwxB7CRZ=y5E4@ON1x;O@lBym1(<|aw(6r4fBIy&1<$&?B+YbfrEiX7TJY@KPtwfy{`4(q z3!d{>CEfCDrC-Ui;JL3;(yj0Q^s6`)Jn!?8blbO;{x#2n=l_0^ZvXeEe*;^{0%p|= z$FC9s;8~^7`#P_T-q#79RT{mobM(H>(fc|NQ{%o)4N-#kb()E~IHpGUui8Q2 zzRrxjtAZ-8`Yc~I#XhqaabM@z)iKv!$xoBoJel#*YS?|99jw~%mZz>v(-T?O*$cU^ zGb(IV_fvir+1+PPS3&OU+&(KZcH7nI4XhV3!cX727WIuM_QfGdrRb>A|K~og*9%>n zU4AGFVWc=zPcgR-gjTQnF-zrTvsHU7_)xc zLhid;S1E6q9pj#1DwsY)H#Gg=Tw{styy+4S;xlH4KA&ec_43wrb>F+Mf1DJ)`@z>d zaW;W_tA2&=es*+~@~#IQzr42cZ<@=Rv$(3k_)O9s)@7?|rMqt*f7iZWfv+&;_J{k7 z)58y(Jo_Ppe^2b1b4mMxeOZ*e8ty#Y=E$&F!Layt0RwyV)qP$$T3N-j;{~OEo``on zQa|}NtH^ST)jD<`LuCsY+~+jzRxMyL$nbd}^rAw{Vp&1c)m>L^?+V@Z&vDbN>uaw5 z_*ed4EcWlZvQV9?8vF@oLV4Mm1r=|v)Vg_$$>T`(!)X#vd{dBq^UMpkYRjEW z6ZD%O=OjJ7WVx#Bm~m+H!Z;U>1-nhZ6)j-bedfG*>fv;?qFJ(MvrLu4clthCHRojY zOP&AWmxG^{ggEbgt@nDyk%DI*C4BB)7br5@u-0$e#JhK%u01#FTEM=qg}Wy_<~*Nt zJa&fR{Pxoi?(;QVNU_^Gv;9pXhsN(a3e!IK%#F>=(2lsp)u+4F+u)&)hUK=`el9uh z*Iec8m)^hHZTo@M-La9|Z{H0MHLAScd-c)2?YnbNd<>EGi*lK7boHs+j$#rhD1xbid*~`{qnHyqTi5N% z*2_yfB&F}Z^W5+IuG@d#RrK$=Eh@WU$Me7M8~FD;VE6wa_Qv)}t^S@z;^%)Hv)=cz z{ru1RKmUH5ivIU$MtJp;gXiQ=rT+up*SR~da7J;R{XF@-ufos&I?62jWYTy4SJD2z zZ{42vcD8ucjr{Y!@BNu?d9Kp z-B{jub8UUYyzKb@NWdc)N1f1dl-t?f_T7xe$1 z+2Q+N40#s(dzxPJkKLB(WBrHrS`Lneh~qilZgDTBm!6thzqGx0IXmN@?Ir(~H!ynC zlwE5`HfK|iXj0^;e|@~+$x{wp0|x$%Qs!li6U-aqmsigoux_%oA>fcgUPb z@jRZB$I%&`-elO=6>_49ZAH1kkFtgvo%S4E`^yuU3QBk)x@;=Cf`7zM|4}mkMb{&? zYSD^DujidxZd7MQl;pnX$Uo7&cSY>}jI!ApwVNb*PI^@PMRfjO+P#mX_u`E9(28^+ ziyn>eo-py~n;F#>H+s)ybUl{nd&JRrGPLh#Th_+(UXvYtTd(&qJnQBA(f4piTcmh= zXGP2U6Xgjf`ZHE^F_`uGUvF&rQN!6;!F_Uq#LS7|+bb`M)*7#vs319sBeQRrdh=oR zNg6XJxrO)2ubiYOIZ3B-lD_3+3CYP~9+NHppX@iw?6JH#`P}x&c9K(kIHov7PPrF8 z@#l?6Zab$ObD!e#bBc)PB$13s=A2W*PFDL>PK`QQ6}fY2oMe^Rh8Rc4(WX_eD5 zW=_l6IW6brw7j3w3M8i&SxzsBoL*Kry<+C{s-4qoZcfjB$?^76jOR{y{euE+l`}eK z&gj}Xqvz&~zMnHDNY0#OIde+n%xRS~XUv>AYv;^4D`(78nISqO_LIe|C6Tk1RnA&5 zbJnVzv)0_4weIJv4U)4@ex3RK>cppNvo_6~y=&*}JvV3X`#Jl-&e>Z!`xi>iIaWF6 z#LPLTcFs9tImc1GMxbH#8Oym>BIjPKoO|l!oFg7nFLlnn_jB$8$$5|d|BSy?*>}fs z-iw*@UhSNBAab56=iIv|=Y6uA|0Qz%lg=qeGw1)>IscF4ymyvUKir(pWVL{0)|~&6 z&5ThCxOOezc{PijtNWked|s=CB2f!ZeO2k?>$uqN)ne(axfWIPtfQ8Ey}8(a z))Et`C6_IiaAqwDm^E+4q9q}(7C(HsElr48e7|$4+)nxETT3&t z=EO@aORQRU%yZfHnahf-=4PK-mfJPgB5U^TC(DX&E#H{Ayg_Sm&8yi3yO!5gt=O;0 z(70>GhyRL8et0hFkXpGja(PeG(&kgMTcs4HYptAft9we+%4tz^XL2dbzO{1Ms@N{B zRoz{SPFt>0s#>)nYu5Z#t3skyZG5>(P-^uqt65u4t=RBWXnE)Azlp1NcdgibYqgix zniap~4*pu zX5HQ>yG(OU)Twn+Q)1W~*55j{dTAG<@P)ZQtQdP&#Y{Hb7;NFk9lcJ~dlqx`j6;Pp zOt;8Rvs}n2y<)AT9CzWy#s4`szLx4!Ssgolqgrg>TI++N8eNR?(sD75aU2SQy{ClV z|Jr!6l_}&Q!!1#U=^Hnmn!3ehw}8*=t$H8j9)8@&s32&Ty@juPRcEH5;lypiPxRX& z8y0R9m^f{QzSQQ1s8t?SYad(^ZLQpNt97%^s?EX&R@oamZggJ%X(}nJy3@Q!t;JjNwjtxKuJtvacl_?!5wAJOl_YUrho0%Q99$L7u*UInC!kz#Bm+oBRz43k3PUeR7y|ZNerFWb(mF%rz zcr49uZT8;Bvtn}&YEG@W>KaC!t4tinHE@!9TsLiy_@UR?)}`$4o1)7 zH*AfU?mKmQ-Id8ZT2{y0TYXSCdoKqA<8JF6&E6YNaPOOH&DbKH`MZkYUi1OMBLcs< zGYi|quIW4=ZT8-!9e(r-p4>oP=I=O)7&Vj9`Hkf4} zzTcFWo^tb4zA)xMIL!%w<*PxguH>S8oc+HC*l zxYHWd6N)=^7doCuH1s~RpzF<%Q&q>FN*&!RaWcZ^_><6+nR8_BJUpo2b29GCp${KV zzLw6EI(;<%&7tqJPF=Lw_w4tU+ktDD-yAhuIAi~#O-(Y}+UIOBI3s(x>jd|&)4V=w zjtlNvl6hwDj?GhSPJZa#^?p{&HR}Vyvo#NGTz|_-GJemlteXA*eNNrKec|;XS<5>| z%l2%#@%!KhN6|YM&)2?LyLZolPk(kFsapGLP7I@V{|}o@C)UJH_1XV*%KpSNJ6>Or zzWIM@z+dnEH@Hu&q>dPH-Zz$@n)4M&#;DxBR*Sb$9gpbU*xo^+)?!DI+?Va`ir?7<3t!1*e)mClq zzI$Vy?$q#!TP&q+cknLWviHRMJ4=LT+}?2Z_RQQxEAQSs)O-7N@SS6KZ}WbDqodr#rj<-r3c4=hEB_SN_M|?J2!`cJAF5eD}TglXta&)Y?^EdkTH$u04D2eAI(yZ*L!w zUct5R)`pu8nD5;akXy~1y+=51>7}T}-0SW!t-CF?@8O17o8;sk_2{nMa(7#D)_t{q z4>aT+X1{u*w{MAo++)?g`=;yef5=_g&iP<_?%lda55jUD+UG4P)Sj88Jx{{!vFE-g zd+t8z{`usE-w&G<-KT)dpWiD<(zf1X63DzEBESo){_-YPp0X;T2Xhtqwdwpd#{e}c{Tgpt4;T| zvo3YH5&L}eyjSzrt=xI<^*`>{3*NonwD0v2tv8$f-mK4hv$pTe&ZzmZA6WTU)UB@6 zyb|~Jnw>!Ghfb3ZtW6Q~&)BV5ugUOx1HamX**EK0U(9pt{-;Mo7}R9$*F!$&>&kDU8I@cw^sZDS{c zNZ>8+viFwJvzQLNSNQ+g{{6?+fKR#wpEUYEasAicxv$Rs!zVxc_eT9&Gzwlm^%88p z+h8^S>ZcPQ<@moS%72;S@M%e>jF0^LvII8w`JXOsxb@me(m=K^JbzXCg-?F*-vi|P z%v@C)Ooa_tjz9P;|Ndi=e*bGHX7=|VGW$QoIDB07 za*u<~nG35fET8q(H~#ygdX^dcQ_TDYV+6l5`n`Jp^Le!Y5BYi?#v{f=WN1qG z;{Jije#3A356oo=><0f?(*HA88ocj2;3n>Wh=E71SL&d`>!ZJ|_S$ZAy2&l*Tq7xc z)~)LQeqlk!C%RVO7uo-t@_s`0B*Ck%W-%swHTS-GP_9?)!B=Mog>Lyz1q=r#H+)F1 zXR&7xh&l5|pyEl#0r@m3NfTja#%4BVJ|Bn11&qy%JO*4d8WIj1XXG_#V3ABe;WG^Ra^AAmeG_Fh2_chNe6HvXyK*Y7QJc%U0xj&SzzDsn?o~ zsppr;emV1BbotIwd&{C@%df3Exbxby4kp$Y3-TZK@?Ez9`9X~AxAfT4BOgw?uh7fs>r-ns2uliw~OSDw?(^Y?Ae z=6HAD*0a^g`Iq2yM8G4yB*5yxhQx1x7fbcQ>UA|ZQeL^b$hZTI$Z0n zI>c~+Iek_}(=?^7OZ(YerV7Q2B(44WAaZwxC?gaj!dCS zdVjg45|`{RX?fUox}{>WRw?D;`WDfeiZ~Mt6yL$iZ!%+oky<7P-82SFM;-B1WIP=Y| zbeTgJP8oimo4dnbd|&<^&wQyF4+FPHeLWC(q&WLVs(1>|6lsOs{gXG}y?gXt{dc)% zCnt&Tzh>B*KZEDV@%LFjg=Wj&DV<>d=&~EfGWp;CCLecj(-GG!pSk@#>jnd{pm;{{ z5|PiQZ`ofOD;&GwuAaKlFT0hyYs)rf##ayd>-&Fnbzk`Q-`BK9*-) zJlrOU{z2FGaaf&CuIjw+p5vm+@kO!uy3}F5N(*I<8!wt2UOELmQ*aU#+R*B)!pRhT zV}tyL37ra+iY%7btX?oK6{wpj#uB8+@j6o=HBRJ+O-@SFmc0+y6QG;Dzc7VW+yW&+l4(C(`GTjkAC0UoiZqza%&4^hcD6j2^ zA&+j~$z}oB*A@${9~E|0#mwd5bU(bXMqR&o)gk3i*AGwTJk-6i!J#a{&}D(bA-=L7 z2ZhfZ@Gcj9#<{2FiE{Z$i3BCXirGGK;_Duq^nax`Wl@Tf=G}Hz`~O*n*2>4CKBlLNJp$J(H+tCNzeF=<{+}lqo{|=) z*xv;<)w(<_{q<;T^Px%Y%{-?KrK&VKmKo30HeqIH*IXi>@nx=8=3>Wn6O1RQuS+!CqT<{x8}$ z<%OQ2=fy6jP{WCva#WvIUD?&zj%hLK&rJJJq=IW-nOFbL>Ks z+@%Gq=?;v!?H_I=dz5YRam+r(~r6+~YWIaq!h*eY4e3vzbpE5VUb=;4!hBEj0Vb5luUT?#3dK z<~=iZ82+F3v1K>&j)q%5w(8bM&h+a^Qrez-)J^VyOzX-gn~Y|jG?t6+eY;v+OWkrk z%Q>TG?<_^Qdm_xVHZa^(D%mV`AJfbIOfaWq26agE?Lrf4q}m``+?UirD=5|EuD)qNiP$ z@>VmruJeV5QdF9>sn3#=Q4(I_%jTYJy*aynQ@`)$BTQ}|Iyqn2WVPJ2Ok3f%b(P|o zS5J2Sn$n<`D0p7@%F-)Qb2lF=b&}i2uCx2cjf;p+;bZn|4Tn)zJHt{#UU%>w8F#qL$`0;@ouv{!?f?(v2P!? z^xie8k3PFE

+8wNKZqgFFHg9C$9gI`>)U7N2v_nf0x*R}$nK4g6P_oPS>-cSu0_ zih|@;PKR8k&S-{*Ugn23d-A?wblCgCg2P~oq{^;sj?;NmF9_6_|7#ZCv*7We-#;9r zS8VH3a=qiTb4{eWp{V|%-OpZfKC@0=mv^3R_q>jY-(@T7TB_YA-ItFnJ7C`1taifT zk&yx8X8TVCE4*bNg=}Y7-neJOn#WUlif6R82`JoM-yZwMFOF}@@?Yr@>{ag$#rzIR ziwiUMIb^|JRw{=ukl`+i5hlkm5TKl)7? zUrppU-J+ZDq3t@`>l3N3uG#iCoS#2!k&oEl7r%WTX3zVn-6$$|OMdOQJA2~hrWyEo z_k8BqEM~Q$;O^41RrT{c7nrwQFlS_0AV0zUfbfCsFPoN0Gx!=eK2zSq*kBj9+PJfO z*Q?n~+!;(JT?cLoFr3*uXX33r=bSk|aPObwWx}Doe}nYy%bL5njTOGno;h#Vipa_K z@t;IE4EXpO8K!5OZ@22-<*gU5W$=yT05i|RKf*f??O+mduxX0k*SLC*rq7PX!w!M7 zZC(oN85(XqeN+9t@j+pigPl_QMN=F@sJvS{;`F%KZwq%0yM$4X=W(hV8#TJ&`#wJ0H4A~Y-r5ij-93(xCED~T)IXoxh z)*kmC`wU$inWB|1J2_u0cD&GdP}fhD3qrgHo#jdxc4ixIzqx7O$4#MYlzToO z@#opEJzFhE$Mur5;N{P&bG`TG{gz#x;Tm4!DreGX>ti0(BB}anwok}0kw-3ZZ;rBC z8}@M>`(%9dyrXMgu1w< z^NPnOO>vxM!*u3Fk8Y&rv?q@3HJ&pkp3t9Y#<=RF-GLK5cRc&vc+UTGqF!fjgU#OX z>cc5Hix>NNmd1GSR=VXEcrK68>*99ox+2~4de0o0!!1`%Ji6^=!g6Y#pw~tdPeCp3 z%__W0W4uH*>}{{{ToN^5=|t_FYrN*1IllYK3DZknd!KljvUo@^olgGay-3CTpvh@r zm(%Gs+sj)#b}8xaSaS07Lz~rGPM>s{q=!y64kEg#zD{XaLbiu}}RL1wx6M_Fn za=bq==-#|xcRWOKUH6&QJ!hFW_}n_;Gv$(xwvpQe?Gp#y_}-Us-qLc^{ekl%mHsOw zGq37s)fM?ZYdL$vWzzGWb9-4%t`|7@rTFk29?N@I&b@td?!lV9)49)|ndpA|gVWPT z%b%_}Ega+j?Fi3{lJh2W{MO&`d2O=${2u>5E~nyn{6v17yWDa->rH32i@x^kWa4P7y|ODMNUAod;tX$3b+Bu1uukq3tF4#UsT{LO zUEBC4=)%ijUzd|!x~CoXUT}Qt@0)u0dy|hv>J`J%E2mpRmMmOv#&i1D5x;FWc^B7& zMudi3Dmfmt_3F|WSLF6ylD~U3F!zGuQopIY^f;ehX0ZxO3B6>bde&~i)wEn?_ULml zSFhSX4c+@Cv}NNmwiO&RHf#u)axm|2n0IK{7L&6@rv6J64rZ`k4X6z*yX#eMD^YQk z!))R8qAlT{KZez)UiN=`?%|v3{~4;nN{=U(>GjhJt9Bg*r}tP9s{L+2}8mhR}i zaqdURhuh)mTO)Ed;HFa%%y%Oe zY`s}`$VqF%=bO%(!g?#>>h)9!y{(%+sJth43iod7!UINpTPb9{?v{ws?#3u-edg8?;g>0V(Fcw z3HK%~7CH4bZ0;Lh-Ge;63cV?|ULRI>7?w2npP4Xi?IfOt2IIL)f}^*ncifxBsPHv6 z{+n<7x2H|zN*tw+E^hg9Pc_zkZp@V>bK;DS@=SARWbyT25Ib{Tc&~=j1IJ)x>kO6+ z+WX}f@bv5uVoc;r5^Okkl4IS634xkY4^>531R5?YbZvTiGm(|+E-U9f-3d2hJ9i3B zWiH@0mz;3r!UN_V!ksG~@`T2fOkCk+&}^fyrSxHwTgSa~76Sf_q5hT=7*23+%$QpH zkxOMFL)}k?_o)s14GlaiICZWi{n^^VKLK=+=8p#c^G3}^Wz+xXGd}vhSmehB>+;4p z&&0>g29I4M#Z9wV^a5J>FHGQ+xX&Zu%r=36Gr`hdFj+S;N#Y#0rW>P!X0P-=t`FIq z96NaC{CKE%F`2!N(e_!Blmv&rS@S35M-@};)8r%coPA!e! z5WL(gaOV<@N2hoh82K1DJp!05AGJT=Ie+0~qG9K0jwea$p74B#W1qmlQt`Zf!gFDf z$JHkpN+#bwG(qIv57ig0T_@gdEoa<)%%kUo$9=Dd>7P7a$p2_L_nV>j1jGL+6)7L2 zQ*<*qbx$^^J)0@ro3=jj(6oA%u4+>mlk;iY`R11Sb;rH4;uW4@)hO&tQS0q3VA$!=sjvGmPOK~J=A?vc98*_x@-F1;RXCv)8LJlA z8NV_yGwj}y1@Y!)k6YUA<*9L3+Idahb}!!Y<^PO^&F2`-1f;V3V0d@t(fSSP9}Yj~ z5$Lr0A^2INe`d4DiHa>JV@1km-f4*wH;H@k-MYiHi`xf$aB4)9EJd%acc@umz>&VT^ZRgFev4}7P+oT_I(^Ig-`y@{JHykvBYnY5zw zs&Cr=z6sp@*Ekk`>lgFoi`^D0td}?6<6-}UyPY?tOnmWTCP(h$r4O&y#%da7t(%v& zv?C^GnMh~SnuoH7+q;mq91_CPJ}b>D`BZEe|?UvTWYcW-CiF~cbhvkp8zdzQt_ ziN9PpMHhViMTW}J(cb+DMe>{fWy&&Oc$?8ylW z9>20ovHK|(S2J1{v;=itm+tFe-oQ6$o%sA^MLiMRp3h5|Ebmz?j8)u!SNLD&&Yv7= z*EyyH2(4Qo^oFf--nFbZdc}N^T+IF)t<&x=RGQ?k|9Ydr-NiYFr*HTwv9G66@mu)v zN%4l98x0)S|4dpdkld}1GVwq|q=R{rK!=6eB(Fxv>ipF#`!!lDI_&44W}ETGvpv1; zVZW{;<2=o07JqqmCYGKy;8yYM{6BreJ>K^(F1&lu7@%C%*%K%~>11k8KmYE0!A|v! zxoas}_5BKhrP3jk5c=$|GGw_D?%m*>gN}t5N+E@5<%g*aluAF=2dTN1w z)#7yjU1#rnINBieVrlb+6-i%pKKCB3cfEdbulHj1s6RZn6IcQaKHhzvC$Myx|5LXa zVgEPIv$(meF?y|d)l%^*yoK#Y?LuZ=z0v#h#tWMOeZu$6N{{NeM z{$E~FU(K7?V9Xl0Z)*f#yog4*AzQ=d(D zU9_(C&k6C`z4BK#vI-w~e(0 z^M8aNjk8L9z%KT)Usm9Z{PD;4{ygDZqgu$la>he@p)`f$u8n$=FT6jyrS0lDxyyH- z=Q78Z@Vu@4?-}Na*VemcFJ`k>J@$LY)K8nXoSDkjA+OJN>!k4q1*fKTHUkHP z2Z~2Jgw%u9WPD6I!X{u=)MC)+*gSz(qCusTfz@-Of_0fq!^edU47@BH5)T*-pJrs2 z+HuBmDbUpQiRI6byg+|14#w|gp~makgMK$A zMRgyE%0%b+v&?^AIdMtZW2&n3A|BgIpP$duFLx?oC~9hM=Tq}>U@Uyz!e-^{S0JHK zo^_USY-2)`?>uNpN5794K1%U)HJ3R#A=?rfAwHk>jedetFNxx zI&m~4DJQWA8>MXEYqV=y^+meotir?QNREUL_t{rYILInqc|&k|G>29Ni)7l3PmatP zo)ZqT^Ol;%u|5B**2wbQ@<4E?r>8aZ>BHkG`WhGOmv^`WGeL9p?8L(bh*0L)J2KjmQ{0WCPkU*9P7AvL-W{kQ-P$2 zxoxcf8(E}NKg?@;QW`48B>AoI0e^{N2D@Z#3`bI%LX$;tyz0>CLITc~d`hwJn{ttaPT- zzBIWSwIzH%mPQ@;^YduHvgdL#8xEakb=~IZJTtMqSYP0lXqoTbU$Pax-`m!n2&gP~`R`_PgC+_nqHFm^>p4F{S4P(o;N^mPGvi*2iMV_bQ=`5Z7|CagqPG3>N(59;? zT<@ovZ5q-s^;cR=OmV4B^3rnGV>eQnE6Z-#Z<>4a_8~V@^Irltx#wp_Jo43_{yKlj zI@h1~z6kW);hgY4bC%vtHHIbUo(Npm;q&cHp5f3Zvib>!s!>H$LT|tX%ViZT+Vae@ zTkMopW`}wO&dieBppqaaIPY}upTveznT9R1O_x|nBphJ(IxtN+U>(c0tOIA3Z8usc zp{M1geX)9)nx^2p7Pk|tZe3aQT<%@Mfe7j7^QC;HD;O*uIJk2KZ9m{{wl(*nVD9bS z*;2)8zby2xD|;L??Lql0tw`4RpWw5B^XZ5J0UfCG` zHO)OHgQe+6`O#cOcEy+*Y)k${2=lb4c%@94EqPmA|58q7Z`?06D|sf7#TzbbxCits zvtU18c}aNQ3rTLf&z=5z&N$6aV&rjP3_jl|v}FN%g-@!`ste*JigWow$I*F}X#J!uOH_C@yj)iWO;svcs3+^;Y&h#&!LjT`Rg>eT zQ?1N)7|$slAb|_kTV)c6FW5)8$!z zS4AAZCE76Q>l&}Wuj6mty4JEsblsattADSl3f`R*QYdzEwd=Q2*LNz2t!dO08cX5cwH;~Zx5X|og>5(@ ztXt&hb!X|UZ(Ddmb7PdFf_M3SnPDydnOPDHp3QAHEZyT#VE+30 zQ?9R@fBJY8xt_jv#OnIm3#`jaR(qX$_37;{;REus|8>jF{WIOr)Kc6tt7x9ZU){}z zzP_qye|4|kPjz2WOBx9~u7JI{ZJ#^~X)}>k%6z!2^FU z{C==#jrd`uxdriBIS;4Yl2{`Zr_*jdXEJ}V`H{b}8)u&Q9zR9H^(cShng47FF8?cq zcNo9R?!WCFCj0)!hRA=f`fp36C?>a@m}I)iHQp&n^>&KS(jqi8vELm7L7GBO* zo)ow5+KwKneKV^ih;f8#m;3X7R5^2-`IES;KFw+RR)oMpB)>OpnT-&$z$>xl~2D&mj8LHD$Z>4(*Lzn z)9<@oc(hD^YvQZ7M(Mk^X02JV_&V=I+w#&Y`@~)_)SXWKIq&!Nmv!s*XbZhAPd0t< znQi;tP~W_+H@^@4{Z_tfZSC8}>!LS5Ei3mIewSWzoqs34d-)MwzSm{`CjHOed^;+r zmv`X66D3~v*T*lPdNtk4=BCK={8_Gjg?(9fj>()qd74?cxFdM?vqSqT7h4vEC%&&) zY&(B_kmcu33!C@6P+ERF{oIP&$oV$U+46V!d&+*T@t%7+WWCjf-ni@~#kXHGvRhlv z_q(-meYe3W+us4{?}E1OpY=$sefy>LeB~V9E4HMhhlhRe`?4iIRw?Iw)t>w7isoOI zt?iosZU4`CFa95AuB&UBU(>?df9|yYy$iF{jh}O`E54vFzEoG<_T|Ap`3>fBw9-I#OQK??{Rs z4g39{I<0kI&bxfN<81d~b+}zpa8>;K?vVEZy!(GFUmw4&+-~^|_x({<&)2TqbbrCS z@Qjb8H$Fz**~Kf@n;HMKG~&PDmH#)xfAA`t5>5E_H;#$xCeyWGW;v-pjtg#oytnpe z+)CCfulL?!Jr~TzcAL3x{>G{EpU=I|&J@OaEs9g?7-!gRwp9#J8dg zf8LGfZ=3SZRbJR?;jX0#B6+e073Z_JNpak|yZLSWV^&3}OZUVdDg2F<=RdU2ai#p9 zn_-;y5*hAIzpon0-L+6&C`6L&QErTaqLZR`#@yYZF)UTLMZ0FL%Uq~zm-OFtk(8g( zyR{0Ub#g*lYzk#chif0b(RI<-cUr?wQGVaUshM+L%g)pCW6@qFEh(Z@(wL~UZ*Fu? zVnkt*-m6957bfa^Dd_~w*V(mjPwYX>Q;It;J~ZT7^kLyc85SjjcL@i!9yF19Y#P?` zd10b-!D6{>ij3=SOKv!-l;z6Pm;Cq0JmE==+WY=1xd$k9yo{6Rx+B+`^Nny|AwlFIBJiQgFs#NtjyiP5n?f~alJib2|CY?jR&n1Ik5#{^6^cFA|EK25(NJ)2 zNuk;D;-o9>jLVCjJqxv4UK*D!shD06v^+Cud3oLQ4Y4UrxywV#)GOwxS6R&~^GmO+ zN~_xUywWYbNN;)Gq2;wv%WEd3r>i}w{idG(OuhQu@_Igvx^?QcVd{-)FLIb(G|pPy zWT%mBq*1<0y(Mf#hF3=Qr{%4AD;$zmG!$ucG%ahNrqP5>sjoxQ3j@(@FRd#9Lzl;M*Gc=hrCw9%5a4u=0+RKYK|7Wymt(^Sp zS@*f;y>^;Y^W-MiWlT+5nI*fjKWpXmb62KL(&($xoLR&+eO>a*Wtw%aE9<&e&f1sx zYtGApLz;8>B4@11nEPzy%zG~$*QLFRpOV?id0?FvOX8htv*)=)SWHP+6TjY4DTHJ7Yma~p zuUZ;xUT;ofm~&=Xb-?Ov&yJT~nYHoXY4f-JuVYq+cydMMXzw(O+ZHCdWQU(1YgW&t z{*77MTR;8x&q>qXdntSSq?_B91@D$y9db5%-=c=C*Y52RI=6YB_M$}-7nxD4X6ZUuYi87^qm)7i#I%9H`oHX{o-F(H)=G-$?$KN2*Yd{o%3)tl?{-lXn*n>Y3Lgt~;B(-ahbKmus4rW4rE3;s3XXnEdZ1z1*MlE|=%pjl2%blF9S-HFOrXLVmb8Q#rJH2&}9=&mm$#c1~=Ka3Dws~*8 zpR}Hsr#;6;|KKF;Z%jIedG$Z4>A$_l_TrrG;l6dVnS7pR9ot@|f8&w%@k{$h2Nk3U-da$fdFGyQvZE{DFYXL)+$Ur`&QvB4aJ zf{#W92UmHY`lr7|DtG(LH;my&K72d6r7h<~9_v=UxBJ^%BN=mDZs?!!nh@TS6aQfS zyC#G8PDl30h4W<_oY}`2K4*hVz*_FVFj-r!RZ`^>hx0+et0L{+%a}y^(ob_-ECd*9Gy4zc(u97hJ!U zE4w&b)J^wC+y*_lk9XZ>{VVz?Rh<9+-iBGCAH~!QZnb@qU%zSdD#NSO-nqzZa;^Bp z5TgC(R*~z2k7+aBxEdO2n}1N<%_n=Qi0!_iYP+7NiO#fpY$C$>VY0=Vs|#IZKCR#O zaq~YhndHr{E!G;iyZA6{Ryn@$-LbX2*FW64^hsTPiSBD7mlejYA2y{O_`g}celwf! zx%-<^EdE_H*v0Pgj*TzjVucck7}Qe+l<&BVOS(#~!tA)6=y*EO>90v7q{g zTXqKa*NYS`e>@yiY}5QjPBqlD`-{e8V-0Hqo$jJLzlvoyzrU1cV&uNrvsx)+#;09! zTg>n4nV$Y+`M=w>y~Jz%TK`}Z&EgWT`zZ{g1SW_J4MrZW^4d>yW)VO8ZNQ z@&7N5!E3aYjs3&(V%a~&25**rUgEc9bLjbx@%#+7%p$t}C5g%fLHb4^%ReRUHnE(w zCU3Ec@8nIth4{lBZ_(Ji)%v?e7_}FsmlxL*e=AonSO5N{`2P0P{hu?vOZQJR z3yLq#mM$?^ZK8R3Te@|rSGsPw_BY$zS-lTjOUn&4Hk&v6{#p~x61sX@n|ek2^$iiX zKesZMH@I()y6v`Y-?k?1AGxzD?rqx^>Hd}R`nRs#6+QpI`5a$m<87Y$y1bxSyK#O= z+kT4*;mWYk|Em+PZ|R?G(dOM!)TdpV&6d5rVzTt6N#T|`!rR)lOTCiIE8m+{9gjZM zw^sJNVS2xXeD=;7e&bs1pVF_lsx3F-;&|sX-`Gj`$M$FClRkfKh_AHuuPBrL+LOOi zz51uu?C)XM)$VT7OzAHQ{$18T`D_2<9UFdqnowUcG5J^Q+}A4d1-%MuJlf3~Z?EsV zzGLOfJvF|)kbZmCS?ztN%KA)F-h|CoTimNFzW+)yuzmgS)1mj9+@1Gw)f?@+S7S7J{R^Y2 z`Rf*4n7H6sR9yI~Ya5I#8MgjSv-vrv{r;WHdzs8!%WhTe%zoLi-n(mc*15@9li2K( z)>rj?+BQkaE?uH_qS3#HuPamJcTe`Jn`pI9;`sl(+5a`339o#XyPK?cV&ZUHtgp+D{qApY2{AxBKvY-@E01KM2=- zbbj?w{pDNz|D9(0Keuaqakp>xv;P{s;%mHpOWOW#+8W=>?HlXtfBatlqyK;HwEaI1 ztN&VFU$xHu_wnT)_W$qMwf|4G`rqsSOOy8T_%QxwJ|fb{%qeD4A&}_Q$}MR&B{9jt zrIT651ae;|Q>f3J6-mh+6P2Z-K2#VbF>`1~I?Xt+!0`+lPX^b9&ZcEPGjyZn99{-K zuv_TdDP~$Fk{Ptrd#0JJR`MxNVc!sotyQAgVatQ9s#;cw{?Ccn=#y&l^JSo_|8#@W zYkOuIKl9rvP&2Q$<8WTw-s+#OTDIWs+WcZKqFv%lsW+$SuL-2QLn4duRL>oT{`TO)4beI{4(#uJ|JpJR4a zd0scmv-@EnHsS zKdI{J&KsuR>t08$yY=4w@c!@i3-)xJ`+a`@*LSme9lk%ZUTys=W7B){Kf9#$*8dZ7 z(Lcb*wnxC0nLVT5`HR?}m5%3Mea_vzL+uW~Dz|fw-eKPOo^#Fu={F* z>zhkEbS^IGJ#8BMyJ(U6=4OMV51uVDGBeK%-fSj3rDmhAt$%Z{lBK^&u&c3lPY%~+ zrK~Ud+!Dq8{$GP8n=GnblXCt9zqRe!PXF&*Ar~%j_-gvdZT($%)L4G=fsPeE2OO{L z2|3Vcq*Q!$L)esrw;5bADG~}dxUA1g)7w&vbd4;wP)9_mOuJJmd5k5}5w6TOzlET7ywbM;Hy-Z@hx47-9` zg#xX=9xh92e=6D)miv9OqMZQ`256!t*c{WO4oI4Nk}g>Nk3FK zFPv35O?7Q%TKC-W;8S&JWg#8^TW%SwyjpZ2DSQ37h{x5%+ipHsc63?K%eISQv$t%E z4O<&N#pLgen42?nE5r8Y?mMls@9(xdC$6R{*StAwZu>Ix7BSU3>k;fN(Zd*jI z^_`V8zpmFJGo{n+-jDyqRqO(`x2JngU-d9?tEkkjgJ1bd@9bUt>i7HIS?gy%Tfvon zUgyZ%ush#7%dZx`vRpjH#^cm%;V=iSY^Pg!&o(S`eIF=e?DTa@o9*#Exh8d&1J=AN zGVx#8ANOqb0?uF4%sc<|rdt%t^ND<0_#-p7E;MS6`z-Z|Vy~XIGGDmD^`CjqG>1OF zH9=E8dR^8($EvCnFq6q;R+u15VN0c(Oasp((Y1RWKIlCE`0xs>eK!Kk51oE^RPFfTk2yJg zj~bP#OjswedMz*CI8*%7tsh(G9}x`i`=7HUfhC#A>)3`kF@r`G%||EdSl8%R?CF#H z?!L#a>A3Cp>hlW6KYKDL1@#&p7FBLuX4&arlz#8@Os+cC1-VaqjJB#Q)ZZPhC-Ta5 zN_OB?xu4gC*IZq1u%=~MRTAgC1&Zf;k5t|BRJb{DZ{_CNd0vvc0wi>NFKMO9{*2hk zq$wHN^s;ct8mS3$UhdCa<|NB+kmmsr%@{@saInGgggQj0tsKZlmqUqcT zrK;ndVyhNiStV>6*|hxmm1FVpdsZgvbk69jGU?S)>HEKCSGv<;UBeufsp4x+E!(F2X(7UHVdexR9E1T7+B2%|mJ~MP|73Y8XbcIK*vPblkHATFy7aaa0 zQSHYidsnffQ}=W>r?ix4a$k1x=IUgl!iZCwuLUnI`*}I1?5X-%N0wApZ3&ktAy+yI z%ED*<)w1(EE_0@7qEYWmUu*Bf=l922i=|fQC%rY2Jstf1j+mWE^}UlBuIAMr zFKt(IlwJ2LZ5ylI*50Rob#L&^JaAyz1e>2fLs4 zX+X;PMViU?dP+qiAH)?)PW&3w(jubg=iBXYd4dKhs!IjSxN9ac zzs{excGL67e{Oa{ZW3XoEHVlDKDNBxEr&Bd8+3H5KKe7QZ2v3kjjVi8>-O?ln61(M z(5<^GSkhR+Mm}wJbmmvj>d*z_zX-<38~bhe?+lSj8&k6iB5 zTq63+z-`l2({)dcHy(>Jbh!R)xAC+$>4B@G!na@apOf2@P$8LirLN$bT;N*IB|HlM z|H>_&B(%|CUD)X?Tj7c;_6s-pl}7Aw+WB2VL(0 zT<-gf`L^d-=T_ghulv5l|LgnC`9B`)uKRH`-0on1es!6?{I8eHQNmOScOw!3MSg0j&R{3qCsae>!%aHT{A~#00kd38py$?BxPRB@@`| zFBsGaaI{a*Z<)YR=+BULgyU~BOL(6BEP;#*HLEvp8|{vHr)DV!29rmpkm>bfCmD`fx?1@j$(#ZdnzsK zELtfjzLA&zt^i|;p>TcS;-)|eMnkdV8~FY!3fT5-6q{@)#6MAT?ni-zALlLED7JE= z)bj~9E7mjJXBItqk#W+5hZ!HG`2|@|81izSlQq34bKbB*LywKuK$_o>m-PXIfP%>P zjdz|HD$Tmc$S{%Hd7`vCFY~L3O3i|b;TNQ?1~UJB%>8~Muc6>ahlz|TizL5ql(aSC z+8fCCJMhiALfQYy3;{+;4u(p~g({vxisu7lCa&UjULqm+QAf{6(U!@dZ-LrrUxAed z%B}C2r>Cet{`hg;CbPOv9Q_ltRu^%s7t*c|(B59ezWb7PzJSi*BDUj`bfPcloHt^< z9Hi?#LHD*0%Y7j|^8mf)mzd7?YcAcy^6;bngNgd~@Ad!tFV_3n#LXJ`_m7gMd6xjQ zg2Cq|#=4K(<$?ZO#R7{DD`X}cd_SbHbc3X#q2bp|5&2F0-<=p_7>u?Gh6 zH(1SE+4$=sfkGoCmIbEim-r=%6?X@iTsvQ$x6$lz0YlFwJ&sFehc}qq^jG@!*uuWZ z!d=+cu2`v~*z)fM^TJ|_*G*Q1pA9;T&0>|!K0mT%SI~4UR5DcHyjY+VctD*cP+i%{ zVj-Kw!(uLG23EC6=2tf>vMX3B7hA|CSiLV+@)tB+xY>KjWP3vgN8iu(uRrpL8}UxQ zr0`MLPCe-FmCtsD#hMe>YxXJ|?l0hSpJ?A0Y+<;7TXl+g{-nbH4Z#co3(W6*W^fa7 zJ7{3ZuE4i+GI#l9e(n%c-z&B!i-j1!*ydhQ=eek!w^5$#`RkcrnBDx!&m)hm8#jBsH1Yl`z&+W>@^*;b@(;GWlLW5w zvtANm%nDKG-NKu^$-DB3uYif&c@c-LCH|osxh)TS?Z2STFx9a!#Q9?2-A(76o4?q9 ze;gWEV($Ib{IG%M{tNm~O_cIBy0IsSoE7xmS>oPW>MLLpnOyR>;;P%pEsFnNe-6qw z^|m}5K6eW{=hR;@!AcuSjFXGFE?&^czrwp(WY)7__0X#tfs+~KHcI9yg-bGs9WS+i zY+~wsRmSm3@c&{ZwIW6DtzOBW{9h-<7>n5Kof?*{?4Ev!H#tc3Ww2wdsbKuo@-;9t312@kbzd_q5mx2fd?&|PWA3+1$!ovgeA3<; z#_VZC#UT1&a>ucX_CYXMomjC-& z{{R1C1^Ltec3x1{j0!2YuA>tFK3%WeJ3%fr7PxyDy( zo*jAp!xbgtKJm)Q;g!>;SI)j(IbXbLad_48=~b(*SFIPX-W*=NUA(Gw@kXhBbrpx2 z&ZV|0+R;)5pZ8_o6-EzS`wC(WC~ z&(}vLwfql`Xbqpy8a<=ddj_BRjn?!TZP_>4lqK3OPt?qx(O!L{y?#e+aYa}wb4T}$ zj{X^KtvgCPB|2x{=$yZ!Y3h!O!{S}5EjrW_m@IB|ZLg?r{9eCPqUUHiQ^-W_tLI%0 zTl6fIsM#;kyY`2eq(k@7?TjpiOt)@yUHu`y@>xSE$bWk-?fUaczX7>H@}j0<1cg41XlL6dN+9e{Pq)sVXnosjeWXIMb_d?*P-IXItn3zaP`$Bsdh$)Zw43d&k!{@(uIf(HS4%LN z{hSfV#Hhl+ERoRtU)^C^=FE!gH>TC@VBBFib72M3Jj-;&21cWsT`MhTtevUqTQSA* zW}Etf<-2}%K96APD`E({Ira3;S?xQgdCr`uEHrO!#Jrc0bKdXlI{Qe?PTE0(F-vZC*#WpC@@m6Ijb zyxp;2t;pXc0xKW>>WrP`{PtI$>l?%D4cyhsKX)|N-r68Odt-FO z2GKPem7_N~{@5sAu}NQgbK#G*#m?^-s~K`<>~fKGb^_EOK-ipeamyDt8;7p{h89+EpKnLwAx-By{UZm+UDpT?X!1u-`>$Ly>oK(&grvv&c3~K zzVxoe(YxF=H=0-MS}(nObM)@*vv=>ly?g)f9sA1{ADq4C^zA+8rT1Qr-rF9%XLc3r zzRu`gs;jgL8&zUOH2oMZVi$4&1X zE1q+_I_6;co#V|hC%#IbXuor!KW1;Y%*p9|w^(3*37Ta^Afly!U`$>_zFl`z7yQRF>U;)Owa;%q9J~`!!{| z&27~T?p_xDqiR-rSy5$)>)y-uvR9dF)HX$2xqng3@79$_o2%)6_ohFxGP4cNeZ=4r zyF^lIL? zj1_;ctd+g9*ZRul*d?`x)x73jPPM&r{qN53xp(xh-btK$dGg%5uWNT%&%JSWuENsV zEA@Bp{olKDvjn4v+KHhC;7`h z-FuIxuGH1r`5<@ErJGH+?Dst~+-Jv8_k`Ku(c`8^fpSj`|2dlMdmztpCiULE!j7lz z`&{Gi-7Wq1IJC}7vF?fGy-TffcNEiS*WSD1UH53J-P4bc9eeIQpJ?}dY23}3_nys_ zdzBdXx_sWV)%RY0m3=L2_oi{~i^NAaPv5%}yXEm^yQ_sKuHLPCENuDeY2D5L)wMIL z?!Bu${4`Sa*;l)(*X#QJ*FD*P?=WZlrR($5{>XhSm3@D4-bdlNcCQw{RkpuZX!lzC z{(1fV&rI(>uavY9yZ_n#{>QBWU)=9SG~WB-fB)RidFFD(U)R@t(v<&p_mAqAj&Irb zzgfzEFTQ`Kyng1ITi@r(y*&ElMyvh#P`Qc5|9|xV`(~f~Vdnlbsq@YG?0?<0`x)wb zckTXD8|(d^-FUS<{#RqX>niy_=kI)-fB%o5z1!=93M>VG^|TV7mT;yv507j$3e8t>wNXMScpy*bU$<|_BE zZSU^xss8iuzFSAS+m zT{_#mdgia(6SvoHF8go$Fa7wer?>a(MTOJDT54&2M&j(gRe6zR&W*(R;gKW;B)hNe#s^- zS+6Y`feYLwYw>oKth~HDU@`bQ!^_M3YmN3E*!r4vO;~G_?XSt{t^7g?77he1dgKf=?vV<#}1mNA8}He8#{c{O;4XLvvF+UuJqtIESN1QHrPem7)i z$v7Zfdx7iR%Gr0ZUuUSXpzcRuC}Y*Mw=9g&A5tSO)I5KhI=v-*YN;bTw@$-sCXHn) za9n4&YsPJ+J%67Cw}|^#M6!t~El6x*SJ^(tPq6LAoK9P$>kK6sCj^-tzgpz6Zu!kg za}^WBtK>Z*!q^2%G@s7_UuRhUPEB=U%C9F3g&EgM95OOqK3V23uG8=wa-CuKJGDnM zstuu0Jlb7CJ$$GtBC);=38T(0U<5#xi!(|CM2Jxni9eE0#Z7^?Kc& zyqT+J2$xwMT)p9z$gPP#S|b`4?KOM-X7hzZ5(mSR>ZVR#>LunKz#zmDo!uepqWx~i zlTW)EHVIw&<+$w@Tf>vB$?saScYe8+tv^q8MbwiN>y5KDne0llUb8>Em;K=&M|x#m z@gBwQrs_sEfe-EJy0bqVl}L|CW>H*s>*e7LubwcryfB0H$269&`M9OylUWg~gwD%% zCsgOJ`E=Go+UIS@wOdc~3)x?!M4q)2&)u^3=eC<0{NxjE-CQtjQqJm=?%}zmTRh8; z=Cd8yXUf2QP)+;&)s*RbU!RNd4P#`9z24$*BlvdCw>u@qzeFcy86UXG`2O1eSHhcn z=S6&f5MIoa(`tWRrd(N!~_eDVH;w1+*KyR>1GWur_(^R}Rc zb9ufqepTJbyd}V)7S#9%ZnRLA%UT=l;N$V3&B9%8V~%#Gmb`yda4VYSm4MeFeR^*KgcHM=7~8wHi=bzn!JE#sfN4fsi3+^6MA|C znRY+g-Lj{*(9ThH`Xn}4m93gncZggUbZ?o)wc(wD@QbH2v>aBoy1Mu5^hsOy>iDSz zC2tu#UM^VKV4|d@`FYkJr-jPzCtENkBsj6WmNz@@d3=3EjxBqpgMO@=PtmU8x-(C@ zQg?(2?DKV-_5RHBmjNCB&3ErvRUBm9t@G9M!ko94qC^WE9QU2HcM$usu+;cDt8~DG zgtF7AkD6Dwo!sVCsnn^!EFfiP?saK#>6P}oN_-}^>Dp$~zAV+a{7hE+z&hSdAqx!7 zzC6o!%e$3pN%6xYEB)GMZB({97PL=>gKL|`nMR?nEB8)%p=@u^*!bpr@Y|MEVH2O7 z;O59x5Ig!R?6}wZn#$C939hGCZSuOd&2Dww#MKuZ6c$`FOue$U!ZdvA6D@&VUJdEn z&#X=;Ee%?BCUmWY?3><6$8Q|73fpL^^~U{oRCuw|G&jx*4Qb}5Z=TB&tDGL1x|PTK z=KfozTMSRFNe|zBi)Fzz*GYd5B>HCGy#9Zi$JRAx-=^oDUUcJJS!nrF-L8Vw(RX)+ z?a$K9T%=1D4e+a)bR;n40WtI1+CI|ANhsv*r%Io%KSR!YpMD6r=bTv zd6d}S3@VGxw^eyWZBkv7c9MLzm3yhfgvscCxK2Xky)zAe{Q+u*N>Y)@X~Qe@PMtH2Xew zhxI(*HkLSMbZ%o;Moywo^`$jdd`0=ULv~9|j#ypp} zIu&tl)3ohpp6XTqJe|O|dB$;{XGXJsp2_grJnQr{&~KEZ_5I9-xp5OzbDqs8%QEw` zFa4^2U0%Sqb=gYAmvy_QpQ`uG>2^8$Ds1j#b%uMIazKN1(^deCp%rs-#MTxLJKnResrhH{%*UiUYjn~}F|+xD|>6YQl0V_!5W z&(nRKxqQWqExtwDub+Ka`hFqHhk4D5PBOd@;#t7{@5FZouLUnM)NkEU*tfM}WAB?X z=|2zl-8(G#>1;tgPlDJNJrl`Y0)=+76UBJXmn;3b`&`K45qI^x!;)+dI4b`)@bvwc zI3W3rA)`Uu!fDdVlBu$57+P-sdA9CY#W88WLMQ(_@}l=YA1&WkXs^DAyEEbE+{Lz> zh3^>!(|@s7QQqjcQ{cE$_V(#$gs--z^UWBzdaeDwNnD|Oet+hG0gW2c$3^oeL+xpxc&QFihu)WQo?uZ`3qU={r3FT*!yEBw+v;IH+U#8~a_nAMWUia=?IlMZaFmBJ*_NkQHfhzn z|HPXnv~#4WH#UaV@rE}9l{Xxn-oSgK>O?wwu|q}dhVtAC#U9hk_it}JvaF)GH23&4 zB_)fNKJ!B64dv<<4c5z>UMw%ut4Mbj&-=ty8f0Gia9erWgNBE0%)0IMnTzXVw`aLn zv{{5R1x|0VPOp>QUhMHAJtd)N#q-*x<>|#YT4gMht{<)V3m}H-_W(@M%TI@T^l62H(7LViRj)|(Y<3v_pTk?dv0{^`_X+sqUR7t_r$XH zWh>hCJ~5rz(R1cT&$%Bx7bJQwS@d3s=)G3adt*lLtsT90ZuH*!(fdH6@B9yzqc@tF z%DehHlpgKqdvl}j-H*Nx68)bn`d@qWEuGQ6?Yh#J9sPf9^#A+O&mcMBMn?a!j(+(S z{Y*0_aP6GHb8`aU${v=?zNQ)N4l`2tDFGMmHxu{V`GYN9W1^7L%=ZPPREYQA@JG{N^O(o0DB4r?~y> zxBpq>*x5II1%vy~DFKpGAI+E&GriAGb85uQsZo->AtxvKyqt9B2TQc&w8tLI$pxT; zXWbw4={8I=ahN6$Ia!-?($5)PcPd!p6{Zz%PEWqTw5ftMeZ#Z|KUmTOX8f3zZ>zx8 zsyX9c1xtLuj0uu6Ct9*qz3kO3U<)tkjrE+)o;m$i1uL_|%xceR;TM?aCX}tnEVDIW zd+0GE{==*dlCyhm_9kCoW}eW-TRvO&L$7ua&3nkPIBCMna0lk>n@nCm7T)n-PEuHMiDQAvt#GB8ldpQr zcr7_kuWAbCFIJNW(@))CGMTWD@A^CosrkIyXML%h%bULBW(CWSZwq|27F~VX>2z|5 zuNIrF!NS0RbX_g$8=SG%&FyZzn1W*uSk_z6nA1-yycW;D~2Mjl|iS9n>ZI&arFt#YFm1$ zch#>YbF3!Ji<-d1HEr>)Rm*No-l(;BW7o3#H&$6MUwunr^=r%3zPsk+XZCtj^_1>h zar?*I$-7p&Wv=;tZBF&8Rkf>D%AK0cV?M83L-Mq0+)lQ&w<}mqc&#FGq`6^XpQdN{rj~+ z;B?>FtO@g!RtZ*blsLVj{nX5^RZ5b-H!65f{%)~u;j3ke)teNpH>z~^sC8}9o4rxE zdbQZ^O(wrLX#GFE-Z*-*RrV^w?#*huH#@vuWd6H(Zst12>Mbu%ZZP`2+2i(>=$o5W zU(NKh-WtWZHTTx0kl9-UR&KHD-Wv6Lt4rqA8(Wveac@hro}6@gQ&9D`9PP={yQkEJ zZq&QPUJ|{%ta^LJ?Cn*%x7Xa>UiW)@gY=Fj>m4o8JKCyubj;q-wR=b9ZMLM4slJ^I zj0QWWMDLtdy>rIwowIiDoO65Uyx%()Nbg!?y=zJIu4UD`R?ObDs(L5;haG2{+pmRf z(YhqZ5xsj`_3j-pA>4*lN#V zn>Bm&YPRYuI$&#a#O2J|J+lwm|JmmlbHwM)k*Vq%*b5k@N+0FOKInO7zrmX$QD+V_ zY9HloWK57bmT*RhU0?(MnmyOAZ}8PT5`E{GPD~R|BSVtS@scxQX=e`nT7As(*s;7j z#}#;v7x^4-iaDMd)5KkK)S^eJ=Kq`HJvj$eB^VlQPE3+H##?xtspr_Ws|UUBoS0K{ zFy*+wWSLXNImf4zpS(8xWbE#fbN8I$<~gzGUfCqDh!pTVUOy`r%%-E-}C15nI6UiJO?V*oZK_vd{yS$^WP_K&N<7GaDp|bcH5sr4|LAGyR-Ln%(*jv&K%0g&eS;@`RClPoW0j| z&OhrpcgJVm_BrQ&$6UB5eg5B@)04O_eCa;1a@#RB*^BJHWjE$rxD|8i+Hdts*@v0$ zo)bDd=fv#YF?$3=b1yFRIe$euh;PG1rnBd|&aCA)z+iNif$#r@6AHD8ccd?KY$*0U z#BlJ>9_EJ2nzBb-Vz0Q>Uh$ZFC3Vl?`8k(v`s}vn-OIrs7`S&H-`}(PYgY;_h_hJ2 zARKTtvx0$>f$_4pxIHf;*9FFdcNo-57+!Y@2g))e$L{4`c%`iNx*P8`6UJ$G3#RBw^0qT%O}WW;{fya?!%j{yceTn8F%Jd|NFpr#_3vyt##(xYs?cEgaz)ra`lm2@NmIo z2d)bY>>E79FEB{D-<^9^>tdtd6#6^3`G1K#doo*-=W_6%>8 zFwX@BNrSs*Za%24xgoGFpU>cW>Xu9Y8tzUEc`i7CG3EHfe+3LO#j|hRy)n`DhU5Z< zJlUK5|9$TY3NWb7%V%G}AT;sj6x$o8i)Qbbo4>&5uFbt0Hh$OsH864&Jk5H`-=oL4 zbLEp=Z?$9>s7(wJXS~4pb|V9CA>+Z4TUKYD#{N4M&-Y9#@ap>BC)xKdIoOp(&^rsQS^FsxWn=enay(uuyVf#%6t_5nHxo_niuJ8!F zv;X((Uw{hF_Lnd9)@Klf)GOEqX7rD3KhvNF2yK*&hH|ME5VP3$nY+v#1 zeZ^C38C>^W-u#&V+`kv1|Ced>*1mil$N2W%D{UhG(H_YZ1=J*vyo-g<-dR2OUzHI&-PKHmi=Wb|B z_@+^CPHldh=KeGGBx2PloV6n|JSBoByFh>y8{bH^*##Vd8uK4B7q6 z4hHA%J-oQ?8P9Ri6TDyY*T2~Ac{4Hp`?bE$*4Mx9iGOa@f34F_#_8@Z@q$-wvJ7^y zPkn_LviTX$*lP$}5aMz8d->db|NhrA?N7}9&wn}0zf$q*p8L1s^mK3L@qIfrFQ4(l z^M3{JWcNLk)%$*;?vYmAJFR`c+TQVRJzt>BFYu4aC4k}J{Qdlbd_QI{NI1k$&!eX^ zWdXyY{oJB_znmvDH122SG;)|Tf#GN)pR5?)nGF~3H!vHfo?8-m`PtcSz0!BkgJc=ps6hKqBY_^l2c2t4FmFR1!yvJ#fKx(3VZyDcc{etuo?jNLz2NQbO-1Xg*YcV$Fzsc!xLf8x!mixI+hk1+ z2$(ih|K?q>+onG7$FYA5CF_lkd=Xx;hfzS`1H<=I)tUQNmF)60=PLDnZ_Z=Pqh-Mo zVDauSzi7*;?Tji73Hzr@O*qi_>X>l%noSpix^=fd5X!eYCk`O}K3AobTTHW?k`(Q;5=c%Q}7fBJQiOmP!`l=J5R7va$VThv#yI8X9b4HPgu z=lYuE^*Qbfr$U%mw)`o!VP%Oe2?_Ex6?ImYZxwMdcbB<(X}!T!5nVQ$UhaThF1DrN zan-6YwuJ^uEs+UYARdcS^)ww`)) zdRT4YTJD4n%arc^J~PdCB*Z@ns`p#H4pC$4%-=$)1J)mxTkyW z^(+1F=axQ6_phr+omXwQIrXJMU1Zz}t8W%JC*-S@pU$W)`<1EKy-tFWMdHGLvr8{K z?54#nS${_1{v6qVUQsJfoBdW_dH(fn3}ia<>t@Vq;qqwmOaQ# zy1%R{S-sb8N}j~gREwNF-^>1dT-Y>wR@U~r)g=}@0Y7eMt$M0^C;Z4>w%aKiPd5Mg zvUlmah}>1i_wU#qu)e?f zzBBCG*{9-l{|?WW|6Q7SJ}<9v;<;nj>{))T$ZyA$~% zXB_5v)vw33DnJm+b%s+o1-4Gv$I*7GJz8g>sN{1yy*9zdxHtE>j#jw+m^iV`#wA&L zfBODAOAFfBOOj>hv%5>O8Fr^^PL@Bt?3kt3h5qI%(h997+?>=tWwzZ}tR%bh#JZ&^ z4XJA$D(jv+;Bl`%Ws1uaRoj>PZqxj_KfXRBxv${V|D`C`8LqQ=s<}I1AHzMr$pRv( z@~uuH!Xk?VvWtozz(sA4?RMB=b+`cLR}hRgo!GGLSXpE7x7@((6G!PGP2aRrmI zmng3a31G8*@sR&zO1h**ppW9$2Rx^*q}lF{^sND1XV~*h{MFC%hI1COzmXDVy3nMw z;J}4O`HTa15Bxaov>|wruy4Ag6XOy_h0pV)bJgr^e+5qX^m&%_-3;@}%W_P-os1ch zy#Wt?Oyf8BV$J&IX}-?^_O53ki&8&4^E2x5oiwe4L1V=-2B)f!CEtu!sK_=yWLvOk z3fIX6k`5Z-Mk`m&nAE-OoUoTm>94CLpANF@PdvbGwCb#9O47<(GR{UlQobT335?tl zXS7%w<}G<9xx38uIU3?(SNor9+O{NPw!=v;v<nof|^D zA|7pUQTCf=tPwZ&g}KlI#wTjw%x^EOG?(7MuprHt&2Yg%IW@s%pOi(sH*=iT>;#$l zw&@_}(JPXbfLQT~u5uBmJ(YL13E41ue{qMO|mnEeCau+N*kfYap5R^~X> z_*CypdIqZ)RVO<%Up%yO-mjEZpAht@vd5VOHFk8wq{X>Bc z4?A@B@f~zw63yW6e|!4Yw-ej9@JxC?&DVE|pUd^rj&ED$gk_}}n`Ac z{<6F0DQBT-Rg87L1c#;ReZ4<1{-T?Ah~2z%c!7Fhj#SHZHp3f7)Ql!_Eb>uM7C+I$ zAof7#foxv-RPpJD4O*@^5&T`vn@+&U)ZKB5=%aCfJx*CL-!O7X6^!p z4_X1&87&%)3okg!pwRG+dxi8BfgKHRoMhkYuAX(zQMP$b{Mq}zj|gbnEs9D!dQ$em zq~OE04-H&}`|c!tI3#<5)!i?6gJOt-_3V>;C8g}KO#C%v{10;vt9^4|-XJ_n%Kv1; z3Qnbc9*?&&&8g!Ke0Fc%jQU+$m%5&3oE4Kb=XL2l>*;SN-%@UzvQ3@&?>A1i3r3RD zUwzsx5%x=?{?U!jIOT_d*IU#gCdMS?dx**@>}8+!_HL%W^;h$GE$pjw-+Eq25|#_d zzL|Znm&@hB+Ay(rXR-ea8alsNFtHeVpZd%19dCd`_?Az14(vAD*7E-KoB*wb ztA2W%ZS&Ij_2J92*yA(RE!KW+7k$7NSF-Q(9tK-idA=!(0{d9!?=3Wo-q8LleKSjL zfm)!A-u~TMb65l&Rvui<$Z_CB_1c4r_Z>Pt;p>G78qE!Ln+;;Cxph`)E;ym7;IJ;` zL0i%e*00=-aUN`Ki&q%k5McYVDX(i2YsG4onu8Iu_KC!-j+{MvG4p<&%{Fc)H4~T* zMt86YTw#g(aqx>a+ml~x!Y&6Tr>r%+(K@TyQKLkaIoe5zMS{y`m%}B;c^SJpt@Oe? z?BhJ_1zDVxMcP$b?6qt*RChU<{gMAK>Ek>pt5M2EzQ9vz$>KdaPZkF}aAXazkJDfi zWI1wfH!JT7E1_T~i#MzLvYg#ox-@-UCS^5Sy3FuiIorBrv)?67y(t>JFAfX*IU=Cq z^32M`q~%Cewu@VhbMPN^&n-@0Jm;T0?DBAuaMz_hDFV&`e~z5@ZZLM49m=xS)#WHl z&5`&u%f&K|)}J)hu~`!Nq%+E8oxX^p>H$~Z9j*Qjdf%OoB*m;U7C83e^`_jO!)6X{ z0lNpio~N8qIY4nv~_dTcVxh6he9j&hiG z#CzSFQz;_LKd*Aw_(!$VX$SvexAV*!+oZjI-1ZI?I3qNJWzU%w>F&neOBy{-9QO%e zUBYr!GRFDv9raL_6Ui}Wrk`25{*T>>GtMg&*72_Ki9O;|W;Fjl;|=eS4?fW=?3c-$ zy=3G2`jgK2MNWrm&ek}cIbvgStK?*(&y*QF`+eSUp2=?K&t^EC)6D&2gWztB2QyCh zWH*ZioQ>RIe{sv{wYT^C-&ptD$NBmczl^T4wR6tCUUT-B;qlZQl?M*$A6(X{d}uDH zXi^T?ufBuHjDabk*xUBT=E+)(SLSS67Q#CHjmN4rdIxRJzMADVx#iry%jYAM+;0By zUw6jO<>on=ls(fIw5WJ6C?qtScrc6XXj1psuUyfnFVQ0Nl{vG(o7;9}-;)!CYwXt@ z@%|lrW>w4KKQ4h`kIol-@fTygP||qu#G77`h6Wkl%O*RT#4`fewz3)eHaj-#`G2ot z|JGL*UaeW4u;fxm(8Yg8F6op8a_>6(R$_;QtEjfXbbLwGmJ+acy6}4p19~C(X1`eEXL6;v$e_ftbqC3)oQI* za%NrjROPbXd&&D~h^?!z-c?`OKsHGS#&0%Ts(!S-$q5Z_4USmK=sEZ5)Sk=HN3Zy7 zofd2Bp*u%HKl)5c>N39>S3)4@ zD96gQwde9#uNUN=xqWBKh9{vVo%_#=oN=D*J)ws&`o?~<8Legkt^OC>lh>SHo9vtR z+B;XM?SJu$H6|I%_76N7qLykFF#XJA`gxF1NrFMC;d=k2Q=+ZcUgt1QKYMlY&Fc|Q z&ZW(n#yG?4{2!Lkj`Pl^)#kqSo_>ZU8o-OQQjdo~A_`7^xjN!fzR-*^&mY8_<-d$(r(R|*kIVbCu$&MSv9PCmY?Aj6x z!U|25s$Ko1OiBzl84Z}!3z%d!FsR>PIuqJ>uCz&0fE4`d z+Nc|G_gAmlhE&h#r7gV|@2Wn%yZP$%!d^i?QxB%U*K@Sua;@Sfh+J$9ZT+k<#pS}X zM_cvw{qgy;_@2<+xKDd;yj$8>EYMbvajV$lj@gDABI{xn9f&&18mVLvDH9O?rSLHf`@%Uj9<3ik}_kmn#c54wM*()qx8P} z*K4nzi@h7UD%3ld(ZygMv%+mi@(^#dB`_XDl+%N8&J9{g(E&S%)`y!!{ z(Yh&zW^&fJKD!s2Ql0sv_|N5%!pns^C(oU-pSVW-|JjvtJPr)*Cla>IwtsSEzs6gS z$8XmZJcv-88YT6ERmOnP;acK~it9_gGW<*TAx=qb)C~#Unz@a=|?8{=RQ)6>Ao8His8hI zoTX7!U#~^J$h@Nwy?S3(3!CI-fgN0TR+YS&TB5q=-H)sdX;~h!&AGO#@??`%>OGqs zab_#ut8LqaB4Td)ehyzZ@8CNDhCN|f?kB=hbzdvmycpTFg=VEFJd@V$;-*mAuM*S5tv`4w z1KauQMN=X#roFkd?F0AxcQXUtzitaz}VXr8sp?bT1l&j#z?8K$4)t1n7?_D*K`C$sm3^6j3w z^FI`aPElIEJ><}5753MX?N`cdi+>y|WUYVmsqpjrwqk{gS^w4*i~pa$dmHE1d&Lbg zZ~3>!I_xhoJ741DUg#YD@vq#c*vK!T$x8#*b4o6MlMr`QTKk={(ns0p^1kkQe*Xpi zmwyf3SgN1BH0FA#t$F6DvfNiM^AgJ)!_~hef1jALyjW>_ar*Pzp!M(W&-?bzr`Ys- zZ}#zTIqyH_UKdQg^U44Jw}A3*oQ>c1Oe-t)cUBTBEZzStRIBV}T{&;(cVDTu$G#Os z2z{Te@V(ajX#Dr@|Les|LWL^Msr}%&d4-!}!@s1AOJP4+_m|hKpVxEkd;9Y3b<;o9 zvsZ*|`f=#k58+9BY@EJ4Wc?m_iC5MBr$|X;qhI-_#GheBKex91yuV@5BC-8?=?DH`g?>cj%P+TgZQ5R3z<5YC75J1Di~NDn>o1II2w{1+stw^rf4`mR&wjo zjf&V4vB=e}M_&IOqvv83&&jIp^X{0ebm{Tc{ic#7!NADX%p=I5)6wv}k&(HMOT+5H zvWA7+QY->iU(zlt3~W`(*u2qowryB?(VvKWu4!hYj|-*|kDCYDyS0IE0McZhl!QCMji@ zmAYzam)q>9`DT?be(c_!bxBsK-oha9;5SxBq*&UwZzUFsT)D6SEc_gmNvAWJr4rFd)^FT;OE7r_^;4NRN%mK|W-RQq6x zy?}#2R7O|jvm5MtU8fyPo07OpGF|-6PKk_?cVF+Z%cmIJVvLddaFB)lPTK)i<`f3T z9>E9G!q_FIJd0&(G-QpLb2w4tXse-S_@q@cEp9mJUSmDr;K*g7dWA)lqm+?NctzO( zCJtAvmCVd44B>2|G7A{iYLo~x)V}RJej$f3<$|MVBE-jPn@_?1mzm!L7*?qYp6%&uL|f;dxQ@-+?8=a@D7pQp1LZ zihmoP`LhadNL_Z?iM2h9cgm(t`@2)JyooRB#l?RzzlCQx8BRFJ&ix}N zoOwoB0AuQ`bEb}Ld?5#nxiS{YGORo#SUz!8ZjI60J>TL?9=aNMO?bZ1I_Z_jEs@tN zifWJcP5Z?9TWyMX?Ow+8fUR-t4>+{m#hG+Io4n`A+k4`1BG;br`Tg*0x^!3fvf85S zvll3wXDdi$o~SQS;K-XWXX8eum;(+$yNv%iZe(`ZvzWz6F{*J{_u|`bBCGR-WLK+H zS6@A@yQXZ~)(N3p#!(Ulwq<9_R{p)t#5{rP&Hh=UyL4uRE>2yZ?iIgbF3Z%PGyh9o z$@QvLzuIRuYv&BMyd8}V=M64M?}%y8G>_zt$|;tdcXw$>0e9a@Jt5XzPjwwX9ttQj zNRhqEdep~HlPA<6)q?#_8iRPioB*i>YghzS7$h7Vm;xFarC%*+WZc2P;x>hI@`Q!O z^Nl%Qew(Hs)$rPJ@&r!aLqUSwTl7@EojSic;aUUR9=2?aTe0k2(-~TQ8N%;69#|vB zVCb*tUm4+ipt8wG_{H0Wee#^APox#??bUIwPLp(C&1i0Vf7GW|;=;{ahpy7m{*agE10%MPh8obXm*#-%fi^KP`UZFLlV zcqrND%a4hx6^;vvRtO8M{i+?!yTF~}f$z_ZrN`V`xtMP2NHJY0n|yu4(+M-?#9#U( zGLz3Fx4xlkmIiaiiZ7``;VIP;Q3?*Tx2*8-sa_ZvbtXxC%Yp|>galmNo|FoePkoTL zO5#y-%qPy*G6}|q{$HFJoT4#yrA0p!2JbtWIyc`IKGigVLcr9=Rvy3dgEE zV5nD6h&<`tU24rRJtlYRuAV3yTV)P!~f}lE@_i3$5Up69f6)h|D!;Imnyl!JFIE5z&w`S8=C7oAH_j zW(tSYdd|Qzm)fMKU4{{{b^!bnwI5Kw`t*;t6o2O9S(=_advNB^Hg`T+L5@j4ZZ>g zdauUrJR|XJ%|5A5PX*@h&|rRMG-rFyI?2mB&)sVN%iQ9n|x&^>c(bEoHhGd>KMZFD(>^H zE6cYPuPW5dwPLK=v&k&$f7aWXVajbs?Pq?wxa$9$_@r*Dn`gwhHaX9cH_(~ceCfoI!Pt*g!nwUtKj+hF;7#V*?sFPk{Zff z54=P_cFZ#Uzwgv8w^{DN{)@PG&eS@@AD6rEo##n`2a^xy#%`TEf5JAKviI?M!S}!J zGC%$I?)u%PXYwW1N*v(s^J-x2*{nBz$7!pFCU-cbKYO|_xX0V+ApdRl!wuUN@5x^~ z$V9Q%>8rkGk&w&<+*pF8B48>;WWbw*^RURKPihn;d%V?rR1|8 zoECq77<%|-jM?Qz4wZfb?uY}b(mNTBi!5Q5jW|$JpR(xXyGX;Sfjo9^S#L&NVd|=` z_G`VhVb0aVsrKQg&Y83xQaAIg@xGWc(Lw3JpG#}@1!}!YXA@cQm!U*~F<-HsMMPl3 z%})DvyU2#0=^HQlURmVu>ukW^g`YXIR1`Y4bviKdcsrh8V<;1z?b6U~a*CaUfi0tJ z;~g%Alb*k}&I=N8sdPCo+vdpIPL5dBhPRBec9!c;t~RmT zcrVgnc)irM^1!@fwQ*Nl?j8EtS($qKf6i_Hg>q4Udf&(%S^pvATJF6$2@Xq5{vS|e z^-T=89{t0a?S}lW8{QW#9!S0DAZXjseB{)OJ)KN1JhcA0uuN!U5%FQ4GV#NjxxYlZ zKYZy3I^g8+IF#K8mE>g4)RE&#;JTyWz-)H(y~F9UP_GBP2b^<4b{x6P zdCG-LY|(|ar=Lk4yLD0^+J>9YDe^_`@vUDIt}rU9aW1rvkcnnsXX)Ynd_nvt>rtj9 z?4KXF|NFp}_@jsMXR|`aInk~&B`HqsC3DzwM6A|6_`AZ5$;I`2!y``7Gu)+}K?gXh zmMEV&@Sl}S!y(1ySe`(r-;;-ox8j)obkrR1+56D_uIz!TDVNn-1YA>2cv~EL%c;Wb zlk!tG<-t?df-gRYR`f8K_$b_!VZG)m`P5(I(;S}KhM9jn0yx?>v$UR{>XG8&z!}o6 zdrm2ZH}GlhkzI2RUNoJQb;7Ot#})l1@im-}UpUTP=sEX~!eS#PZnM+|@w2WbtInVO z!=k__B9fJSj-#o$WX^}PEm|rQ8*Bm<-yP&Tc+@JZ_sGGc)@{>l*3ET#cY$}Sh@F{F z)Fb!x8V4L$xMzDzXSQJY$?NT8b#8UM`%feFrQy?;x(=^?@`RW@D9i17W7_H&HHrJA`=*p+!J^0Eb;)~b zTJ9wrk<&T(aV^{QHA_{uU5NhY7n9Y+P{El~*vWa;kD0@Pd4rGG*As6poH7g*H?!@o zmU&Vu=dkIF7thwFZjz%g(%g*vGQRHF{;ogOy9BFtGN`o9|^5 zvioe4R~%>EgpTVEUc44farC$vkQ02WfjxJlXRyJ68C+9>bgl;+&Ol zZDo5>7%D6p3Rd=AF!kiF;LGQnyUm81#bf32PnV_cE$CC?EqKNmWF@=y?}K!iImZg; z*0kQ<9UF18^k#NP)J@sdU8b)eF1>xyv_;urwWG%D*>k10Ro&e@ch=gfdu!!<4Zdk? z6MekCPJ3Qq%*JoMoez1IrM+GpsJ+K))>f%`yLbIxy*c-F&&^dk|E2wXv1*6V;~=TW z`-CKSEz|CP>bmz+)E>Px&1bbYtL1FFr@gPQbst|2pWM>7y>aWARvy)h+Lgp|L`~)^D$RYdWtTk86W}mt=^8%mlx^Jsre06;|qf% zw!*C0XQaHebbZr*^yC`!pXcb*67`#>;Tl~e_{LWE%BQzi{z>gslWtD^FS+93>}QMC z+}{;(ok=g?>8ckW6(07jExhEZ70C1W7>|kRB&LAQtQ|tBFSMe9yyt$EF*^GHekywCMb8;;(ez%f)!y5*?swd4Cgc2P@88aP^1W>R z?L8jne;j62;pe*0w&In~hI#tDJ|7mV&RA~wfjK_+#U%sQ@DHcrHvG|lVWg76)g=Io1pZHoTyAI5D`O1`DSe1p&WwVeAW$#x@! z)gPtejV?ZW^;aTSuvkaC-e|FJ(I>g}ot^76=NoAqk5nzz)((!oxKHPRnEKbMBH7y; ze-}xry#HkMy+~{IM+N@eFI~AZ{XqtoH_g=iY@9z++4{4tbFs>>m>+$BJuZ(3@ z8=IZ~WWQYB=KC#&=LL?NXV^MRTmO$#yRYx8{gySmc&6?LqvOWL&Bdxt>;V zL(jxsexvhz{Xc9a+s=LV>@Q|j{&FB}bHC9{!S%&v_9pk;O7_nB^HI*#({FV~G%_4oPAJDq3$^w|R4& zw04vFj;8r$&8xRWi5Iu9fBhL|kr;1b!fa7=|5e-ecj?kU+M6wEx+`2K@5s_Nk3Vgc zvs}A-_m9BCJ1U#s#u=A4dhg%~x9E%B+IRVTW%vKJ{+E9g-~Qn;`|gDQ9jp8X@N`Dbvq<&|4oDyCaX&EGj?xn;}$ z9qrqH<{aKRbM;4_;}x~vE9OR9O7|a}!M$zX{gTrD#;o;b{pMS3Px;bLY$NoMrz_RruHONndAo?~;`+SJJUsoW7wp z`g2){g>`k+>hq>+o>#C=w_3|>7*ti}p1xJN#%l4;U+a~vRw^^Cy}ql$Ft$v7cM$&p z361KRZ$GRyuU_*z_x{w1xXYI9w|9E+AK1{iOTOBAVt%glnWi26|94NfuIf@Z+xA&} zS*yiv@h!X6o%WorUL0OsRs6lm`cRlo_4fC_<^9*VoXOadZMurxYWef+$|vq`zUUyQ zy=Pjl&hcu=Fb|d!?KLN?{~UJylT^Q;VA}2l@zvrMha^;(&K%!!M&0T7^*v|r+nl@q z=gj*WC5uCnf2!@Sf1mFCXV&yR7v*g)ZQdAVKRx+-@g;lPEAD&yv`QAW_+H+>?MnLI zYx#Ru1@4V)2wFGW`g*_Z&FOpHHkT#p@T_h4dtmn7+x!3CC^ifH+dDhA>f&MBd(Uf^ zasOr0S4fYuUGjSG1AaSe;p+R>Z6B;Qf2eQwSlH-(blZLIhP$h)9*6IHI{QbM`@bjF z|NlNM-}hWQ_L=g&S6lx+pT6&<_w0x2GoPv39h|@K_5OcB`riTqkG-}4xTOfA&}(KDXbX=Ie1puvh2$b!ITs2vV2l89~K^BohoTm zBk)1!5Id(9tB_7-K#Jd7>&kCmehOz?n4^+E&u8bRr{0W)>8oTagqH`b3|<+wwMz80 z>mt?OODQjf{x9>H<-btvOqEz})Yj~qT6d~GsGQ}FpK2tV{dw8VZJ>i^f15vwo~*G~ zjraBzwGh@SM)R^i5=H?BS$8Y3?lxgg+El4r{_T&X$7!n@!M8M6~4Z)yYl-qtLg>eoZEuc^eYx3V{w_n%YH?y?j>+9tH{R$7i zYYJ6FIA13m_;K#>IfeHLcQ*b%%&)(~&_!(b;0QGm%K%#7F=ZbG3Db5n3}*^Rk<~=&O+M&|04`fALjUFKhqbv(dh->gv{up@*|Y zZ-%_J@xB&a%oG+izbMy?fl=mlXmEnPZkNI73nE5-$#d6!zLAso`m0jfM%U2DRg0Ex zy_}`!D;`@T{rKB;zx_|O?(7V?qkl%%ymkBkh1s>Gy|LYQdZ%9qc|0{ME-LZw%Lpgm zvl0fj;a|Ub-!{_T5z{JA*KzB*)VdcB9jmvNuj;aAE=z2`(|Xfzf@5grfksBv(v5Yw zQ#i#@A|hA0C_LMxUNphKPMN1aGGg-9x;Xl)$ZSUh&ByA1+c6*_VZ|IhQO@>~PN=M99@8tEK<(s`| z^F=w4cPkmU72Hrc`o*lUWb3@?&#Lr8Z(Q4QS@*7DS$}DG_#DYezZb6HuM3S#xNi6N zW}DWux%u_4ztxucd*0uwbWb*_O7Wt9xOqiLF@Md^=hgXF{)g1h6`A^AwRhN^MH$oO ztx7tX>%(8al=YqZHC(;Pw)BDdU%Q*Ha&yC~>VK8R|BI~exBv0*WB-W?(UszV?zFA> zb@P4fDXUpe9|*}5_O6QHkh$ghVXFRf-VJ`|yk~soGuSp+lcg^C^n$e)w(k4?@?7op zTbDUHw{Htyc)D`+uKO(JH&%RFsMD&SqWE8>X;1G9fp`gV5dW`QYj>LHG21Wpe{MqrWI})Jh!fU-+uc=y5E=@8vFX_xO5v zC6nEbeAlj-J25XYBw_ib8OQ(a%#3N|k`hX`m^l5YUUyW;qa%IaHf@qU+mio(&m!K# z9UlJ**H5}US?u|z=et~Y2__ZG$n%;`@(}L|YBGH+`&}%|(4 z?dr;(yBGeyvg`W1AhiVEQ*i<8m4dTkUo9)V^O8ZgbnPCuZ7U0MPZ*@C2jv8ouFjgH zroPeZ`jJ^zCRF8SZB0+|)F$^_;ub7JXgQJoi!g{!`JtZPO-2nW#HgM%_6rE8fI@DNlX>tLTeyqCG{{ zI;D|p$5x!3J}dKcV(!lWq0zV7%D30BW;bnrZS`f^l>0l0JUk zE*q9p`G04K;Cs_as_WLfuKlh3q3+8Ti*B(>`_6?-D^Tm4khg~wRHLU%VpD2@Tl6PYdcU{5HfXX$3^_O>u zPwVeDeRATK?52I}?>^7DAMXCIX~)SKVMVp-I<}Y1&R>o+DR}WECbhaM^pM83M-QIN zy87Ad`_eh@!glfHp07W6)X65Ur0C?yga5bK>7Si!cm0ze_jQeHnkQ2?r=RtBdn1-< zo-Skllf)Lj0!=+<(e6!Vf<-qCYh2nS+-}SGOH1x7dOEkX z!^XrTJbd!?t@BE_w#|7Hq~F!LVf*XCWwI}4>g~Ss@Z5x(PySmKs|aV^W%T<#Ieq5) zyhNqml31CI(mEghAX+8JFB1Ez4zhFVxHJ;`TSG% z>nlYb%C2bFk9;||uH>5jlXp+sI(76H{Mx%;?tAD>n;O;s-|qbXXTE%T?R#7Pn#aFW zzH2S~wIk>L&BylgKbM#DxvaITd=Ve7zVUzH!`1Jr-_*}nz7@XZ;adK>&-pHY?kxBG z99muXy*~Db$wY>)*W>?vzyIIrBft9RxBd11XV<+oIG&!-|M%Z{2Kj#Nfc39lo&U`z z@PE?!wJv-b8$0S(?wHYQUT-@cWYf`;ZUJg$C@F1?<`D*=q%Ul^A?&o50bZ|9zecN4Np! z+4HO~``OhCf(s6Ctry_l9KgMO0{89<-1`N14jXX$K2QvC;5jeAdpUsjd;!;%1BzP~ za9#`Gdp?2h@C7EVLmajfxc(mGT7H24zkmR9paA_;X_8kxSB$K!b7#uQ)Dd)rs@>PbS;f0D#AMFzY z#au7i|L-dlY1|-^AD}qrqJ7;&(bz(%_KD(aKiX|D{I9T{&9FdR{E+l}W|_r-GJl(; zIxot+WtL@bW;@<5n;ak(_)&UcpyHB=awjj!zTar~N${Ubz(u!bo^uS8?p?HdRH*R$ zqur~GGMN{}HcwOx4&d+=RCpMu@V8Mx|B}LPLB*TSih>4`*C#UW+$iZ>q~yLy~owvlQ?=5 zbRC_zKLsgo3)1|*+eo%>V)`^e6-Ou5Z3jek6SyY@DF!hpElc1IQP93~NlECkSg(WO z--8@;4(Rb-;;MGyb_zEBzKLh@CW-Zh%Iup}>yH>Ly<}pzNZ)p|$-@a83K!+wKAX8u zQc<6*=pZPxH9^nuB8Scaj*bVqmWPZszSaACkaN~$PQQm zn$H%?6t(p?nkKZJ_+tAEV$*?fN z`0rOh09fwJ$7wLTlG zIV-W6TsBU>%rE-c?e7Loz9~+B4>HYKAiDj5$9n;1-vn-hjZT?HmWqoU{!TWW?cnuy zGS5*HD^VpY^UIe08!Zo(Se>0B^Y^o#RWZY?U?cNQZa*(@ew(bwU~GE6(6sW3ovn~k zYmmSGBKzXcmcpX!avv?f3Q5csv7R67r8?Q`{$;O?UzByGD9KIXcvj-Hy_j2Mi{or# zw~JR?UY3NkPYkg%_Wx-RT7B6sU)Y~JRPjfM-N9zNw|>%J1mji$Uv6R7^^hd=}XLkNxQOIm9P+YE=ctpFQtxwvFBr=q(bP{MGjFXIt$@v8qno%MwKW zzl9ZVj##|W|F%+iV!ph7!Ed%wo!e8DwiTN$KfuvfXi|NR6c5j zyv&r=h`426%T|2EiH4py_qxQ z0Jp(Mx6bdD;gdaXZ|0pHZpgi*!qs0f?z;RuqiGFAUpcOd3vO3<_^r@1L{#*1cxSlU z#Bjv}C0sUJ^>2TOelC)^^h(|S>zpA6ys|%fr5Bs{UK4&K?f=J`#zOC_hs)&; z6{!|GeiSp7cB=c!nD0BqR{4tvw}|8V>+yfT`@P%baB@P>|IJ?;eos>^HxfVj{aZz0 z!F1;G>&i|2g%J5kJJlW3J+JAk%nJo1%r?@>1iKp@xmq1Ko?|>Te`0-fX||v&iovW68}HYl9PP zi<5*mTg<;4dRw4Iu%b&eqM&w>&)tool}<68*SarWSGoGV{9=itr;$r;V3;|puj0<2 zmE~0*OGK?BTuxr?E1Xh2_W_^og2dBeN`EZMwilLhhjO=-=APIvb$J-8-cgpUoxahP z3_;?F>#{0i=XW`))%3Bf` z%*5UK)9p7(8mTkQmQx9XHK+UWW9I0et=~_e1MLe z(FDyyF}F9*bKPm!TkNJTlA0fEyz&a?6w~CzSNPd)#@;Qj%D=)R86FuL;x+l_g6c;d z8->|_O%8k>((t)tcI%_IP@}HbpYxxKw#_|XV;<_vImKbKsBylqQNBpj)UOGvtCn8A zGWq?4pwlLay9<|o6jPoOsbV4==y`?r<`;|pU>DvSE7nGuo}OuPPIAKP3MCK8rT=|r zty=oUZM{|C-GG?XsjJUUnLkq^`KZS7tH?Lm$XvIvU zm8DzVnZwtsDzW-R@vz-&URRSE|BiO>v((UtMXF<06Oavy8j9PkujT+w_o_=0%&YGB5uB&C|ttuBOto za>?#!u|(I6(?prH`TrbXzQdpSTSfNDfkew$b1Ow>93nlX@5Fpd`aT0%@)&LD{uFg9dtf3z4^@kRmT z@TMC1M=7rZZcd-e*Z4y)+1}I4 zaSy!vAE@4az;E|?+J*g%QyvQUKiFUUP&w|={~1TW%RbWG_wJK{m&U&vmir#wmDMtp zyW?v2R_NXnZMmnx^PXJ%sV2a%DsA4g?0e7h<(?PEJujd4y!zhrdbtD7sx-Tz)Ly!YB{{eH%KFV@a`GijdW&UwBzcI$u2zB%dg z#xVKZX}Op2b#Kh$-tIiYv~}OB`*l8#2sYHlLM1NgA1R6ni(sz(St9sM;IfRSaqT_RP5hgS7I=7{$|fX0HGXD>wQgchAB$tze;eq)m2hUoy7#J*e zYvRzY`mQJob9icedcem^DhiMmAhIA)=*=g9^yvo`2nn@&Ms@F6<)&o;!PI?y38np9~%R`ZD zHlDoKw`wI8Bs911JW4xz;oG_gyTUi7o!L-c;JV(_e^t!Pg-=h3$2T zZJGORSk-jvY<@Xi_ZRl&-+67L*QUuA;{twv-ClA|K!nfXzyizW*Kb7MxxC5W+47Z{ zZ$bXQoqr2oNhQ8s?mMY0*){B1z!)p*HvPKOGSdeaDuq-m7U#%EeUC5OKJ&)i z#(ifiU37VS-~_s8ac^O7=Kk8^4verX;m#F3VwWskqybV7qK- z;~vF`v^hoRJkv9>xNbPk)+oyio?G{iGh>0sUCC=@EMh;OO>ELTm9aQg=+v`?X`Crf zy87a}GM622ta4)nRgmSCwQ^a|(M)!iyvUW)m+#}sULz!^b#3|1)>G=9@uyB@ zukS0eda$;7)r(c@kICKE*s%JMSI+zscHFvi4?NRs++ehF)@zU5Zl`m%AHO(D=>OJh z+g`2N{%l_Moh?;GH`lCtv5z}H?Ezc$B9^4oZppdK3nbs}`{T4Fzvllw=?}4|)_Jen zy+-kZ!(ojHo)=lfr=4;*%%fmn;LM!DVCd>QcRJ$&2@{{9VapdD+i7&@w&jk#pkhMp)0e;g^w=QFF7=rh0na%d@|V5pfQ1cd(Vm$UF()@ zsT6)7A{#pX&t_MGxFuii=#1(ry~34hdy7SYW$u*cF8yZ@Fy`{> zCNIv?-(vNjOUTDy{v(IOeUC4EOPwIVv_qfeG0TSgSGKd%oByah>-f}k!n+#Qxtr}@ z9;{Ogap+}Zd^2mc&(E@u%~h2fXTD!;9V=k5L7$~0wc!GTz#8{&*Sg-nTXF06w@~?n zuKI%Qf0rfud^zT*87m`VkPt8Q+5N*LGe&le5B0(~t{?xzw7vHruV#9a!^0_`_ASj= zTElE|_Uo%6g8<&_kcjRx?9YPi+0UG*{broNJiB~LS7MjC`W?aGD`v*Cd)4y1H_N_k zd$!~khX9j>)r0u~#~M^V`MdqQz0gVOK?B2@^(DKl{g39X*e~XFF2QM6uS1^e%6C(> zO(H+Hvuv9*T}AC7cr0ZfqiK5q+2BzcD|0 zX7^&6K-9Uc8On>FUD-bGOGPe|qbC>BEFqB)E~DVp%`ypBj{7<+6SUp&dEw_<$0o2v zd`J&8PMIrkQsC~w1HBfHlnrJc-}v2eR$ETG z9yndpZE4>|XPK)J@j=g5Pf1#lmeGB^XUR>^jdewCN!bEy&or*?$~wcCTbkYe;MUyg zt*;l>>8?pEJQN_fsk@~;cTKV)lV)h)C)LEG*SE=aM{Z}DR$Zi;lV)Fi#=*=9`a;Y9F}>vq1~S6kwACF5f#0So&G(KL{?WE)ydn~Z7$PrqAF|8 z^{-QU^Lrl4yskKI^KN5*J|?4-asL+dhb*&ettlP3wzxSn2^#7X|w^r)! zUDc-V+g){KiQd+gd(XUNsIE$*h>HUhV6# z>F&{t(X-2U2rn=(uWw-F^El5W6u`(L@aK*!NB432xW?YkzaHGuF5lNXw}F4g7Ur+t zVh=oQQ{-TgVdOhs#_~_JVb64V|PKZ-lBpKZbC zh|e)dCQ%zO*4x7@4Qf~+P$rLUE|s*weJKj{Jwnj->a36Z(rRT=Mc?1 zKesWPVLflB1FO`7ADf*2U6~aA`+TJ=Q=p@%zsjnc0vO$m)#cO2Mw$x58{PA7?_k6 z{8?=@|B6j|={n8$H+&P$yIpJl_S(duc8|*gCW(w+kI(-r573r*D*r?3+2*wj5*HW+ z+QpeWn8Xw67q!>dZ~LFa>7H@Et%kjTL1}re_=3WH&+84I=4u$%?Qg4 zSltD4q4X1NxfL0i99mP4Hs^V?m!3!~h-liA-d=s8J+-Ypw4$h1qodg(kLO0Q=Zf~0 z6&>9>TCH!ic5`&r-stF%sGXS6S!2<8-A%~+MCaTWo%1=m7HV`Y_UKxg(Y3szYvqcr z)hD{vzUW%t(dEC*GoGbmdgA}??H%1aS9I?_(Y^OY_kNC^gBm@DJ$jC2^c?r-iA<`x z-r95aMbCMT-isQ&mpyu~R`i^j(SB`3@9h)4cVG10=jc1gk=m}&{xGBOc}L&N6@9N) zbZ`4uQ&>^=Mx+0;NB`H1{{0=j4eYVsPxSwN(f|KO?@y1&j~o+NGbgZjPB@@3p>M_n z-j@^jUrylm>=f{vD4IF(rN+cr6%(aTPL$Q`RdsUS`eLH8=OopY6TWZjQCm4l`{X2# z6%%ziCmUu?l&sV}r8?QXb26LfO@PLHgd@}p>awdIr$&*>~Dr#HTwTzRsmDRYJa=cN3~8Qq$beKT{v zU7FgXIaAhh`fH(z$(=LRZcgc%IdksGi7hW@F8n!DV`lH7%vqc}CkP7&Pp_P{wsXSD zowFvsoVD4q|1pQ8WPq6PgC_0=5?fx*{(qzJ?y=dj8yI#TWY}|Z_UCd&!4I>~X7cW^=DecCKa3Yk1TeBr6uWtHvFEC8%}HXCA1aNt7}yJ( zBn^cBuUr!Ts&Z?PfF%1;6RFwv0~y1$mc>6@taVX_oogvaz>*NIW%?G&@}o2~3!VIL zEoJ(^sD5N}zSg3;olA|PmVXqJoGT=jw`#fPsU`OXC8dLw2ku&Sa_Qn$sRcj@GQlgE1Q>d6<;*^{NVa|PuIEcM1XeK_Fiii&IMZvzvQz&T zME+XrTIIxiVb!9lsd zz4qx%y0=%$KUkrmxY?L{y5NO%?@q0sGHY|@uFWT1*E&>BzY(b|cY6Jk;w`aV4CY>& zRDW;wc|CR8$2ha<|BE>cx0cQl&$e2dz_lpKdzv42#*T~Y6ScP~zh2@cy*Xp{RHxU4 z_N!JgbZ^d!UT(p91d<@J4>gS1D8@M;O&)zyYdXk33&I<0Weo>74i+5glwLMsB^@QJrbB$InvYxa=diS#3 z%glEvuYSFL*W(>4xpr^#o^w%h*Ou4)tX-?hxic((tz$SHzGwCZNuedtUEBK^i=016 zn6qVCuw~v}z4tVC`~&G7H@SB{%-(g6d*{PPd+odSy|CW*aCY+B*ZZDv@AHb@|A>44 z>)-ocX774$z5i49{{F!IAAaxs>%IR~_W!-tyZ6^uFBUamtmE1%Y8{yPaMuCreQDKu zCD$C3K66m^%|UsdLy9_wlzk4V<{VP*Ii$Jg;OEs^!Ckw=L=L)Zn~T5RG4J=@KiUT@ z*BrJ!bJ%vxVevPI9d(X4`y6r2IpW@P#By^kMU? z1HQ+O#=bck&vPtM=U6h&X1$u_i60N_@;cgGEoTyQSVwSqiq7$3pW~%D$4hIL?2Qum zBPEt@ax{C*PId=lUY-qldycpFoakJ0qWjE=-Zv-uc}`B$IXT(q^WI} z>wvt0odC~?>>PQMU|ogdCztn}TDj)b>NBU-zB#p?=k!J$x6_+_PH)XQy}jr3PMOoo zctpiNA799Grs%P0=p)Pd$!CuDoH@DX%;_^{&b~Qwp6BdEowJvH&R)$qd%frE%{OPF ziw%1|pI#^{sYSa7hJLs-tCp`o#bM_$Wg zj>pDDN4q7Av+kVOxcGR#f^(OQ=cPxjlQs0G$ozD4Vs2v>IyJ|0^RlzES=defTu^js zn$Ic1(d)2f#l^)QleJ<`Z9!bE61td+OX9|^tqo19gIcv1q_#M>PG>Gy6?;1D83UVS z4V&t&1RzcR;W#alB24)$oY=*bF7_qwuppfJZzP)@(2rOWw4kZ z%l$>ok>U5$j9><#KPt0h?GoGPG^QNYc-&)hE#q;o#j_QU`)t0wc--&6rn#h_!|Zq{ z)90(-gIPIdyjbGMnDXIrtX;0?{0Mu=fCJ3@D~_L^p3tWGY(~no%x5z*maTj?D`(rw zXR`~AX+EEmvg&whyCdh+rC}agW@3~5tt=0)BwrOxV*2pZ^4c`T3ol$e^J1pb0M9bD`#m26YG-nm#a4%`>*wS&6aarE0-n9W@ZHZ z>^(J&A&5<4(W)fL4-RWjsI7jp>5S6pxOp9UPgnZOPI&R!m*Yo>d|8#$g^bM)+O*&8 zcycY0VM~jlx>6Ce3`6S6DPv1GR%b4G<|D(0w z;KpBOZ$2Crn13>E``lYo9oT*Od@eA`svHPk#l|w#VY=Y-oKGiz*ezJO|9RaShhtLm z0*m&{)%v4*%HsJN<4tk8T=$Q29;ne}a=Q6cf3LOr+Ao(T+bL|ye)VmN!|ugGx^Ie= zjL&?z7E``9jf0`+)z!S<-%D=t&Z6MBa3=W|?GE!h+TQ+nz301M_MPrBjeO?Aya%LLUD8@my7u+Y zcYB^IvV4s>y-coe@|4Wa(GHtlZe^1`dA|LB8HZD;=z@F89+&)n_9yGWUuH$;*YDpw zy1xGlb8+~*11#CR0!hc`zLNPYab5L?Zdctm?}T$n&!u=Hc#6#$ujj=GPkDNhE7(!< z0GrtX4!0bJ`ZZ1u&h0wBRA|6YAT3W*n&c zSI}^L)yef8r@ky-8?4UitJ>I{(7%|Ac~y|;4EB0S-!5S z6Ta#;MA+?J6>&H0;zr*h1)dKPVM*fGHtw35T@-=oy3m=9DW}f%`Fld zPhDL3?kjuEey#g#(^nqgI#;ANsA2ZzUQfMjY0NE9g90a(CU2?|Z8r zPiqM0StfG(#{mon4cpt88&9=$zO02_i|2F%ADG{@^a^JDu~aE@7s8 ztl1Jawo8l_{gS6kNxk<-TvTqJQ7IacvqCmR3n zZ$D-!r`x~ZeBW!i;8W>-pJ(0v^UR|B`zc!+bCvCphdwh}?k@KGvPk;xi@)odFTI}d zY3^o=3OoJ$3)5xQmPG%36`^h=+IF$cc#fjr;RT0I&VBbjZ~W0+RU4k*$mjMx6J15lsF(>7x4awb5vqW zvdv*#j~w%8BMUf1(dHn6i8aL%1x&YZwL=R5Okah?>1{Kw|0+{Z;y;0^ze#i!P zji>y!1#GolW;wvhexW(`Lvyb{i}Q^<(TuWZ&y#n5ukf#EulP|Fdx107p-^>QEEAjF zJ*#JCZ1`}jwR1s<;RV*}|LV0CE1EN3G&^Xtd=_hWw`dP_D@~ZxKChy^)?Fa|L)%~V zj^>KiMeH4K-B>#f*pikvoM7)xw_vbWXxMU{d771a zN0c7k(f9T^Yx)J|YKN}Yj4sxLJ#88S9TNGu3ias;>>&o+@f&LIRH$#F3V~lpCMk-x4iC6#KgVV%liVFzNs~ECiWL62z-plk6BQjG@ZSFM`c<=O~Ld@ z9mgkZ71QPCOnY&>Gd^je+0E+y|35ls{E*r;z0Rj%vV-Ilcky<X5ZW$Q-Um~Ds7+a@uSe)lR;*~)R>!7<9<#}kerreIV~k}T3Y3_jG5E2c23K= zIW6z!v;xWLMV!<0Dyx$&Pj=^IsMweDKAUV4;a#m`2i{A5OW6r7ZO0)OeoW1Yo>;sZ>YCLD_&#V@oG5yfYIj45cIdgMP zv*(=c<+CbQ&i?aY&b7+9H)hUV!#VfK@t75zGm{q0ee~aQ-jm3AttaPNFYEMLG3(jQ zdGCJCyVg0+_ItOm{UjjNNtN~-;mT) z93Atx>6|^OmllY;nyK`FSz^{gsjP*PT1=w5rvEuPnd8*F?w1SYs}@z=WHQ;XNaxfd z-3hF-W-bgruqeD>p`F4^sacDyq-Kg+&3@VuA^u~2!l4;<1uV%4%o<(`l^mGe6PW#e zE%E=w6yLC9LMC&3!BW=3rNL24lNT(F`?WOw0@K7w*7y%h9Vh4eRrWpLT+kJ{#MXh? z(`%u-LX&O4^0KPs{~VarUd;%zVypYLyuoW}?@MM~g(l;~9`_G3%oGG{{}(K7lU!sY zwuE!m3?+l*RZ`1&w3u4Ggl#u0uldEYKx)-Otrg8)%t;Ik$qfRw7nr)E7S}{oCO=s5 z{AA%h&-s(9W=#FHY+lz&=2a_AFRq;UlZo>q%Y>KACKs3^A29nXutm;V}d1?!Gk2pr&=d8kX^$gdg4w3@;f zObBK!ffnFBME;M!hICbJEz`J!e{tXgY#a#`ESwYCMUT)Q`LSF>!gTK6t;9n(e5 z$c81qR;^>$J>zYa!24G-K57Z1e_*nkz`ADEYUYFMdu}qN7qCcL&*ZO~`aFDtbJdJ^ z1L2MTxi)@?T$j9HgGbcL@C!`GcC72)xsmPS#-&jLCJD^SvsbY+ZtB{(Lg~i_#@pT3 zx~6WAnqd}D87Z(XrhBvH?v=J5*4O;p==6GqOSFKS_Y!e$f&5wPd~a_unY}zndj?z7 zR)b}EDp@lkU-KKh+RCK0F>&{FdF`fx?iuOc0-3vKGfI1cGP*#6MD^`#IV@=)V5jKGuo{MI&Uw_KD}e{?U|+48^UI9Z=bn!=I)(8Ztk2n zn{}o4j)PWfj?UU`ba6-L%Jm0cZ?Xwrt$eGXXXe(q4y(4@p0_P~1#958-pE}{g}XX; z?l3;Msc$ALv*Gr}&SeJwy?372Im2wiinG7Bnkurb_MUO4Ti~+QmOO3Y^aFdkHTS&c zW?;*jBWShn`RUbW25jY02THZr-gD1%&_0k9wXjrr-|UL5f4f&&NgbS>x!AI5|A(Ci zR#@iz`F%j<&Oy7VJ(F)A*zo$GO3b19nfrPEB;Smht9Iv*-kRwXR?T~{{g8glVO5z$ zJUSEkat_=0?9O<#t2}z=%$tW@-W=YNHS>_#=37xme0Vmlt)Bg1>TKmZM?(HAl=w3x zIOb^7o&^>>M;W#qj+Qyb+;!;H&Sl2h$1-e=xz9Ni)Uzk^&#_;eD+BISB=8(B^O^TR z`>>Oan#zH#b$^aG$ed`hInfexqOImc$D9*g|M#5expSiL&xr{#Cp+&PJ=wPQiX`)l zIVWfBIXUOf$$5WHE|593$mZ0Nm{ZGYPOX@8YSo@oYwny{_vh5yJ?zh~xH$Oi)b3&8 znRt5Fp3{5ooZk26^Z}VOhiuLqi8*tu=FEvXXHM-obLP&ObAQh4m0|cSGFQ>(xMWAL z)a0|b_ME+Q=j^>dXE_be-jzIb%5Clqo^vneoO`wB+?$?rkM1NnNG;T^Irlc^{I{C( zKjxh0_;{|U<0!BD`Cl>@m~1bwyw(1rGxvD38bj;_p1T+L{$5}!yzutnc`n|IVzn0~ z&R+OaGo5>Rndsb$3bL1!-k!6MI_7nTpWF75*4|4xd&PuePb>X7uWNhRja<-vtJH=UMw+P078;-N?u@;ab+-Ydh{-W&V3ft#*oL?6s1!=a>Z;G&WqT z+IyX+fr(L|>1)je51AuQvDe$yo@bwMy=L!?oWIw9@?4o(bE&xY#+12dtq$IpvGzL0 zfzY=+H?v+{OfkAS)%MonNjGNJ-pcA_nzQ%ZyuJIM`P|xY_bmGZh83~5c^)vZEj_nZ zx77I0t&M+gGaKLDmV0}L?2W6hqy#i`jFw$FaQBX%(w)P;cUJSBb3c1*(%U;XVwIR1 zu3fafv+DoZa|ON^7tX!=L|10|-Mbfi85-`M-7R~hDE8imUg2=x+b?AA_RT%_ws&^5 zjM^gE`%m`X{d)JV#)f;hW$qbO-u*ZC0Y~9g#=d*seeb&STuu9<`giSv1GV>f-`!~JmGM!W&Lhpcj1_K~e-fO{1sS9t_+=ktkl%1i{DLo=B4feir%V?aPJx1Pu7ZLX^h56p^%)4!LPmD3axqqG5 zWW9UIMbG#Xtk?x?|6O1Ze!w8k5XBR~D67E8WDw)@j-P|!t&oBivjU^mM0sY1m~21O ze;?BLJq7p<@b?PKFf%Y}3AzSevXCz@7rwy2bAj=|!xzjK8AJ;h1rn?{4hTyo1caSq zV7+jaU4bD`j?t^`YM>nhNB#!^|EJsP7aPg~T0aeFTV|Gx9bWv}nc zeR*_V(1*wTy-)p0>-6rX=t>tv^Zai}=e*!+XvlE;u+|@e562GivpIybD85?MXUYA6 zVUe6LdqcSLVSb(lhGR@-5}_`CJ}^pbV04}OO7DCoZ-e7YMHA+Dh8-e|iWeA<9hNe8 z{TR)}$iCpijwcMEd9N7*J|33+w4LRx=e_Gp!VG*D7z6~wAIV)jZhJg;&SQ>?>B}xN zNGCY&ee$N^9lwMCQ-J8_e*z3A-tim9hs@V|!p-pN-v`Dy@dAtfJrA#E$gcbHSdPix z&S|5$UB-}R~fWjTgF7Z|03pR(99tgmNaZD0~CU}XBh$lbue zy5JuF0|toz2C3Ai1^VB&^)g)6yDzyRiv8m2tN-QB*}povSNE#PMaCzK)hpbTPAMC3 z3oy-4{-&bzQ(yz*D%q!T5Bar%BV`kw%$Iwm$RO}fNJQd+KC?X!x08&-2Ze)-9Rfcr zKU`OIajcgT`ti6Up}D0?-167%3l0yM*;xe@Bwq4A^6uc_XyYhQU~J|P=2NIR;J|!@ zPo6Ji0q5sbZ}8PBx30Xryo8C#U_sQ?SD|Ynw`RE-Fer!gF{^m|uo61RI?bQ^L-Oru z_nBEac_cJ8Bs4Zn6cs7CU=ZHOxIOQ()z%pY3RpPVEwm~cHogz%l#a;z@nHR-+w3!) zB_s~4Z@zSzaZ=ol8wrc{Gf6A!*c{Mzd3NTHT1&~EjgSAk*Y`}dVu%rE4q9xOer?ap z&kS8mJUk9D6Aq>|vK*= zXuCu>Tt5*1HHY2&?S%-<L#G$~Y(9E^3`N3|^0FFr>LEf>aJXC}4_MQA7Fhgju`pVUpba&i3ZDe

dOh8zm!}5CT|FGO@*fMsH*Gej8x3n&4XiFaV7I%sfZ-xrM8jHMr-I7~ z{V#JnS1_FadTl%ISdDQN}M4_{4|XL2taI8`>>K2Ttp z<-o*ts>t-xv>4wlOBwjm%Qo!dp3cz3@wag5(y4Az4p(+7Htbo-_^)o}=M~P=`?4;r zwqs~s!|3LiplH3rp^#DK#Q~KspS*vEZF^p1y+7OA|5d}pTJgGyN8R&h9r6nH-^v)a zo|}QmNO9?ktEKnvw)dn$NXGE$@e(qLV^gYj;flk;~s)sFI?{ z9O0~XZDZ%ORq>K%BMzB333Pl;T&!^Wlaq5=(BwZ(D$F9C4sJ#(YadxJ=2ZQ8+$-+G zq}eH{+AGx>=Bfxamd2c9WHImxa@y2C`AR7F<`-TGY@0n!I5Wr|{GlIpjlWA$M|JU) z3Rmw}?lUegc^14!$Xn#U$%Dx&XC06TpLCqp>~rJShff7=@7SNUEO_p;=2V7P&lqy$ zE_VvAP-c_b>BIZZaMBZ(rL5gM{2IbO&w5w0-0Y>Me{gb!@#n^Uj z{SsrBmzvEP=Q6&%Tk4#;G+d*_Zst0dmG{?hI)C_iK`82czjlbGV7+I^nzT#W8)`T` zJ-H@F&zdqbQ1*Gq^T~TV7a6b4zPc*#?yE4pcY&)Wi#+oBe==;(v#(21dKK(fo)TXj z6tccqbtN+&sTR&HjuZ}!(ifdBp(^ReZvm!6FiD^EX zl9Mj39dU7)+2&tU=ErMGL~LCZwyj?@-OKj$jhoNH*0*nco5{S{YCT){j>D>Xg}v1= zPt?M9o=(jxp8GrIgF?M*->z((!mFirusW{cli9|- zYu;#G{C#iXs`5R#+WIwbvjcznecQHTZ+`u9&3h*oZQZN;HNUZy^FdMB^}TFq1=>$< z-D3DHyq$MhflT)9xL=z>R){{^(7LjHY5FDiLx0{DboS4P-}TOXn_8Nof9cKW8&%tn z8oyI$TC4GB%P9*RO$i4Lu8+yW7R|?;`MM)4iynSt3O)Snf2tQ3*Ftui4GyP*(;DSY z32-UdUpsN#u_1OwkJQ^~VT>W68~YAAb!r4ho(;a{thmW1!gH!0Q`yW(QFc0^tQrnX z-(R0@F!z}o@FMYS^Ri8HXRbHsMt?rLOv-`HN?~iQUnqE6Fgnx=`F#S0(W0}bUhM7NkLiE_P`O+PlCfpPEG|kQwxa`g> z-!Uc0-Zx=IK+}bC3s=oKx?Sc|s!mlLC@M7yX!!KzZOD2(QLY~o8F$(;c=;bZz`QRe zDfYIi#>3mRo=cIEqG^`8UFpIV08H0Go0d}sY z0>6h*+k3ZppS|jv89dj9sjIlF=K0!7$^Un1E|l1!@u&7nUCsmEY%`Nm4!woF9xR3{ z3pry{CN%I?xGD<=Fl=KMXyW#HVE3=Xp+@e)qKP&Q>4}Xu*%btuPR&qbj8pCkkXm$z z=|Z<}^0dl4X$Se)*JE01-A~OC4|qPW-lF%*&z%jgxkVjU|K#&<-*xG_59jh^=~s`K zc2-CkHwt8|F&3#}h~&GVv}fV_x3-Hp*>a9e6j-oVHGYa0yO~mU?@wlC#e|bAa{nio zWH~*aN$~#R8tYIoMuw zGBViqCG2EhFn6LyL*ZxkNaK!Wf(=|dY@06FPm{2pfKLxjh061#^;|AE=; z0X(e_IR3M9G@i@myx`4!ctyt_Zjs2t9e0yC^OHN73wFxS*~qA2S9@?ahlRt^1cu!S z=F2|pyi@5=9n57HvH9d?JM9oA)eS9mlO4o78sFY#2*{Z^=VnJgvtxN>_tpy%*^%90 z+zspvhgk$TuVidpvSAwggZ4kd(*z#0f17ad&uXjbh8%Sf+!_}Jl`eNKk&q5Dv^f%d zz##cR%*hSsqqzLmxYR#x4^v?AIda7N#dgQr4%toGa}uqKV&)V+;b1Ias9ia~c#i!X z=Pt$o_j3njB4)Zr9Gp`Wvt{B1v*$Y-xI1LhbL`w+uAX0g$is&#*qXKOHbZy>Ti6Y^ z4waTyrw=&2Ua;9f_>NWC?GtljjFx}! z)H>W;_n_TD<5X0~i3K4|4i+tS7o1G9cP%y97_>+!qeX2+PS=Ex&8yC^cGR5CeRHbL zf|XO`^u{+E=4|QN)#S1;xXZM0X=DtGf0rpWtxd4ZEI% zmk&=%V8v17%$CttG*cvzj<Q9iXEZR@ z`2Q&Ok6Z26n`5{6h;Q>7@1+{dTE@G0S2Ri;Xlidc9r~d`>cW}21s-A@XTPZQduvX! z;%=BC+|T`iYl}q#hepToi|2pb(&;NXRK=#SZ=XO{6vbo0NE7F@gH^_jE%F98a>xi;$I~MIbvuuU#j6E@g^t!d zHv*p3m`FaEdHtcuET-HHe?i&_ZOla88eWKw`^0e;Pl3RW^+|Ov>mT+*=z2c;GTJ4JW@yot$y3Ij18p0p2 zSUzAesbKWtWeNJwX!wEIasqRFLQCBWHdhY*@?uVpX3?GkjX9Ev5qJG7HqUeUdV;gK zi_0Rcpteo@17i>ei=aZQ=Y!CKugv0m!k8yt64^>8Kj-pH92S7@4epwVDMgT;h^S-i};9)U6uTuL{X>TfV9?})tS z6si0+jPF+iRk%efHB1 zksr|;E-xTkAy$8hhR=fwwHZIz^5WzL(< z_*^k*O9MmwpTL7RFYs5)bYk{X=}ujaJ4e2@TX-~TPKf2^V3tu}dc(@B`{Vx1 zy|GFcn4Vr`Ql4?^|5gTWhUOD@nO3MW>3?8X{t&gHH&%utZbfV8ZI#=s94gVKi$1mb z8wJMqO=#fx&}Es)$vK1fQfMp}m&}%oThk`oTF?9NUn!HmK@)FTlO_kV@CT+1st+bE zi0aE=T5~pDF)dEFAg10ShKY}v_gRAS1tyhajNBjYDW>Wgn_fKQyGZ2TCDFF%|MfE- zo-aMf{DQ$^orU1d71u4rWaTT_%|uOips0njOt*$D;Iq zBs=6aX`YKx=zF9yEv9}00}o&G!@qGtNAG-eO?;9(?ekL^)4YZP$0-vX7(10$&hJgU zUO3rWj#J^>13SOQeP54gesEDwh^uE{exiEEV#CcNtSNmbo_d^1PCWLAciJ7z0wzzs z7(q4dO=oY2&R!_cJ9$Fn!ReJkyE(bO|LNlTF-Pz8%=S0+Ydw)HPR`9^Yl+>Wh77d++hlTdD67%G;i2+lEXD4Vh=7bor>{v7cPo zrV=~?GXJ}#`6<8TFgp2k;)dA11B}WYXPNz;pK$Hc{PEzRFQd$l2POBC-kN6WZeW-_ zx9P^07ZdxQ&5=#sYjR`u);rd-L+!L5&)t@(c}Qr^GvleeSqtNw7U|tsZWFX**;zq5 z?~k!BmR~!sboJ#bzQ;$8_-2`HUbD};=jqM$>&{L4>c3Gg`#ewf0=L&kMAp0LK3tsk zdRv&ye70Tw(_YV3(ecCotUP)({c6l#tZ0bu-@u(MgOJ4#&#&y`0DW{9D$N39j`M z-haHw5c%- zXVU&xeqC#%IrEI&Pg?SqO_8bfoYUvg&EJ2#sbKlE4f!wb9N#(j1=EFX{O(*L-`n{U zZvUO}aUSExo7sg24I8*V6g0fynB>*XI3r`1C4bEkcb*frA_5G`*Y`60XkcLYpj1B5 z;AW@R`wxdY=Ujf-ajAR4uf}sq|GNyg&rx5_@xRQ5LH40#TIIYS8lRan8U!q47#$kb zA`4t^&en=AQheUQD&D}RV8M_h~>LME0XY}?lmL2`Lm&L)P_W}c}!eu0l`V9SJ>Qlf@1zOzS$aTei}gUy?H+|Db$lgV>aV z0u0~87;S6+_ZHlrz;mEeK;u1EK}RUNbN2~`uHR-GD;WNFKd`j%|8dnrB>(j;W1EKG z5fwcj+#U00aNgH@;rylf$OoI?296U}rpqTxix%0b*uiQsZN2uCCm-0eE=Jpz|43Cl zAoBg-3hz#~3=xZUR=Z~waV_kec!J@!!1u3*XFj;)mByP*|~i%Khwy-Uhx&j=gCn4L4^r$h_$+N%jZ!u>zEHhgDicCbEm{`QX*3|kB)&-2)Qcx6Law$y*d4n{>Kmw*5T z2S&DkVtOV92k*DAaC4i?FfeFp%6ha6j)qA)7Dij!A6Hf(~K@2QJ)i;{L~$ zqp;$^LB=}$Od+2IjtWfmyedW&ABvh9TLnaZI0sy4Xk=t}(n$%};BdT^Q-LQ|;=#cN zc1M02he8F%CO*f%tPk2Bs@~bb`s3o-)9)E?F|qRKL@Y33YT@Gg#G&l^d-Z~E2U9NVyK-H9u>OEEo2Tgny$@vz zTju{eVzftL^UFqlSt+B0fCmlktyaE9tv15WemrwT`)gJx96rP^C^0W4;vo~$74FkU zEqe`??dKQo`_26@;aBzq9$BZ7zq=fpnJ%kqzx`3T(Br!96h{_j1px zFuz;ZOgyRl=MwRZIzf>e32bjtBl=4MmnVDl>9R;NDK0qRz#%0Y(2ytj;`x-x!j{wJ znIvou{AY+1P?&Hni+kga+w2?~6GT{k`92VHj#4r>$kru#bS9$&hkHy@`KQG)c|J=^ z9oT$6mL6b{GF;-o%HHsEX3ayx1_wrQ9tO=tZn-HA46X-HoG<&QvETqB&xt7lF`P9A zR@92LvL9p=H`y?;UZFEZCY|4;A(J^hF};E1#N6+*nPivzYP1*B`0dEVoN`KIvm}@5 z#2J6iE_pDst7*FfW6;Ou1_xn<1&(2hYfTR_a^;jeGBKX%baF}jG)-%=wOZ$NHivIt z#V5*2WHL@zjznmql_yStARN<7x-b(lV0+o?I?9xtq=yJy{w0FQFlfO_n1d zis^%vnFBj#jrxE2Vy-(}jmHHSOqjv!+{h>^@Pvf8W{o~gG3IS zPT6h5dckN_LF$ACrcWpLTwv5)pQ3ffN%Dfj31!VA4NUDuR~%Lf?$|OrX!)eJvZ%h3 zVTG(WHoBGuFzOhT+;?)H5E;OuV{kb^-?CwAR=V1RgG@{tVic|jJ$d2JzTHZpguRdV zLpggYuR)<__jB#*`!XaYRSq->x_wAwo4+q$y0Kx%@1z~`!#BRQzWDC<`-AuIS$|k= zeKYm#3I2GS&)t>=2bReR^NYW`s_$R>;pFksKVNqm=huEd#l8Obj~m7-|2lEV2uS)J zI=?Qz|IaUNm4v3!1&)auzgPVK@cXa+v+|HX>IWEwHZ=U(eJPk{#Q|2M4UK#<33Ufb zcd~xsF#L4+^!~OdRx1wZc|NX6_|MkB^r>+}i`pDnHn)mHqMJ6fDohdi&lBOe`++KB z)kE&T9Z#k!zvMSGEKw9_);OXlw6Vi2=K;^_L;8Ga8|qw7&1bJ)p?rM712b0>G4AW- zN2arQcku0)$+VF}+-+ijJIAT%3~CKFhR?!oN&e zw}ZohsZa2N^lx!@`LEo~mTMX|zq@hx$Qi!>RT>Q4$yOg2U%X{0pK(d-N8a(hhH~qB z9$L$PnxnGpS?&7@wu;+6ThgW5I1*-UQI0pbTRtyV?feY=i7}aa+fQcCdS8}(N$q!o zrrVubR!gQ=XP6sj!u`VzMNPItws zOPjeOwuaoEw0icYs;gVLt^|Jka>*vT)45u2LA>mgyT8BfoWPf(Zo~0PGE(HTcW|Vd zmQa94xmAIM$cMwK+y-xI4+)E~IvBgwEA^NCFLjgN*+- zWI3&i+`-t8pUL<9ZpBjG*|Mg7%#cf_30uHQOmPVa%m1LZy<-o@G!Cbum^Ht-2JUh=E z7GV2uIGH~oj9KZ}YybA{6FYdf-H3ZL@u7i2(zRb*843j}ncC+yn zOuN?KVy{t>st!6@MHg2!`r`>b?^*P0C(Yu1aeTIOz& zC$u-t?Aqm%J_${tALF@m1mBAyFa;}qD81enk9Cp#t_NqxYT$hD_Y zfpgjd&S$6Dt_92P<&!hL$2n_4hzlZwvTOO=eY`$DAi{ zB=ZKR?@4~Og+h7@c>@1S9Oiv!`!pdoWC7DrvB?|6h0_*_{d&MA#VNAsfsSvYE^hT>lc^q|`^6+GhAM2dEV%HYRCM9siMg4VQkZ?$XV6D?(gNPM*()`&Gie2jX8u7&a#`gqk#DY-tS;IdC$g z{lc6x7Y$pN`!3Snrz9vVH~DUYeBFZ26~2YNPjQG`dz8(gHEu(X>OmzV zwa3hc4_?(Mso&ng4!g9OnjINVNR6*gj~A@TvvEoIO^%682dfP0>6O zY2q5@=)K|(+gXDpzwWSn=A3>h^sIeaircLR0#@ArJQ7W^mU#3%5i?xuV3!~$rQ*3x z#d}kLoK><{R+9HQ72m7`hf9x)e5xkvP8Ee)n$t#<7$uxS^J)G zPI{KUN+svoGS)J+@J*@l^Oog)TPE=E8P~HVy1r34G&W)w7~73w`_x|b5UORd-~ zy?EZ^RH4K)FZHrrDy^oA)78{-)6^^U78jSL$J(V<%u^TYP0y=JPh7OTT4+M5-SfPC z>a}7QBEr&x&!yMCTkfwnM|@M7wxfDIpT=aF^aC4~rwFZRn&$5CXnEeNCrx@UYEP-R zxM?&VTb}+YwKYnk-Y%^!Pow?X^Oi}^OWQKq|EUOXRBM{Gyz|}jj$6UmuhP5ct*Ezp z(e>+T&!y$565q<% zdg^n2v0gK2_*?OEZrXCsM-$lQ+-LAv+4E1c*zf7%smI#CX)L<6Y>`pR!mO8z4?WA$ zd$q(WYw5mcOTDz_PI|elEwlL>>#{hd*=<>cZmY^oRuwJMTHdF=?2*FMU8|PfQ<0yh z)xS+^ZCBd7V_EC!p0E3~O88&;nzWV6*j}f6TeYO@=^CZ%jIvj&%%;VetlpgUtmDxt z_E%Y3&D6F`JEnRqb9SC~)GDpTR+&v**_-&Z`9xRGU-UX{((2b4FE;La9ee2i>NRp& zdySTPvU#rT)7<*(b^JT+l~t=YW@#Td^mJ?0s-0>t4?bFYK#yT}Q}*Fk+H16OcKhih zlxe3lX&vi(y~63uCNZ52Wp5(2R+k%S@0+ydxSCk`rq?T15~p)!-I}09 zE7#w8bGA*j?cJNx-*Qf#db2BP%@MA*7dE{)Ber^vl+LAZPY!?6K5wTR;+A{DPxq=< z>d|>`*4t%Xb9!60OXxz;o9pkE?Cn}}eOj*Xt=ETct-Q7F?KPuS`+U|&vAVQo`#0gRH@XieEqS1)x72K%o87zpUh6y#tzPN$ zZco&@h-11srT^dVEPD5}O?Uq@-J4zO?4#bEJeT`qQC{4ncL%P$d9dr3z^zeC%HKYq9s*tMYcP^LwhLZ^ZTPrPKRQReG!YbiT5s zy($QJdFtJlwDl_U-rjFoubriHN=^UgwTBOReHibodTE#c%WBz~vi#5I^8QSF|L4=Y zV~5`V%~SlGw(9b?^ht>(SZ@9gwkn`XBug^Bfq$i~B(hZA1C{X*X^a%$MTi<~LG}%I9a_prrrl?xpwt%04I`50-nr_L)_Y>hhpN zMY#&=KdL!z3fi{j;=4i(`HO`p6ah$;kM#-ufbw^#acheS9Bg%pv=6xz=X0 z*%wUj7s=Ec3z!<;vomI!T+BIhv&wZt4Sg3b2}Z#)|BY?0Z??T}Z1;S#-FsvE@0;x} zZx)O=kmS>7^MA9WyorBsl9n*=X_&aUZ*lov?6Cj5>f+CP&J_#in7Eg3aj!S= zXy4+|Z{j(9i)Zl`L5oJQ1Fft%jC^xUe2#DNIse7en$h`itIg#te(z2Er7{H%xExfg_;$WbNA;UV|89={tsJwzC8pmrcJTv~ z$6vz^yT*Ti9B=HB@LxIdda3CA;=gtIan|4B)XOaOzB!p}v$y+imN@&X^y01dA!X_D zW`YJ=gO6{Gu>O`Hu5nT9gT!Ma=Lu!@N~THkzlAS1OITl)zI|KY1;a3Xw%qB9bG1$L z_HWHIHp{=hEnoeqe*F@~`S17enOlmLhs$q|SO1=-Z(e9_E@1aP&ZRu3zc@;MTOspA z)`IV>S=;Oj%!;RPedx8Rgx#Q&UqFyytG&QMu_X-*z6ZtLm`?1~O_jfPz zgJNq;Cbu{i;IxPr zwwUbWC}XfixPh@l*iph@N1w$HZvn=R^%DfoSV-LX@iJsbRsZ7j-QUFS|JO^kzaVy{ zqG!DcU(P|XfUROLws(CtmDsXP(4kWJM{|9;Wsvnju{RaWD=pGqcZjtd%&cbYt8NU8 zW|$$&SpRy5@8ch}vm1mPe#~wD>6`wuw%c-g^G?AvCG&Qd^a(TaRWM3OeErSwGbVY5 z(Ah_!-DU0H%_RgJm;P=RJi<8R{|@1nMnMIudhM@Mja`<#HkXKS5I)i<9`UOq|KN)L zoteM+r(`?IT=+3lxKXU(;Hv%~8LzGQEGm{YTg+bWAhE!5QMThUYsa3~-$Yb)EwwI{ z2-!7jz1i&F-*wNeFx_kNW zgmjzJ#~Jqqn@;)7ywCc=zTf`4ntvXhU)8g{R3@T9d`a1s`0B&fUk}Uwb^7qj$)ZvG z!*;1O<~)X`9C(T-g!!X`vK!4o7xX{C;vV6-e%4HZ48x; z7me*M$^Ts{zOQ-p9`O}_>a1%n&i--7xwb=lkMNaU50h=4=GQFK{`ID$cAtr3(DDN! zJeDyVY@IcBd_5jK@&7j6*ShV^0k;qD-V|JFW6iiuc=rr*)9Cx(ogX*Mb}#LJUGrT! z;QVi!Iqg4^{df8@x7gRa6>z8oU1*xD$?{kBi}}$`v3iR+5}c1+7C3Ob%n`}yxX{_! z7R%$ReG8Mfa!lGS&RtzW8d;|`fQ6ArZ-yy8}S`AFeVx9$a_M{3G0-G^87 z?D)*du4{EAsawSQ%kl1xLl!S=M7N*l==kO%!1KRdYU<@j`_9Of=&$%Ur)kx;>nu}x z9hmuj0?Zdyte$Y{nZg^tLt0zkyxrNeh`VdK8jJQC!-;2DTc+Xr_hE>ksV@Wnh6$IXS1cDk-Ez@+ z&Mc0{ZW7#|pD&v*=R}eJ!3#=;9pNL+du4d-3K%gIDx>kF8J3)~-4HV&$4H zYu%V-vCBg{UtBdQxb(nf>!Ym9WwYX5zMK*CK}jz>eA|IVAGwM|dBWfJ%-kpKe=e_k z<#vhpM<;GtY?(Ic_P66#j~#0^d(pl1%tWhM?!t37{IobO`&8@yw;krcc&=CIE@Ty* z@qe{*yY4n^mcrdJOV1=1nYJ2ulzhF{a>%1^OHC}F>9gG1uQzfkotxcsccb~1ggfuW z4smN*|Lnb29xBDlcg>aMa|1`Iu~EPHA$M~wUH&S;oYRkjE?KDTR4WqAm~?5&b&WRJ zrG{b;WuEs4u+Ny$uC%AK-~CqkruI3@YhO%~UH9vYlJK16^>zyv_-Ul-YW1p_7*2{W z|MjFrS3uEr@uR!Umd{k5c?v{ditc1(FWd53cUAL=Uk?Iw4y;q&$H%T}^RUw9`MUQW zOIppH5_yu%jVJbGahV-Sj6SBM&T#H+c>>2-*R~VOH7l0uJ(?)OvT%i=rcqy2+o^=f zJRz2>Z;$E3{kJ&pn-ygDRK=w=F5~L9V~?CfPVpOlO+NYeQ~6@)X~&9tUtVA1BYZ&q zRUo&xrNX~ACc@Kt*_2tX2q|()9+l;4>GC|KaL9sVsV?8sFoRtJWo+$xeD8L|&-<*B z8Db(AD8*v0W~`c7qGrVUtJOIF!zlr0R-=H5jlGNbi&9&4 zye@V$`6>%Op8e?o=Z?y}mGdMy)$FFLSiZ5GRIT~(-qH()<^?2X%~9Vgre7+xatc#$ z0>dV`$=j0F=;-lG657)6f6bH5OTFK|%+<@(3eo%$Tptj);^&b?hLgQQmp{6=I`C*f z#MYFnQ>8K{>f35v{^c3Q{`AUP(MOpv(ka&#Y!b;laz-wEsmpb)gICw{&dN^cO%A`R z;5kwEtQWi7c7Oh|sT;zovQiq2BTl_Ky5V0Z*M>H&or|o(x+m<_DPXXU(wkbcYFVgv zTK|?vZ8@L$2Dz)V-h0RtUAktu;;3#~edX2N`>rkJj9&dTZTIc4rmH)$H|7*IUyEMj zdwXly}k$VzlmRy_H-rH5Nc=feURpQ4j zj};nZ_L#7r4m<8BwyAJq*FmYt)8{*feVU|PeeIl{*vbCXPg7Rj6 zRDJIcJ2QIUg>9*MtUq7!Y}6ye0`c3k*oDo{7Eb%z(skz9Nm1jI#n(R1`x=za_I#^D z{xhTboUs{KbRt$)Oxv_j@@~e0Q#a1m?^_gj_DzQOYs)i})Uu{HS7-VsTkXlY_aN@` zpUmLJJJ0#cnNIM(o29>AB;sd@@#@&vY_-o3XYABAF9>X4_#gjY>c&B_ZyP#ebCTty zZ=MYMwrS?woOFBXTNkH&+p;n?H<3Nz?5e)E(~`5Y^RvI7;{B(yc7JT%`(;t9Q}wp) zyg4_fM1JO_*LmOfgmS-De?9fimuu#!_jxn?g3IqSs^{!qzv7)Mx6H#l-p$+o^FC~` zw^_48kD;+f=0%OO%;UW=7DwAn^YYSVo~Wx@OwQ4HKViO1w2=Rn1-pBTriW`@*53c) zbf(|ZiR|GQ=dSyCE>|wA?YYbY8~yJLB~ecj4$gk6ZtJjc`n*ZY#CI|B3jDNnef!ZX zn}<pRnxL`N?{Y2lN*(3m-Ue`Fst- z@%V(7?dmnm0uCi6@ee+(^sjxgQ2brj;@S(6&JOG$@{N2B4WC25+1z$s|BKb-0TZvm z-syqwtFLahOI|m>GWqj=OMbAsYC2cL%_u(kiFqAMz=hN=tLOh+r_Zp9=idVTzt8wD z^_;oiaPHwT{jc-hGqN7|Z>7Lw)bK*#!AC0wCi(SmSFK^Fea|2mu$f`WKarykO3pDd zpOn55@7;n8Uk_XX(!t1;ugbK5x#Gj0 zM}7(`E^q}Wy}jdq;z%Bcs{gS$OIYf8IJrBkH+(o_prFduP%YrVyElhPXTzI!FJJt5 z%hG&qbs38{^Yt$o6?58vCcW@FkqVfR2iv3GAI4BmPKiavkz z%w)w1kGBuP0>w@*68)IVpDrlQugmx|Z?%t}c)j540728vGn z$nm#Nve59h5U*IEfz%(+CiQ!tI zEH|&L<`dzEYo%l-dhPRLQdz*LVyI!qz&4Yc>60LnjDl?GBL<}ehO0^M{xGtNDQx&) zC@&c39TOl|`|gd2q3Bi?CcOqmsRztr36jPMOzT!Nt1M8qe!#?4q{^1SD0E3@&IS22 z{S68Wm?RFUOu@A}s%m)0{CsTv+&oaHA_D4@goh(X9m zFF8otc+re`?9+9y8w&%>%A>7rrm#I%Kv*Zu*MmIKpGKSS$;moA?e9D)?Y z76>SJYiUn@Gv@PiCZ??(!|HQXe4%#=*vA17x4AB#QT`0VIi2?foMxD#@P3PrV8kGM! zTCp!+O1NNd&+x`CSoLL>)=C2=wgjWYN(>?o7#@9KQ&!N7E@Jp`US5sC^6x@Mw@dOy z3~aHRUtjvfyr}O>-Dl>GKE_AyOpY2cdHFZA9`UGBqh0vBrqojDtnpZjTB zANXSA@FVJy{HKfZRtx?IDKSVTC_nADPyfWEaKJM#fx+mqyikY}>mhf+1Z(vN3Y~mb z>JCf|C9da#6@E=R_fCt)oL4mYyk+|)<|TP==UnkTndkcVAY;%a^T?uaT_tWO-~&!N|;R6tGP}#Tg>Nk zakA0#Vs+b(+b0>`OPc7#HQ7%$)F*txi)UL5YK=XN4=8488yh>kmb&ysMIey1Ntt)6 zqqx87isCTYuR6bl&F&inelWJZemUsrqz{J{F`azp`oB|;!LF3Gv)IS|t3`q?KX;3z zagp!s4W9LrJq#D9ZQRUcu)yKBf7o@u$mN&*Fex~$HDWWd0DQP89QvTdjn zlimTwcYd$sOd~@VDM~Uh>pA>eRl=Gc{CD2eIB5n}yP&Aj$NsVh7_AoARbEn>m}fAX z+4G~26Z-)5kG6zv>-%X zB0;h|n}MfX^}C?-OMVf zW}^8-v5683%WDn&%51yK9*0-`54s}8P$A3`@!>-KfAI!p&UD_wf7Slw*la$d+4@Gay+n(1M2q{37VjG^{t~VKgCknQXS7D& zXpNU>OO|LcPpD!~$rE;ID~@O{pV3}@qrG0DqdB5IQ=(oy?~GI7>6VDj=`%WK-{_n# z(G@(Sqjh4Arh4aMiSEr2-P>n$@2+TGzJtTETT1q1_vsrw=OubC&uBR~BOvBdX)r|h%H~RnYXn9{@YB13`k$(ceQ3Z zZWCo-dbgQbE`eEyfptRV)ZI#qha%@3y*c~$k14;W&kU`cWbVLNvyCC|C$n9{jLAno zn>jE`ILxrMWVvUUyLe~c`N+w_3DbptGD;ly!uDXU=gtM4Gv%LF8hZa^Y)fMJGqaB| zDo;3V0pm>hC1tF_wdPk@-I{Z?Sk?B{q@RrQ zZyIPwBrqE(Fp5-Z1l{U4+_~|c?rF1~Gym>j__S@Y=q|=zjOw9D3{DD6S_hT~MlF3T zu|U&*LI13N`3CvvwgtRQ#)9j3qbJ_1)P8$wrsM*KWtO@9w|5lZUe$bS6L;m-$59g%tr?gW%sISkws``h zggJwe!<5*9?ZTDJI@5Gc-`-=au;=2BIfdr6+oN`=OHRqX$q?YcXqLb%$FSdZCxg@j zrc2R#-bx=}zO(Ot#a{jG+6LA$MJ-uW79_IXVmQ{v$a7%7aLtBb>m7@vcWCZe=4`B> z?J(Q)&Lr-diN|;kI^H?*-@j(5|MiLbf3_P-9F3Pbb~$22b&f;Q>LbZA$BXxLrq9Vq ze|xM{=0x+H_Cm{g!&zHfWlm1EX{)YgbMIx&e~vl-ea`vc zch3KpxxgHIfqm`;?z?QTNm$dI*(wDt#9DCV(?q%z{ zm+fV*7~ahY={@3%dbLXA-KNf4C*tQquU09Jy>9!rDeR6Y>*b5(u{YZ1-srx2qkr$# zOZbQrX*^V{dPtdqej}bo$)ehhy&?pL=Kd-K|l# zj-8IZdwuTRn|mkvZasQC_TKZk_xk7FI&N{}b?p7`bMO1_4Y(1#Yt5ef?DHOQ$6cLm3JSf{=HQ{??v~&OZBV| zPWC+So%eF~KcS;Ho@C#9x%}U=`lTlf=e*jy??w8*iH~Dn@89>z*z)00xi_cd9+%#$ z7H@rXcHUcQxrgrGpWKdncmCgTJSYL{`PLADe3=aZo9BR@rd69;hJC47X+UiXJMUH^_RUBsqWaQ}Gj&+ySRoxz*c&tu{LYsIHC-l;sk*q(zytg+;fCuiCWZpDs77S52Vs-6r^>*j%l zcD@T3vnCn-%2=-Kwrc)N+sId!*<&^xpSpY%AIGYqDXea*{MkzmJe^a!uIqK_+Fz`( z3pYlU9$5WYwIX}nF~04ui|P|UypCf0Gc_sx_0GzitqXXkIuxz@cQt-mId;v~)42ioq%vAtUvnikEl_4z&PumuHQkFMMOK2EG*rsX;A#T`H7?u6{R zb*}x^rvG*3+SAuRoVJ#cO}S^I?+q!M z?^1@M6Zc!E&zxB+$?*AfWmDO<_}g-)?&TrrPt1SJL>u1p3{~YTtV1Lgod=X)&KW7*Z{{t=`>8@fQ7h^Vlce zpi|DUHHKa=E&sXLZ3GR(+0{JX7JI;86Tx$5`({`1v*IK=N?^YNH`f6b>;`sZyv z2dw(~GOe0G2R^B1rC?SGu@H-5MJ&i_Mf4rc@dYRwPbVSYAs^ZtwlOlJ<=+s((& zIQilXoTC8@Nmun9B|=R!?nZU@>uEl3&rF>EC%O vWL44BH91SQ<2g@9oGO~OWzACE{La%cuRcwcKXHJ?prJvm?vgwsBZD;nzl68S literal 0 HcmV?d00001 diff --git a/tensorflow/lite/g3doc/models/smart_reply/overview.md b/tensorflow/lite/g3doc/models/smart_reply/overview.md new file mode 100644 index 0000000000..c35a5f2642 --- /dev/null +++ b/tensorflow/lite/g3doc/models/smart_reply/overview.md @@ -0,0 +1,40 @@ +# Smart reply + + +Smart replies are contextually relevant, one-touch responses that help the user to reply to an incoming text message (or email) efficiently and effortlessly. + +Download starter model and labels + +## Tutorials (coming soon) +iOS +Android + +## How it works +The model generates reply suggestions to input conversational chat messages with an efficient inference that can be easily be plugged in to your chat application to power on-device conversational intelligence. + +The on-device model comes with several benefits. It is: +

    +
  • Faster: The model resides on the device and does not require internet connectivity. Thus, the inference is very fast and has an average latency of only a few milliseconds.
  • +
  • Resource efficient: The model has a small memory footprint on the device.
  • +
  • Privacy-friendly: The user data never leaves the device and this eliminates any privacy restrictions.
  • +
+ +## Example output + + +## How to use this model? +We have provided a pre-built demo APK that you can download, install, and test on your phone. Go to the GitHub page for instructions and list of support ops and functionalities. + +## Read more about this + + +## Users + diff --git a/tensorflow/lite/g3doc/models/speech_recognition/overview.md b/tensorflow/lite/g3doc/models/speech_recognition/overview.md new file mode 100644 index 0000000000..5d6d855db3 --- /dev/null +++ b/tensorflow/lite/g3doc/models/speech_recognition/overview.md @@ -0,0 +1,14 @@ +# Speech recognition + + + +Recognize audio keywords! + +Download starter model + +## Tutorials (coming soon) +iOS +Android + +## What is speech recognition? +Coming soon. -- GitLab From 21665efa9e8b6873de7b319b3dcd0e645dc5c48b Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 21 Feb 2019 13:55:50 -0800 Subject: [PATCH 278/766] Workaround for compiler bug(?) PiperOrigin-RevId: 235062822 --- .../core/kernels/lookup_tables/op_kernel_templates.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tensorflow/core/kernels/lookup_tables/op_kernel_templates.h b/tensorflow/core/kernels/lookup_tables/op_kernel_templates.h index d830062575..d767ca0661 100644 --- a/tensorflow/core/kernels/lookup_tables/op_kernel_templates.h +++ b/tensorflow/core/kernels/lookup_tables/op_kernel_templates.h @@ -102,7 +102,7 @@ class ResourceConstructionOp : public OpKernel { // Create other alias resources. Status status; - char dummy[sizeof...(AliasesToRegister)] = { + int dummy[sizeof...(AliasesToRegister)] = { (status.Update(RegisterAlias(resource)), 0)...}; (void)dummy; OP_REQUIRES_OK(ctx, status); @@ -119,7 +119,7 @@ class ResourceConstructionOp : public OpKernel { } // Attempt to delete other resource aliases. Status dummy_status; - char dummy[sizeof...(AliasesToRegister)] = { + int dummy[sizeof...(AliasesToRegister)] = { (dummy_status.Update(DeleteAlias()), 0)...}; (void)dummy; } @@ -237,7 +237,7 @@ class TableWithFallbackConstructionOp : public OpKernel { // Create other alias resources. Status status; - char dummy[sizeof...(AliasesToRegister)] = { + int dummy[sizeof...(AliasesToRegister)] = { (status.Update(RegisterAlias(table)), 0)...}; (void)dummy; OP_REQUIRES_OK(ctx, status); @@ -254,7 +254,7 @@ class TableWithFallbackConstructionOp : public OpKernel { } // Attempt to delete other resource aliases. Status dummy_status; - char dummy[sizeof...(AliasesToRegister)] = { + int dummy[sizeof...(AliasesToRegister)] = { (dummy_status.Update(DeleteAlias()), 0)...}; (void)dummy; } -- GitLab From 66c462c92105fced8190972e2d5551a5c3d23f2b Mon Sep 17 00:00:00 2001 From: Scott Zhu Date: Thu, 21 Feb 2019 13:56:57 -0800 Subject: [PATCH 279/766] Internal change. PiperOrigin-RevId: 235063052 --- tensorflow/contrib/seq2seq/BUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/contrib/seq2seq/BUILD b/tensorflow/contrib/seq2seq/BUILD index 2a70b08f5c..8e2ce82294 100644 --- a/tensorflow/contrib/seq2seq/BUILD +++ b/tensorflow/contrib/seq2seq/BUILD @@ -269,4 +269,5 @@ cuda_py_test( "//tensorflow/python:platform_test", "//tensorflow/python:variables", ], + shard_count = 4, ) -- GitLab From 4181d35f238f6a04a01d621467e2527f6ad40131 Mon Sep 17 00:00:00 2001 From: David Majnemer Date: Thu, 21 Feb 2019 13:58:24 -0800 Subject: [PATCH 280/766] [TF:XLA] Simplify ResourceApplyAdadelta Use higher level functions like Sqrt and Rsqrt rather than Pow with different exponents. PiperOrigin-RevId: 235063328 --- tensorflow/compiler/tf2xla/kernels/training_ops.cc | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/tensorflow/compiler/tf2xla/kernels/training_ops.cc b/tensorflow/compiler/tf2xla/kernels/training_ops.cc index 26d4214099..ceb7620380 100644 --- a/tensorflow/compiler/tf2xla/kernels/training_ops.cc +++ b/tensorflow/compiler/tf2xla/kernels/training_ops.cc @@ -856,15 +856,12 @@ class ResourceApplyAdadelta : public XlaOpKernel { xla::XlaOp grad = ctx->Input(6); xla::XlaBuilder* b = ctx->builder(); - xla::XlaOp neg_half = XlaHelpers::FloatLiteral(b, dtype_, -0.5); - xla::XlaOp half = XlaHelpers::FloatLiteral(b, dtype_, 0.5); xla::XlaOp one = XlaHelpers::FloatLiteral(b, dtype_, 1.0); - xla::XlaOp two = XlaHelpers::FloatLiteral(b, dtype_, 2.0); - accum = rho * accum + (one - rho) * xla::Pow(grad, two); - xla::XlaOp update = xla::Pow(accum_update + epsilon, half) * - xla::Pow(accum + epsilon, neg_half) * grad; - accum_update = rho * accum_update + (one - rho) * xla::Pow(update, two); + accum = rho * accum + (one - rho) * xla::Square(grad); + xla::XlaOp update = + xla::Sqrt(accum_update + epsilon) * xla::Rsqrt(accum + epsilon) * grad; + accum_update = rho * accum_update + (one - rho) * xla::Square(update); var = var - update * lr; OP_REQUIRES_OK(ctx, ctx->AssignVariable(0, dtype_, var)); OP_REQUIRES_OK(ctx, ctx->AssignVariable(1, dtype_, accum)); -- GitLab From 74016a0d5180bcb4ea66fcffc60d55d42db2eaff Mon Sep 17 00:00:00 2001 From: Katherine Wu Date: Thu, 21 Feb 2019 14:00:30 -0800 Subject: [PATCH 281/766] Remove export utility functions from estimator, update references to point to the same code in core. PiperOrigin-RevId: 235063726 --- .../tensorflow.estimator.export.-classification-output.pbtxt | 4 ++-- .../v1/tensorflow.estimator.export.-export-output.pbtxt | 2 +- .../v1/tensorflow.estimator.export.-predict-output.pbtxt | 4 ++-- .../v1/tensorflow.estimator.export.-regression-output.pbtxt | 4 ++-- .../tensorflow.estimator.export.-classification-output.pbtxt | 4 ++-- .../v2/tensorflow.estimator.export.-export-output.pbtxt | 2 +- .../v2/tensorflow.estimator.export.-predict-output.pbtxt | 4 ++-- .../v2/tensorflow.estimator.export.-regression-output.pbtxt | 4 ++-- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.-classification-output.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.-classification-output.pbtxt index 52874dd9b9..ce486807a4 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.-classification-output.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.-classification-output.pbtxt @@ -1,7 +1,7 @@ path: "tensorflow.estimator.export.ClassificationOutput" tf_class { - is_instance: "" - is_instance: "" + is_instance: "" + is_instance: "" is_instance: "" member { name: "classes" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.-export-output.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.-export-output.pbtxt index 964c315e97..9775b2cca8 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.-export-output.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.-export-output.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.estimator.export.ExportOutput" tf_class { - is_instance: "" + is_instance: "" is_instance: "" member_method { name: "__init__" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.-predict-output.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.-predict-output.pbtxt index bb82bc9e58..a4389fb998 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.-predict-output.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.-predict-output.pbtxt @@ -1,7 +1,7 @@ path: "tensorflow.estimator.export.PredictOutput" tf_class { - is_instance: "" - is_instance: "" + is_instance: "" + is_instance: "" is_instance: "" member { name: "outputs" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.-regression-output.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.-regression-output.pbtxt index 8522834433..fc7f844789 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.-regression-output.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.-regression-output.pbtxt @@ -1,7 +1,7 @@ path: "tensorflow.estimator.export.RegressionOutput" tf_class { - is_instance: "" - is_instance: "" + is_instance: "" + is_instance: "" is_instance: "" member { name: "value" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.-classification-output.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.-classification-output.pbtxt index 52874dd9b9..ce486807a4 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.-classification-output.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.-classification-output.pbtxt @@ -1,7 +1,7 @@ path: "tensorflow.estimator.export.ClassificationOutput" tf_class { - is_instance: "" - is_instance: "" + is_instance: "" + is_instance: "" is_instance: "" member { name: "classes" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.-export-output.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.-export-output.pbtxt index 964c315e97..9775b2cca8 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.-export-output.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.-export-output.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.estimator.export.ExportOutput" tf_class { - is_instance: "" + is_instance: "" is_instance: "" member_method { name: "__init__" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.-predict-output.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.-predict-output.pbtxt index bb82bc9e58..a4389fb998 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.-predict-output.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.-predict-output.pbtxt @@ -1,7 +1,7 @@ path: "tensorflow.estimator.export.PredictOutput" tf_class { - is_instance: "" - is_instance: "" + is_instance: "" + is_instance: "" is_instance: "" member { name: "outputs" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.-regression-output.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.-regression-output.pbtxt index 8522834433..fc7f844789 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.-regression-output.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.-regression-output.pbtxt @@ -1,7 +1,7 @@ path: "tensorflow.estimator.export.RegressionOutput" tf_class { - is_instance: "" - is_instance: "" + is_instance: "" + is_instance: "" is_instance: "" member { name: "value" -- GitLab From 454225d370c3fa31b457fe8368cdadfc93af0857 Mon Sep 17 00:00:00 2001 From: Rick Chao Date: Thu, 21 Feb 2019 14:04:00 -0800 Subject: [PATCH 282/766] Add convenient functions in multi_worker_test_base.py for task type, task index, and is_chief of current environment. PiperOrigin-RevId: 235064548 --- .../python/multi_worker_test_base.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tensorflow/contrib/distribute/python/multi_worker_test_base.py b/tensorflow/contrib/distribute/python/multi_worker_test_base.py index b05aac431f..7dca13a5b4 100644 --- a/tensorflow/contrib/distribute/python/multi_worker_test_base.py +++ b/tensorflow/contrib/distribute/python/multi_worker_test_base.py @@ -409,3 +409,25 @@ class IndependentWorkerTestBase(test.TestCase): def join_independent_workers(self, worker_threads): self._coord.join(worker_threads) + + +def get_tf_config_task(): + return json.loads(os.environ['TF_CONFIG'])['task'] + + +def get_tf_config_cluster_spec(): + return json.loads(os.environ['TF_CONFIG'])['cluster'] + + +def get_task_type(): + return get_tf_config_task()['type'] + + +def get_task_index(): + return get_tf_config_task()['index'] + + +def is_chief(): + return ('chief' not in get_tf_config_cluster_spec() + and get_task_type() == 'worker' + and get_task_index() == 0) -- GitLab From 117f2176340e53c3f0247fb5b6321f35fc985468 Mon Sep 17 00:00:00 2001 From: Francois Chollet Date: Thu, 21 Feb 2019 14:07:34 -0800 Subject: [PATCH 283/766] Automated rollback of commit 3ffc4e514f8a953c6444e58d65a2dc96139e0a83. Revert #25851. PiperOrigin-RevId: 235065282 --- tensorflow/python/keras/engine/network.py | 3 +-- .../python/keras/saving/hdf5_format_test.py | 26 ------------------- 2 files changed, 1 insertion(+), 28 deletions(-) diff --git a/tensorflow/python/keras/engine/network.py b/tensorflow/python/keras/engine/network.py index 50be870dce..a6fdfad833 100644 --- a/tensorflow/python/keras/engine/network.py +++ b/tensorflow/python/keras/engine/network.py @@ -1390,8 +1390,6 @@ class Network(base_layer.Layer): if not proceed: return if save_format == 'h5': - if not os.path.exists(os.path.dirname(filepath)): - os.makedirs(os.path.dirname(filepath)) with h5py.File(filepath, 'w') as f: hdf5_format.save_weights_to_hdf5_group(f, self.layers) else: @@ -1839,3 +1837,4 @@ def _map_graph_network(inputs, outputs): str(all_names.count(name)) + ' times in the model. ' 'All layer names should be unique.') return network_nodes, nodes_by_depth, layers, layers_by_depth + diff --git a/tensorflow/python/keras/saving/hdf5_format_test.py b/tensorflow/python/keras/saving/hdf5_format_test.py index db9d0e9d85..534c78da17 100644 --- a/tensorflow/python/keras/saving/hdf5_format_test.py +++ b/tensorflow/python/keras/saving/hdf5_format_test.py @@ -748,32 +748,6 @@ class TestWholeModelSaving(test.TestCase): os.close(fd) os.remove(fname) - def test_saving_with_dir_not_created(self): - if h5py is None: - self.skipTest('h5py required to run this test') - - temp_dir = self.get_temp_dir() - self.addCleanup(shutil.rmtree, temp_dir) - for f in ['tf', 'h5']: - with self.cached_session(): - data = np.random.random((1000, 32)).astype(np.float32) - labels = np.random.random((1000, 10)).astype(np.float32) - - model = keras.models.Sequential([ - keras.layers.Dense(10, activation='softmax'), - keras.layers.Dense(10, activation='softmax') - ]) - - model.compile( - optimizer=training_module.RMSPropOptimizer(0.001), - loss='categorical_crossentropy', - metrics=['accuracy']) - - model.fit(data, labels) - prefix = os.path.join(temp_dir, f, 'ckpt') - model.save_weights(prefix, save_format=f) - model.load_weights(prefix) - class SubclassedModel(training.Model): -- GitLab From 9de560b36a74ab53a8bd4d840efd3a7f955b495e Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 21 Feb 2019 14:18:49 -0800 Subject: [PATCH 284/766] Removed obsolete use_gradient_accumulation proto field from TPU embedding API. This also changes the default for gradient accumulation from false to true. PiperOrigin-RevId: 235067405 --- .../core/protobuf/tpu/optimization_parameters.proto | 9 +++------ tensorflow/python/tpu/tpu_embedding.py | 3 --- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/tensorflow/core/protobuf/tpu/optimization_parameters.proto b/tensorflow/core/protobuf/tpu/optimization_parameters.proto index a4ca7f3986..752f0dbc9c 100644 --- a/tensorflow/core/protobuf/tpu/optimization_parameters.proto +++ b/tensorflow/core/protobuf/tpu/optimization_parameters.proto @@ -175,8 +175,7 @@ message ProximalAdagradParameters { // them using the actual optimization algorithm). The extra message is to wrap // the enum for scoping. message GradientAccumulationStatus { - // Defaults to value of use_gradient_accumulation (temporary compatibility - // behavior). + // Defaults to ENABLED. enum Status { UNSPECIFIED = 0; ENABLED = 1; @@ -210,10 +209,6 @@ message OptimizationParameters { // apply them using the actual optimization algorithm). GradientAccumulationStatus.Status gradient_accumulation_status = 17; - // Old gradient accumulation flag; overridden by gradient_accumulation_status - // when it is set. - bool use_gradient_accumulation = 15; - // Optimization algorithm parameters; which field is selected determines which // algorithm to use. oneof parameters { @@ -228,6 +223,8 @@ message OptimizationParameters { AdadeltaParameters adadelta = 12; ProximalAdagradParameters proximal_adagrad = 14; } + + reserved 15; // Old use_gradient_accumulation. } // Specification of an optimization algorithm's state variables (both the main diff --git a/tensorflow/python/tpu/tpu_embedding.py b/tensorflow/python/tpu/tpu_embedding.py index ee2658f914..c7f17395f1 100644 --- a/tensorflow/python/tpu/tpu_embedding.py +++ b/tensorflow/python/tpu/tpu_embedding.py @@ -467,9 +467,6 @@ class TPUEmbedding(object): optimization_parameters_pb2.GradientAccumulationStatus.ENABLED if self._optimization_parameters.use_gradient_accumulation else optimization_parameters_pb2.GradientAccumulationStatus.DISABLED) - # For compatibility with old TPU workers. - table_descriptor.optimization_parameters.use_gradient_accumulation = ( - self._optimization_parameters.use_gradient_accumulation) self._optimizer_handler.set_optimization_parameters(table_descriptor) config_proto.mode = self._mode -- GitLab From d59044d8daeffdcca613a73573b3f10833d437d2 Mon Sep 17 00:00:00 2001 From: Wenhao Jia Date: Thu, 21 Feb 2019 14:21:02 -0800 Subject: [PATCH 285/766] Make sure device execution errors seen by Device::RefreshStatus in ExecutorState::Finish properly invoke cancellation for devices that run in asynchronous execution mode. PiperOrigin-RevId: 235067876 --- tensorflow/core/common_runtime/executor.cc | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tensorflow/core/common_runtime/executor.cc b/tensorflow/core/common_runtime/executor.cc index 7e2a85ba6d..e4022ad3af 100644 --- a/tensorflow/core/common_runtime/executor.cc +++ b/tensorflow/core/common_runtime/executor.cc @@ -2487,6 +2487,20 @@ void ExecutorState::Finish() { // device has finished all relevant work at this point. if (!device->AllowsSyncOnCompletion()) { status.Update(device->RefreshStatus()); + if (!status.ok()) { + // In device async execution mode, it's possible for device execution to + // lag behind ExecutorState scheduling so much that this is the first + // place a device execution error surfaces. + // If so, all ExecutorState::NodeDone calls have already happened with OK + // status. This is the last defense where StartCancel must be called to + // abort all computation still running on any device. + // TODO(b/124523000): Always call Finish in a separate thread, so even if + // StartCancel blocks the current thread's execution, we won't encounter + // deadlocks caused by inter-op thread exhaustion. + if (cancellation_manager_) { + cancellation_manager_->StartCancel(); + } + } delete this; runner([=]() { done_cb(status); }); return; -- GitLab From ba90c2b64350fa55688a344f20ee57d15bc3ea31 Mon Sep 17 00:00:00 2001 From: Yu-Cheng Ling Date: Thu, 21 Feb 2019 14:35:35 -0800 Subject: [PATCH 286/766] Toco should not reject models with unread inputs PiperOrigin-RevId: 235071097 --- tensorflow/lite/testing/generate_examples.py | 16 +++++++++++++--- tensorflow/lite/toco/tooling_util.cc | 5 ----- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/tensorflow/lite/testing/generate_examples.py b/tensorflow/lite/testing/generate_examples.py index 1c245b9c82..20c23017c5 100644 --- a/tensorflow/lite/testing/generate_examples.py +++ b/tensorflow/lite/testing/generate_examples.py @@ -873,6 +873,9 @@ def make_constant_tests(zip_path): "dtype": [tf.float32, tf.int32], "input_shape": [[], [1], [2], [1, 1, 1, 1], [2, 2, 2, 2]], "constant_is_also_output": [True, False], + # This is a regression test for a bug where Toco rejects models with + # unread inputs. + "has_unread_input": [True, False], }] def build_graph(parameters): @@ -882,11 +885,18 @@ def make_constant_tests(zip_path): shape=parameters["input_shape"]) constant = tf.constant( create_tensor_data(parameters["dtype"], parameters["input_shape"])) - out = [tf.maximum(dummy_input, constant)] + outputs = [tf.maximum(dummy_input, constant)] if parameters["constant_is_also_output"]: - out.append(constant) + outputs.append(constant) + inputs = [dummy_input] + if parameters["has_unread_input"]: + unread_input = tf.placeholder( + dtype=parameters["dtype"], + name="unread_input", + shape=parameters["input_shape"]) + inputs.append(unread_input) - return [dummy_input], out + return inputs, outputs def build_inputs(parameters, sess, inputs, outputs): dummy_input = np.zeros( diff --git a/tensorflow/lite/toco/tooling_util.cc b/tensorflow/lite/toco/tooling_util.cc index 4a843a26ba..4177335638 100644 --- a/tensorflow/lite/toco/tooling_util.cc +++ b/tensorflow/lite/toco/tooling_util.cc @@ -904,11 +904,6 @@ void CheckNonExistentIOArrays(const Model& model) { static constexpr char general_comment[] = "Is it a typo? To silence this message, pass this flag: " "allow_nonexistent_arrays"; - for (const auto& input_array : model.flags.input_arrays()) { - QCHECK(GetOpWithInput(model, input_array.name())) - << "Specified input array \"" << input_array.name() - << "\" is not consumed by any op in this graph. " << general_comment; - } for (const string& output_array : model.flags.output_arrays()) { if (IsConstantParameterArray(model, output_array)) { continue; // It is OK to request that a constant be an output. -- GitLab From 4225a3ad56d84f4d5e8907cdde180d45de6884d1 Mon Sep 17 00:00:00 2001 From: Rohan Jain Date: Thu, 21 Feb 2019 14:36:46 -0800 Subject: [PATCH 287/766] Resetting the MultiDeviceIterator in eager mode instead of creating a new one at the end of each epoch. This saves the memory used in creating new functions each time we create a new MultiDeviceIterator. PiperOrigin-RevId: 235071336 --- tensorflow/python/distribute/input_lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/distribute/input_lib.py b/tensorflow/python/distribute/input_lib.py index b6adad05d3..14b153b219 100644 --- a/tensorflow/python/distribute/input_lib.py +++ b/tensorflow/python/distribute/input_lib.py @@ -306,7 +306,7 @@ class _SingleWorkerDatasetIterator(object): A list of any initializer ops that should be run. """ if context.executing_eagerly(): - self._make_iterator() + self._iterator._eager_reset() # pylint: disable=protected-access return [] else: return [self._iterator.initializer] -- GitLab From b0d1767f039ac3ab02daafbbf18c76fdd727ec3f Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 21 Feb 2019 14:37:57 -0800 Subject: [PATCH 288/766] Internal change. PiperOrigin-RevId: 235071567 --- tensorflow/contrib/rnn/python/ops/rnn_cell.py | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/tensorflow/contrib/rnn/python/ops/rnn_cell.py b/tensorflow/contrib/rnn/python/ops/rnn_cell.py index 482e547a16..d25afc8b9c 100644 --- a/tensorflow/contrib/rnn/python/ops/rnn_cell.py +++ b/tensorflow/contrib/rnn/python/ops/rnn_cell.py @@ -3153,7 +3153,7 @@ class IndyGRUCell(rnn_cell_impl.LayerRNNCell): r"""Independently Gated Recurrent Unit cell. Based on IndRNNs (https://arxiv.org/abs/1803.04831) and similar to GRUCell, - yet with the \(U_r\), \(U_z\), and \(U\) matrices in equations 5, 6, and + yet with the \\(U_r\\), \\(U_z\\), and \\(U\\) matrices in equations 5, 6, and 8 of http://arxiv.org/abs/1406.1078 respectively replaced by diagonal matrices, i.e. a Hadamard product with a single vector: @@ -3164,12 +3164,10 @@ class IndyGRUCell(rnn_cell_impl.LayerRNNCell): $$\tilde{h}^{(t)}_j = \phi\left([\mathbf W \mathbf x]_j + [\mathbf u \circ \mathbf r \circ \mathbf h_{(t-1)}]_j\right)$$ - where \(\circ\) denotes the Hadamard operator. This means that each IndyGRU + where \\(\circ\\) denotes the Hadamard operator. This means that each IndyGRU node sees only its own state, as opposed to seeing all states in the same layer. - TODO(gonnet): Write a paper describing this and add a reference here. - Args: num_units: int, The number of units in the GRU cell. activation: Nonlinearity to use. Default: `tanh`. @@ -3254,7 +3252,7 @@ class IndyGRUCell(rnn_cell_impl.LayerRNNCell): self.built = True def call(self, inputs, state): - """Gated recurrent unit (GRU) with nunits cells.""" + """Recurrently independent Gated Recurrent Unit (GRU) with nunits cells.""" gate_inputs = math_ops.matmul(inputs, self._gate_kernel_w) + ( gen_array_ops.tile(state, [1, 2]) * self._gate_kernel_u) @@ -3278,10 +3276,9 @@ class IndyLSTMCell(rnn_cell_impl.LayerRNNCell): r"""Basic IndyLSTM recurrent network cell. Based on IndRNNs (https://arxiv.org/abs/1803.04831) and similar to - BasicLSTMCell, yet with the \(U_f\), \(U_i\), \(U_o\) and \(U_c\) - matrices in - https://en.wikipedia.org/wiki/Long_short-term_memory#LSTM_with_a_forget_gate - replaced by diagonal matrices, i.e. a Hadamard product with a single vector: + BasicLSTMCell, yet with the \\(U_f\\), \\(U_i\\), \\(U_o\\) and \\(U_c\\) + matrices in the regular LSTM equations replaced by diagonal matrices, i.e. a + Hadamard product with a single vector: $$f_t = \sigma_g\left(W_f x_t + u_f \circ h_{t-1} + b_f\right)$$ $$i_t = \sigma_g\left(W_i x_t + u_i \circ h_{t-1} + b_i\right)$$ @@ -3289,8 +3286,8 @@ class IndyLSTMCell(rnn_cell_impl.LayerRNNCell): $$c_t = f_t \circ c_{t-1} + i_t \circ \sigma_c\left(W_c x_t + u_c \circ h_{t-1} + b_c\right)$$ - where \(\circ\) denotes the Hadamard operator. This means that each IndyLSTM - node sees only its own state \(h\) and \(c\), as opposed to seeing all + where \\(\circ\\) denotes the Hadamard operator. This means that each IndyLSTM + node sees only its own state \\(h\\) and \\(c\\), as opposed to seeing all states in the same layer. We add forget_bias (default: 1) to the biases of the forget gate in order to @@ -3298,11 +3295,6 @@ class IndyLSTMCell(rnn_cell_impl.LayerRNNCell): It does not allow cell clipping, a projection layer, and does not use peep-hole connections: it is the basic baseline. - - For advanced models, please use the full `tf.nn.rnn_cell.LSTMCell` - that follows. - - TODO(gonnet): Write a paper describing this and add a reference here. """ def __init__(self, -- GitLab From 84ca99614405a2f12ea060652e91f85656594a1e Mon Sep 17 00:00:00 2001 From: Keith Rush Date: Thu, 21 Feb 2019 14:39:17 -0800 Subject: [PATCH 289/766] Automated rollback of commit bd45ab895d2c65229ae5f28b3202af853101a0db PiperOrigin-RevId: 235071844 --- tensorflow/core/grappler/optimizers/BUILD | 1 - .../grappler/optimizers/function_optimizer.cc | 145 +++++++----------- .../optimizers/function_optimizer_test.cc | 40 ++--- tensorflow/python/eager/function_test.py | 19 --- 4 files changed, 75 insertions(+), 130 deletions(-) diff --git a/tensorflow/core/grappler/optimizers/BUILD b/tensorflow/core/grappler/optimizers/BUILD index 62f53349fb..cdf6180ff5 100644 --- a/tensorflow/core/grappler/optimizers/BUILD +++ b/tensorflow/core/grappler/optimizers/BUILD @@ -153,7 +153,6 @@ cc_library( "//tensorflow/core/grappler:op_types", "//tensorflow/core/grappler:utils", "//tensorflow/core/grappler/utils:functions", - "//tensorflow/core/grappler/utils:topological_sort", "//tensorflow/core/grappler/utils:traversal", "@com_google_absl//absl/algorithm:container", "@com_google_absl//absl/container:flat_hash_map", diff --git a/tensorflow/core/grappler/optimizers/function_optimizer.cc b/tensorflow/core/grappler/optimizers/function_optimizer.cc index 566270238e..b722e6b332 100644 --- a/tensorflow/core/grappler/optimizers/function_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/function_optimizer.cc @@ -47,7 +47,6 @@ limitations under the License. #include "tensorflow/core/grappler/op_types.h" #include "tensorflow/core/grappler/utils.h" #include "tensorflow/core/grappler/utils/functions.h" -#include "tensorflow/core/grappler/utils/topological_sort.h" #include "tensorflow/core/grappler/utils/traversal.h" #include "tensorflow/core/lib/gtl/map_util.h" @@ -75,11 +74,9 @@ constexpr char kNoInlineAttr[] = "_noinline"; // also used as a new destination for incoming control edges. constexpr char kInputsReadyNodeName[] = "inputs_ready"; -// Name of the node that will have control edges from function control output -// nodes, and also used as a new source of outgoing control edges. This node -// will guarantee that all side-effects inside function body will be executed -// after function inlining. -constexpr char kSideEffectsExecutedNodeName[] = "side_effects_executed"; +// Name of the node that will have control edges from function output nodes, and +// also used as a new source of outgoing control edges. +constexpr char kOutputsReadyNodeName[] = "outputs_ready"; bool AttrIsTrue(const FunctionDef& func, const string& attr) { return func.attr().count(attr) != 0 && func.attr().at(attr).b(); @@ -1399,12 +1396,12 @@ Status IsInlinableIndirectFunctionCall(const FunctionOptimizerContext& ctx, } // Checks that all side-effects will be executed in well defined order. We do it -// by checking if there is a path from stateful/dataset ops to one of the -// control output nodes. +// by checking if there is a path from stateful/dataset ops to one of the output +// nodes. Status CheckThatSideEffectsWillExecute( const FunctionOptimizerContext& ctx, const GraphTopologyView& graph_topo_view, - const absl::flat_hash_set control_output_nodes) { + const absl::flat_hash_set output_nodes) { // In aggressive mode we just print a warning for side-effectful nodes that // might not be executed after inlining. const bool aggressive = ctx.opt_level() == RewriterConfig::AGGRESSIVE; @@ -1427,8 +1424,8 @@ Status CheckThatSideEffectsWillExecute( // Check if we reached one of the output nodes. const auto callbacks = DfsCallbacks::PreOrder([&](const NodeDef* node) { - if (control_output_nodes.contains(node->name())) { - VLOG(4) << "Found a path to control output node: " << node->name(); + if (output_nodes.contains(node->name())) { + VLOG(4) << "Found a path to output node: " << node->name(); will_execute = true; } }); @@ -1613,7 +1610,7 @@ Status InlineIndirectFunctionCall(const NodeDef& func_node, VLOG(3) << "Happens after set (size = " << happens_after.size() << "): " << absl::StrJoin(happens_after, ", "); - // Regular (data) inputs to the function call. + // Regular (positional) inputs to the function call. std::vector inputs; for (const string& input : func_node.input()) { SafeTensorId tensor_id = ParseTensorName(input); @@ -1674,10 +1671,6 @@ Status InlineIndirectFunctionCall(const NodeDef& func_node, // to it from all input nodes. Inlined function arguments (Identity nodes) // will have a control dependency on it. // - // TODO(ezhulenev): We do not need to provide this guarantee for ALL nodes in - // the function body. We must only ensure that we do not generate observable - // side effects. - // // If the function call node has incoming control edges, we will update them // to use this node as destination, to ensure side-effects execution order. NodeDef* inputs_ready_node = nullptr; @@ -1687,26 +1680,17 @@ Status InlineIndirectFunctionCall(const NodeDef& func_node, inputs_ready_node->set_name(kInputsReadyNodeName); } - // All nodes that have a control edge from the function call node, will be - // updated to have a control edge from 'side_effects_executed_node`. This node - // will have control edges from all function control outputs (see - // `control_ret` in FunctionDef). This a "barrier" that guarantees that all - // ops with side effects in the function body were executed - // - // If the function call node has no outgoing control edges, it means that no - // one is interested in the function side-effect affecting captured resources. - NodeDef* side_effects_executed_node = nullptr; - if (!happens_after.empty()) { - side_effects_executed_node = item.graph.add_node(); - side_effects_executed_node->set_op("NoOp"); - side_effects_executed_node->set_name(kSideEffectsExecutedNodeName); - } - - // If function executed only for the regular data outputs, it's totally safe - // to prune side-effects. If side-effects order is important, it must be - // captured at graph construction time via control edges. - if (item.control_output_size() > 0 && happens_after.empty()) { - VLOG(2) << "Function has control outputs and empty happens after set."; + // All nodes that have control edge from the function call node, will be + // updated to have a control edge from 'outputs_ready_node`. This node will + // have control edges from all function outputs. This a "barrier" that + // guarantees that all function side effects were executed, and it will also + // allow to propagate deadness flag (if there is a deadness mismatch between + // output nodes). + NodeDef* outputs_ready_node = nullptr; + if (item.output_size() > 0 || !happens_after.empty()) { + outputs_ready_node = item.graph.add_node(); + outputs_ready_node->set_op("NoOp"); + outputs_ready_node->set_name(kOutputsReadyNodeName); } // ------------------------------------------------------------------------ // @@ -1776,12 +1760,9 @@ Status InlineIndirectFunctionCall(const NodeDef& func_node, // function call has no regular and control inputs, we will not add // inputs_ready node to the function body graph. node_name != kInputsReadyNodeName && - // The node acting as a return barrier for execution of side effects - // might not have any inputs (in case function has no control outputs, - // but we still added it because of non-empty happens-after set), so - // we must make sure it's executed in correct frame. - (node_name != kSideEffectsExecutedNodeName || - item.control_output_size() == 0); + // Outputs ready node might not have any inputs (in case function has + // no outputs), so we must make sure it's executed in correct frame. + (node_name != kOutputsReadyNodeName || item.output_size() == 0); if (node_needs_empty_inputs_hook) { *func_body_node.add_input() = @@ -1818,19 +1799,12 @@ Status InlineIndirectFunctionCall(const NodeDef& func_node, return inlined_output_nodes.find(node.name()) != inlined_output_nodes.end(); }; - // Names of the inlined control output nodes. - absl::flat_hash_set inlined_control_output_nodes; - for (const ControlOutput& control_output : item.control_outputs()) { - inlined_control_output_nodes.insert( - inlined_node_name(control_output.node_name)); - } - // Construct a graph topology view for DFS traversals (skip invalid edges for // input nodes connected to nodes in the optimized graph). GraphTopologyView placed_topo_view(/*skip_invalid_edges=*/true); TF_RETURN_IF_ERROR(placed_topo_view.InitializeFromGraph(placed_graph_def)); - TF_RETURN_IF_ERROR(CheckThatSideEffectsWillExecute( - *ctx, placed_topo_view, inlined_control_output_nodes)); + TF_RETURN_IF_ERROR(CheckThatSideEffectsWillExecute(*ctx, placed_topo_view, + inlined_output_nodes)); // ------------------------------------------------------------------------ // // Move all the nodes to the optimized graph after successful preprocessing. @@ -1841,15 +1815,7 @@ Status InlineIndirectFunctionCall(const NodeDef& func_node, absl::flat_hash_set input_nodes; for (const string& input : func_node.input()) { - SafeTensorId tensor = ParseTensorName(input); - - // Input node might have been a function call that was already inlined. - auto it = ctx->tensor_mapping().find(tensor); - while (it != ctx->tensor_mapping().end()) { - tensor = it->second; - it = ctx->tensor_mapping().find(tensor); - } - + const SafeTensorId tensor = ParseTensorName(input); if (input_nodes.insert(tensor.node()).second) { placed_graph_def.mutable_node(*node_idx)->add_input( AsControlDependency(tensor.node())); @@ -1857,14 +1823,14 @@ Status InlineIndirectFunctionCall(const NodeDef& func_node, } } - if (side_effects_executed_node != nullptr) { - string inlined_node = inlined_node_name(side_effects_executed_node->name()); + if (outputs_ready_node != nullptr) { + string inlined_node = inlined_node_name(outputs_ready_node->name()); absl::optional node_idx = placed_topo_view.GetNodeIndex(inlined_node); - // Add control edges from all control output nodes. - for (const string& node_name : inlined_control_output_nodes) { + // Add control edges from all nodes producing output tensors. + for (const string& node_name : output_tensors_nodes) { placed_graph_def.mutable_node(*node_idx)->add_input( - AsControlDependency(node_name)); + AsControlDependency(inlined_node_name(node_name))); } // Forward all control dependencies in the optimized graph to the new node. @@ -1993,12 +1959,7 @@ Status FunctionOptimizer::RunFunctionOptimizerPass( bool inline_func = options_.enable_function_inlining; bool specialize_func = options_.enable_function_specialization; - // We will process all the nodes in topological order, to correctly handle - // inlining of function call chains. - std::vector topo_ordered_nodes; - TF_RETURN_IF_ERROR(ComputeTopologicalOrder(graph, &topo_ordered_nodes)); - - for (const NodeDef* node : topo_ordered_nodes) { + for (const NodeDef& node : graph.node()) { // Each node optimization can modify optimized graph only by adding new // nodes, we can check node size to make sure that graph was not modified. const int num_nodes_before = optimized_graph->node_size(); @@ -2009,12 +1970,12 @@ Status FunctionOptimizer::RunFunctionOptimizerPass( }; // Copy node from the `graph` to the `optimized_graph`. - const auto copy_node = [&]() { *optimized_graph->add_node() = *node; }; + const auto copy_node = [&]() { *optimized_graph->add_node() = node; }; // If we already failed to optimize this node during one of the previous // passes, we just give up, and do not try on more time. - if (skip_nodes->find(node->name()) != skip_nodes->end()) { - VLOG(3) << "Skip optimization for node: " << node->name(); + if (skip_nodes->find(node.name()) != skip_nodes->end()) { + VLOG(3) << "Skip optimization for node: " << node.name(); copy_node(); continue; } @@ -2027,7 +1988,7 @@ Status FunctionOptimizer::RunFunctionOptimizerPass( return _status; \ if (TF_PREDICT_FALSE(!_status.ok() && !is_graph_modified())) { \ VLOG(3) << "Skip error: " << _status.error_message(); \ - skip_nodes->insert(node->name()); \ + skip_nodes->insert(node.name()); \ copy_node(); \ } \ } while (0) @@ -2036,19 +1997,19 @@ Status FunctionOptimizer::RunFunctionOptimizerPass( // 1. Inline symbolic gradients into the optimized graph. // // ---------------------------------------------------------------------- // - if (IsSymbolicGradient(*node) && inline_gradients) { + if (IsSymbolicGradient(node) && inline_gradients) { // Inline symbolic gradients only if the corresponding function is not // marked as `_noinline`. - const auto* f_attr = gtl::FindOrNull(node->attr(), "f"); + const auto* f_attr = gtl::FindOrNull(node.attr(), "f"); const string f_name = f_attr != nullptr ? f_attr->func().name() : ""; const FunctionDef* func = ctx.function_library().Find(f_name); if (func && !MarkedNoInline(*func)) { TF_SKIP_ERROR_IF_GRAPH_UNMODIFIED( - InlineSymbolicGradient(*node, &ctx, optimized_graph)); + InlineSymbolicGradient(node, &ctx, optimized_graph)); continue; } else { VLOG(2) << "Skip SymbolicGradient inlining: function=" << f_name; - skip_nodes->insert(node->name()); + skip_nodes->insert(node.name()); } } @@ -2057,45 +2018,45 @@ Status FunctionOptimizer::RunFunctionOptimizerPass( // ---------------------------------------------------------------------- // // Find if a node is a function call (direct or indirect). - const FunctionDef* func = FindFunctionCall(ctx, *node); + const FunctionDef* func = FindFunctionCall(ctx, node); if (func != nullptr) { const string& func_name = func->signature().name(); - const bool is_direct_func = IsDirectFunctionCall(*func, *node); - const bool is_indirect_func = IsIndirectFunctionCall(*func, *node); + const bool is_direct_func = IsDirectFunctionCall(*func, node); + const bool is_indirect_func = IsIndirectFunctionCall(*func, node); // 2a. Inline direct function call if it's inlinable. if (inline_func && is_direct_func) { - Status inlinable = IsInlinableDirectFunctionCall(ctx, *func, *node); + Status inlinable = IsInlinableDirectFunctionCall(ctx, *func, node); if (inlinable.ok()) { TF_SKIP_ERROR_IF_GRAPH_UNMODIFIED( - InlineDirectFunctionCall(*node, *func, ctx, optimized_graph)); + InlineDirectFunctionCall(node, *func, ctx, optimized_graph)); continue; } else { VLOG(2) << inlinable.error_message(); - skip_nodes->insert(node->name()); + skip_nodes->insert(node.name()); } } // 2b. Inline indirect function call if it's inlinable. if (inline_func && is_indirect_func) { - Status inlinable = IsInlinableIndirectFunctionCall(ctx, *func, *node); + Status inlinable = IsInlinableIndirectFunctionCall(ctx, *func, node); if (inlinable.ok()) { TF_SKIP_ERROR_IF_GRAPH_UNMODIFIED( - InlineIndirectFunctionCall(*node, *func, &ctx, optimized_graph)); + InlineIndirectFunctionCall(node, *func, &ctx, optimized_graph)); continue; } else { VLOG(2) << inlinable.error_message(); - skip_nodes->insert(node->name()); + skip_nodes->insert(node.name()); } } // 2c. Specialize it to its instantiation context if can't be inlined, // and it has something worth specializing. bool specialization_worthy = IsParametrized(*func) || - HasTrulyConstInputs(*node, ctx) || - HasUnusedOutputs(*node, *func, ctx); + HasTrulyConstInputs(node, ctx) || + HasUnusedOutputs(node, *func, ctx); // Do not specialize if function has custom gradient. const string grad_func = ctx.function_library().FindGradient(func_name); @@ -2104,11 +2065,11 @@ Status FunctionOptimizer::RunFunctionOptimizerPass( // TODO(ezhulenev): Specialize function call if input has a known shape. // Specialize function body for its instantiation attributes and inputs. TF_SKIP_ERROR_IF_GRAPH_UNMODIFIED( - SpecializeFunction(*node, *func, &ctx, optimized_graph)); + SpecializeFunction(node, *func, &ctx, optimized_graph)); continue; } else { VLOG(2) << "Skip function specialization: " << func->signature().name(); - skip_nodes->insert(node->name()); + skip_nodes->insert(node.name()); } } diff --git a/tensorflow/core/grappler/optimizers/function_optimizer_test.cc b/tensorflow/core/grappler/optimizers/function_optimizer_test.cc index 8ea49dc3dd..2787d9d525 100644 --- a/tensorflow/core/grappler/optimizers/function_optimizer_test.cc +++ b/tensorflow/core/grappler/optimizers/function_optimizer_test.cc @@ -742,6 +742,7 @@ TEST_F(FunctionOptimizerTest, InlineIndirectFunctionSimpleFunction) { NDef("c/y", "Identity", {"b:0", "^c/inputs_ready"}, {{"T", DT_FLOAT}}, kDevice), NDef("c/mul", "Mul", {"c/x", "c/y"}, {{"T", DT_FLOAT}}, kDevice), + NDef("c/outputs_ready", "NoOp", {"^c/mul"}, {}, kDevice), NDef("d", "Identity", {"c/mul:0"}, {{"T", DT_FLOAT}}, kDevice)}, // Function library. @@ -765,7 +766,7 @@ TEST_F(FunctionOptimizerTest, InlineIndirectFunctionWithControlDependencies) { using test::function::NDef; using FDH = FunctionDefHelper; - FunctionOptimizer optimizer(RewriterConfig::ON); + FunctionOptimizer optimizer(RewriterConfig::AGGRESSIVE); const Tensor kOne = test::AsScalar(1.0); const Tensor kTwo = test::AsScalar(2.0); @@ -779,11 +780,9 @@ TEST_F(FunctionOptimizerTest, InlineIndirectFunctionWithControlDependencies) { "AssignAddVariableOp", {"v", "one:output:0"}, {{"dtype", DT_FLOAT}}}, - {{"mul"}, "Mul", {"x", "y"}, {{"T", "$T"}}}}, + {{"mul"}, "Mul", {"x", "y", "^add"}, {{"T", "$T"}}}}, /* Mapping between function returns and function node outputs. */ - {{"z", "mul:z:0"}}, - /* Control output to ensure that side effects will be executed. */ - {{"size_effects", "add"}}); + {{"z", "mul:z:0"}}); // Build a graph to compute: // a = Placeholder @@ -852,15 +851,16 @@ TEST_F(FunctionOptimizerTest, InlineIndirectFunctionWithControlDependencies) { {{"dtype", DT_FLOAT}, {"value", kOne}}, kDevice), NDef("f1/add", "AssignAddVariableOp", {"f1/v", "f1/one"}, {{"dtype", DT_FLOAT}}, kDevice), - NDef("f1/mul", "Mul", {"f1/x", "f1/y"}, {{"T", DT_FLOAT}}, kDevice), + NDef("f1/mul", "Mul", {"f1/x", "f1/y", "^f1/add"}, {{"T", DT_FLOAT}}, + kDevice), - NDef("f1/side_effects_executed", "NoOp", {"^f1/add"}, {}, kDevice), + NDef("f1/outputs_ready", "NoOp", {"^f1/mul"}, {}, kDevice), // Function body of a second function call also inlined into the graph, // and input nodes read directly from the inlined nodes of the first // function call. - NDef("f2/inputs_ready", "NoOp", - {"^v", "^f1/mul", "^f1/side_effects_executed"}, {}, kDevice), + NDef("f2/inputs_ready", "NoOp", {"^v", "^f1/outputs_ready"}, {}, + kDevice), NDef("f2/x", "Identity", {"f1/mul:0", "^f2/inputs_ready"}, {{"T", DT_FLOAT}}, kDevice), @@ -873,14 +873,15 @@ TEST_F(FunctionOptimizerTest, InlineIndirectFunctionWithControlDependencies) { {{"dtype", DT_FLOAT}, {"value", kOne}}, kDevice), NDef("f2/add", "AssignAddVariableOp", {"f2/v", "f2/one"}, {{"dtype", DT_FLOAT}}, kDevice), - NDef("f2/mul", "Mul", {"f2/x", "f2/y"}, {{"T", DT_FLOAT}}, kDevice), + NDef("f2/mul", "Mul", {"f2/x", "f2/y", "^f2/add"}, {{"T", DT_FLOAT}}, + kDevice), - NDef("f2/side_effects_executed", "NoOp", {"^f2/add"}, {}, kDevice), + NDef("f2/outputs_ready", "NoOp", {"^f2/mul"}, {}, kDevice), // Return values read directly from inlined nodes. NDef("out_1", "Identity", {"f2/mul:0"}, {{"T", DT_FLOAT}}, kDevice), NDef("out_2", "ReadVariableOp", - {"v", "^f1/side_effects_executed", "^f2/side_effects_executed"}, + {"v", "^f1/outputs_ready", "^f2/outputs_ready"}, {{"dtype", DT_FLOAT}}, kDevice)}, // Function library. @@ -952,6 +953,7 @@ TEST_F(FunctionOptimizerTest, InlineIndirectFunctionWithDevicePlacement) { NDef("c/y", "Identity", {"b:0", "^c/inputs_ready"}, {{"T", DT_FLOAT}}, cpu1), NDef("c/mul", "Mul", {"c/x", "c/y"}, {{"T", DT_FLOAT}}, cpu1), + NDef("c/outputs_ready", "NoOp", {"^c/mul"}, {}, cpu0), NDef("d", "Identity", {"c/mul:0"}, {{"T", DT_FLOAT}}, cpu0)}, // Function library. @@ -1025,21 +1027,18 @@ TEST_F(FunctionOptimizerTest, NDef("f1/y", "Identity", {"b:0", "^f1/inputs_ready"}, {{"T", DT_FLOAT}}, kDevice), NDef("f1/mul", "Mul", {"f1/x", "f1/y"}, {{"T", DT_FLOAT}}, kDevice), - // Control input from `inputs_ready` node is added to ensure correct - // frame execution. - NDef("f1/side_effects_executed", "NoOp", {"^f1/inputs_ready"}, {}, - kDevice), + NDef("f1/outputs_ready", "NoOp", {"^f1/mul"}, {}, kDevice), // Function body of a second function call also inlined into the graph, // and input nodes read directly from the inlined nodes of the first // function call, and control dependency edge removed. - NDef("f2/inputs_ready", "NoOp", {"^f1/mul", "^f1/side_effects_executed"}, - {}, kDevice), + NDef("f2/inputs_ready", "NoOp", {"^f1/outputs_ready"}, {}, kDevice), NDef("f2/x", "Identity", {"f1/mul:0", "^f2/inputs_ready"}, {{"T", DT_FLOAT}}, kDevice), NDef("f2/y", "Identity", {"f1/mul:0", "^f2/inputs_ready"}, {{"T", DT_FLOAT}}, kDevice), NDef("f2/mul", "Mul", {"f2/x", "f2/y"}, {{"T", DT_FLOAT}}, kDevice), + NDef("f2/outputs_ready", "NoOp", {"^f2/mul"}, {}, kDevice), // Return directly from inlined node of f2. NDef("out", "Identity", {"f2/mul:0"}, {{"T", DT_FLOAT}}, kDevice)}, @@ -1197,6 +1196,7 @@ TEST_F(FunctionOptimizerTest, InlineIndirectFunctionWithMergedDeadTensors) { kDevice), NDef("fn/merge", "Merge", {"fn/if_false:0", "fn/if_true:0"}, {{"T", DT_FLOAT}, {"N", 2}}, kDevice), + NDef("fn/outputs_ready", "NoOp", {"^fn/merge"}, {}, kDevice), // Return directly from inlined node. NDef("out", "Identity", {"fn/merge:0"}, {{"T", DT_FLOAT}}, kDevice)}, @@ -1283,6 +1283,10 @@ TEST_F(FunctionOptimizerTest, InlineIndirectFunctionWithNestedFunctionCall) { NDef("b/square/mul", "Mul", {"b/square/x", "b/square/y"}, {{"T", DT_FLOAT}}, kDevice), + NDef("b/square/outputs_ready", "NoOp", {"^b/square/mul"}, {}, kDevice), + NDef("b/outputs_ready", "NoOp", {"^b/square/outputs_ready"}, {}, + kDevice), + NDef("c", "Identity", {"b/square/mul:0"}, {{"T", DT_FLOAT}}, kDevice)}, // Function library. {mul_func}); diff --git a/tensorflow/python/eager/function_test.py b/tensorflow/python/eager/function_test.py index 270e420fa8..7a17eb6ee2 100644 --- a/tensorflow/python/eager/function_test.py +++ b/tensorflow/python/eager/function_test.py @@ -1831,25 +1831,6 @@ class FunctionTest(test.TestCase, parameterized.TestCase): side_effecting_function.python_function() self.assertAllEqual(state, [0, 0]) - def testFunctionWithNestedFunctionCallAndSideEffects(self): - v1 = variables.Variable(1.0) - v2 = variables.Variable(1.0) - - @def_function.function - def add_one(a): - a.assign_add(1.0) - - # Grappler will inline calls to `add_one` into the function body, we check - # that all side-effects were executed. - @def_function.function - def side_effecting_function(a, b): - add_one(a) - add_one(b) - return a + b - - result = side_effecting_function(v1, v2) - self.assertEqual(result.numpy(), 4.0) - def testFunctionWithExtraAttributes(self): @function.defun_with_attributes(attributes={'experimental_1': 'value1', 'experimental_2': 2}) -- GitLab From 1ffa29b8dd3022ef9edfec4cca3bfb821efbf74b Mon Sep 17 00:00:00 2001 From: Allen Lavoie Date: Thu, 21 Feb 2019 14:40:48 -0800 Subject: [PATCH 290/766] Allow positional arguments when calling single-argument SavedModel signatures from Python They aren't ambiguous, so requiring keywords is just annoying. (And was making guide writing harder.) PiperOrigin-RevId: 235072129 --- tensorflow/python/eager/function.py | 10 +++---- tensorflow/python/saved_model/load_test.py | 28 ++++++++++++++++-- .../python/saved_model/load_v1_in_v2.py | 7 ++++- .../python/saved_model/load_v1_in_v2_test.py | 4 +-- .../saved_model/signature_serialization.py | 29 ++++++++++++++----- 5 files changed, 59 insertions(+), 19 deletions(-) diff --git a/tensorflow/python/eager/function.py b/tensorflow/python/eager/function.py index c53cde692e..487fd5a972 100644 --- a/tensorflow/python/eager/function.py +++ b/tensorflow/python/eager/function.py @@ -496,12 +496,10 @@ class ConcreteFunction(object): "through the public interface. Use get_concrete_function instead.") if len(args) > self._num_positional_args: raise TypeError( - ("Expected at most {} positional arguments ({}), got {}. When " - "calling a concrete function, positional arguments may not be bound " - "to Tensors within nested structures.").format( - self._num_positional_args, - self._arg_keywords[:self._num_positional_args], - args)) + ("Expected at most {} positional arguments (and the rest keywords, " + "of {}), got {}. When calling a concrete function, positional " + "arguments may not be bound to Tensors within nested structures." + ).format(self._num_positional_args, self._arg_keywords, args)) args = list(args) for keyword in self._arg_keywords[len(args):]: try: diff --git a/tensorflow/python/saved_model/load_test.py b/tensorflow/python/saved_model/load_test.py index 6b0734a560..845fc40e5c 100644 --- a/tensorflow/python/saved_model/load_test.py +++ b/tensorflow/python/saved_model/load_test.py @@ -939,12 +939,36 @@ class LoadTest(test.TestCase, parameterized.TestCase): self.assertEqual(6., imported_function(x=two)["output_0"].numpy()) imported.v.assign(4.) self.assertEqual(8., imported_function(x=two)["output_0"].numpy()) - with self.assertRaisesRegexp(TypeError, "positional"): - imported_function(two) + self.assertEqual(8., imported_function(two)["output_0"].numpy()) with self.assertRaises(TypeError): # The signatures mapping is immutable imported.signatures["random_key"] = 3 + def test_multiple_argument_signatures_no_positional(self, cycles): + + class Exported(tracking.AutoTrackable): + + @def_function.function + def do(self, x, y): + return x + y + + exported = Exported() + imported = self.cycle( + exported, signatures=exported.do.get_concrete_function( + tensor_spec.TensorSpec(None, dtypes.float32), + tensor_spec.TensorSpec(None, dtypes.float32))) + for _ in range(cycles - 1): + imported = self.cycle(imported, signatures=imported.signatures) + with self.assertRaises(TypeError): + imported.signatures["serving_default"]( + constant_op.constant(1.), + y=constant_op.constant(2.)) + self.assertEqual( + {"output_0": 3.}, + self.evaluate(imported.signatures["serving_default"]( + x=constant_op.constant(1.), + y=constant_op.constant(2.)))) + def _make_model_with_tables(self): default_val = -1 keys = constant_op.constant(["brain", "salad", "surgery"]) diff --git a/tensorflow/python/saved_model/load_v1_in_v2.py b/tensorflow/python/saved_model/load_v1_in_v2.py index c5ba01070a..bb8ed7cfcc 100644 --- a/tensorflow/python/saved_model/load_v1_in_v2.py +++ b/tensorflow/python/saved_model/load_v1_in_v2.py @@ -107,7 +107,12 @@ class _EagerSavedModelLoader(loader_impl.SavedModelLoader): for name, out in signature_def.outputs.items()}) # pylint: disable=protected-access signature_fn._arg_keywords = input_names - signature_fn._num_positional_args = 0 + if len(input_names) == 1: + # Allowing positional arguments does not create any ambiguity if there's + # only one. + signature_fn._num_positional_args = 1 + else: + signature_fn._num_positional_args = 0 # pylint: enable=protected-access signature_functions[signature_key] = signature_fn return signature_functions diff --git a/tensorflow/python/saved_model/load_v1_in_v2_test.py b/tensorflow/python/saved_model/load_v1_in_v2_test.py index e8bd61a8c3..ce2c0cd960 100644 --- a/tensorflow/python/saved_model/load_v1_in_v2_test.py +++ b/tensorflow/python/saved_model/load_v1_in_v2_test.py @@ -80,10 +80,8 @@ class LoadTest(test.TestCase): imported = load.load(self._v1_single_metagraph_saved_model( use_resource=True)) fn = imported.signatures["serving_default"] - with self.assertRaisesRegexp(TypeError, "positional"): - fn(constant_op.constant(2.)) self.assertEqual({"output": 6.}, - self.evaluate(fn(start=constant_op.constant(2.)))) + self.evaluate(fn(constant_op.constant(2.)))) self.assertAllEqual([3., 1.], self.evaluate(imported.variables)) imported.variables[0].assign(4.) self.assertEqual({"output": 8.}, diff --git a/tensorflow/python/saved_model/signature_serialization.py b/tensorflow/python/saved_model/signature_serialization.py index 181ee83080..9cfd5b14ef 100644 --- a/tensorflow/python/saved_model/signature_serialization.py +++ b/tensorflow/python/saved_model/signature_serialization.py @@ -99,9 +99,8 @@ def canonicalize_signatures(signatures): "got {}. Only `tf.functions` with an input signature or " "concrete functions can be used as a signature.").format(function)) - # Re-wrap the function so that it only takes keyword arguments and it - # returns a dictionary of Tensors. This matches the format of 1.x-style - # signatures. + # Re-wrap the function so that it returns a dictionary of Tensors. This + # matches the format of 1.x-style signatures. # pylint: disable=cell-var-from-loop @def_function.function def signature_wrapper(**kwargs): @@ -117,8 +116,19 @@ def canonicalize_signatures(signatures): keyword = compat.as_str(keyword) tensor_spec_signature[keyword] = tensor_spec.TensorSpec.from_tensor( tensor, name=keyword) - concrete_signatures[signature_key] = ( - signature_wrapper.get_concrete_function(**tensor_spec_signature)) + final_concrete = signature_wrapper.get_concrete_function( + **tensor_spec_signature) + # pylint: disable=protected-access + if len(final_concrete._arg_keywords) == 1: + # If there is only one input to the signature, a very common case, then + # ordering is unambiguous and we can let people pass a positional + # argument. Since SignatureDefs are unordered (protobuf "map") multiple + # arguments means we need to be keyword-only. + final_concrete._num_positional_args = 1 + else: + final_concrete._num_positional_args = 0 + # pylint: enable=protected-access + concrete_signatures[signature_key] = final_concrete # pylint: enable=cell-var-from-loop return concrete_signatures @@ -225,8 +235,13 @@ def create_signature_map(signatures): # assertions. assert isinstance(func, defun.ConcreteFunction) assert isinstance(func.structured_outputs, collections.Mapping) - assert 0 == func._num_positional_args # pylint: disable=protected-access - signature_map._add_signature(name, func) # pylint: disable=protected-access + # pylint: disable=protected-access + if len(func._arg_keywords) == 1: + assert 1 == func._num_positional_args + else: + assert 0 == func._num_positional_args + signature_map._add_signature(name, func) + # pylint: enable=protected-access return signature_map -- GitLab From df64f4e73da3b0d954f5f2f948129cc6efb2566a Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 21 Feb 2019 14:43:00 -0800 Subject: [PATCH 291/766] Move dump_tpu_profile and trace_events_to_json out from contrib. PiperOrigin-RevId: 235072595 --- tensorflow/contrib/tpu/profiler/BUILD | 41 ------------------- tensorflow/core/profiler/rpc/client/BUILD | 41 ++++++++++++++++++- .../profiler/rpc/client/capture_profile.cc | 6 +-- .../profiler/rpc/client}/dump_tpu_profile.cc | 17 ++++---- .../profiler/rpc/client}/dump_tpu_profile.h | 13 +++--- .../rpc/client}/trace_events_to_json.cc | 41 ++++++++++--------- .../rpc/client}/trace_events_to_json.h | 15 ++++--- .../rpc/client}/trace_events_to_json_test.cc | 11 +++-- 8 files changed, 98 insertions(+), 87 deletions(-) rename tensorflow/{contrib/tpu/profiler => core/profiler/rpc/client}/dump_tpu_profile.cc (94%) rename tensorflow/{contrib/tpu/profiler => core/profiler/rpc/client}/dump_tpu_profile.h (83%) rename tensorflow/{contrib/tpu/profiler => core/profiler/rpc/client}/trace_events_to_json.cc (78%) rename tensorflow/{contrib/tpu/profiler => core/profiler/rpc/client}/trace_events_to_json.h (73%) rename tensorflow/{contrib/tpu/profiler => core/profiler/rpc/client}/trace_events_to_json_test.cc (94%) diff --git a/tensorflow/contrib/tpu/profiler/BUILD b/tensorflow/contrib/tpu/profiler/BUILD index 2eff4f2468..9fb29f5e17 100644 --- a/tensorflow/contrib/tpu/profiler/BUILD +++ b/tensorflow/contrib/tpu/profiler/BUILD @@ -2,23 +2,6 @@ licenses(["notice"]) # Apache 2.0 load("//tensorflow:tensorflow.bzl", "tf_cc_binary") load("//tensorflow:tensorflow.bzl", "tf_cc_test") -load("//tensorflow/core:platform/default/build_config.bzl", "tf_proto_library") -load("//tensorflow/core:platform/default/build_config.bzl", "tf_additional_all_protos") - -cc_library( - name = "dump_tpu_profile", - srcs = ["dump_tpu_profile.cc"], - hdrs = ["dump_tpu_profile.h"], - visibility = ["//visibility:public"], - deps = [ - ":trace_events_to_json", - "//tensorflow/core:framework", - "//tensorflow/core:grpc_services", - "//tensorflow/core:lib", - "//tensorflow/core:protos_all_cc", - "//tensorflow/core/profiler:protos_all_cc", - ], -) cc_library( name = "version", @@ -39,27 +22,3 @@ tf_cc_binary( "//tensorflow/core/profiler/rpc/client:capture_profile", ], ) - -cc_library( - name = "trace_events_to_json", - srcs = ["trace_events_to_json.cc"], - hdrs = ["trace_events_to_json.h"], - deps = [ - "//tensorflow/core:lib", - "//tensorflow/core/profiler:protos_all_cc", - "@jsoncpp_git//:jsoncpp", - ], -) - -tf_cc_test( - name = "trace_events_to_json_test", - srcs = ["trace_events_to_json_test.cc"], - deps = [ - ":trace_events_to_json", - "//tensorflow/core:lib", - "//tensorflow/core:test", - "//tensorflow/core:test_main", - "//tensorflow/core/profiler:protos_all_cc", - "@jsoncpp_git//:jsoncpp", - ], -) diff --git a/tensorflow/core/profiler/rpc/client/BUILD b/tensorflow/core/profiler/rpc/client/BUILD index 4f963f564f..14dd865a17 100644 --- a/tensorflow/core/profiler/rpc/client/BUILD +++ b/tensorflow/core/profiler/rpc/client/BUILD @@ -1,6 +1,7 @@ licenses(["notice"]) # Apache 2.0 load("//tensorflow:tensorflow.bzl", "tf_cuda_library") +load("//tensorflow:tensorflow.bzl", "tf_cc_test") tf_cuda_library( name = "capture_profile", @@ -12,8 +13,8 @@ tf_cuda_library( ], visibility = ["//visibility:public"], deps = [ + ":dump_tpu_profile", "//tensorflow:grpc++", - "//tensorflow/contrib/tpu/profiler:dump_tpu_profile", "//tensorflow/core:framework_internal", "//tensorflow/core:grpc_services", "//tensorflow/core:lib", @@ -22,3 +23,41 @@ tf_cuda_library( "//tensorflow/core/profiler:protos_all_cc", ], ) + +cc_library( + name = "dump_tpu_profile", + srcs = ["dump_tpu_profile.cc"], + hdrs = ["dump_tpu_profile.h"], + visibility = ["//visibility:public"], + deps = [ + ":trace_events_to_json", + "//tensorflow/core:framework", + "//tensorflow/core:grpc_services", + "//tensorflow/core:lib", + "//tensorflow/core/profiler:protos_all_cc", + ], +) + +cc_library( + name = "trace_events_to_json", + srcs = ["trace_events_to_json.cc"], + hdrs = ["trace_events_to_json.h"], + deps = [ + "//tensorflow/core:lib", + "//tensorflow/core/profiler:protos_all_cc", + "@jsoncpp_git//:jsoncpp", + ], +) + +tf_cc_test( + name = "trace_events_to_json_test", + srcs = ["trace_events_to_json_test.cc"], + deps = [ + ":trace_events_to_json", + "//tensorflow/core:lib", + "//tensorflow/core:test", + "//tensorflow/core:test_main", + "//tensorflow/core/profiler:protos_all_cc", + "@jsoncpp_git//:jsoncpp", + ], +) diff --git a/tensorflow/core/profiler/rpc/client/capture_profile.cc b/tensorflow/core/profiler/rpc/client/capture_profile.cc index 055211937f..a24e0faa41 100644 --- a/tensorflow/core/profiler/rpc/client/capture_profile.cc +++ b/tensorflow/core/profiler/rpc/client/capture_profile.cc @@ -20,7 +20,6 @@ limitations under the License. #include #include -#include "tensorflow/contrib/tpu/profiler/dump_tpu_profile.h" #include "tensorflow/core/distributed_runtime/rpc/grpc_util.h" #include "tensorflow/core/lib/core/errors.h" #include "tensorflow/core/lib/core/status.h" @@ -28,6 +27,7 @@ limitations under the License. #include "tensorflow/core/lib/strings/numbers.h" #include "tensorflow/core/lib/strings/str_util.h" #include "tensorflow/core/platform/grpc_services.h" +#include "tensorflow/core/profiler/rpc/client/dump_tpu_profile.h" namespace tensorflow { namespace profiler { @@ -100,8 +100,8 @@ Status Profile(const string& service_addr, const string& logdir, FromGrpcStatus(stub->Profile(&context, request, &response))); if (!response.encoded_trace().empty()) { - TF_CHECK_OK(tensorflow::tpu::WriteTensorboardTPUProfile( - logdir, session_id, "", response, &std::cout)); + TF_CHECK_OK(WriteTensorboardTPUProfile(logdir, session_id, "", response, + &std::cout)); // Print this at the end so that it's not buried in irrelevant LOG messages. std::cout << "NOTE: using the trace duration " << duration_ms << "ms." diff --git a/tensorflow/contrib/tpu/profiler/dump_tpu_profile.cc b/tensorflow/core/profiler/rpc/client/dump_tpu_profile.cc similarity index 94% rename from tensorflow/contrib/tpu/profiler/dump_tpu_profile.cc rename to tensorflow/core/profiler/rpc/client/dump_tpu_profile.cc index d138c1fb4c..f0675c4cf4 100644 --- a/tensorflow/contrib/tpu/profiler/dump_tpu_profile.cc +++ b/tensorflow/core/profiler/rpc/client/dump_tpu_profile.cc @@ -13,13 +13,12 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/contrib/tpu/profiler/dump_tpu_profile.h" +#include "tensorflow/core/profiler/rpc/client/dump_tpu_profile.h" #include #include #include -#include "tensorflow/contrib/tpu/profiler/trace_events_to_json.h" #include "tensorflow/core/lib/core/errors.h" #include "tensorflow/core/lib/io/compression.h" #include "tensorflow/core/lib/io/path.h" @@ -28,11 +27,14 @@ limitations under the License. #include "tensorflow/core/platform/env.h" #include "tensorflow/core/platform/protobuf.h" #include "tensorflow/core/profiler/op_profile.pb.h" +#include "tensorflow/core/profiler/rpc/client/trace_events_to_json.h" #include "tensorflow/core/profiler/trace_events.pb.h" #include "tensorflow/core/util/events_writer.h" namespace tensorflow { -namespace tpu { + +namespace profiler { +namespace client { namespace { using ::tensorflow::io::JoinPath; @@ -72,7 +74,7 @@ Status DumpTraceToLogDirectory(StringPiece run_dir, const string& host_prefix, LOG(INFO) << "Dumped raw-proto trace data to " << proto_path; string json_path = JoinPath(run_dir, StrCat(host_prefix, kJsonTraceFileName)); - profiler::Trace trace; + Trace trace; trace.ParseFromString(encoded_trace); if (os) { *os << "Trace contains " << trace.trace_events_size() << " events." @@ -88,7 +90,7 @@ Status DumpTraceToLogDirectory(StringPiece run_dir, const string& host_prefix, Status DumpOpProfileToLogDirectory(StringPiece run_dir, const string& host_prefix, - const profiler::op_profile::Profile& profile, + const op_profile::Profile& profile, std::ostream* os) { string path = JoinPath(run_dir, StrCat(host_prefix, kJsonOpProfileFileName)); string json; @@ -109,7 +111,7 @@ Status DumpOpProfileToLogDirectory(StringPiece run_dir, Status DumpToolDataToLogDirectory(StringPiece run_dir, const string& host_prefix, - const tensorflow::ProfileToolData& tool, + const ProfileToolData& tool, std::ostream* os) { // Don't save the intermediate results for combining the per host tool data. if (EndsWith(tool.name(), kFlatProfilerFileName) || @@ -155,5 +157,6 @@ Status WriteTensorboardTPUProfile(const string& logdir, const string& run, return Status::OK(); } -} // namespace tpu +} // namespace client +} // namespace profiler } // namespace tensorflow diff --git a/tensorflow/contrib/tpu/profiler/dump_tpu_profile.h b/tensorflow/core/profiler/rpc/client/dump_tpu_profile.h similarity index 83% rename from tensorflow/contrib/tpu/profiler/dump_tpu_profile.h rename to tensorflow/core/profiler/rpc/client/dump_tpu_profile.h index 7ddd7b1c9b..961f4e9498 100644 --- a/tensorflow/contrib/tpu/profiler/dump_tpu_profile.h +++ b/tensorflow/core/profiler/rpc/client/dump_tpu_profile.h @@ -13,14 +13,16 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_CONTRIB_TPU_PROFILER_DUMP_TPU_PROFILE_H_ -#define TENSORFLOW_CONTRIB_TPU_PROFILER_DUMP_TPU_PROFILE_H_ +#ifndef TENSORFLOW_CORE_PROFILER_RPC_CLIENT_DUMP_TPU_PROFILE_H_ +#define TENSORFLOW_CORE_PROFILER_RPC_CLIENT_DUMP_TPU_PROFILE_H_ #include "tensorflow/core/lib/core/status.h" #include "tensorflow/core/platform/grpc_services.h" namespace tensorflow { -namespace tpu { + +namespace profiler { +namespace client { // Dumps all profiling tool data in a TPU profile to a TensorBoard log directory // with the given run name. This writes user-facing log messages to `os`. @@ -36,7 +38,8 @@ Status WriteTensorboardTPUProfile(const string& logdir, const string& run, const ProfileResponse& response, std::ostream* os); -} // namespace tpu +} // namespace client +} // namespace profiler } // namespace tensorflow -#endif // TENSORFLOW_CONTRIB_TPU_PROFILER_DUMP_TPU_PROFILE_H_ +#endif // TENSORFLOW_CORE_PROFILER_RPC_CLIENT_DUMP_TPU_PROFILE_H_ diff --git a/tensorflow/contrib/tpu/profiler/trace_events_to_json.cc b/tensorflow/core/profiler/rpc/client/trace_events_to_json.cc similarity index 78% rename from tensorflow/contrib/tpu/profiler/trace_events_to_json.cc rename to tensorflow/core/profiler/rpc/client/trace_events_to_json.cc index 96d1094c26..6adaec5546 100644 --- a/tensorflow/contrib/tpu/profiler/trace_events_to_json.cc +++ b/tensorflow/core/profiler/rpc/client/trace_events_to_json.cc @@ -13,14 +13,16 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/contrib/tpu/profiler/trace_events_to_json.h" +#include "tensorflow/core/profiler/rpc/client/trace_events_to_json.h" #include "include/json/json.h" #include "tensorflow/core/lib/strings/strcat.h" #include "tensorflow/core/lib/strings/stringprintf.h" #include "tensorflow/core/profiler/trace_events.pb.h" namespace tensorflow { -namespace tpu { + +namespace profiler { +namespace client { namespace { using ::tensorflow::strings::Appendf; @@ -33,13 +35,12 @@ inline void AppendEscapedName(string *json, const string &name) { } // Adds resource events for a single device. -void AddResourceMetadata( - uint32 device_id, - const std::map &resources, - string *json) { +void AddResourceMetadata(uint32 device_id, + const std::map &resources, + string *json) { for (const auto &pair : resources) { uint32 resource_id = pair.first; - const profiler::Resource &resource = *pair.second; + const Resource &resource = *pair.second; if (!resource.name().empty()) { Appendf(json, R"({"ph":"M","pid":%u,"tid":%u,)" @@ -55,11 +56,11 @@ void AddResourceMetadata( } } -void AddDeviceMetadata( - const std::map &devices, string *json) { +void AddDeviceMetadata(const std::map &devices, + string *json) { for (const auto &pair : devices) { uint32 device_id = pair.first; - const profiler::Device &device = *pair.second; + const Device &device = *pair.second; if (!device.name().empty()) { Appendf(json, R"({"ph":"M","pid":%u,"name":"process_name",)" @@ -73,7 +74,7 @@ void AddDeviceMetadata( R"("args":{"sort_index":%u}},)", device_id, device_id); // Convert to a std::map so that devices are sorted by the device id. - std::map sorted_resources; + std::map sorted_resources; for (const auto &pair : device.resources()) { sorted_resources[pair.first] = &pair.second; } @@ -81,7 +82,7 @@ void AddDeviceMetadata( } } -inline void AddTraceEvent(const profiler::TraceEvent &event, string *json) { +inline void AddTraceEvent(const TraceEvent &event, string *json) { Appendf(json, R"({"pid":%u,"tid":%u,"ts":%.5f,)", event.device_id(), event.resource_id(), event.timestamp_ps() / kPicosPerMicro); AppendEscapedName(json, event.name()); @@ -96,19 +97,18 @@ inline void AddTraceEvent(const profiler::TraceEvent &event, string *json) { } // namespace -string TraceEventsToJson(const profiler::Trace &trace) { +string TraceEventsToJson(const Trace &trace) { string json; - Appendf(&json, - R"({"displayTimeUnit":"ns","metadata":{"highres-ticks":true},)"); - Appendf(&json, - R"("traceEvents":[)"); + Appendf( + &json, R"({"displayTimeUnit":"ns","metadata":{"highres-ticks":true},)"); + Appendf(&json, R"("traceEvents":[)"); // Convert to a std::map so that devices are sorted by the device id. - std::map sorted_devices; + std::map sorted_devices; for (const auto &pair : trace.devices()) { sorted_devices[pair.first] = &pair.second; } AddDeviceMetadata(sorted_devices, &json); - for (const profiler::TraceEvent &event : trace.trace_events()) { + for (const TraceEvent &event : trace.trace_events()) { AddTraceEvent(event, &json); } // Add one fake event to avoid dealing with no-trailing-comma rule. @@ -116,5 +116,6 @@ string TraceEventsToJson(const profiler::Trace &trace) { return json; } -} // namespace tpu +} // namespace client +} // namespace profiler } // namespace tensorflow diff --git a/tensorflow/contrib/tpu/profiler/trace_events_to_json.h b/tensorflow/core/profiler/rpc/client/trace_events_to_json.h similarity index 73% rename from tensorflow/contrib/tpu/profiler/trace_events_to_json.h rename to tensorflow/core/profiler/rpc/client/trace_events_to_json.h index 554d6ff3d8..d54cc3c619 100644 --- a/tensorflow/contrib/tpu/profiler/trace_events_to_json.h +++ b/tensorflow/core/profiler/rpc/client/trace_events_to_json.h @@ -13,20 +13,23 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_CONTRIB_TPU_PROFILER_TRACE_EVENTS_TO_JSON_H_ -#define TENSORFLOW_CONTRIB_TPU_PROFILER_TRACE_EVENTS_TO_JSON_H_ +#ifndef TENSORFLOW_CORE_PROFILER_RPC_CLIENT_TRACE_EVENTS_TO_JSON_H_ +#define TENSORFLOW_CORE_PROFILER_RPC_CLIENT_TRACE_EVENTS_TO_JSON_H_ #include "tensorflow/core/platform/types.h" #include "tensorflow/core/profiler/trace_events.pb.h" namespace tensorflow { -namespace tpu { + +namespace profiler { +namespace client { // Converts trace events in the trace proto to a JSON string that can be // consumed by catapult trace viewer. -string TraceEventsToJson(const profiler::Trace &trace); +string TraceEventsToJson(const Trace &trace); -} // namespace tpu +} // namespace client +} // namespace profiler } // namespace tensorflow -#endif // TENSORFLOW_CONTRIB_TPU_PROFILER_TRACE_EVENTS_TO_JSON_H_ +#endif // TENSORFLOW_CORE_PROFILER_RPC_CLIENT_TRACE_EVENTS_TO_JSON_H_ diff --git a/tensorflow/contrib/tpu/profiler/trace_events_to_json_test.cc b/tensorflow/core/profiler/rpc/client/trace_events_to_json_test.cc similarity index 94% rename from tensorflow/contrib/tpu/profiler/trace_events_to_json_test.cc rename to tensorflow/core/profiler/rpc/client/trace_events_to_json_test.cc index 8b39fc56c2..0f883b04dc 100644 --- a/tensorflow/contrib/tpu/profiler/trace_events_to_json_test.cc +++ b/tensorflow/core/profiler/rpc/client/trace_events_to_json_test.cc @@ -13,18 +13,20 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/contrib/tpu/profiler/trace_events_to_json.h" +#include "tensorflow/core/profiler/rpc/client/trace_events_to_json.h" #include "include/json/json.h" #include "tensorflow/core/platform/protobuf.h" #include "tensorflow/core/platform/test.h" #include "tensorflow/core/profiler/trace_events.pb.h" namespace tensorflow { -namespace tpu { + +namespace profiler { +namespace client { namespace { string ConvertTextFormattedTraceToJson(const string& trace_str) { - profiler::Trace trace; + Trace trace; ::tensorflow::protobuf::TextFormat::ParseFromString(trace_str, &trace); return TraceEventsToJson(trace); } @@ -110,5 +112,6 @@ TEST(TraceEventsToJson, JsonConversion) { } } // namespace -} // namespace tpu +} // namespace client +} // namespace profiler } // namespace tensorflow -- GitLab From 8518346d6be505cf10771cef24444ecddf1c713b Mon Sep 17 00:00:00 2001 From: Brian Lee Date: Thu, 21 Feb 2019 14:46:38 -0800 Subject: [PATCH 292/766] Raise error upon seeing global statements PiperOrigin-RevId: 235073426 --- tensorflow/python/autograph/core/BUILD | 2 + .../core/unsupported_features_checker.py | 54 +++++++++++++++++++ tensorflow/python/autograph/impl/api_test.py | 11 ++-- .../python/autograph/impl/conversion.py | 2 + .../pyct/static_analysis/activity.py | 4 +- .../pyct/static_analysis/liveness.py | 4 +- .../static_analysis/reaching_definitions.py | 4 +- 7 files changed, 69 insertions(+), 12 deletions(-) create mode 100644 tensorflow/python/autograph/core/unsupported_features_checker.py diff --git a/tensorflow/python/autograph/core/BUILD b/tensorflow/python/autograph/core/BUILD index 3ab2e7b1bc..fae327e50d 100644 --- a/tensorflow/python/autograph/core/BUILD +++ b/tensorflow/python/autograph/core/BUILD @@ -22,6 +22,7 @@ py_library( "errors.py", "function_wrapping.py", "naming.py", + "unsupported_features_checker.py", ], srcs_version = "PY2AND3", visibility = ["//tensorflow:__subpackages__"], @@ -30,6 +31,7 @@ py_library( "//tensorflow/python/autograph/pyct", "//tensorflow/python/autograph/pyct/static_analysis", "//tensorflow/python/autograph/utils", + "@gast_archive//:gast", ], ) diff --git a/tensorflow/python/autograph/core/unsupported_features_checker.py b/tensorflow/python/autograph/core/unsupported_features_checker.py new file mode 100644 index 0000000000..6ccbb76fea --- /dev/null +++ b/tensorflow/python/autograph/core/unsupported_features_checker.py @@ -0,0 +1,54 @@ +# 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. +# ============================================================================== +"""Checkers for detecting unsupported Python features.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import gast + +from tensorflow.python.autograph.pyct import errors + + +class UnsupportedFeaturesChecker(gast.NodeTransformer): + """Quick check for Python features we know we don't support. + + Any features detected will cause AutoGraph to not compile a function. + """ + + # TODO(b/124103128): Implement support for `global` statements + def visit_Global(self, node): + raise errors.AutoGraphError( + 'The global keyword is not yet supported.') + + def visit_Nonlocal(self, node): + raise errors.AutoGraphError( + 'The nonlocal keyword is not yet supported.') + + # These checks could potentially be replaced with inspect.isgeneratorfunction + # to avoid a getsource/parse/ast-walk round trip. + def visit_Yield(self, node): + raise errors.AutoGraphError( + 'Generators are not supported by AutoGraph') + + def visit_YieldFrom(self, node): + raise errors.AutoGraphError( + 'Generators are not supported by AutoGraph') + + +def verify(node): + UnsupportedFeaturesChecker().visit(node) + diff --git a/tensorflow/python/autograph/impl/api_test.py b/tensorflow/python/autograph/impl/api_test.py index ebc6cc4f71..aa9ad0c882 100644 --- a/tensorflow/python/autograph/impl/api_test.py +++ b/tensorflow/python/autograph/impl/api_test.py @@ -27,6 +27,7 @@ import numpy as np from tensorflow.python.autograph import utils from tensorflow.python.autograph.core import converter from tensorflow.python.autograph.impl import api +from tensorflow.python.autograph.pyct import errors from tensorflow.python.autograph.pyct import inspect_utils from tensorflow.python.autograph.pyct import parser from tensorflow.python.autograph.utils import py_func @@ -496,12 +497,10 @@ class ApiTest(test.TestCase): testing_global_numeric = x + testing_global_numeric return testing_global_numeric - compiled_fn = api.to_graph(test_fn) - - x = compiled_fn(constant_op.constant(3)) - self.assertEqual(5, self.evaluate(x)) - # TODO(b/122368197): This should be the constant 5! - self.assertEqual(2, testing_global_numeric) + # TODO(b/122368197) + with self.assertRaisesRegex( + errors.AutoGraphError, 'global keyword is not yet supported'): + api.to_graph(test_fn) def test_to_graph_with_kwargs_clashing_converted_call(self): diff --git a/tensorflow/python/autograph/impl/conversion.py b/tensorflow/python/autograph/impl/conversion.py index dda98ca5e9..4d93f02695 100644 --- a/tensorflow/python/autograph/impl/conversion.py +++ b/tensorflow/python/autograph/impl/conversion.py @@ -46,6 +46,7 @@ from tensorflow.python.autograph.core import config from tensorflow.python.autograph.core import converter from tensorflow.python.autograph.core import errors as ag_errors from tensorflow.python.autograph.core import function_wrapping +from tensorflow.python.autograph.core import unsupported_features_checker from tensorflow.python.autograph.lang import special_functions from tensorflow.python.autograph.pyct import ast_util from tensorflow.python.autograph.pyct import compiler @@ -430,6 +431,7 @@ def node_to_graph(node, context): dependencies that this node has. """ # TODO(mdan): Insert list_comprehensions somewhere. + unsupported_features_checker.verify(node) node = converter.standard_analysis(node, context, is_initial=True) # Past this point, line numbers are no longer accurate so we ignore the diff --git a/tensorflow/python/autograph/pyct/static_analysis/activity.py b/tensorflow/python/autograph/pyct/static_analysis/activity.py index 65e4682215..dd3d1d5d13 100644 --- a/tensorflow/python/autograph/pyct/static_analysis/activity.py +++ b/tensorflow/python/autograph/pyct/static_analysis/activity.py @@ -265,10 +265,10 @@ class ActivityAnalyzer(transformer.Base): self._exit_scope() return node - def visit_nonlocal(self, node): + def visit_Nonlocal(self, node): raise NotImplementedError() - def visit_global(self, node): + def visit_Global(self, node): raise NotImplementedError() def visit_Expr(self, node): diff --git a/tensorflow/python/autograph/pyct/static_analysis/liveness.py b/tensorflow/python/autograph/pyct/static_analysis/liveness.py index 691b786db0..ad567a0a4f 100644 --- a/tensorflow/python/autograph/pyct/static_analysis/liveness.py +++ b/tensorflow/python/autograph/pyct/static_analysis/liveness.py @@ -144,10 +144,10 @@ class WholeTreeAnalyzer(transformer.Base): self.current_analyzer = parent_analyzer return node - def visit_nonlocal(self, node): + def visit_Nonlocal(self, node): raise NotImplementedError() - def visit_global(self, node): + def visit_Global(self, node): raise NotImplementedError() diff --git a/tensorflow/python/autograph/pyct/static_analysis/reaching_definitions.py b/tensorflow/python/autograph/pyct/static_analysis/reaching_definitions.py index 6f0f09ee88..ce6f3c5284 100644 --- a/tensorflow/python/autograph/pyct/static_analysis/reaching_definitions.py +++ b/tensorflow/python/autograph/pyct/static_analysis/reaching_definitions.py @@ -217,10 +217,10 @@ class TreeAnnotator(transformer.Base): return node - def visit_nonlocal(self, node): + def visit_Nonlocal(self, node): raise NotImplementedError() - def visit_global(self, node): + def visit_Global(self, node): raise NotImplementedError() def visit_ExceptHandler(self, node): -- GitLab From 8f87922a5cb2743b9c70e444f4288964be161e20 Mon Sep 17 00:00:00 2001 From: Lucas Hendren Date: Thu, 21 Feb 2019 15:56:39 -0800 Subject: [PATCH 293/766] Added second unit test for trace --- tensorflow/python/ops/special_math_ops_test.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tensorflow/python/ops/special_math_ops_test.py b/tensorflow/python/ops/special_math_ops_test.py index 5e46b2ea50..d29f0fcbeb 100644 --- a/tensorflow/python/ops/special_math_ops_test.py +++ b/tensorflow/python/ops/special_math_ops_test.py @@ -250,7 +250,8 @@ class EinsumTest(test.TestCase): 'abc, bac', 'iJ, Ki -> JK', 'iJk, Jklm -> Jk', - 'ii' + # 'ii', + 'ijji' ] long_cases = [ @@ -349,10 +350,13 @@ class EinsumTest(test.TestCase): with self.session(use_gpu=True): output_value = self.evaluate(output_tensor) - correct_value = np.einsum(axes, *input_vals) - + correct_value=0 + if axes=='ijji': + output = math_ops.trace(*input_tensors) + correct_value = self.evaluate(output) + else: + correct_value = np.einsum(axes, *input_vals) err = np.abs(correct_value - output_value).max() - # print(axes, err) self.assertLess(err, 1e-8) def test_input_is_placeholder(self): -- GitLab From 2b331395d5bfbf524c29aa0b0d08b1018078d8d2 Mon Sep 17 00:00:00 2001 From: Lucas Hendren Date: Thu, 21 Feb 2019 15:57:05 -0800 Subject: [PATCH 294/766] Added in base trace unit test --- tensorflow/python/ops/special_math_ops_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/ops/special_math_ops_test.py b/tensorflow/python/ops/special_math_ops_test.py index d29f0fcbeb..dfb960f503 100644 --- a/tensorflow/python/ops/special_math_ops_test.py +++ b/tensorflow/python/ops/special_math_ops_test.py @@ -250,7 +250,7 @@ class EinsumTest(test.TestCase): 'abc, bac', 'iJ, Ki -> JK', 'iJk, Jklm -> Jk', - # 'ii', + 'ii', 'ijji' ] -- GitLab From b32297eaa1976ed74e52c3eea698dcf1c8ff4392 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 21 Feb 2019 14:51:06 -0800 Subject: [PATCH 295/766] DepthwiseConv dot-product optimization: Fix msan bug #2. PiperOrigin-RevId: 235074364 --- .../optimized/depthwiseconv_uint8_3x3_filter.h | 4 ++++ .../optimized/depthwiseconv_uint8_transitional.h | 12 +++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/tensorflow/lite/kernels/internal/optimized/depthwiseconv_uint8_3x3_filter.h b/tensorflow/lite/kernels/internal/optimized/depthwiseconv_uint8_3x3_filter.h index f43c2b33e1..8a87d424e8 100644 --- a/tensorflow/lite/kernels/internal/optimized/depthwiseconv_uint8_3x3_filter.h +++ b/tensorflow/lite/kernels/internal/optimized/depthwiseconv_uint8_3x3_filter.h @@ -27,6 +27,10 @@ namespace depthwise_conv { constexpr int kDepthwiseConvScratchWorkspaceSize = 10 * 10 * 64; constexpr int kDepthwiseConvAdjustedBiasLimit = 256; +// In cases such as depth multiplication, we want to be able to load data from +// the workspace that is beyond the valid range. Macro-block sizes are adjusted +// to allow for this. +constexpr int kWorkspaceExtension = 16; // See CategorizeDotProductKernel for definitive taxonomy. enum class DotProduct3x3KernelType { diff --git a/tensorflow/lite/kernels/internal/optimized/depthwiseconv_uint8_transitional.h b/tensorflow/lite/kernels/internal/optimized/depthwiseconv_uint8_transitional.h index a00544f2ca..1940353f74 100644 --- a/tensorflow/lite/kernels/internal/optimized/depthwiseconv_uint8_transitional.h +++ b/tensorflow/lite/kernels/internal/optimized/depthwiseconv_uint8_transitional.h @@ -418,7 +418,7 @@ struct PackMacroBlock Date: Thu, 21 Feb 2019 15:06:37 -0800 Subject: [PATCH 296/766] Fix typos in no_oss tags. PiperOrigin-RevId: 235077618 --- tensorflow/compiler/xla/tests/BUILD | 2 +- tensorflow/core/distributed_runtime/BUILD | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/compiler/xla/tests/BUILD b/tensorflow/compiler/xla/tests/BUILD index 72c5ed58e2..5628547566 100644 --- a/tensorflow/compiler/xla/tests/BUILD +++ b/tensorflow/compiler/xla/tests/BUILD @@ -681,7 +681,7 @@ xla_test( tags = [ "optonly", # This is a big test that we skip for capacity reasons in OSS testing. - "nooss", + "no_oss", ], deps = [ ":client_library_test_base", diff --git a/tensorflow/core/distributed_runtime/BUILD b/tensorflow/core/distributed_runtime/BUILD index 9196d91800..af744ce790 100644 --- a/tensorflow/core/distributed_runtime/BUILD +++ b/tensorflow/core/distributed_runtime/BUILD @@ -644,7 +644,7 @@ tf_cuda_cc_test( "manual", # TODO(b/27683709): Re-enable when not flaky. "notap", # TODO(b/27683709): Re-enable when not flaky. "noguitar", # TODO(b/27683709): Re-enable when not flaky. - "nooss", # TODO(b/27683709): Re-enable when not flaky. + "no_oss", # TODO(b/27683709): Re-enable when not flaky. ], deps = [ ":master", -- GitLab From a98d95c77702d577f34fbd9543766e393a2ac98e Mon Sep 17 00:00:00 2001 From: Thomas O'Malley Date: Thu, 21 Feb 2019 15:06:58 -0800 Subject: [PATCH 297/766] Allow raw variable assignment in Layer subclasses. PiperOrigin-RevId: 235077713 --- tensorflow/python/keras/engine/base_layer.py | 29 +++++++++++-------- .../python/keras/engine/base_layer_test.py | 21 ++++++++++++++ 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/tensorflow/python/keras/engine/base_layer.py b/tensorflow/python/keras/engine/base_layer.py index f4ba35d14c..9730ecbef5 100644 --- a/tensorflow/python/keras/engine/base_layer.py +++ b/tensorflow/python/keras/engine/base_layer.py @@ -1685,18 +1685,23 @@ class Layer(trackable.Trackable): value._use_resource_variables = True # Append value to list of trainable / non-trainable weights if relevant - if isinstance(value, tf_variables.Variable): - # Users may add extra weights/variables - # simply by assigning them to attributes (invalid for graph networks) - if not hasattr(self, '_trainable_weights'): - super(Layer, self).__setattr__('_trainable_weights', []) - if not hasattr(self, '_non_trainable_weights'): - super(Layer, self).__setattr__('_non_trainable_weights', []) - if value not in self._trainable_weights + self._non_trainable_weights: - if value.trainable: - self._trainable_weights.append(value) - else: - self._non_trainable_weights.append(value) + # TODO(b/125122625): This won't pick up on any variables added to a + # list/dict after creation. + for val in nest.flatten(value): + if isinstance(val, tf_variables.Variable): + # Users may add extra weights/variables + # simply by assigning them to attributes (invalid for graph networks) + if not hasattr(self, '_trainable_weights'): + super(Layer, self).__setattr__('_trainable_weights', []) + if not hasattr(self, '_non_trainable_weights'): + super(Layer, self).__setattr__('_non_trainable_weights', []) + if val not in self._trainable_weights + self._non_trainable_weights: + if val.trainable: + self._trainable_weights.append(val) + else: + self._non_trainable_weights.append(val) + backend.track_variable(val) + super(Layer, self).__setattr__(name, value) def _gather_children_attribute(self, attribute): diff --git a/tensorflow/python/keras/engine/base_layer_test.py b/tensorflow/python/keras/engine/base_layer_test.py index b5d76f6772..b4748d1301 100644 --- a/tensorflow/python/keras/engine/base_layer_test.py +++ b/tensorflow/python/keras/engine/base_layer_test.py @@ -342,6 +342,27 @@ class BaseLayerTest(keras_parameterized.TestCase): history = model.fit(np.zeros((1, 1)), np.zeros((1, 1))) self.assertEqual(history.history['loss'][0], 0.) + @keras_parameterized.run_with_all_model_types + @keras_parameterized.run_all_keras_modes + def test_raw_variable_assignment(self): + + class RawVariableLayer(keras.layers.Layer): + + def __init__(self, **kwargs): + super(RawVariableLayer, self).__init__(**kwargs) + # Test variables in nested structure. + self.var_list = [variables.Variable(1.), {'a': variables.Variable(2.)}] + + def call(self, inputs): + return inputs * self.var_list[0] * self.var_list[1]['a'] + + model = testing_utils.get_model_from_layers([RawVariableLayer()], + input_shape=(10,)) + model.compile('sgd', 'mse', run_eagerly=testing_utils.should_run_eagerly()) + x, y = np.ones((10, 10)), np.ones((10, 10)) + # Checks that variables get initialized. + model.fit(x, y, batch_size=2, epochs=2) + class SymbolicSupportTest(test.TestCase): -- GitLab From 1e2c0c4f6dc7ee96171afe77489fad732ccbeb2a Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 21 Feb 2019 15:39:13 -0800 Subject: [PATCH 298/766] Fix error "TypeError: in method 'TFE_ProfilerClientStartTracing', argument 1 of type 'char *'". PiperOrigin-RevId: 235084042 --- tensorflow/python/pywrap_tfe.i | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tensorflow/python/pywrap_tfe.i b/tensorflow/python/pywrap_tfe.i index bacac165c1..fe7a41afb4 100755 --- a/tensorflow/python/pywrap_tfe.i +++ b/tensorflow/python/pywrap_tfe.i @@ -157,6 +157,27 @@ limitations under the License. $1 = const_cast(TFE_GetPythonString($input)); } +// For const parameters in a function, SWIG pretty much ignores the const. +// See: http://www.swig.org/Doc2.0/SWIG.html#SWIG_nn13 +// Hence the 'const_cast'. +%typemap(in) const char* service_addr { + $1 = const_cast(TFE_GetPythonString($input)); +} + +// For const parameters in a function, SWIG pretty much ignores the const. +// See: http://www.swig.org/Doc2.0/SWIG.html#SWIG_nn13 +// Hence the 'const_cast'. +%typemap(in) const char* logdir { + $1 = const_cast(TFE_GetPythonString($input)); +} + +// For const parameters in a function, SWIG pretty much ignores the const. +// See: http://www.swig.org/Doc2.0/SWIG.html#SWIG_nn13 +// Hence the 'const_cast'. +%typemap(in) const char* worker_list { + $1 = const_cast(TFE_GetPythonString($input)); +} + %typemap(in) (TFE_Context*) { $1 = (TFE_Context*)PyCapsule_GetPointer($input, nullptr); -- GitLab From 59ef786b7dfed545c0e54fb19517a392fd395127 Mon Sep 17 00:00:00 2001 From: Sourabh Bajaj Date: Thu, 21 Feb 2019 15:44:43 -0800 Subject: [PATCH 299/766] Update TPUMirroredVariable assign_*_fn signatures PiperOrigin-RevId: 235085131 --- tensorflow/python/distribute/values.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tensorflow/python/distribute/values.py b/tensorflow/python/distribute/values.py index 2eeb165d7d..40a9f9b012 100644 --- a/tensorflow/python/distribute/values.py +++ b/tensorflow/python/distribute/values.py @@ -983,7 +983,8 @@ class TPUMirroredVariable(trackable.Trackable): return self._read_variable_op() def assign_sub(self, *args, **kwargs): - def assign_sub_fn(var, delta, **kw): + def assign_sub_fn(var, delta, *ar, **kw): + del ar name = kw.pop("name", None) read_value = kw.pop("read_value", True) with self._handle_graph(var.handle): @@ -997,7 +998,8 @@ class TPUMirroredVariable(trackable.Trackable): return self._assign_func(f=assign_sub_fn, *args, **kwargs) def assign_add(self, *args, **kwargs): - def assign_add_fn(var, delta, **kw): + def assign_add_fn(var, delta, *ar, **kw): + del ar name = kw.pop("name", None) read_value = kw.pop("read_value", True) with self._handle_graph(var.handle): @@ -1011,7 +1013,8 @@ class TPUMirroredVariable(trackable.Trackable): return self._assign_func(f=assign_add_fn, *args, **kwargs) def assign(self, *args, **kwargs): - def assign_fn(var, value, **kw): + def assign_fn(var, value, *ar, **kw): + del ar name = kw.pop("name", None) read_value = kw.pop("read_value", True) with self._handle_graph(var.handle): -- GitLab From 56d630022aa7270d92abc8fb90a0b84e1c34d482 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 21 Feb 2019 15:50:10 -0800 Subject: [PATCH 300/766] Separating out bazel build flags and test flags. PiperOrigin-RevId: 235086165 --- tensorflow/tools/ci_build/builds/pip_new.sh | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/tensorflow/tools/ci_build/builds/pip_new.sh b/tensorflow/tools/ci_build/builds/pip_new.sh index d95a796ed4..cff1fe6657 100755 --- a/tensorflow/tools/ci_build/builds/pip_new.sh +++ b/tensorflow/tools/ci_build/builds/pip_new.sh @@ -28,9 +28,10 @@ # TF_PYTHON_VERSION: (python2 | python2.7 | python3.5 | python3.7) # # Optional environment variables. If provided, overwrites any default values. -# TF_BUILD_FLAGS: Bazel build flags excluding `--test_tag_filters` and -# test targets. -# e.g. TF_BUILD_FLAGS="--verbose_failures=true \ +# TF_BUILD_FLAGS: Bazel build flags. +# e.g. TF_BUILD_FLAGS="--config=opt" +# TF_TEST_FLAGS: Bazel test flags. +# e.g. TF_TEST_FLAGS="--verbose_failures=true \ # --build_tests_only --test_output=errors" # TF_TEST_FILTER_TAGS: Filtering tags for bazel tests. More specifically, # input tags for `--test_filter_tags` flag. @@ -38,7 +39,7 @@ # TF_TEST_TARGETS: Bazel test targets. # e.g. TF_TEST_TARGETS="//tensorflow/contrib/... \ # //tensorflow/... \ -# //tensorflow/python/... " +# //tensorflow/python/..." # TF_PIP_TESTS: PIP tests to run. If NOT specified, skips all tests. # e.g. TF_PIP_TESTS="test_pip_virtualenv_clean \ # test_pip_virtualenv_clean \ @@ -134,8 +135,10 @@ update_bazel_flags() { fi # Clean up whitespaces BAZEL_BUILD_FLAGS=$(str_strip "${BAZEL_BUILD_FLAGS}") + BAZEL_TEST_FLAGS=$(str_strp "${BAZEL_TEST_FLAGS}") # Cleaned bazel flags echo "Bazel build flags (cleaned):\n" "${BAZEL_BUILD_FLAGS}" + echo "Bazel test flags (cleaned):\n" "${BAZEL_TEST_FLAGS}" } update_test_filter_tags() { @@ -210,7 +213,8 @@ echo "PYTHON_BIN_PATH: ${PYTHON_BIN_PATH} (version: ${PYTHON_VER_CFG})" # Default values for optional global variables in case they are not user # defined. -DEFAULT_BAZEL_BUILD_FLAGS='--test_output=errors --verbose_failures=true' +DEFAULT_BAZEL_BUILD_FLAGS='--config=opt' +DEFAULT_BAZEL_TEST_FLAGS='--test_output=errors --verbose_failures=true' DEFAULT_BAZEL_TEST_FILTERS='-no_oss,-oss_serial' DEFAULT_BAZEL_TEST_TARGETS='//tensorflow/python/... -//tensorflow/core/... -//tensorflow/compiler/... ' DEFAULT_PIP_TESTS="" # Do not run any tests by default @@ -220,6 +224,7 @@ DEFAULT_PIP_TEST_ROOT="pip_test" # Take in optional global variables BAZEL_BUILD_FLAGS=${TF_BUILD_FLAGS:-$DEFAULT_BAZEL_BUILD_FLAGS} +BAZEL_TEST_FLAGS=${TF_TEST_FLAGS:-$DEFAULT_BAZEL_TEST_FLAGS} BAZEL_TEST_TARGETS=${TF_TEST_TARGETS:-$DEFAULT_BAZEL_TEST_TARGETS} BAZEL_TEST_FILTER_TAGS=${TF_TEST_FILTER_TAGS:-$DEFAULT_BAZEL_TEST_FILTERS} PIP_TESTS=${TF_PIP_TESTS:-$DEFAULT_PIP_TESTS} @@ -538,7 +543,7 @@ run_test_with_bazel() { # TODO(hyey): Update test target after validation. # Run the test. - bazel test --build_tests_only ${BAZEL_BUILD_FLAGS} ${BAZEL_PARALLEL_TEST_FLAGS} --test_tag_filters=${BAZEL_TEST_FILTER_TAGS} -k -- //$PIP_TEST_PREFIX/tensorflow/python/... + bazel test --build_tests_only ${BAZEL_TEST_FLAGS} ${BAZEL_PARALLEL_TEST_FLAGS} --test_tag_filters=${BAZEL_TEST_FILTER_TAGS} -k -- //$PIP_TEST_PREFIX/tensorflow/python/... unlink ${TEST_ROOT}/tensorflow } -- GitLab From e609a2ce8c5ceff0f1a84d6ecd81346b2f64a6d6 Mon Sep 17 00:00:00 2001 From: Lucas Hendren Date: Thu, 21 Feb 2019 17:25:05 -0800 Subject: [PATCH 301/766] Fixed Linting issues --- tensorflow/python/ops/special_math_ops_test.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tensorflow/python/ops/special_math_ops_test.py b/tensorflow/python/ops/special_math_ops_test.py index dfb960f503..12271573e5 100644 --- a/tensorflow/python/ops/special_math_ops_test.py +++ b/tensorflow/python/ops/special_math_ops_test.py @@ -350,12 +350,12 @@ class EinsumTest(test.TestCase): with self.session(use_gpu=True): output_value = self.evaluate(output_tensor) - correct_value=0 - if axes=='ijji': - output = math_ops.trace(*input_tensors) - correct_value = self.evaluate(output) + correct_value = 0 + if axes == 'ijji': + output = math_ops.trace(*input_tensors) + correct_value = self.evaluate(output) else: - correct_value = np.einsum(axes, *input_vals) + correct_value = np.einsum(axes, *input_vals) err = np.abs(correct_value - output_value).max() self.assertLess(err, 1e-8) -- GitLab From ab5c2f5258e5e0fc5cfc3c39669fa574df1a8bfa Mon Sep 17 00:00:00 2001 From: Stephan Lee Date: Thu, 21 Feb 2019 15:56:48 -0800 Subject: [PATCH 302/766] Internal Summary API for writing Keras model as a summary. PiperOrigin-RevId: 235087375 --- tensorflow/python/kernel_tests/BUILD | 2 ++ .../python/kernel_tests/summary_ops_test.py | 23 +++++++++++++ tensorflow/python/ops/summary_ops_v2.py | 33 +++++++++++++++++++ 3 files changed, 58 insertions(+) diff --git a/tensorflow/python/kernel_tests/BUILD b/tensorflow/python/kernel_tests/BUILD index 51599dcf1b..6ed8f69afa 100644 --- a/tensorflow/python/kernel_tests/BUILD +++ b/tensorflow/python/kernel_tests/BUILD @@ -1104,6 +1104,8 @@ tf_py_test( "//tensorflow/python:variables", "//tensorflow/python/eager:function", "//tensorflow/python/eager:context", + "//tensorflow/python/keras:engine", + "//tensorflow/python/keras:layers", ], ) diff --git a/tensorflow/python/kernel_tests/summary_ops_test.py b/tensorflow/python/kernel_tests/summary_ops_test.py index 350e51a212..f8cc3299fe 100644 --- a/tensorflow/python/kernel_tests/summary_ops_test.py +++ b/tensorflow/python/kernel_tests/summary_ops_test.py @@ -36,6 +36,9 @@ from tensorflow.python.framework import ops from tensorflow.python.framework import tensor_spec from tensorflow.python.framework import tensor_util from tensorflow.python.framework import test_util +from tensorflow.python.keras.engine.sequential import Sequential +from tensorflow.python.keras.layers.core import Activation +from tensorflow.python.keras.layers.core import Dense from tensorflow.python.lib.io import tf_record from tensorflow.python.ops import math_ops from tensorflow.python.ops import summary_ops_v2 as summary_ops @@ -491,6 +494,17 @@ class SummaryOpsTest(test_util.TensorFlowTestCase): ], step_stats=step_stats) + def keras_model(self, *args, **kwargs): + logdir = self.get_temp_dir() + writer = summary_ops.create_file_writer(logdir) + with writer.as_default(): + summary_ops.keras_model(*args, **kwargs) + writer.close() + events = events_from_logdir(logdir) + # The first event contains no summary values. The written content goes to + # the second event. + return events[1].summary + @test_util.run_v2_only def testRunMetadata_usesNameAsTag(self): meta = config_pb2.RunMetadata() @@ -584,6 +598,15 @@ class SummaryOpsTest(test_util.TensorFlowTestCase): first_val.tensor.string_val[0]) self.assertProtoEquals(expected_run_metadata, actual_run_metadata) + @test_util.run_v2_only + def testKerasModel(self): + model = Sequential( + [Dense(10, input_shape=(100,)), + Activation('relu', name='my_relu')]) + summary = self.keras_model(name='my_name', data=model, step=1) + first_val = summary.value[0] + self.assertEqual(model.to_json(), first_val.tensor.string_val[0]) + def events_from_file(filepath): """Returns all events in a single event file. diff --git a/tensorflow/python/ops/summary_ops_v2.py b/tensorflow/python/ops/summary_ops_v2.py index 6ddecb2948..25ee77aaf2 100644 --- a/tensorflow/python/ops/summary_ops_v2.py +++ b/tensorflow/python/ops/summary_ops_v2.py @@ -802,3 +802,36 @@ def run_metadata_graphs(name, data, step): data.SerializeToString(), dtype=dtypes.string), step=step, metadata=summary_metadata) + + +def keras_model(name, data, step): + """Writes a Keras model as JSON to as a Summary. + + Writing the Keras model configuration allows the TensorBoard graph plugin to + render a conceptual graph, as opposed to graph of ops. + + Args: + name: A name for this summary. The summary tag used for TensorBoard will be + this name prefixed by any active name scopes. + data: A Keras Model to write. + step: Required `int64`-castable monotonic step value. + + Returns: + True on success, or false if no summary was written because no default + summary writer was available. + """ + summary_metadata = summary_pb2.SummaryMetadata() + # Hard coding a plugin name. Please refer to go/tb-plugin-name-hardcode for + # the rationale. + summary_metadata.plugin_data.plugin_name = "graph_keras_model" + # version number = 1 + summary_metadata.plugin_data.content = "1" + + json_string = data.to_json() + + with summary_scope(name, "graph_keras_model", [data, step]) as (tag, _): + return write( + tag=tag, + tensor=constant_op.constant(json_string, dtype=dtypes.string), + step=step, + metadata=summary_metadata) -- GitLab From 4d7e530361c6ea8ab5566477d97a608d9e573cc8 Mon Sep 17 00:00:00 2001 From: Gunhan Gulsoy Date: Thu, 21 Feb 2019 16:04:32 -0800 Subject: [PATCH 303/766] Fix the circular dependency introduced to the kernel shared objects. PiperOrigin-RevId: 235089008 --- tensorflow/tensorflow.bzl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/tensorflow.bzl b/tensorflow/tensorflow.bzl index 6163236791..a42d3c9018 100644 --- a/tensorflow/tensorflow.bzl +++ b/tensorflow/tensorflow.bzl @@ -418,7 +418,7 @@ def tf_cc_shared_object( srcs = srcs + framework_so, deps = deps, linkshared = 1, - data = data + tf_binary_dynamic_kernel_dsos(), + data = data, linkopts = linkopts + _rpath_linkopts(name) + select({ clean_dep("//tensorflow:darwin"): [ "-Wl,-install_name,@rpath/" + name.split("/")[-1], -- GitLab From d2022b83cbe0876f415f5f3f8b560fd5ee5733e2 Mon Sep 17 00:00:00 2001 From: Shashi Shekhar Date: Thu, 21 Feb 2019 16:44:24 -0800 Subject: [PATCH 304/766] Add import array call to Python CPP wrapper. Fixes test. PiperOrigin-RevId: 235096096 --- tensorflow/lite/python/BUILD | 1 - tensorflow/lite/python/optimize/calibration_wrapper.cc | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/python/BUILD b/tensorflow/lite/python/BUILD index a252fcb0c7..a31f6cec70 100644 --- a/tensorflow/lite/python/BUILD +++ b/tensorflow/lite/python/BUILD @@ -88,7 +88,6 @@ py_test( shard_count = 4, srcs_version = "PY2AND3", tags = [ - "no_oss", # TODO(b/125045671) reenable once segfault is fixed. "no_windows", ], deps = [ diff --git a/tensorflow/lite/python/optimize/calibration_wrapper.cc b/tensorflow/lite/python/optimize/calibration_wrapper.cc index 5a481d4cab..12bcd6a628 100644 --- a/tensorflow/lite/python/optimize/calibration_wrapper.cc +++ b/tensorflow/lite/python/optimize/calibration_wrapper.cc @@ -180,6 +180,7 @@ PyObject* CalibrationWrapper::QuantizeModel() { char* buf = nullptr; Py_ssize_t length; std::unique_ptr error_reporter(new PythonErrorReporter); + ::tflite::python::ImportNumpy(); if (python_utils::ConvertFromPyString(data, &buf, &length) == -1) { return nullptr; -- GitLab From 9b5f3d67f2d70963abb08e833404163856846ab0 Mon Sep 17 00:00:00 2001 From: Peter Hawkins Date: Thu, 21 Feb 2019 17:07:35 -0800 Subject: [PATCH 305/766] [XLA] Attach an arity to each HloOpcode. Remove per-op code in hlo_verifier and hlo_instruction.cc that checks instruction arities for non-variadic ops, use generic code instead. Refactoring only, NFC intended. This is a small step towards reducing the amount of boilerplate needed to define a new op. PiperOrigin-RevId: 235100117 --- .../compiler/xla/service/hlo_instruction.cc | 92 +------- tensorflow/compiler/xla/service/hlo_opcode.cc | 21 +- tensorflow/compiler/xla/service/hlo_opcode.h | 211 +++++++++--------- .../compiler/xla/service/hlo_opcode_test.cc | 10 +- .../compiler/xla/service/hlo_verifier.cc | 62 ++--- 5 files changed, 157 insertions(+), 239 deletions(-) diff --git a/tensorflow/compiler/xla/service/hlo_instruction.cc b/tensorflow/compiler/xla/service/hlo_instruction.cc index 5a69150058..0667a4d9c2 100644 --- a/tensorflow/compiler/xla/service/hlo_instruction.cc +++ b/tensorflow/compiler/xla/service/hlo_instruction.cc @@ -96,36 +96,31 @@ StatusOr> HloInstruction::CreateFromProto( Shape shape(proto.shape()); TF_RETURN_IF_ERROR(ShapeUtil::ValidateShapeWithOptionalLayout(shape)); + absl::optional arity = HloOpcodeArity(opcode); + if (arity) { + TF_RET_CHECK(proto.operand_ids_size() == *arity) + << proto.opcode() << " instruction should have " << *arity + << " operands but sees " << proto.operand_ids_size(); + } + switch (opcode) { // Ops migrated to subclasses. case HloOpcode::kBatchNormTraining: - TF_RET_CHECK(proto.operand_ids_size() == 3) - << "BatchNormTraining instruction should have 3 operands but sees " - << proto.operand_ids_size(); instruction = CreateBatchNormTraining(shape, operands(0), operands(1), operands(2), proto.epsilon(), proto.feature_index()); break; case HloOpcode::kBatchNormInference: - TF_RET_CHECK(proto.operand_ids_size() == 5) - << "BatchNormInference instruction should have 5 operands but sees " - << proto.operand_ids_size(); instruction = CreateBatchNormInference( shape, operands(0), operands(1), operands(2), operands(3), operands(4), proto.epsilon(), proto.feature_index()); break; case HloOpcode::kBatchNormGrad: - TF_RET_CHECK(proto.operand_ids_size() == 5) - << "BatchNormGrad instruction should have 5 operands but sees " - << proto.operand_ids_size(); instruction = CreateBatchNormGrad(shape, operands(0), operands(1), operands(2), operands(3), operands(4), proto.epsilon(), proto.feature_index()); break; case HloOpcode::kFft: { - TF_RET_CHECK(proto.operand_ids_size() == 1) - << "Fft instruction should have 1 operand but sees " - << proto.operand_ids_size(); std::vector fft_length(proto.fft_length().begin(), proto.fft_length().end()); instruction = CreateFft(shape, operands(0), proto.fft_type(), @@ -133,43 +128,25 @@ StatusOr> HloInstruction::CreateFromProto( break; } case HloOpcode::kTriangularSolve: { - TF_RET_CHECK(proto.operand_ids_size() == 2) - << "Triangular solve instruction should have 2 operands but sees " - << proto.operand_ids_size(); instruction = CreateTriangularSolve(shape, operands(0), operands(1), proto.triangular_solve_options()); break; } case HloOpcode::kSend: - TF_RET_CHECK(proto.operand_ids_size() == 2) - << "Send instruction should have 2 operand but sees " - << proto.operand_ids_size(); instruction = CreateSend(operands(0), operands(1), proto.channel_id(), proto.is_host_transfer()); break; case HloOpcode::kSendDone: - TF_RET_CHECK(proto.operand_ids_size() == 1) - << "SendDone instruction should have 1 operand but sees " - << proto.operand_ids_size(); instruction = CreateSendDone(operands(0), proto.is_host_transfer()); break; case HloOpcode::kRecv: - TF_RET_CHECK(proto.operand_ids_size() == 1) - << "Recv instruction should have 1 operand but sees " - << proto.operand_ids_size(); instruction = CreateRecv(shape.tuple_shapes(0), operands(0), proto.channel_id(), proto.is_host_transfer()); break; case HloOpcode::kRecvDone: - TF_RET_CHECK(proto.operand_ids_size() == 1) - << "RecvDone instruction should have 1 operand but sees " - << proto.operand_ids_size(); instruction = CreateRecvDone(operands(0), proto.is_host_transfer()); break; case HloOpcode::kReverse: - TF_RET_CHECK(proto.operand_ids_size() == 1) - << "Reverse instruction should have 1 operand but sees " - << proto.operand_ids_size(); instruction = CreateReverse(shape, operands(0), std::vector(proto.dimensions().begin(), proto.dimensions().end())); @@ -218,18 +195,12 @@ StatusOr> HloInstruction::CreateFromProto( break; } case HloOpcode::kTranspose: - TF_RET_CHECK(proto.operand_ids_size() == 1) - << "Transpose instruction should have 1 operand but sees " - << proto.operand_ids_size(); instruction = CreateTranspose(shape, operands(0), std::vector(proto.dimensions().begin(), proto.dimensions().end())); break; case HloOpcode::kBroadcast: - TF_RET_CHECK(proto.operand_ids_size() == 1) - << "Broadcast instruction should have 1 operand but sees " - << proto.operand_ids_size(); instruction = CreateBroadcast(shape, operands(0), std::vector(proto.dimensions().begin(), @@ -242,9 +213,6 @@ StatusOr> HloInstruction::CreateFromProto( instruction = CreateMap(shape, all_operands(), computations(0)); break; case HloOpcode::kSlice: { - TF_RET_CHECK(proto.operand_ids_size() == 1) - << "Slice instruction should have 1 operand but sees " - << proto.operand_ids_size(); std::vector slice_starts, slice_limits, slice_strides; for (const HloInstructionProto::SliceDimensions& slice_dimensions : proto.slice_dimensions()) { @@ -268,9 +236,6 @@ StatusOr> HloInstruction::CreateFromProto( break; } case HloOpcode::kTrace: { - TF_RET_CHECK(proto.operand_ids_size() == 1) - << "Trace instruction should have 1 operand but sees " - << proto.operand_ids_size(); TF_RET_CHECK(proto.has_literal()); TF_ASSIGN_OR_RETURN(auto literal, Literal::CreateFromProto(proto.literal())); @@ -310,16 +275,10 @@ StatusOr> HloInstruction::CreateFromProto( } break; case HloOpcode::kGetTupleElement: - TF_RET_CHECK(proto.operand_ids_size() == 1) - << "GetTupleElement instruction should have 1 operand but sees " - << proto.operand_ids_size(); instruction = CreateGetTupleElement(shape, operands(0), proto.tuple_index()); break; case HloOpcode::kReducePrecision: - TF_RET_CHECK(proto.operand_ids_size() == 1) - << "ReducePrecision instruction should have 1 operand but sees " - << proto.operand_ids_size(); instruction = CreateReducePrecision( shape, operands(0), proto.exponent_bits(), proto.mantissa_bits()); break; @@ -329,16 +288,10 @@ StatusOr> HloInstruction::CreateFromProto( << "Infeed should have a tuple shape with 2 operands, but has: " << shape; const Shape& data_shape = ShapeUtil::GetTupleElementShape(shape, 0); - TF_RET_CHECK(proto.operand_ids_size() == 1) - << "Infeed instruction should have 1 operand but sees " - << proto.operand_ids_size(); instruction = CreateInfeed(data_shape, operands(0), proto.infeed_config()); } break; case HloOpcode::kOutfeed: { - TF_RET_CHECK(proto.operand_ids_size() == 2) - << "Outfeed instruction should have 2 operands but sees " - << proto.operand_ids_size(); Shape outfeed_shape(proto.outfeed_shape()); TF_RETURN_IF_ERROR( ShapeUtil::ValidateShapeWithOptionalLayout(outfeed_shape)); @@ -372,9 +325,6 @@ StatusOr> HloInstruction::CreateFromProto( break; } case HloOpcode::kCollectivePermute: { - TF_RET_CHECK(proto.operand_ids_size() == 1) - << "CollectivePermute instruction should have 1 operand but sees " - << proto.operand_ids_size(); std::vector> source_target_pairs( proto.source_target_pairs_size()); for (int i = 0; i < source_target_pairs.size(); i++) { @@ -386,16 +336,10 @@ StatusOr> HloInstruction::CreateFromProto( break; } case HloOpcode::kReplicaId: { - TF_RET_CHECK(proto.operand_ids_size() == 0) - << "ReplicaId instruction should have 0 operand but sees " - << proto.operand_ids_size(); instruction = CreateReplicaId(); break; } case HloOpcode::kConvolution: { - TF_RET_CHECK(proto.operand_ids_size() == 2) - << "Convolution instruction should have 2 operands but sees " - << proto.operand_ids_size(); TF_RET_CHECK(proto.has_window()); TF_RET_CHECK(proto.has_convolution_dimension_numbers()); PrecisionConfig precision_config = proto.precision_config(); @@ -409,9 +353,6 @@ StatusOr> HloInstruction::CreateFromProto( break; } case HloOpcode::kReduceWindow: - TF_RET_CHECK(proto.operand_ids_size() == 2) - << "ReduceWindow instruction should have 2 operands but sees " - << proto.operand_ids_size(); TF_RET_CHECK(proto.called_computation_ids_size() == 1) << "ReduceWindow should have 1 called computation but sees " << proto.called_computation_ids_size(); @@ -419,9 +360,6 @@ StatusOr> HloInstruction::CreateFromProto( proto.window(), computations(0)); break; case HloOpcode::kSelectAndScatter: - TF_RET_CHECK(proto.operand_ids_size() == 3) - << "SelectAndScatter instruction should have 3 operands but sees " - << proto.operand_ids_size(); TF_RET_CHECK(proto.called_computation_ids_size() == 2) << "SelectAndScatter should have 2 called computations but sees " << proto.called_computation_ids_size(); @@ -464,9 +402,6 @@ StatusOr> HloInstruction::CreateFromProto( std::max(static_cast(proto.batch_group_count()), 1LL)); break; case HloOpcode::kPad: - TF_RET_CHECK(proto.operand_ids_size() == 2) - << "Pad instruction should have 2 operands but sees " - << proto.operand_ids_size(); TF_RET_CHECK(proto.has_padding_config()); instruction = CreatePad(shape, operands(0), operands(1), proto.padding_config()); @@ -512,9 +447,6 @@ StatusOr> HloInstruction::CreateFromProto( break; } case HloOpcode::kGather: { - TF_RET_CHECK(proto.operand_ids_size() == 2) - << "Gather instruction should have 2 operands but sees " - << proto.operand_ids_size(); TF_RET_CHECK(proto.has_gather_dimension_numbers()) << "Gather instruction should have GatherDimensionNumbers set."; std::unique_ptr gather_dimension_numbers = @@ -529,9 +461,6 @@ StatusOr> HloInstruction::CreateFromProto( break; } case HloOpcode::kScatter: { - TF_RET_CHECK(proto.operand_ids_size() == 3) - << "Scatter instruction should have 3 operands but sees " - << proto.operand_ids_size(); TF_RET_CHECK(proto.has_scatter_dimension_numbers()) << "Scatter instruction should have ScatterDimensionNumbers set."; TF_RET_CHECK(proto.called_computation_ids_size() == 1) @@ -553,9 +482,6 @@ StatusOr> HloInstruction::CreateFromProto( case HloOpcode::kDot: { TF_RET_CHECK(proto.has_dot_dimension_numbers()) << "Dot instruction should have dot_dimension_numbers."; - TF_RET_CHECK(proto.operand_ids_size() == 2) - << "Dot instruction should have 2 operands but sees " - << proto.operand_ids_size(); PrecisionConfig precision_config = proto.precision_config(); precision_config.mutable_operand_precision()->Resize( proto.operand_ids_size(), PrecisionConfig::DEFAULT); @@ -565,9 +491,6 @@ StatusOr> HloInstruction::CreateFromProto( break; } case HloOpcode::kDomain: { - TF_RET_CHECK(proto.operand_ids_size() == 1) - << "Domain instruction should have 1 operands but sees " - << proto.operand_ids_size(); std::shared_ptr entry_hlo_sharding; std::shared_ptr exit_hlo_sharding; if (proto.has_domain_entry_sharding()) { @@ -589,7 +512,6 @@ StatusOr> HloInstruction::CreateFromProto( break; } case HloOpcode::kGetDimensionSize: - TF_RET_CHECK(proto.operand_ids_size() == 1); TF_RET_CHECK(proto.dimensions_size() == 1); instruction = CreateGetDimensionSize(shape, operands(0), proto.dimensions(0)); diff --git a/tensorflow/compiler/xla/service/hlo_opcode.cc b/tensorflow/compiler/xla/service/hlo_opcode.cc index 4551a1c2e2..548fbb873a 100644 --- a/tensorflow/compiler/xla/service/hlo_opcode.cc +++ b/tensorflow/compiler/xla/service/hlo_opcode.cc @@ -53,8 +53,8 @@ StatusOr StringToHloOpcode(const string& opcode_name) { bool HloOpcodeIsComparison(HloOpcode opcode) { switch (opcode) { -#define CASE_IS_COMPARISON(enum_name, ...) \ - case HloOpcode::enum_name: \ +#define CASE_IS_COMPARISON(enum_name, opcode_name, ...) \ + case HloOpcode::enum_name: \ return HAS_PROPERTY(kHloOpcodeIsComparison, __VA_ARGS__); HLO_OPCODE_LIST(CASE_IS_COMPARISON) #undef CASE_IS_COMPARISON @@ -63,14 +63,25 @@ bool HloOpcodeIsComparison(HloOpcode opcode) { bool HloOpcodeIsVariadic(HloOpcode opcode) { switch (opcode) { -#define CASE_IS_VARIADIC(enum_name, ...) \ - case HloOpcode::enum_name: \ - return HAS_PROPERTY(kHloOpcodeIsVariadic, __VA_ARGS__); +#define CASE_IS_VARIADIC(enum_name, opcode_name, arity, ...) \ + case HloOpcode::enum_name: \ + return arity == kHloOpcodeIsVariadic; HLO_OPCODE_LIST(CASE_IS_VARIADIC) #undef CASE_IS_VARIADIC } } +absl::optional HloOpcodeArity(HloOpcode opcode) { + switch (opcode) { +#define CASE_ARITY(enum_name, opcode_name, arity, ...) \ + case HloOpcode::enum_name: \ + return arity == kHloOpcodeIsVariadic ? absl::nullopt \ + : absl::make_optional(arity); + HLO_OPCODE_LIST(CASE_ARITY) +#undef CASE_ARITY + } +} + #undef HAS_PROPERTY #undef RESOLVE #undef CHECK_DEFAULT diff --git a/tensorflow/compiler/xla/service/hlo_opcode.h b/tensorflow/compiler/xla/service/hlo_opcode.h index 35626ba375..ddb08a58c1 100644 --- a/tensorflow/compiler/xla/service/hlo_opcode.h +++ b/tensorflow/compiler/xla/service/hlo_opcode.h @@ -18,6 +18,7 @@ limitations under the License. #include #include +#include "absl/types/optional.h" #include "tensorflow/compiler/xla/statusor.h" #include "tensorflow/compiler/xla/types.h" @@ -30,9 +31,9 @@ namespace xla { // See the XLA documentation for the semantics of each opcode. // // Each entry has the format: -// (enum_name, opcode_name) +// (enum_name, opcode_name, arity) // or -// (enum_name, opcode_name, p1 | p2 | ...) +// (enum_name, opcode_name, arity, p1 | p2 | ...) // // with p1, p2, ... are members of HloOpcodeProperty. They are combined // using bitwise-or. @@ -44,104 +45,104 @@ namespace xla { // - In fully qualified names (HloInstruction::FullyQualifiedName()), to // separate the qualifiers (name of the computation and potentially the // fusion instruction) from the name -#define HLO_OPCODE_LIST(V) \ - V(kAbs, "abs") \ - V(kAdd, "add") \ - V(kAddDependency, "add-dependency") \ - V(kAfterAll, "after-all", kHloOpcodeIsVariadic) \ - V(kAllReduce, "all-reduce") \ - V(kAllToAll, "all-to-all") \ - V(kAtan2, "atan2") \ - V(kBatchNormGrad, "batch-norm-grad") \ - V(kBatchNormInference, "batch-norm-inference") \ - V(kBatchNormTraining, "batch-norm-training") \ - V(kBitcast, "bitcast") \ - V(kBitcastConvert, "bitcast-convert") \ - V(kBroadcast, "broadcast") \ - V(kCall, "call", kHloOpcodeIsVariadic) \ - V(kCeil, "ceil") \ - V(kClamp, "clamp") \ - V(kCollectivePermute, "collective-permute") \ - V(kClz, "count-leading-zeros") \ - V(kComplex, "complex") \ - V(kConcatenate, "concatenate", kHloOpcodeIsVariadic) \ - V(kConditional, "conditional") \ - V(kConstant, "constant") \ - V(kConvert, "convert") \ - V(kConvolution, "convolution") \ - V(kCopy, "copy") \ - V(kCos, "cosine") \ - V(kCustomCall, "custom-call") \ - V(kDivide, "divide") \ - V(kDomain, "domain") \ - V(kDot, "dot") \ - V(kDynamicSlice, "dynamic-slice") \ - V(kDynamicUpdateSlice, "dynamic-update-slice") \ - V(kEq, "equal-to", kHloOpcodeIsComparison) \ - V(kExp, "exponential") \ - V(kExpm1, "exponential-minus-one") \ - V(kFft, "fft") \ - V(kFloor, "floor") \ - V(kFusion, "fusion", kHloOpcodeIsVariadic) \ - V(kGather, "gather") \ - V(kGe, "greater-than-or-equal-to", kHloOpcodeIsComparison) \ - V(kGetDimensionSize, "get-dimension-size") \ - V(kGetTupleElement, "get-tuple-element") \ - V(kGt, "greater-than", kHloOpcodeIsComparison) \ - V(kImag, "imag") \ - V(kInfeed, "infeed") \ - V(kIota, "iota") \ - V(kIsFinite, "is-finite") \ - V(kLe, "less-than-or-equal-to", kHloOpcodeIsComparison) \ - V(kLog, "log") \ - V(kLog1p, "log-plus-one") \ - V(kAnd, "and") \ - V(kNot, "not") \ - V(kOr, "or") \ - V(kXor, "xor") \ - V(kLt, "less-than", kHloOpcodeIsComparison) \ - V(kMap, "map", kHloOpcodeIsVariadic) \ - V(kMaximum, "maximum") \ - V(kMinimum, "minimum") \ - V(kMultiply, "multiply") \ - V(kNe, "not-equal-to", kHloOpcodeIsComparison) \ - V(kNegate, "negate") \ - V(kOutfeed, "outfeed") \ - V(kPad, "pad") \ - V(kParameter, "parameter") \ - V(kPower, "power") \ - V(kReal, "real") \ - V(kRecv, "recv") \ - V(kRecvDone, "recv-done") \ - V(kReduce, "reduce") \ - V(kReducePrecision, "reduce-precision") \ - V(kReduceWindow, "reduce-window") \ - V(kRemainder, "remainder") \ - V(kReshape, "reshape") \ - V(kReverse, "reverse") \ - V(kRng, "rng") \ - V(kRoundNearestAfz, "round-nearest-afz") \ - V(kScatter, "scatter") \ - V(kSelect, "select") \ - V(kSelectAndScatter, "select-and-scatter") \ - V(kSend, "send") \ - V(kSendDone, "send-done") \ - V(kShiftLeft, "shift-left") \ - V(kShiftRightArithmetic, "shift-right-arithmetic") \ - V(kShiftRightLogical, "shift-right-logical") \ - V(kSign, "sign") \ - V(kSin, "sine") \ - V(kSlice, "slice") \ - V(kSort, "sort") \ - V(kSubtract, "subtract") \ - V(kTanh, "tanh") \ - V(kTrace, "trace") \ - V(kTranspose, "transpose") \ - V(kTriangularSolve, "triangular-solve") \ - V(kTuple, "tuple", kHloOpcodeIsVariadic) \ - V(kTupleSelect, "tuple-select") \ - V(kWhile, "while") \ - V(kReplicaId, "replica-id") +#define HLO_OPCODE_LIST(V) \ + V(kAbs, "abs", 1) \ + V(kAdd, "add", 2) \ + V(kAddDependency, "add-dependency", 2) \ + V(kAfterAll, "after-all", kHloOpcodeIsVariadic) \ + V(kAllReduce, "all-reduce", kHloOpcodeIsVariadic) \ + V(kAllToAll, "all-to-all", kHloOpcodeIsVariadic) \ + V(kAtan2, "atan2", 2) \ + V(kBatchNormGrad, "batch-norm-grad", 5) \ + V(kBatchNormInference, "batch-norm-inference", 5) \ + V(kBatchNormTraining, "batch-norm-training", 3) \ + V(kBitcast, "bitcast", 1) \ + V(kBitcastConvert, "bitcast-convert", 1) \ + V(kBroadcast, "broadcast", 1) \ + V(kCall, "call", kHloOpcodeIsVariadic) \ + V(kCeil, "ceil", 1) \ + V(kClamp, "clamp", 3) \ + V(kCollectivePermute, "collective-permute", 1) \ + V(kClz, "count-leading-zeros", 1) \ + V(kComplex, "complex", 2) \ + V(kConcatenate, "concatenate", kHloOpcodeIsVariadic) \ + V(kConditional, "conditional", 3) \ + V(kConstant, "constant", 0) \ + V(kConvert, "convert", 1) \ + V(kConvolution, "convolution", 2) \ + V(kCopy, "copy", 1) \ + V(kCos, "cosine", 1) \ + V(kCustomCall, "custom-call", kHloOpcodeIsVariadic) \ + V(kDivide, "divide", 2) \ + V(kDomain, "domain", 1) \ + V(kDot, "dot", 2) \ + V(kDynamicSlice, "dynamic-slice", kHloOpcodeIsVariadic) \ + V(kDynamicUpdateSlice, "dynamic-update-slice", kHloOpcodeIsVariadic) \ + V(kEq, "equal-to", 2, kHloOpcodeIsComparison) \ + V(kExp, "exponential", 1) \ + V(kExpm1, "exponential-minus-one", 1) \ + V(kFft, "fft", 1) \ + V(kFloor, "floor", 1) \ + V(kFusion, "fusion", kHloOpcodeIsVariadic) \ + V(kGather, "gather", 2) \ + V(kGe, "greater-than-or-equal-to", 2, kHloOpcodeIsComparison) \ + V(kGetDimensionSize, "get-dimension-size", 1) \ + V(kGetTupleElement, "get-tuple-element", 1) \ + V(kGt, "greater-than", 2, kHloOpcodeIsComparison) \ + V(kImag, "imag", 1) \ + V(kInfeed, "infeed", 1) \ + V(kIota, "iota", 0) \ + V(kIsFinite, "is-finite", 1) \ + V(kLe, "less-than-or-equal-to", 2, kHloOpcodeIsComparison) \ + V(kLog, "log", 1) \ + V(kLog1p, "log-plus-one", 1) \ + V(kAnd, "and", 2) \ + V(kNot, "not", 1) \ + V(kOr, "or", 2) \ + V(kXor, "xor", 2) \ + V(kLt, "less-than", 2, kHloOpcodeIsComparison) \ + V(kMap, "map", kHloOpcodeIsVariadic) \ + V(kMaximum, "maximum", 2) \ + V(kMinimum, "minimum", 2) \ + V(kMultiply, "multiply", 2) \ + V(kNe, "not-equal-to", 2, kHloOpcodeIsComparison) \ + V(kNegate, "negate", 1) \ + V(kOutfeed, "outfeed", 2) \ + V(kPad, "pad", 2) \ + V(kParameter, "parameter", 0) \ + V(kPower, "power", 2) \ + V(kReal, "real", 1) \ + V(kRecv, "recv", 1) \ + V(kRecvDone, "recv-done", 1) \ + V(kReduce, "reduce", kHloOpcodeIsVariadic) \ + V(kReducePrecision, "reduce-precision", 1) \ + V(kReduceWindow, "reduce-window", 2) \ + V(kRemainder, "remainder", 2) \ + V(kReplicaId, "replica-id", 0) \ + V(kReshape, "reshape", 1) \ + V(kReverse, "reverse", 1) \ + V(kRng, "rng", kHloOpcodeIsVariadic) \ + V(kRoundNearestAfz, "round-nearest-afz", 1) \ + V(kScatter, "scatter", 3) \ + V(kSelect, "select", 3) \ + V(kSelectAndScatter, "select-and-scatter", 3) \ + V(kSend, "send", 2) \ + V(kSendDone, "send-done", 1) \ + V(kShiftLeft, "shift-left", 2) \ + V(kShiftRightArithmetic, "shift-right-arithmetic", 2) \ + V(kShiftRightLogical, "shift-right-logical", 2) \ + V(kSign, "sign", 1) \ + V(kSin, "sine", 1) \ + V(kSlice, "slice", 1) \ + V(kSort, "sort", kHloOpcodeIsVariadic) \ + V(kSubtract, "subtract", 2) \ + V(kTanh, "tanh", 1) \ + V(kTrace, "trace", 1) \ + V(kTranspose, "transpose", 1) \ + V(kTriangularSolve, "triangular-solve", 2) \ + V(kTuple, "tuple", kHloOpcodeIsVariadic) \ + V(kTupleSelect, "tuple-select", 3) \ + V(kWhile, "while", 1) enum class HloOpcode { #define DECLARE_ENUM(enum_name, opcode_name, ...) enum_name, @@ -149,12 +150,16 @@ enum class HloOpcode { #undef DECLARE_ENUM }; +// Arity value that denotes that an operator is variadic. +enum { + kHloOpcodeIsVariadic = -1, +}; + // List of properties associated with opcodes. // Properties are defined as increasing powers of two, so that we can use // bitwise-or to combine properties, and bitwise-and to test for them. enum HloOpcodeProperty { kHloOpcodeIsComparison = 1 << 0, - kHloOpcodeIsVariadic = 1 << 1, }; // Returns a string representation of the opcode. @@ -173,6 +178,10 @@ bool HloOpcodeIsComparison(HloOpcode opcode); // Returns true iff the given opcode has variadic operands. bool HloOpcodeIsVariadic(HloOpcode opcode); +// Returns the arity of opcode. If the opcode is variadic, +// returns nullopt. +absl::optional HloOpcodeArity(HloOpcode opcode); + // Returns the number of HloOpcode values. inline const uint32_t HloOpcodeCount() { #define HLO_COUNT_ONE(...) +1 diff --git a/tensorflow/compiler/xla/service/hlo_opcode_test.cc b/tensorflow/compiler/xla/service/hlo_opcode_test.cc index 6f3f83f63a..c599690f44 100644 --- a/tensorflow/compiler/xla/service/hlo_opcode_test.cc +++ b/tensorflow/compiler/xla/service/hlo_opcode_test.cc @@ -54,11 +54,19 @@ TEST(HloOpcodeTest, OpcodeProperties) { EXPECT_FALSE(HloOpcodeIsComparison(opcode)); } switch (opcode) { + case HloOpcode::kAfterAll: + case HloOpcode::kAllReduce: + case HloOpcode::kAllToAll: case HloOpcode::kCall: case HloOpcode::kConcatenate: + case HloOpcode::kCustomCall: + case HloOpcode::kDynamicSlice: + case HloOpcode::kDynamicUpdateSlice: case HloOpcode::kFusion: case HloOpcode::kMap: - case HloOpcode::kAfterAll: + case HloOpcode::kReduce: + case HloOpcode::kRng: + case HloOpcode::kSort: case HloOpcode::kTuple: EXPECT_TRUE(HloOpcodeIsVariadic(opcode)); break; diff --git a/tensorflow/compiler/xla/service/hlo_verifier.cc b/tensorflow/compiler/xla/service/hlo_verifier.cc index 97e6ea9dad..56a06a182a 100644 --- a/tensorflow/compiler/xla/service/hlo_verifier.cc +++ b/tensorflow/compiler/xla/service/hlo_verifier.cc @@ -58,15 +58,6 @@ bool IsCallerInstruction(HloInstruction* hlo) { } } -Status ShapeVerifier::Preprocess(HloInstruction* hlo) { - if (!hlo->called_computations().empty() && !IsCallerInstruction(hlo)) { - return InternalError( - "Called computations specified for non-caller instruction %s", - hlo->ToString()); - } - return VerifyNotSparse(hlo->shape()); -} - namespace { Status CheckOperandCount(const HloInstruction* hlo, int expected) { @@ -91,6 +82,21 @@ Status CheckParameterCount(const HloInstruction* calling_instruction, } // namespace +Status ShapeVerifier::Preprocess(HloInstruction* hlo) { + if (!hlo->called_computations().empty() && !IsCallerInstruction(hlo)) { + return InternalError( + "Called computations specified for non-caller instruction %s", + hlo->ToString()); + } + TF_RETURN_IF_ERROR(VerifyNotSparse(hlo->shape())); + + absl::optional arity = HloOpcodeArity(hlo->opcode()); + if (arity) { + TF_RETURN_IF_ERROR(CheckOperandCount(hlo, *arity)); + } + return Status::OK(); +} + Status ShapeVerifier::HandleElementwiseUnary(HloInstruction* hlo) { return CheckUnaryShape(hlo); } @@ -122,14 +128,12 @@ Status ShapeVerifier::HandleConcatenate(HloInstruction* concatenate) { } Status ShapeVerifier::HandleConvert(HloInstruction* convert) { - TF_RETURN_IF_ERROR(CheckOperandCount(convert, 1)); return CheckShape(convert, ShapeInference::InferConvertShape( convert->operand(0)->shape(), convert->shape().element_type())); } Status ShapeVerifier::HandleBitcastConvert(HloInstruction* convert) { - TF_RETURN_IF_ERROR(CheckOperandCount(convert, 1)); return CheckShape(convert, ShapeInference::InferBitcastConvertShape( convert->operand(0)->shape(), convert->shape().element_type())); @@ -140,7 +144,6 @@ Status ShapeVerifier::HandleCopy(HloInstruction* copy) { } Status ShapeVerifier::HandleDot(HloInstruction* dot) { - TF_RETURN_IF_ERROR(CheckOperandCount(dot, 2)); TF_ASSIGN_OR_RETURN(const Shape expected, ShapeInference::InferDotOpShape( dot->operand(0)->shape(), dot->operand(1)->shape(), @@ -149,7 +152,6 @@ Status ShapeVerifier::HandleDot(HloInstruction* dot) { } Status ShapeVerifier::HandleConvolution(HloInstruction* convolution) { - TF_RETURN_IF_ERROR(CheckOperandCount(convolution, 2)); TF_ASSIGN_OR_RETURN( const Shape expected, ShapeInference::InferConvolveShape( @@ -160,7 +162,6 @@ Status ShapeVerifier::HandleConvolution(HloInstruction* convolution) { } Status ShapeVerifier::HandleFft(HloInstruction* fft) { - TF_RETURN_IF_ERROR(CheckOperandCount(fft, 1)); TF_ASSIGN_OR_RETURN( const Shape expected, ShapeInference::InferFftShape(fft->operand(0)->shape(), fft->fft_type(), @@ -169,7 +170,6 @@ Status ShapeVerifier::HandleFft(HloInstruction* fft) { } Status ShapeVerifier::HandleTriangularSolve(HloInstruction* hlo) { - TF_RETURN_IF_ERROR(CheckOperandCount(hlo, 2)); TF_ASSIGN_OR_RETURN(const Shape expected, ShapeInference::InferTriangularSolveShape( hlo->operand(0)->shape(), hlo->operand(1)->shape(), @@ -199,13 +199,11 @@ Status ShapeVerifier::HandleReplicaId(HloInstruction* hlo) { } Status ShapeVerifier::HandleCollectivePermute(HloInstruction* hlo) { - TF_RETURN_IF_ERROR(CheckOperandCount(hlo, 1)); return CheckShape(hlo, ShapeInference::InferCollectivePermuteShape( hlo->operand(0)->shape())); } Status ShapeVerifier::HandleReducePrecision(HloInstruction* reduce_precision) { - TF_RETURN_IF_ERROR(CheckOperandCount(reduce_precision, 1)); return CheckShape(reduce_precision, ShapeInference::InferReducePrecisionShape( reduce_precision->operand(0)->shape(), reduce_precision->exponent_bits(), @@ -239,7 +237,6 @@ Status ShapeVerifier::CheckOperandAndParameter( } Status ShapeVerifier::HandleInfeed(HloInstruction* instruction) { - TF_RETURN_IF_ERROR(CheckOperandCount(instruction, 1)); HloInfeedInstruction* infeed = Cast(instruction); TF_RETURN_IF_ERROR(CheckIsTokenOperand(instruction, 0)); @@ -250,7 +247,6 @@ Status ShapeVerifier::HandleInfeed(HloInstruction* instruction) { } Status ShapeVerifier::HandleOutfeed(HloInstruction* instruction) { - TF_RETURN_IF_ERROR(CheckOperandCount(instruction, 2)); HloOutfeedInstruction* outfeed = Cast(instruction); TF_RETURN_IF_ERROR(CheckIsTokenOperand(instruction, 1)); @@ -326,7 +322,6 @@ Status ShapeVerifier::HandleRng(HloInstruction* instruction) { } Status ShapeVerifier::HandleReverse(HloInstruction* reverse) { - TF_RETURN_IF_ERROR(CheckOperandCount(reverse, 1)); return CheckShape( reverse, ShapeInference::InferReverseShape(reverse->operand(0)->shape(), reverse->dimensions())); @@ -387,7 +382,6 @@ Status ShapeVerifier::HandleSort(HloInstruction* sort) { } Status ShapeVerifier::HandleConstant(HloInstruction* constant) { - TF_RETURN_IF_ERROR(CheckOperandCount(constant, 0)); if (!Cast(constant)->HasLiteral()) { return InternalError("Constant is required to have a valid literal: %s", constant->ToString()); @@ -396,7 +390,6 @@ Status ShapeVerifier::HandleConstant(HloInstruction* constant) { } Status ShapeVerifier::HandleIota(HloInstruction* instruction) { - TF_RETURN_IF_ERROR(CheckOperandCount(instruction, 0)); auto* iota = Cast(instruction); if (!iota->shape().IsArray()) { return InternalError("Iota does not support non-array result."); @@ -414,7 +407,6 @@ Status ShapeVerifier::HandleIota(HloInstruction* instruction) { } Status ShapeVerifier::HandleGetTupleElement(HloInstruction* get_tuple_element) { - TF_RETURN_IF_ERROR(CheckOperandCount(get_tuple_element, 1)); return CheckShape(get_tuple_element, ShapeInference::InferGetTupleElementShape( get_tuple_element->operand(0)->shape(), @@ -462,7 +454,6 @@ Status ShapeVerifier::HandleReduce(HloInstruction* reduce) { } Status ShapeVerifier::HandleBitcast(HloInstruction* bitcast) { - TF_RETURN_IF_ERROR(CheckOperandCount(bitcast, 1)); // Bitcasts are not allowed to change the element type. if (bitcast->operand(0)->shape().element_type() != bitcast->shape().element_type()) { @@ -475,7 +466,6 @@ Status ShapeVerifier::HandleBitcast(HloInstruction* bitcast) { } Status ShapeVerifier::HandleBroadcast(HloInstruction* broadcast) { - TF_RETURN_IF_ERROR(CheckOperandCount(broadcast, 1)); // HLO broadcast has no exact analog at the proto level so there is no // ShapeInference method. Check the output shape explicitly. const Shape& operand_shape = broadcast->operand(0)->shape(); @@ -495,7 +485,6 @@ Status ShapeVerifier::HandleBroadcast(HloInstruction* broadcast) { } Status ShapeVerifier::HandleReshape(HloInstruction* reshape) { - TF_RETURN_IF_ERROR(CheckOperandCount(reshape, 1)); // Check for mixed precision. const Shape& operand_shape = reshape->operand(0)->shape(); TF_RET_CHECK(SameElementType(reshape->shape(), operand_shape)); @@ -505,14 +494,12 @@ Status ShapeVerifier::HandleReshape(HloInstruction* reshape) { } Status ShapeVerifier::HandleTranspose(HloInstruction* transpose) { - TF_RETURN_IF_ERROR(CheckOperandCount(transpose, 1)); return CheckShape( transpose, ShapeInference::InferTransposeShape( transpose->operand(0)->shape(), transpose->dimensions())); } Status ShapeVerifier::HandleParameter(HloInstruction* hlo) { - TF_RETURN_IF_ERROR(CheckOperandCount(hlo, 0)); return Status::OK(); } @@ -572,7 +559,6 @@ Status ShapeVerifier::HandleCustomCall(HloInstruction* instruction) { } Status ShapeVerifier::HandleSlice(HloInstruction* slice) { - TF_RETURN_IF_ERROR(CheckOperandCount(slice, 1)); return CheckShape(slice, ShapeInference::InferSliceShape( slice->operand(0)->shape(), slice->slice_starts(), @@ -627,7 +613,6 @@ Status ShapeVerifier::HandleMap(HloInstruction* map) { } Status ShapeVerifier::HandleReduceWindow(HloInstruction* reduce_window) { - TF_RETURN_IF_ERROR(CheckOperandCount(reduce_window, 2)); TF_RETURN_IF_ERROR(CheckShape( reduce_window, ShapeInference::InferReduceWindowShape( @@ -642,7 +627,6 @@ Status ShapeVerifier::HandleReduceWindow(HloInstruction* reduce_window) { } Status ShapeVerifier::HandleSelectAndScatter(HloInstruction* instruction) { - TF_RETURN_IF_ERROR(CheckOperandCount(instruction, 3)); return CheckShape( instruction, ShapeInference::InferSelectAndScatterShape( @@ -653,7 +637,6 @@ Status ShapeVerifier::HandleSelectAndScatter(HloInstruction* instruction) { } Status ShapeVerifier::HandleWhile(HloInstruction* xla_while) { - TF_RETURN_IF_ERROR(CheckOperandCount(xla_while, 1)); TF_RETURN_IF_ERROR( CheckParameterCount(xla_while, xla_while->while_body(), 1)); TF_RETURN_IF_ERROR( @@ -677,7 +660,6 @@ Status ShapeVerifier::HandleWhile(HloInstruction* xla_while) { } Status ShapeVerifier::HandleConditional(HloInstruction* conditional) { - TF_RETURN_IF_ERROR(CheckOperandCount(conditional, 3)); TF_RETURN_IF_ERROR( CheckParameterCount(conditional, conditional->true_computation(), 1)); TF_RETURN_IF_ERROR( @@ -696,14 +678,12 @@ Status ShapeVerifier::HandleConditional(HloInstruction* conditional) { } Status ShapeVerifier::HandlePad(HloInstruction* pad) { - TF_RETURN_IF_ERROR(CheckOperandCount(pad, 2)); return CheckShape(pad, ShapeInference::InferPadShape(pad->operand(0)->shape(), pad->operand(1)->shape(), pad->padding_config())); } Status ShapeVerifier::HandleSend(HloInstruction* send) { - TF_RETURN_IF_ERROR(CheckOperandCount(send, 2)); return CheckShape(send, ShapeUtil::MakeTupleShape({send->operand(0)->shape(), ShapeUtil::MakeShape(U32, {}), @@ -711,12 +691,10 @@ Status ShapeVerifier::HandleSend(HloInstruction* send) { } Status ShapeVerifier::HandleSendDone(HloInstruction* send_done) { - TF_RETURN_IF_ERROR(CheckOperandCount(send_done, 1)); return CheckShape(send_done, ShapeUtil::MakeTokenShape()); } Status ShapeVerifier::HandleRecv(HloInstruction* recv) { - TF_RETURN_IF_ERROR(CheckOperandCount(recv, 1)); return CheckShape( recv, ShapeUtil::MakeTupleShape( {ShapeUtil::GetTupleElementShape(recv->shape(), 0), @@ -724,7 +702,6 @@ Status ShapeVerifier::HandleRecv(HloInstruction* recv) { } Status ShapeVerifier::HandleRecvDone(HloInstruction* recv_done) { - TF_RETURN_IF_ERROR(CheckOperandCount(recv_done, 1)); return CheckShape( recv_done, ShapeUtil::MakeTupleShape( @@ -734,7 +711,6 @@ Status ShapeVerifier::HandleRecvDone(HloInstruction* recv_done) { Status ShapeVerifier::HandleBatchNormTraining( HloInstruction* batch_norm_training) { - TF_RETURN_IF_ERROR(CheckOperandCount(batch_norm_training, 3)); return CheckShape(batch_norm_training, ShapeInference::InferBatchNormTrainingShape( batch_norm_training->operand(0)->shape(), @@ -745,7 +721,6 @@ Status ShapeVerifier::HandleBatchNormTraining( Status ShapeVerifier::HandleBatchNormInference( HloInstruction* batch_norm_inference) { - TF_RETURN_IF_ERROR(CheckOperandCount(batch_norm_inference, 5)); return CheckShape(batch_norm_inference, ShapeInference::InferBatchNormInferenceShape( batch_norm_inference->operand(0)->shape(), @@ -757,7 +732,6 @@ Status ShapeVerifier::HandleBatchNormInference( } Status ShapeVerifier::HandleBatchNormGrad(HloInstruction* batch_norm_grad) { - TF_RETURN_IF_ERROR(CheckOperandCount(batch_norm_grad, 5)); return CheckShape(batch_norm_grad, ShapeInference::InferBatchNormGradShape( batch_norm_grad->operand(0)->shape(), batch_norm_grad->operand(1)->shape(), @@ -825,7 +799,6 @@ Status CheckMixedPrecisionOperands(const HloInstruction* instruction) { } // namespace Status ShapeVerifier::HandleGather(HloInstruction* gather) { - TF_RETURN_IF_ERROR(CheckOperandCount(gather, 2)); return CheckShape( gather, ShapeInference::InferGatherShape( @@ -834,7 +807,6 @@ Status ShapeVerifier::HandleGather(HloInstruction* gather) { } Status ShapeVerifier::HandleScatter(HloInstruction* scatter) { - TF_RETURN_IF_ERROR(CheckOperandCount(scatter, 3)); return CheckShape( scatter, ShapeInference::InferScatterShape( scatter->operand(0)->shape(), scatter->operand(1)->shape(), @@ -852,7 +824,6 @@ Status ShapeVerifier::HandleAfterAll(HloInstruction* token) { } Status ShapeVerifier::HandleAddDependency(HloInstruction* add_dependency) { - TF_RETURN_IF_ERROR(CheckOperandCount(add_dependency, 2)); TF_RETURN_IF_ERROR(CheckIsTokenOperand(add_dependency, 1)); return CheckShape(add_dependency, add_dependency->operand(0)->shape()); } @@ -934,14 +905,12 @@ Status ShapeVerifier::CheckShape(const HloInstruction* instruction, } Status ShapeVerifier::CheckUnaryShape(const HloInstruction* instruction) { - TF_RETURN_IF_ERROR(CheckOperandCount(instruction, 1)); return CheckShape(instruction, ShapeInference::InferUnaryOpShape(instruction->opcode(), instruction->operand(0))); } Status ShapeVerifier::CheckBinaryShape(const HloInstruction* instruction) { - TF_RETURN_IF_ERROR(CheckOperandCount(instruction, 2)); return CheckShape( instruction, ShapeInference::InferBinaryOpShape(instruction->opcode(), instruction->operand(0), @@ -949,7 +918,6 @@ Status ShapeVerifier::CheckBinaryShape(const HloInstruction* instruction) { } Status ShapeVerifier::CheckTernaryShape(const HloInstruction* instruction) { - TF_RETURN_IF_ERROR(CheckOperandCount(instruction, 3)); return CheckShape(instruction, ShapeInference::InferTernaryOpShape( instruction->opcode(), instruction->operand(0), -- GitLab From 68b80250e3f80e4fb6a6fba400863dcf78f078e7 Mon Sep 17 00:00:00 2001 From: Stephan Lee Date: Thu, 21 Feb 2019 17:26:39 -0800 Subject: [PATCH 306/766] Write PluginData.content as bytes explicitly PiperOrigin-RevId: 235102987 --- tensorflow/python/ops/summary_ops_v2.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tensorflow/python/ops/summary_ops_v2.py b/tensorflow/python/ops/summary_ops_v2.py index 25ee77aaf2..0f7e8cf12c 100644 --- a/tensorflow/python/ops/summary_ops_v2.py +++ b/tensorflow/python/ops/summary_ops_v2.py @@ -46,7 +46,6 @@ from tensorflow.python.util import deprecation from tensorflow.python.util import tf_contextlib from tensorflow.python.util.tf_export import tf_export - # A global dictionary mapping graph keys to a list of summary writer init ops. _SUMMARY_WRITER_INIT_OP = {} @@ -756,7 +755,7 @@ def run_metadata(name, data, step): # the rationale. summary_metadata.plugin_data.plugin_name = "graph_run_metadata" # version number = 1 - summary_metadata.plugin_data.content = "1" + summary_metadata.plugin_data.content = b"1" with summary_scope(name, "graph_run_metadata_summary", @@ -787,7 +786,7 @@ def run_metadata_graphs(name, data, step): # the rationale. summary_metadata.plugin_data.plugin_name = "graph_run_metadata_graph" # version number = 1 - summary_metadata.plugin_data.content = "1" + summary_metadata.plugin_data.content = b"1" data = config_pb2.RunMetadata( function_graphs=data.function_graphs, @@ -825,7 +824,7 @@ def keras_model(name, data, step): # the rationale. summary_metadata.plugin_data.plugin_name = "graph_keras_model" # version number = 1 - summary_metadata.plugin_data.content = "1" + summary_metadata.plugin_data.content = b"1" json_string = data.to_json() -- GitLab From 9a9a5a5e813c72c9154be25bf86b8b67087bc7a6 Mon Sep 17 00:00:00 2001 From: Smit Hinsu Date: Thu, 21 Feb 2019 17:38:26 -0800 Subject: [PATCH 307/766] Add back LeakyRelu TRT test which was dropped as part of merge PiperOrigin-RevId: 235104911 --- .../tf2tensorrt/convert/convert_nodes_test.cc | 68 +++++++------------ 1 file changed, 23 insertions(+), 45 deletions(-) diff --git a/tensorflow/compiler/tf2tensorrt/convert/convert_nodes_test.cc b/tensorflow/compiler/tf2tensorrt/convert/convert_nodes_test.cc index 0aa48913f4..45afc76d75 100644 --- a/tensorflow/compiler/tf2tensorrt/convert/convert_nodes_test.cc +++ b/tensorflow/compiler/tf2tensorrt/convert/convert_nodes_test.cc @@ -2133,48 +2133,6 @@ TEST_F(OpConverterTest, ConvertQuantize) { } } -TEST_F(OpConverterTest, ConvertRelu6) { - { - // Input list is empty, should fail. - NodeDef node_def = MakeNodeDef("my_relu6", "Relu6", {}); - RunValidationAndConversion( - node_def, error::INVALID_ARGUMENT, - "Relu6 got 0 inputs but expected 1, at my_relu6"); - } - - // Get the NodeDef for Relu6. - Scope s = Scope::NewRootScope(); - auto input = ops::Placeholder(s.WithOpName("input"), DT_FLOAT); - auto relu6 = ops::Relu6(s.WithOpName("my_relu6"), input); - const NodeDef node_def = relu6.operation.node()->def(); - { - // Input is weights, should fail. - Reset(); - AddTestWeights("input", {1}, {1.0f}); - RunValidationAndConversion( - node_def, error::UNIMPLEMENTED, - "The input \"input\" for Relu6 must be a tensor, at my_relu6"); - } - { - // Clip tensor values and set quantization ranges, ok. - Reset(); - AddTestTensor("input", {1, 2, 3}); - RunValidationAndConversion(node_def); - TRT_TensorOrWeights output; - TF_EXPECT_OK(GetTensorOrWeights("my_relu6", &output)); - EXPECT_TRUE(output.is_tensor()); - auto ranges = quantization_ranges(); - EXPECT_EQ(ranges[output.tensor()], 6.0f); - - const DataVec input_data{ - {"input", test::AsTensor({-100, -1, 0, 3, 5, 9})}}; - DataVec output_data{{"my_relu6", ConstructTensor(6)}}; - BuildAndRun(input_data, &output_data); - EXPECT_THAT(GetSpanForData(output_data[0]), - ElementsAre(0, 0, 0, 3, 5, 6)); - } -} - template void TestConvertSquare(OpConverterTest* test) { test->Reset(); @@ -2255,13 +2213,23 @@ TEST_F(OpConverterTest, ConvertActivation) { "The input \"input\" for Relu must be a tensor, at my_act"); } + constexpr float kAlpha = 0.2f; + // Get nodedef for activation layer. auto get_act_nodedef = [](string op_name) -> NodeDef { Scope s = Scope::NewRootScope(); auto input = ops::Placeholder(s.WithOpName("input"), DT_FLOAT); - if (op_name == "Relu") { + if (op_name == "LeakyRelu") { + // LeakyRelu does not have a C++ API + NodeDef node_def = MakeNodeDef("my_act", "LeakyRelu", {"input"}); + (*node_def.mutable_attr())["alpha"].set_f(kAlpha); + return node_def; + } else if (op_name == "Relu") { auto act = ops::Relu(s.WithOpName("my_act"), input); return act.operation.node()->def(); + } else if (op_name == "Relu6") { + auto act = ops::Relu6(s.WithOpName("my_act"), input); + return act.operation.node()->def(); } else if (op_name == "Sigmoid") { auto act = ops::Sigmoid(s.WithOpName("my_act"), input); return act.operation.node()->def(); @@ -2274,8 +2242,12 @@ TEST_F(OpConverterTest, ConvertActivation) { }; // Get expected output for activation layer. auto get_act_output = [](string op_name, float input) -> float { - if (op_name == "Relu") { + if (op_name == "LeakyRelu") { + return (input > 0.0f) ? input : input * kAlpha; + } else if (op_name == "Relu") { return (input > 0.0f) ? input : 0.0f; + } else if (op_name == "Relu6") { + return std::min(std::max(input, 0.0f), 6.0f); } else if (op_name == "Sigmoid") { return 1.0f / (1.0f + std::exp(-input)); } else if (op_name == "Tanh") { @@ -2286,7 +2258,8 @@ TEST_F(OpConverterTest, ConvertActivation) { }; // Ok. - for (string op_name : {"Relu", "Sigmoid", "Tanh"}) { + for (const string& op_name : + {"LeakyRelu", "Relu", "Relu6", "Sigmoid", "Tanh"}) { Reset(); NodeDef node_def = get_act_nodedef(op_name); AddTestTensor("input", {1, 2, 3}); @@ -2295,6 +2268,11 @@ TEST_F(OpConverterTest, ConvertActivation) { TF_EXPECT_OK(GetTensorOrWeights("my_act", &output)); EXPECT_TRUE(output.is_tensor()); ExpectTrtDimsEqualsArray({1, 2, 3}, output.tensor()->getDimensions()); + if (op_name == "Relu6") { + // Relu6 should set quantization range automatically. + auto ranges = quantization_ranges(); + EXPECT_EQ(ranges[output.tensor()], 6.0f); + } const std::vector input = {-100, -2, -1, 0, 1, 100}; const DataVec input_data{{"input", test::AsTensor(input)}}; -- GitLab From ad09e7584b824378782b1c7a2f67e6ccb1b83c18 Mon Sep 17 00:00:00 2001 From: David Majnemer Date: Thu, 21 Feb 2019 17:44:41 -0800 Subject: [PATCH 308/766] [XLA] Add sqrt and rsqrt opcodes Pow(x, y) acts like std::pow which complicates trying to use Pow(x, 0.5) to implement sqrt. Pow(-0, 0.5) is +0 but Sqrt(-0) should be -0. As such, it makes sense to provide an explicit Sqrt and Rsqrt. PiperOrigin-RevId: 235105846 --- .../compiler/tests/matrix_band_part_test.py | 5 + tensorflow/compiler/xla/client/lib/math.cc | 4 - tensorflow/compiler/xla/client/lib/math.h | 6 -- .../xla/client/lib/math_exhaustive_test.cc | 5 +- tensorflow/compiler/xla/client/xla_builder.cc | 6 ++ tensorflow/compiler/xla/client/xla_builder.h | 8 ++ .../xla/service/algebraic_simplifier.cc | 18 ++++ .../xla/service/algebraic_simplifier_test.cc | 36 +++++++ .../xla/service/batchnorm_expander.cc | 11 +- .../compiler/xla/service/dfs_hlo_visitor.h | 6 ++ .../xla/service/elemental_ir_emitter.cc | 102 ++++++++++++------ .../xla/service/elemental_ir_emitter.h | 11 ++ .../service/gpu/cudnn_batchnorm_rewriter.cc | 9 +- .../xla/service/gpu/elemental_ir_emitter.cc | 10 ++ .../xla/service/gpu/elemental_ir_emitter.h | 6 ++ .../compiler/xla/service/hlo_cost_analysis.cc | 7 +- .../xla/service/hlo_evaluator_typed_visitor.h | 23 +++- .../compiler/xla/service/hlo_graph_dumper.cc | 2 + .../compiler/xla/service/hlo_instruction.cc | 12 +++ tensorflow/compiler/xla/service/hlo_opcode.h | 2 + tensorflow/compiler/xla/service/hlo_parser.cc | 2 + .../xla/service/instruction_fusion.cc | 2 + .../compiler/xla/service/layout_assignment.cc | 2 + .../compiler/xla/service/pattern_matcher.h | 2 + .../compiler/xla/service/shape_inference.cc | 2 + 25 files changed, 233 insertions(+), 66 deletions(-) diff --git a/tensorflow/compiler/tests/matrix_band_part_test.py b/tensorflow/compiler/tests/matrix_band_part_test.py index c61965b97f..0eec070a90 100644 --- a/tensorflow/compiler/tests/matrix_band_part_test.py +++ b/tensorflow/compiler/tests/matrix_band_part_test.py @@ -167,6 +167,11 @@ class MatrixBandPartTest(xla_test.XLATestCase, parameterized.TestCase): }, ) def testMatrixBandPart(self, batch_shape, rows, cols): + # TODO(b/125505881): Disabled due to LLVM backend crash. + if self.device == 'XLA_CPU' and cols == 7 and rows == 1 and batch_shape == [ + 1, 3, 2 + ]: + pass for dtype in self.float_types: with self.cached_session(): mat = np.ones(batch_shape + [rows, cols]).astype(dtype) diff --git a/tensorflow/compiler/xla/client/lib/math.cc b/tensorflow/compiler/xla/client/lib/math.cc index 19d98d1001..f3fe3d0b5e 100644 --- a/tensorflow/compiler/xla/client/lib/math.cc +++ b/tensorflow/compiler/xla/client/lib/math.cc @@ -107,10 +107,6 @@ XlaOp IsNegZero(XlaOp operand) { }); } -XlaOp Sqrt(XlaOp operand) { return Pow(operand, ScalarLike(operand, 0.5)); } - -XlaOp Rsqrt(XlaOp operand) { return Pow(operand, ScalarLike(operand, -0.5)); } - XlaOp Square(XlaOp operand) { return operand * operand; } XlaOp Reciprocal(XlaOp operand) { return ScalarLike(operand, 1.0) / operand; } diff --git a/tensorflow/compiler/xla/client/lib/math.h b/tensorflow/compiler/xla/client/lib/math.h index b036fa299d..71a3acedce 100644 --- a/tensorflow/compiler/xla/client/lib/math.h +++ b/tensorflow/compiler/xla/client/lib/math.h @@ -37,12 +37,6 @@ XlaOp IsNegZero(XlaOp operand); // std::nextafter(from, to) would. XlaOp NextAfter(XlaOp from, XlaOp to); -// Computes the square root of 'operand'. -XlaOp Sqrt(XlaOp operand); - -// Computes the reciprocal of the square root of 'operand'. -XlaOp Rsqrt(XlaOp operand); - // Computes the square of 'operand'. XlaOp Square(XlaOp operand); diff --git a/tensorflow/compiler/xla/client/lib/math_exhaustive_test.cc b/tensorflow/compiler/xla/client/lib/math_exhaustive_test.cc index f4ed0db56f..7f423d54db 100644 --- a/tensorflow/compiler/xla/client/lib/math_exhaustive_test.cc +++ b/tensorflow/compiler/xla/client/lib/math_exhaustive_test.cc @@ -27,7 +27,8 @@ namespace { using Eigen::half; struct Testcase { - Testcase(string name, XlaOp (*op)(XlaOp), float (*host_op)(float)) + Testcase(string name, const std::function& op, + float (*host_op)(float)) : name(name), op(op), host_op(host_op) {} Testcase& set_tolerance(float abs_err, float rel_err) { @@ -68,7 +69,7 @@ struct Testcase { } string name; - XlaOp (*op)(XlaOp); + std::function op; float (*host_op)(float); ErrorSpec error{0.01, 0.01}; diff --git a/tensorflow/compiler/xla/client/xla_builder.cc b/tensorflow/compiler/xla/client/xla_builder.cc index 9b7c01a727..16381155c3 100644 --- a/tensorflow/compiler/xla/client/xla_builder.cc +++ b/tensorflow/compiler/xla/client/xla_builder.cc @@ -3287,6 +3287,12 @@ XlaOp Real(const XlaOp& operand) { XlaOp Imag(const XlaOp& operand) { return operand.builder()->UnaryOp(HloOpcode::kImag, operand); } +XlaOp Sqrt(const XlaOp& operand) { + return operand.builder()->UnaryOp(HloOpcode::kSqrt, operand); +} +XlaOp Rsqrt(const XlaOp& operand) { + return operand.builder()->UnaryOp(HloOpcode::kRsqrt, operand); +} XlaOp Pow(const XlaOp& lhs, const XlaOp& rhs, absl::Span broadcast_dimensions) { diff --git a/tensorflow/compiler/xla/client/xla_builder.h b/tensorflow/compiler/xla/client/xla_builder.h index b5bdb75998..129e516742 100644 --- a/tensorflow/compiler/xla/client/xla_builder.h +++ b/tensorflow/compiler/xla/client/xla_builder.h @@ -910,6 +910,8 @@ class XlaBuilder { friend XlaOp Tanh(const XlaOp& operand); friend XlaOp Real(const XlaOp& operand); friend XlaOp Imag(const XlaOp& operand); + friend XlaOp Sqrt(const XlaOp& operand); + friend XlaOp Rsqrt(const XlaOp& operand); friend XlaOp Pow(const XlaOp& lhs, const XlaOp& rhs, absl::Span broadcast_dimensions); friend XlaOp IsFinite(const XlaOp& operand); @@ -1638,6 +1640,12 @@ XlaOp Real(const XlaOp& operand); // Enqueues an imaginary-part instruction onto the computation. XlaOp Imag(const XlaOp& operand); +// Enqueues a sqrt computation onto the computation. +XlaOp Sqrt(const XlaOp& operand); + +// Enqueues a rsqrt computation onto the computation. +XlaOp Rsqrt(const XlaOp& operand); + // Enqueues a lhs^rhs computation onto the computation. XlaOp Pow(const XlaOp& lhs, const XlaOp& rhs, absl::Span broadcast_dimensions = {}); diff --git a/tensorflow/compiler/xla/service/algebraic_simplifier.cc b/tensorflow/compiler/xla/service/algebraic_simplifier.cc index bd17e96106..d566062e74 100644 --- a/tensorflow/compiler/xla/service/algebraic_simplifier.cc +++ b/tensorflow/compiler/xla/service/algebraic_simplifier.cc @@ -1031,6 +1031,24 @@ Status AlgebraicSimplifierVisitor::HandleDivide(HloInstruction* divide) { divide->shape(), HloOpcode::kMultiply, a, new_power)); } + // A/sqrt(B) => A*rsqrt(X). + if (Match(divide, m::Divide(m::Op(&a), m::Sqrt(m::Op(&b))))) { + auto* rsqrt = computation_->AddInstruction( + HloInstruction::CreateUnary(divide->shape(), HloOpcode::kRsqrt, b)); + return ReplaceWithNewInstruction( + divide, HloInstruction::CreateBinary(rsqrt->shape(), + HloOpcode::kMultiply, a, rsqrt)); + } + + // A/rsqrt(B) => A*sqrt(B). + if (Match(divide, m::Divide(m::Op(&a), m::Rsqrt(m::Op(&b))))) { + auto* sqrt = computation_->AddInstruction( + HloInstruction::CreateUnary(divide->shape(), HloOpcode::kSqrt, b)); + return ReplaceWithNewInstruction( + divide, HloInstruction::CreateBinary(sqrt->shape(), + HloOpcode::kMultiply, a, sqrt)); + } + // Simplifying integral division would produce unexpected results. if (ShapeUtil::ElementIsIntegral(divide->shape())) { return Status::OK(); diff --git a/tensorflow/compiler/xla/service/algebraic_simplifier_test.cc b/tensorflow/compiler/xla/service/algebraic_simplifier_test.cc index af03fcb100..06f6206a3b 100644 --- a/tensorflow/compiler/xla/service/algebraic_simplifier_test.cc +++ b/tensorflow/compiler/xla/service/algebraic_simplifier_test.cc @@ -4879,5 +4879,41 @@ TEST_F(AlgebraicSimplifierTest, DividedByConstantInstructionWithoutLayout) { EXPECT_THAT(root, GmockMatch(m::Multiply())); } +// Test that 1/sqrt(X) is simplified to rsqrt(X). +TEST_F(AlgebraicSimplifierTest, RecipSqrt) { + const char* kModuleStr = R"( + HloModule m + test { + p0 = f32[] parameter(0) + p1 = f32[] parameter(1) + sqrt = f32[] sqrt(p0) + ROOT div = f32[] divide(p1, sqrt) + } + )"; + TF_ASSERT_OK_AND_ASSIGN(auto m, ParseAndReturnVerifiedModule(kModuleStr)); + ASSERT_TRUE(AlgebraicSimplifier(default_options_).Run(m.get()).ValueOrDie()); + EXPECT_THAT(m->entry_computation()->root_instruction(), + GmockMatch(m::MultiplyAnyOrder(m::Parameter(1), + m::Rsqrt(m::Parameter(0))))); +} + +// Test that 1/rsqrt(X) is simplified to sqrt(X). +TEST_F(AlgebraicSimplifierTest, RecipRsqrt) { + const char* kModuleStr = R"( + HloModule m + test { + p0 = f32[] parameter(0) + p1 = f32[] parameter(1) + rsqrt = f32[] rsqrt(p0) + ROOT div = f32[] divide(p1, rsqrt) + } + )"; + TF_ASSERT_OK_AND_ASSIGN(auto m, ParseAndReturnVerifiedModule(kModuleStr)); + ASSERT_TRUE(AlgebraicSimplifier(default_options_).Run(m.get()).ValueOrDie()); + EXPECT_THAT(m->entry_computation()->root_instruction(), + GmockMatch(m::MultiplyAnyOrder(m::Parameter(1), + m::Sqrt(m::Parameter(0))))); +} + } // namespace } // namespace xla diff --git a/tensorflow/compiler/xla/service/batchnorm_expander.cc b/tensorflow/compiler/xla/service/batchnorm_expander.cc index e5f5c3edb2..620876c264 100644 --- a/tensorflow/compiler/xla/service/batchnorm_expander.cc +++ b/tensorflow/compiler/xla/service/batchnorm_expander.cc @@ -95,15 +95,8 @@ class BatchNormExpanderVisitor : public DfsHloVisitorWithDefault { HloInstruction* operand, const std::function)>& add_instruction) { - HloInstruction* exponent = add_instruction(HloInstruction::CreateBroadcast( - operand->shape(), - add_instruction(HloInstruction::CreateConvert( - ShapeUtil::MakeShape(operand->shape().element_type(), {}), - add_instruction(HloInstruction::CreateConstant( - LiteralUtil::CreateR0(-0.5f))))), - {})); - return HloInstruction::CreateBinary(operand->shape(), HloOpcode::kPower, - operand, exponent); + return HloInstruction::CreateUnary(operand->shape(), HloOpcode::kRsqrt, + operand); } std::unique_ptr Mean( diff --git a/tensorflow/compiler/xla/service/dfs_hlo_visitor.h b/tensorflow/compiler/xla/service/dfs_hlo_visitor.h index af039776b7..2f7fddb96d 100644 --- a/tensorflow/compiler/xla/service/dfs_hlo_visitor.h +++ b/tensorflow/compiler/xla/service/dfs_hlo_visitor.h @@ -103,6 +103,12 @@ class DfsHloVisitorBase { virtual Status HandlePower(HloInstructionPtr hlo) { return HandleElementwiseBinary(hlo); } + virtual Status HandleSqrt(HloInstructionPtr hlo) { + return HandleElementwiseUnary(hlo); + } + virtual Status HandleRsqrt(HloInstructionPtr hlo) { + return HandleElementwiseUnary(hlo); + } virtual Status HandleConvolution(HloInstructionPtr hlo) = 0; virtual Status HandleFft(HloInstructionPtr fft) = 0; virtual Status HandleTriangularSolve(HloInstructionPtr hlo) = 0; diff --git a/tensorflow/compiler/xla/service/elemental_ir_emitter.cc b/tensorflow/compiler/xla/service/elemental_ir_emitter.cc index 808929be75..a62a743802 100644 --- a/tensorflow/compiler/xla/service/elemental_ir_emitter.cc +++ b/tensorflow/compiler/xla/service/elemental_ir_emitter.cc @@ -423,6 +423,10 @@ StatusOr ElementalIrEmitter::EmitFloatUnaryOp( return EmitSin(op->shape().element_type(), operand_value); case HloOpcode::kTanh: return EmitTanh(op->shape().element_type(), operand_value); + case HloOpcode::kSqrt: + return EmitSqrt(op->shape().element_type(), operand_value); + case HloOpcode::kRsqrt: + return EmitRsqrt(op->shape().element_type(), operand_value); case HloOpcode::kFloor: return llvm_ir::EmitCallToIntrinsic(llvm::Intrinsic::floor, {operand_value}, @@ -655,6 +659,20 @@ StatusOr ElementalIrEmitter::EmitComplexUnaryOp( EmitComposeComplex(op, FDiv(EmitExtractReal(operand_value), cplx_abs), FDiv(EmitExtractImag(operand_value), cplx_abs))); } + case HloOpcode::kSqrt: { + auto a = EmitExtractReal(operand_value); + auto b = EmitExtractImag(operand_value); + auto c = llvm::ConstantFP::get(a->getType(), 0.5); + auto d = llvm::ConstantFP::get(b->getType(), 0.0); + return EmitComplexPower(op, a, b, c, d); + } + case HloOpcode::kRsqrt: { + auto a = EmitExtractReal(operand_value); + auto b = EmitExtractImag(operand_value); + auto c = llvm::ConstantFP::get(a->getType(), -0.5); + auto d = llvm::ConstantFP::get(b->getType(), 0.0); + return EmitComplexPower(op, a, b, c, d); + } case HloOpcode::kNegate: return EmitComposeComplex(op, FNeg(EmitExtractReal(operand_value)), FNeg(EmitExtractImag(operand_value))); @@ -738,6 +756,43 @@ StatusOr ElementalIrEmitter::EmitFloatBinaryOp( } } +// (a+bi)^(c+di) = +// (a*a+b*b)^(0.5c) * exp(-d*atan2(b,a)) * (cos(q) + i*sin(q)), +// where q = c*atan2(b,a)+0.5d*ln(a*a+b*b) +StatusOr ElementalIrEmitter::EmitComplexPower( + const HloInstruction* op, llvm::Value* a, llvm::Value* b, llvm::Value* c, + llvm::Value* d) { + PrimitiveType component_type = + primitive_util::ComplexComponentType(op->shape().element_type()); + auto aa_p_bb = FAdd(FMul(a, a), FMul(b, b)); + auto zero = llvm::ConstantFP::get(a->getType(), 0); + auto one_half = llvm::ConstantFP::get(a->getType(), 0.5); + auto one = llvm::ConstantFP::get(a->getType(), 1); + auto half_c = FMul(one_half, c); + + TF_ASSIGN_OR_RETURN(auto aa_p_bb_to_half_c, + EmitPow(component_type, aa_p_bb, half_c)); + + auto neg_d = FNeg(d); + TF_ASSIGN_OR_RETURN(auto arg_lhs, EmitAtan2(component_type, b, a)); + auto neg_d_arg_lhs = FMul(neg_d, arg_lhs); + TF_ASSIGN_OR_RETURN(auto e_to_neg_d_arg_lhs, + EmitExp(component_type, neg_d_arg_lhs)); + auto coeff = FMul(aa_p_bb_to_half_c, e_to_neg_d_arg_lhs); + TF_ASSIGN_OR_RETURN(auto ln_aa_p_bb, EmitLog(component_type, aa_p_bb)); + auto half_d = FMul(one_half, d); + auto q = FAdd(FMul(c, arg_lhs), FMul(half_d, ln_aa_p_bb)); + TF_ASSIGN_OR_RETURN(auto cos_q, EmitCos(component_type, q)); + TF_ASSIGN_OR_RETURN(auto sin_q, EmitSin(component_type, q)); + // 0^c is 0 if d is 0 and c > 0. 0^0 is defined to be 1.0, see + // Branch Cuts for Complex Elementary Functions or Much Ado About + // Nothing's Sign Bit, W. Kahan, Section 10. + return Select( + And(And(FCmpOEQ(aa_p_bb, zero), FCmpOEQ(d, zero)), FCmpOLE(zero, c)), + EmitComposeComplex(op, Select(FCmpOEQ(zero, c), one, zero), zero), + EmitComposeComplex(op, FMul(coeff, cos_q), FMul(coeff, sin_q))); +} + StatusOr ElementalIrEmitter::EmitComplexBinaryOp( const HloInstruction* op, llvm::Value* lhs_value, llvm::Value* rhs_value) { switch (op->opcode()) { @@ -804,42 +859,11 @@ StatusOr ElementalIrEmitter::EmitComplexBinaryOp( EmitExtractImag(rhs_value), b_)); case HloOpcode::kPower: { - // (a+bi)^(c+di) = - // (a*a+b*b)^(0.5c) * exp(-d*atan2(b,a)) * (cos(q) + i*sin(q)), - // where q = c*atan2(b,a)+0.5d*ln(a*a+b*b) - PrimitiveType component_type = - primitive_util::ComplexComponentType(op->shape().element_type()); auto a = EmitExtractReal(lhs_value); auto b = EmitExtractImag(lhs_value); auto c = EmitExtractReal(rhs_value); auto d = EmitExtractImag(rhs_value); - auto aa_p_bb = FAdd(FMul(a, a), FMul(b, b)); - auto zero = llvm::ConstantFP::get(a->getType(), 0); - auto one_half = llvm::ConstantFP::get(a->getType(), 0.5); - auto one = llvm::ConstantFP::get(a->getType(), 1); - auto half_c = FMul(one_half, c); - - TF_ASSIGN_OR_RETURN(auto aa_p_bb_to_half_c, - EmitPow(component_type, aa_p_bb, half_c)); - - auto neg_d = FNeg(d); - TF_ASSIGN_OR_RETURN(auto arg_lhs, EmitAtan2(component_type, b, a)); - auto neg_d_arg_lhs = FMul(neg_d, arg_lhs); - TF_ASSIGN_OR_RETURN(auto e_to_neg_d_arg_lhs, - EmitExp(component_type, neg_d_arg_lhs)); - auto coeff = FMul(aa_p_bb_to_half_c, e_to_neg_d_arg_lhs); - TF_ASSIGN_OR_RETURN(auto ln_aa_p_bb, EmitLog(component_type, aa_p_bb)); - auto half_d = FMul(one_half, d); - auto q = FAdd(FMul(c, arg_lhs), FMul(half_d, ln_aa_p_bb)); - TF_ASSIGN_OR_RETURN(auto cos_q, EmitCos(component_type, q)); - TF_ASSIGN_OR_RETURN(auto sin_q, EmitSin(component_type, q)); - // 0^c is 0 if d is 0 and c > 0. 0^0 is defined to be 1.0, see - // Branch Cuts for Complex Elementary Functions or Much Ado About - // Nothing's Sign Bit, W. Kahan, Section 10. - return Select( - And(And(FCmpOEQ(aa_p_bb, zero), FCmpOEQ(d, zero)), FCmpOLE(zero, c)), - EmitComposeComplex(op, Select(FCmpOEQ(zero, c), one, zero), zero), - EmitComposeComplex(op, FMul(coeff, cos_q), FMul(coeff, sin_q))); + return EmitComplexPower(op, a, b, c, d); } default: return Unimplemented("binary complex op '%s'", @@ -1052,6 +1076,18 @@ StatusOr ElementalIrEmitter::EmitLog1p(PrimitiveType prim_type, return Select(x_is_small, for_small_x, for_large_x); } +StatusOr ElementalIrEmitter::EmitSqrt(PrimitiveType prim_type, + llvm::Value* value) { + return llvm_ir::EmitCallToIntrinsic(llvm::Intrinsic::sqrt, {value}, + {value->getType()}, b_); +} + +StatusOr ElementalIrEmitter::EmitRsqrt(PrimitiveType prim_type, + llvm::Value* value) { + TF_ASSIGN_OR_RETURN(auto sqrt, EmitSqrt(prim_type, value)); + return FDiv(llvm::ConstantFP::get(sqrt->getType(), 1.0), sqrt); +} + StatusOr ElementalIrEmitter::EmitSin(PrimitiveType prim_type, llvm::Value* value) { return llvm_ir::EmitCallToIntrinsic(llvm::Intrinsic::sin, {value}, @@ -2188,8 +2224,10 @@ llvm_ir::ElementGenerator ElementalIrEmitter::MakeElementGenerator( case HloOpcode::kNegate: case HloOpcode::kNot: case HloOpcode::kReal: + case HloOpcode::kRsqrt: case HloOpcode::kSign: case HloOpcode::kSin: + case HloOpcode::kSqrt: case HloOpcode::kTanh: return [this, hlo, &operand_to_generator]( const IrArray::Index& index) -> StatusOr { diff --git a/tensorflow/compiler/xla/service/elemental_ir_emitter.h b/tensorflow/compiler/xla/service/elemental_ir_emitter.h index 7d360fe38c..819465f1e5 100644 --- a/tensorflow/compiler/xla/service/elemental_ir_emitter.h +++ b/tensorflow/compiler/xla/service/elemental_ir_emitter.h @@ -119,6 +119,12 @@ class ElementalIrEmitter : public IrBuilderMixin { virtual StatusOr EmitLog(PrimitiveType prim_type, llvm::Value* value); + virtual StatusOr EmitSqrt(PrimitiveType prim_type, + llvm::Value* value); + + virtual StatusOr EmitRsqrt(PrimitiveType prim_type, + llvm::Value* value); + virtual StatusOr EmitLog1p(PrimitiveType prim_type, llvm::Value* value); @@ -211,6 +217,11 @@ class ElementalIrEmitter : public IrBuilderMixin { const HloModuleConfig& hlo_module_config_; private: + // Computes the complex power function, returns (a + i*b)^(c + i*d). + StatusOr EmitComplexPower(const HloInstruction* op, + llvm::Value* a, llvm::Value* b, + llvm::Value* c, llvm::Value* d); + // Returns a ElementGenerator for an RNG HloInstruction using the Philox // random number generation algorithm. llvm_ir::ElementGenerator MakePhiloxRngElementGenerator( diff --git a/tensorflow/compiler/xla/service/gpu/cudnn_batchnorm_rewriter.cc b/tensorflow/compiler/xla/service/gpu/cudnn_batchnorm_rewriter.cc index 6028950652..2cceb0422d 100644 --- a/tensorflow/compiler/xla/service/gpu/cudnn_batchnorm_rewriter.cc +++ b/tensorflow/compiler/xla/service/gpu/cudnn_batchnorm_rewriter.cc @@ -188,13 +188,8 @@ Status Visitor::HandleBatchNormGrad(HloInstruction* batch_norm) { computation_->AddInstruction(HloInstruction::CreateBroadcast( batch_norm->operand(3)->shape(), epsilon, {})))); HloInstruction* inverse_stddev = - computation_->AddInstruction(HloInstruction::CreateBinary( - var_plus_epsilon->shape(), HloOpcode::kPower, var_plus_epsilon, - computation_->AddInstruction(HloInstruction::CreateBroadcast( - var_plus_epsilon->shape(), - computation_->AddInstruction(HloInstruction::CreateConstant( - LiteralUtil::CreateR0(-.5))), - {})))); + computation_->AddInstruction(HloInstruction::CreateUnary( + var_plus_epsilon->shape(), HloOpcode::kRsqrt, var_plus_epsilon)); std::vector operands(batch_norm->operands().begin(), batch_norm->operands().end()); diff --git a/tensorflow/compiler/xla/service/gpu/elemental_ir_emitter.cc b/tensorflow/compiler/xla/service/gpu/elemental_ir_emitter.cc index 80ddb3e5cd..dd74788a0e 100644 --- a/tensorflow/compiler/xla/service/gpu/elemental_ir_emitter.cc +++ b/tensorflow/compiler/xla/service/gpu/elemental_ir_emitter.cc @@ -270,6 +270,16 @@ StatusOr GpuElementalIrEmitter::EmitPow(PrimitiveType prim_type, prim_type); } +StatusOr GpuElementalIrEmitter::EmitSqrt(PrimitiveType prim_type, + llvm::Value* value) { + return EmitLibdeviceMathCall("__nv_sqrt", {value}, {prim_type}, prim_type); +} + +StatusOr GpuElementalIrEmitter::EmitRsqrt(PrimitiveType prim_type, + llvm::Value* value) { + return EmitLibdeviceMathCall("__nv_rsqrt", {value}, {prim_type}, prim_type); +} + StatusOr GpuElementalIrEmitter::EmitAtan2(PrimitiveType prim_type, llvm::Value* lhs, llvm::Value* rhs) { diff --git a/tensorflow/compiler/xla/service/gpu/elemental_ir_emitter.h b/tensorflow/compiler/xla/service/gpu/elemental_ir_emitter.h index e8b56a39ce..2aedbf05ab 100644 --- a/tensorflow/compiler/xla/service/gpu/elemental_ir_emitter.h +++ b/tensorflow/compiler/xla/service/gpu/elemental_ir_emitter.h @@ -76,6 +76,12 @@ class GpuElementalIrEmitter : public ElementalIrEmitter { StatusOr EmitExpm1(PrimitiveType prim_type, llvm::Value* value) override; + StatusOr EmitSqrt(PrimitiveType prim_type, + llvm::Value* value) override; + + StatusOr EmitRsqrt(PrimitiveType prim_type, + llvm::Value* value) override; + StatusOr EmitPow(PrimitiveType prim_type, llvm::Value* lhs, llvm::Value* rhs) override; diff --git a/tensorflow/compiler/xla/service/hlo_cost_analysis.cc b/tensorflow/compiler/xla/service/hlo_cost_analysis.cc index 29ac263c5f..6d9e01e3a7 100644 --- a/tensorflow/compiler/xla/service/hlo_cost_analysis.cc +++ b/tensorflow/compiler/xla/service/hlo_cost_analysis.cc @@ -91,9 +91,10 @@ Status HloCostAnalysis::HandleElementwiseOp( auto opcode = hlo_instruction->opcode(); // We treat transcendental operations separately since one transcendental // operation can correspond to several floating point ops. - if (opcode == HloOpcode::kExp || opcode == HloOpcode::kPower || - opcode == HloOpcode::kTanh || opcode == HloOpcode::kSin || - opcode == HloOpcode::kCos) { + if (opcode == HloOpcode::kExp || opcode == HloOpcode::kLog || + opcode == HloOpcode::kPower || opcode == HloOpcode::kSqrt || + opcode == HloOpcode::kRsqrt || opcode == HloOpcode::kTanh || + opcode == HloOpcode::kSin || opcode == HloOpcode::kCos) { current_properties_[kTranscendentalsKey] = computation_count; } else { // Note: transcendental operations are considered a separate category from diff --git a/tensorflow/compiler/xla/service/hlo_evaluator_typed_visitor.h b/tensorflow/compiler/xla/service/hlo_evaluator_typed_visitor.h index d516a6258c..2d8a578985 100644 --- a/tensorflow/compiler/xla/service/hlo_evaluator_typed_visitor.h +++ b/tensorflow/compiler/xla/service/hlo_evaluator_typed_visitor.h @@ -329,10 +329,10 @@ class HloEvaluatorTypedVisitor : public DfsHloVisitorWithDefault { template < typename NativeT, typename std::enable_if::value>::type* = nullptr> - Status HandleLog1p(HloInstruction* expm1) { + Status HandleLog1p(HloInstruction* log1p) { TF_ASSIGN_OR_RETURN( - parent_->evaluated_[expm1], - ElementWiseUnaryOp(expm1, [](ElementwiseT elem_operand) { + parent_->evaluated_[log1p], + ElementWiseUnaryOp(log1p, [](ElementwiseT elem_operand) { return std::log1p(elem_operand); })); return Status::OK(); @@ -664,6 +664,23 @@ class HloEvaluatorTypedVisitor : public DfsHloVisitorWithDefault { return Status::OK(); } + Status HandleSqrt(HloInstruction* sqrt) override { + TF_ASSIGN_OR_RETURN(parent_->evaluated_[sqrt], + ElementWiseUnaryOp(sqrt, [](ElementwiseT elem_operand) { + return std::sqrt(elem_operand); + })); + return Status::OK(); + } + + Status HandleRsqrt(HloInstruction* rsqrt) override { + TF_ASSIGN_OR_RETURN( + parent_->evaluated_[rsqrt], + ElementWiseUnaryOp(rsqrt, [](ElementwiseT elem_operand) { + return static_cast(1) / std::sqrt(elem_operand); + })); + return Status::OK(); + } + template ::value>::type* = nullptr> Status HandleRemainder(HloInstruction* remainder) { diff --git a/tensorflow/compiler/xla/service/hlo_graph_dumper.cc b/tensorflow/compiler/xla/service/hlo_graph_dumper.cc index 49300b3ffe..254f66021d 100644 --- a/tensorflow/compiler/xla/service/hlo_graph_dumper.cc +++ b/tensorflow/compiler/xla/service/hlo_graph_dumper.cc @@ -953,6 +953,7 @@ ColorScheme HloDotDumper::GetInstructionColor(const HloInstruction* instr) { case HloOpcode::kRemainder: case HloOpcode::kRng: case HloOpcode::kRoundNearestAfz: + case HloOpcode::kRsqrt: case HloOpcode::kSelect: case HloOpcode::kShiftLeft: case HloOpcode::kShiftRightArithmetic: @@ -961,6 +962,7 @@ ColorScheme HloDotDumper::GetInstructionColor(const HloInstruction* instr) { case HloOpcode::kSin: case HloOpcode::kSlice: case HloOpcode::kSort: + case HloOpcode::kSqrt: case HloOpcode::kSubtract: case HloOpcode::kTanh: // De-emphasize scalar-shaped elementwise ops -- they're generally diff --git a/tensorflow/compiler/xla/service/hlo_instruction.cc b/tensorflow/compiler/xla/service/hlo_instruction.cc index 0667a4d9c2..6c47bb8935 100644 --- a/tensorflow/compiler/xla/service/hlo_instruction.cc +++ b/tensorflow/compiler/xla/service/hlo_instruction.cc @@ -631,8 +631,10 @@ HloInstruction::CreateGetTupleElement(const Shape& shape, case HloOpcode::kNot: case HloOpcode::kNegate: case HloOpcode::kReal: + case HloOpcode::kRsqrt: case HloOpcode::kSign: case HloOpcode::kSin: + case HloOpcode::kSqrt: case HloOpcode::kTanh: break; default: @@ -1312,8 +1314,10 @@ std::unique_ptr HloInstruction::CloneWithNewOperands( case HloOpcode::kNot: case HloOpcode::kNegate: case HloOpcode::kReal: + case HloOpcode::kRsqrt: case HloOpcode::kSign: case HloOpcode::kSin: + case HloOpcode::kSqrt: case HloOpcode::kTanh: CHECK_EQ(new_operands.size(), 1); clone = CreateUnary(shape, opcode_, new_operands[0]); @@ -1673,12 +1677,14 @@ bool HloInstruction::IdenticalSlowPath( case HloOpcode::kReshape: case HloOpcode::kReplicaId: case HloOpcode::kRoundNearestAfz: + case HloOpcode::kRsqrt: case HloOpcode::kSelect: case HloOpcode::kShiftLeft: case HloOpcode::kShiftRightArithmetic: case HloOpcode::kShiftRightLogical: case HloOpcode::kSign: case HloOpcode::kSin: + case HloOpcode::kSqrt: case HloOpcode::kSubtract: case HloOpcode::kTanh: case HloOpcode::kTuple: @@ -2033,8 +2039,10 @@ bool HloInstruction::IsElementwiseImpl( case HloOpcode::kNegate: case HloOpcode::kReal: case HloOpcode::kReducePrecision: + case HloOpcode::kRsqrt: case HloOpcode::kSign: case HloOpcode::kSin: + case HloOpcode::kSqrt: case HloOpcode::kTanh: CHECK_EQ(1, operand_count()); return true; @@ -2471,6 +2479,10 @@ Status HloInstruction::Visit(DfsHloVisitorBase* visitor) { return visitor->HandleCos(this); case HloOpcode::kSin: return visitor->HandleSin(this); + case HloOpcode::kSqrt: + return visitor->HandleSqrt(this); + case HloOpcode::kRsqrt: + return visitor->HandleRsqrt(this); case HloOpcode::kReal: return visitor->HandleReal(this); case HloOpcode::kImag: diff --git a/tensorflow/compiler/xla/service/hlo_opcode.h b/tensorflow/compiler/xla/service/hlo_opcode.h index ddb08a58c1..c571664c81 100644 --- a/tensorflow/compiler/xla/service/hlo_opcode.h +++ b/tensorflow/compiler/xla/service/hlo_opcode.h @@ -123,6 +123,7 @@ namespace xla { V(kReverse, "reverse", 1) \ V(kRng, "rng", kHloOpcodeIsVariadic) \ V(kRoundNearestAfz, "round-nearest-afz", 1) \ + V(kRsqrt, "rsqrt", 1) \ V(kScatter, "scatter", 3) \ V(kSelect, "select", 3) \ V(kSelectAndScatter, "select-and-scatter", 3) \ @@ -135,6 +136,7 @@ namespace xla { V(kSin, "sine", 1) \ V(kSlice, "slice", 1) \ V(kSort, "sort", kHloOpcodeIsVariadic) \ + V(kSqrt, "sqrt", 1) \ V(kSubtract, "subtract", 2) \ V(kTanh, "tanh", 1) \ V(kTrace, "trace", 1) \ diff --git a/tensorflow/compiler/xla/service/hlo_parser.cc b/tensorflow/compiler/xla/service/hlo_parser.cc index f448571082..4aa1090f48 100644 --- a/tensorflow/compiler/xla/service/hlo_parser.cc +++ b/tensorflow/compiler/xla/service/hlo_parser.cc @@ -725,8 +725,10 @@ bool HloParser::ParseInstructionRhs(HloComputation::Builder* builder, case HloOpcode::kNot: case HloOpcode::kNegate: case HloOpcode::kReal: + case HloOpcode::kRsqrt: case HloOpcode::kSign: case HloOpcode::kSin: + case HloOpcode::kSqrt: case HloOpcode::kTanh: { if (!ParseOperands(&operands, /*expected_size=*/1) || !ParseAttributes(attrs)) { diff --git a/tensorflow/compiler/xla/service/instruction_fusion.cc b/tensorflow/compiler/xla/service/instruction_fusion.cc index a5767774f2..f5770eee22 100644 --- a/tensorflow/compiler/xla/service/instruction_fusion.cc +++ b/tensorflow/compiler/xla/service/instruction_fusion.cc @@ -151,11 +151,13 @@ bool IsAlwaysDuplicable(const HloInstruction& instruction) { case HloOpcode::kReduceWindow: case HloOpcode::kRemainder: case HloOpcode::kRng: + case HloOpcode::kRsqrt: case HloOpcode::kScatter: case HloOpcode::kSelectAndScatter: case HloOpcode::kSend: case HloOpcode::kSendDone: case HloOpcode::kSort: + case HloOpcode::kSqrt: case HloOpcode::kTanh: case HloOpcode::kTrace: case HloOpcode::kTriangularSolve: diff --git a/tensorflow/compiler/xla/service/layout_assignment.cc b/tensorflow/compiler/xla/service/layout_assignment.cc index 2dd9e05550..aa791ea195 100644 --- a/tensorflow/compiler/xla/service/layout_assignment.cc +++ b/tensorflow/compiler/xla/service/layout_assignment.cc @@ -2058,6 +2058,7 @@ bool LayoutAssignment::InstructionCanChangeLayout( case HloOpcode::kRemainder: case HloOpcode::kReverse: case HloOpcode::kRoundNearestAfz: + case HloOpcode::kRsqrt: case HloOpcode::kScatter: case HloOpcode::kSelect: case HloOpcode::kSelectAndScatter: @@ -2068,6 +2069,7 @@ bool LayoutAssignment::InstructionCanChangeLayout( case HloOpcode::kSin: case HloOpcode::kSlice: case HloOpcode::kSort: + case HloOpcode::kSqrt: case HloOpcode::kSubtract: case HloOpcode::kTanh: case HloOpcode::kTriangularSolve: diff --git a/tensorflow/compiler/xla/service/pattern_matcher.h b/tensorflow/compiler/xla/service/pattern_matcher.h index 9e3d106021..7164bfc4cd 100644 --- a/tensorflow/compiler/xla/service/pattern_matcher.h +++ b/tensorflow/compiler/xla/service/pattern_matcher.h @@ -2053,10 +2053,12 @@ XLA_UNOP_PATTERN(RecvDone) XLA_UNOP_PATTERN(ReducePrecision) XLA_UNOP_PATTERN(Reshape) XLA_UNOP_PATTERN(Reverse) +XLA_UNOP_PATTERN(Rsqrt) XLA_UNOP_PATTERN(SendDone) XLA_UNOP_PATTERN(Sign) XLA_UNOP_PATTERN(Sin) XLA_UNOP_PATTERN(Slice) +XLA_UNOP_PATTERN(Sqrt) XLA_UNOP_PATTERN(Tanh) XLA_UNOP_PATTERN(Transpose) #undef XLA_UNOP_PATTERN diff --git a/tensorflow/compiler/xla/service/shape_inference.cc b/tensorflow/compiler/xla/service/shape_inference.cc index 3f4456c1bb..431c2e3a5e 100644 --- a/tensorflow/compiler/xla/service/shape_inference.cc +++ b/tensorflow/compiler/xla/service/shape_inference.cc @@ -262,6 +262,8 @@ StatusOr InferWindowOutputShape(const Shape& base_shape, case HloOpcode::kExpm1: case HloOpcode::kLog: case HloOpcode::kLog1p: + case HloOpcode::kRsqrt: + case HloOpcode::kSqrt: case HloOpcode::kTanh: if (!ShapeUtil::ElementIsFloating(shape) && !ShapeUtil::ElementIsComplex(shape)) { -- GitLab From d73b6bd7f8bb0f20324be1ae68e1bc942db8d741 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 21 Feb 2019 17:52:44 -0800 Subject: [PATCH 309/766] Typo fix. PiperOrigin-RevId: 235106933 --- tensorflow/tools/ci_build/builds/pip_new.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/tools/ci_build/builds/pip_new.sh b/tensorflow/tools/ci_build/builds/pip_new.sh index cff1fe6657..ed687bf846 100755 --- a/tensorflow/tools/ci_build/builds/pip_new.sh +++ b/tensorflow/tools/ci_build/builds/pip_new.sh @@ -135,7 +135,7 @@ update_bazel_flags() { fi # Clean up whitespaces BAZEL_BUILD_FLAGS=$(str_strip "${BAZEL_BUILD_FLAGS}") - BAZEL_TEST_FLAGS=$(str_strp "${BAZEL_TEST_FLAGS}") + BAZEL_TEST_FLAGS=$(str_strip "${BAZEL_TEST_FLAGS}") # Cleaned bazel flags echo "Bazel build flags (cleaned):\n" "${BAZEL_BUILD_FLAGS}" echo "Bazel test flags (cleaned):\n" "${BAZEL_TEST_FLAGS}" -- GitLab From cbe8172cba4f2ed05a0980585ba48ef257922c18 Mon Sep 17 00:00:00 2001 From: Jonathan Hseu Date: Thu, 21 Feb 2019 17:53:20 -0800 Subject: [PATCH 310/766] Fix Windows test failures from TPU code PiperOrigin-RevId: 235107020 --- tensorflow/contrib/cmake/python_modules.txt | 1 + tensorflow/contrib/tpu/profiler/BUILD | 2 + tensorflow/core/platform/windows/error.h | 3 + tensorflow/core/platform/windows/wide_char.h | 3 + tensorflow/core/profiler/rpc/client/BUILD | 1 - .../profiler/rpc/client/dump_tpu_profile.cc | 3 + tensorflow/python/tpu/ops/tpu_ops.py | 784 +++++++++--------- 7 files changed, 409 insertions(+), 388 deletions(-) diff --git a/tensorflow/contrib/cmake/python_modules.txt b/tensorflow/contrib/cmake/python_modules.txt index 3d86ab9abb..fd205a4b9b 100644 --- a/tensorflow/contrib/cmake/python_modules.txt +++ b/tensorflow/contrib/cmake/python_modules.txt @@ -71,6 +71,7 @@ tensorflow/python/summary/writer tensorflow/python/tools tensorflow/python/tools/api tensorflow/python/tools/api/generator +tensorflow/python/tpu tensorflow/python/training tensorflow/python/training/tracking tensorflow/python/user_ops diff --git a/tensorflow/contrib/tpu/profiler/BUILD b/tensorflow/contrib/tpu/profiler/BUILD index 9fb29f5e17..e2ce77e118 100644 --- a/tensorflow/contrib/tpu/profiler/BUILD +++ b/tensorflow/contrib/tpu/profiler/BUILD @@ -14,11 +14,13 @@ tf_cc_binary( srcs = [ "capture_tpu_profile.cc", ], + tags = ["no_windows"], visibility = ["//visibility:public"], deps = [ ":version", "//tensorflow/core:framework_internal", "//tensorflow/core:lib", + "//tensorflow/core/platform/cloud:gcs_file_system", "//tensorflow/core/profiler/rpc/client:capture_profile", ], ) diff --git a/tensorflow/core/platform/windows/error.h b/tensorflow/core/platform/windows/error.h index ba643a0fa8..22875ac2bc 100644 --- a/tensorflow/core/platform/windows/error.h +++ b/tensorflow/core/platform/windows/error.h @@ -19,6 +19,9 @@ limitations under the License. #include #include +// Windows.h #defines ERROR, but it is also used in +// tensorflow/core/util/event.proto +#undef ERROR namespace tensorflow { namespace internal { diff --git a/tensorflow/core/platform/windows/wide_char.h b/tensorflow/core/platform/windows/wide_char.h index 1b86abc3fa..5aca95454f 100644 --- a/tensorflow/core/platform/windows/wide_char.h +++ b/tensorflow/core/platform/windows/wide_char.h @@ -17,6 +17,9 @@ limitations under the License. #define TENSORFLOW_CORE_PLATFORM_WINDOWS_WIDE_CHAR_H_ #include +// Windows.h #defines ERROR, but it is also used in +// tensorflow/core/util/event.proto +#undef ERROR #include #include "tensorflow/core/platform/types.h" diff --git a/tensorflow/core/profiler/rpc/client/BUILD b/tensorflow/core/profiler/rpc/client/BUILD index 14dd865a17..6036ff9701 100644 --- a/tensorflow/core/profiler/rpc/client/BUILD +++ b/tensorflow/core/profiler/rpc/client/BUILD @@ -19,7 +19,6 @@ tf_cuda_library( "//tensorflow/core:grpc_services", "//tensorflow/core:lib", "//tensorflow/core/distributed_runtime/rpc:grpc_util", - "//tensorflow/core/platform/cloud:gcs_file_system", "//tensorflow/core/profiler:protos_all_cc", ], ) diff --git a/tensorflow/core/profiler/rpc/client/dump_tpu_profile.cc b/tensorflow/core/profiler/rpc/client/dump_tpu_profile.cc index f0675c4cf4..ed65c110c9 100644 --- a/tensorflow/core/profiler/rpc/client/dump_tpu_profile.cc +++ b/tensorflow/core/profiler/rpc/client/dump_tpu_profile.cc @@ -26,6 +26,9 @@ limitations under the License. #include "tensorflow/core/lib/strings/strcat.h" #include "tensorflow/core/platform/env.h" #include "tensorflow/core/platform/protobuf.h" +// Windows.h #defines ERROR, but it is also used in +// tensorflow/core/util/event.proto +#undef ERROR #include "tensorflow/core/profiler/op_profile.pb.h" #include "tensorflow/core/profiler/rpc/client/trace_events_to_json.h" #include "tensorflow/core/profiler/trace_events.pb.h" diff --git a/tensorflow/python/tpu/ops/tpu_ops.py b/tensorflow/python/tpu/ops/tpu_ops.py index e6e411cbc9..38dd2734ac 100644 --- a/tensorflow/python/tpu/ops/tpu_ops.py +++ b/tensorflow/python/tpu/ops/tpu_ops.py @@ -19,400 +19,410 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -import platform - from tensorflow.python.framework import dtypes from tensorflow.python.framework import ops from tensorflow.python.ops import array_ops +# pylint: disable=wildcard-import,unused-import +from tensorflow.python.ops import gen_tpu_ops +from tensorflow.python.ops.gen_tpu_ops import * +# pylint: enable=wildcard-import,unused-import from tensorflow.python.platform import tf_logging as logging from tensorflow.python.tpu import tpu_function -if platform.system() != "Windows": - # pylint: disable=wildcard-import,unused-import,g-import-not-at-top - from tensorflow.python.ops import gen_tpu_ops - from tensorflow.python.ops.gen_tpu_ops import * - # pylint: enable=wildcard-import,unused-import,g-import-not-at-top - - def _create_default_group_assignment(): - num_shards = tpu_function.get_tpu_context().number_of_shards - if num_shards is None: - logging.warning( - "cross_replica_sum should be used within a tpu_shard_context, but " - "got unset number_of_shards. Assuming 1.") - num_shards = 1 - group_assignment = [list(range(num_shards))] - return group_assignment - - def all_to_all(x, - concat_dimension, - split_dimension, - split_count, - group_assignment=None, - name=None): - """Exchange data across TPU replicas. - - Args: - x: The local tensor. - concat_dimension: The dimension number to concatenate. - split_dimension: The dimension number to split. - split_count: The number of splits, this number must equal to the sub-group - size(group_assignment.get_shape()[1]) - group_assignment: Optional 2d int32 lists with shape [num_groups, - num_replicas_per_group]. `group_assignment[i]` represents the replica - ids in the ith subgroup. - name: Optional op name. - - Returns: - A `Tensor` which is concatenated by data from different replicas. - """ - if group_assignment is None: - group_assignment = _create_default_group_assignment() - return gen_tpu_ops.all_to_all( - x, - group_assignment, - concat_dimension=concat_dimension, - split_dimension=split_dimension, - split_count=split_count, - name=name) - - @ops.RegisterGradient("AllToAll") - def _all_to_all_grad(op, grad): - # The gradient of a all-to-all is also a all-to-all but the - # split_dimension and concat_dimension is swapped. - # The graident with respect to group_assignment is None. - return [ - gen_tpu_ops.all_to_all( - grad, - op.inputs[1], - concat_dimension=op.get_attr("split_dimension"), - split_dimension=op.get_attr("concat_dimension"), - split_count=op.get_attr("split_count")), None - ] - - def cross_replica_sum(x, group_assignment=None, name=None): - """Sum the input tensor across replicas according to group_assignment. - - Args: - x: The local tensor to the sum. - group_assignment: Optional 2d int32 lists with shape [num_groups, - num_replicas_per_group]. `group_assignment[i]` represents the replica - ids in the ith subgroup. - name: Optional op name. - - Returns: - A `Tensor` which is summed across replicas. - """ - if group_assignment is None: - group_assignment = _create_default_group_assignment() - - return gen_tpu_ops.cross_replica_sum(x, group_assignment, name=name) - - def collective_permute(x, source_target_pairs, name=None): - """Permute the input tensor across replicas given source_target_pairs. - - For each source_target_pair , we send replica a's input to replica b. - Each replica id must only appear once in the source column. Also it must - only appear once in the target column. - For the replica id not in the target column, this op returns a zero tensor - with the same shape and dtype of the input x. - - For example, suppose there are 4 TPU instances: `[A, B, C, D]`. Passing - source_target_pairs=`[[0,1],[1,2],[2,3]]` gets the outputs: - `[0, A, B, C]`. - - Args: - x: The local tensor to be permuted. - source_target_pairs: 2d int lists with shape [num_pairs, 2]. - source_target_pairs[i][0] represents the source replica id and - source_target_pairs[i][1] represents the target replica id. - name: Optional op name. - - Returns: - A `Tensor` which is permuted. - """ - return gen_tpu_ops.collective_permute(x, source_target_pairs, name=name) - - @ops.RegisterGradient("CollectivePermute") - def _collective_permute_grad(op, grad): - # The gradient of a collective permute operation is also a collective - # permute, but with source/target pairs reversed. The gradient with respect - # to input argument `source_target_pairs` is `None`. - source_target_pairs = op.inputs[1][:, ::-1] - return [gen_tpu_ops.collective_permute(grad, source_target_pairs), None] - - @ops.RegisterGradient("CrossReplicaSum") - def _cross_replica_sum_grad(op, grad): - # The gradient of a cross replica sum is also a cross-replica sum. - # The gradient with respect to group_assignment is None. - return [gen_tpu_ops.cross_replica_sum(grad, op.inputs[1]), None] - - # This extra type checking exists to give a more helpful error message in - # the common case that uint8 and int64 values are infed. Remove when both - # types are supported. - - _SUPPORTED_INFEED_DTYPES = set([ - dtypes.bool, dtypes.int32, dtypes.int64, dtypes.bfloat16, dtypes.float32, - dtypes.complex64, dtypes.uint32 - ]) - - @ops.RegisterGradient("TPUEmbeddingActivations") - def _embedding_activations_grad(activations_op, grad_wrt_activations): - """Saves the gradient of embedding activations ops in a graph collection.""" - g = ops.get_default_graph() - table_id = activations_op.get_attr("table_id") - lookup_id = activations_op.get_attr("lookup_id") - table_gradients = g.get_collection_ref( - "tpu_embedding_gradients_table_%d" % table_id) - - if not table_gradients: - raise RuntimeError( - "Gradients for TPUEmbedding have been generated in non-training mode." - "This is not expected. Consider putting your Optimizer.minimize code " - "behind the training mode condition check. For Estimator, you can " - "do \n\n" - " if mode == tf.estimator.ModeKeys.TRAIN:\n" - " train_op = opt.minimize(loss)\n" - "\n") - - table_gradients[lookup_id] = array_ops.identity(grad_wrt_activations) - return [ - # RegisterGradient requires that value be returned for all inputs. Since - # the first argument (tpu_gradient_variable_{table_name}) has shape [1], - # we will return zeros(shape=[1]). The actual gradient w.r.t. the - # embedding activations (grad_wrt_activations) has the same shape as the - # activations returned by embedding_activations. - array_ops.zeros(arg.shape, dtype=dtypes.float32) - for arg in activations_op.inputs - ] - - def infeed_dequeue(dtype, shape, name=None): - """A placeholder op for a value that will be fed into the computation. - - Args: - dtype: A `tf.DType`. The type of elements in the tensor. - shape: A `tf.TensorShape` or list of `ints`. The shape of the tensor. - name: A name for the operation (optional). - - Returns: - A `Tensor` of type `dtype`. - A tensor that will be provided using the infeed mechanism. - - Raises: - TypeError: If 'dtype` is not a supported infeed type. - """ + +def _create_default_group_assignment(): + num_shards = tpu_function.get_tpu_context().number_of_shards + if num_shards is None: + logging.warning( + "cross_replica_sum should be used within a tpu_shard_context, but " + "got unset number_of_shards. Assuming 1.") + num_shards = 1 + group_assignment = [list(range(num_shards))] + return group_assignment + + +def all_to_all(x, + concat_dimension, + split_dimension, + split_count, + group_assignment=None, + name=None): + """Exchange data across TPU replicas. + + Args: + x: The local tensor. + concat_dimension: The dimension number to concatenate. + split_dimension: The dimension number to split. + split_count: The number of splits, this number must equal to the sub-group + size(group_assignment.get_shape()[1]) + group_assignment: Optional 2d int32 lists with shape [num_groups, + num_replicas_per_group]. `group_assignment[i]` represents the replica + ids in the ith subgroup. + name: Optional op name. + + Returns: + A `Tensor` which is concatenated by data from different replicas. + """ + if group_assignment is None: + group_assignment = _create_default_group_assignment() + return gen_tpu_ops.all_to_all( + x, + group_assignment, + concat_dimension=concat_dimension, + split_dimension=split_dimension, + split_count=split_count, + name=name) + + +@ops.RegisterGradient("AllToAll") +def _all_to_all_grad(op, grad): + # The gradient of a all-to-all is also a all-to-all but the + # split_dimension and concat_dimension is swapped. + # The graident with respect to group_assignment is None. + return [ + gen_tpu_ops.all_to_all( + grad, + op.inputs[1], + concat_dimension=op.get_attr("split_dimension"), + split_dimension=op.get_attr("concat_dimension"), + split_count=op.get_attr("split_count")), None + ] + + +def cross_replica_sum(x, group_assignment=None, name=None): + """Sum the input tensor across replicas according to group_assignment. + + Args: + x: The local tensor to the sum. + group_assignment: Optional 2d int32 lists with shape [num_groups, + num_replicas_per_group]. `group_assignment[i]` represents the replica + ids in the ith subgroup. + name: Optional op name. + + Returns: + A `Tensor` which is summed across replicas. + """ + if group_assignment is None: + group_assignment = _create_default_group_assignment() + + return gen_tpu_ops.cross_replica_sum(x, group_assignment, name=name) + + +def collective_permute(x, source_target_pairs, name=None): + """Permute the input tensor across replicas given source_target_pairs. + + For each source_target_pair , we send replica a's input to replica b. + Each replica id must only appear once in the source column. Also it must + only appear once in the target column. + For the replica id not in the target column, this op returns a zero tensor + with the same shape and dtype of the input x. + + For example, suppose there are 4 TPU instances: `[A, B, C, D]`. Passing + source_target_pairs=`[[0,1],[1,2],[2,3]]` gets the outputs: + `[0, A, B, C]`. + + Args: + x: The local tensor to be permuted. + source_target_pairs: 2d int lists with shape [num_pairs, 2]. + source_target_pairs[i][0] represents the source replica id and + source_target_pairs[i][1] represents the target replica id. + name: Optional op name. + + Returns: + A `Tensor` which is permuted. + """ + return gen_tpu_ops.collective_permute(x, source_target_pairs, name=name) + + +@ops.RegisterGradient("CollectivePermute") +def _collective_permute_grad(op, grad): + # The gradient of a collective permute operation is also a collective + # permute, but with source/target pairs reversed. The gradient with respect + # to input argument `source_target_pairs` is `None`. + source_target_pairs = op.inputs[1][:, ::-1] + return [gen_tpu_ops.collective_permute(grad, source_target_pairs), None] + + +@ops.RegisterGradient("CrossReplicaSum") +def _cross_replica_sum_grad(op, grad): + # The gradient of a cross replica sum is also a cross-replica sum. + # The gradient with respect to group_assignment is None. + return [gen_tpu_ops.cross_replica_sum(grad, op.inputs[1]), None] + + +# This extra type checking exists to give a more helpful error message in +# the common case that uint8 and int64 values are infed. Remove when both +# types are supported. + +_SUPPORTED_INFEED_DTYPES = set([ + dtypes.bool, dtypes.int32, dtypes.int64, dtypes.bfloat16, dtypes.float32, + dtypes.complex64, dtypes.uint32 +]) + + +@ops.RegisterGradient("TPUEmbeddingActivations") +def _embedding_activations_grad(activations_op, grad_wrt_activations): + """Saves the gradient of embedding activations ops in a graph collection.""" + g = ops.get_default_graph() + table_id = activations_op.get_attr("table_id") + lookup_id = activations_op.get_attr("lookup_id") + table_gradients = g.get_collection_ref( + "tpu_embedding_gradients_table_%d" % table_id) + + if not table_gradients: + raise RuntimeError( + "Gradients for TPUEmbedding have been generated in non-training mode." + "This is not expected. Consider putting your Optimizer.minimize code " + "behind the training mode condition check. For Estimator, you can " + "do \n\n" + " if mode == tf.estimator.ModeKeys.TRAIN:\n" + " train_op = opt.minimize(loss)\n" + "\n") + + table_gradients[lookup_id] = array_ops.identity(grad_wrt_activations) + return [ + # RegisterGradient requires that value be returned for all inputs. Since + # the first argument (tpu_gradient_variable_{table_name}) has shape [1], + # we will return zeros(shape=[1]). The actual gradient w.r.t. the + # embedding activations (grad_wrt_activations) has the same shape as the + # activations returned by embedding_activations. + array_ops.zeros(arg.shape, dtype=dtypes.float32) + for arg in activations_op.inputs + ] + + +def infeed_dequeue(dtype, shape, name=None): + """A placeholder op for a value that will be fed into the computation. + + Args: + dtype: A `tf.DType`. The type of elements in the tensor. + shape: A `tf.TensorShape` or list of `ints`. The shape of the tensor. + name: A name for the operation (optional). + + Returns: + A `Tensor` of type `dtype`. + A tensor that will be provided using the infeed mechanism. + + Raises: + TypeError: If 'dtype` is not a supported infeed type. + """ + if dtype not in _SUPPORTED_INFEED_DTYPES: + raise TypeError( + "{} is not a supported TPU infeed type. Supported types are: " + "{}".format(dtype, list(_SUPPORTED_INFEED_DTYPES))) + + return gen_tpu_ops.infeed_dequeue(dtype, shape, name=name) + + +# pylint: disable=redefined-outer-name +def infeed_dequeue_tuple(dtypes, shapes, name=None): + """A placeholder op for values fed into the TPU simultaneously as a tuple. + + Args: + dtypes: A list of `tf.DType`s that has length `>= 1`. + The element types of each element in `outputs`. + shapes: A list of shapes (each a `tf.TensorShape` or list of `ints`). + The shapes of each tensor in `outputs`. + name: A name for the operation (optional). + + Returns: + A list of `Tensor` objects of type `dtypes`. + A list of tensors that will be provided using the infeed mechanism. + + Raises: + TypeError: If a type in 'dtypes` is not a supported infeed type. + """ + for dtype in dtypes: if dtype not in _SUPPORTED_INFEED_DTYPES: raise TypeError( "{} is not a supported TPU infeed type. Supported types are: " "{}".format(dtype, list(_SUPPORTED_INFEED_DTYPES))) - - return gen_tpu_ops.infeed_dequeue(dtype, shape, name=name) - - # pylint: disable=redefined-outer-name - def infeed_dequeue_tuple(dtypes, shapes, name=None): - """A placeholder op for values fed into the TPU simultaneously as a tuple. - - Args: - dtypes: A list of `tf.DType`s that has length `>= 1`. - The element types of each element in `outputs`. - shapes: A list of shapes (each a `tf.TensorShape` or list of `ints`). - The shapes of each tensor in `outputs`. - name: A name for the operation (optional). - - Returns: - A list of `Tensor` objects of type `dtypes`. - A list of tensors that will be provided using the infeed mechanism. - - Raises: - TypeError: If a type in 'dtypes` is not a supported infeed type. - """ - for dtype in dtypes: - if dtype not in _SUPPORTED_INFEED_DTYPES: - raise TypeError( - "{} is not a supported TPU infeed type. Supported types are: " - "{}".format(dtype, list(_SUPPORTED_INFEED_DTYPES))) - return gen_tpu_ops.infeed_dequeue_tuple(dtypes, shapes, name=name) - # pylint: enable=redefined-outer-name - - # pylint: disable=protected-access - def send_tpu_embedding_gradients(inputs, - config, - learning_rates=None, - name=None): - """A placeholder op for feeding per-sample gradients to the embedding layer. - - Args: - inputs: A TensorList of gradients with which to update embedding tables. - This argument has the same length and shapes as the return value of - RecvTPUEmbeddingActivations, but contains gradients of the model's - loss with respect to the embedding activations. The embedding tables - are updated from these gradients via the optimizers specified in the - TPU embedding configuration given to tpu.initialize_system. - config: Serialized TPUEmbeddingConfiguration proto. - learning_rates: A TensorList of float32 scalars, one for each dynamic - learning rate tag: see the comments in - //third_party/tensorflow/core/protobuf/tpu/ - optimization_parameters.proto. - Multiple tables can share the same dynamic learning rate tag as - specified in the configuration. If the learning rates for all tables - are constant, this list should be empty. - name: A name for the operation (optional). - - Returns: - A SendTPUEmbeddingGradients operation. - """ - if learning_rates is None: - learning_rates = [] - return gen_tpu_ops.send_tpu_embedding_gradients( - inputs=inputs, learning_rates=learning_rates, config=config, name=name) - - send_tpu_embedding_gradients.__doc__ = ( - gen_tpu_ops.send_tpu_embedding_gradients.__doc__) - - # pylint: disable=protected-access - def enqueue_tpu_embedding_integer_batch(batch, - device_ordinal, - mode_override=None, - name=None): - """A placeholder op for enqueueing embedding IDs to the TPU. - - Args: - batch: A list of 1D tensors, one for each embedding table, containing the - indices into the tables. - device_ordinal: The TPU device to use. Should be >= 0 and less than the - number of TPU cores in the task on which the node is placed. - mode_override: A string input that overrides the mode specified in the - TPUEmbeddingConfiguration. Supported values are {'unspecified', - 'inference', 'training', 'backward_pass_only'}. When set to - 'unspecified', the mode set in TPUEmbeddingConfiguration is used, - otherwise mode_override is used (optional). - name: A name for the operation (optional). - - Returns: - An EnqueueTPUEmbeddingIntegerBatch operation. - """ - if mode_override is None: - mode_override = "unspecified" - return gen_tpu_ops.enqueue_tpu_embedding_integer_batch( - batch=batch, - device_ordinal=device_ordinal, - mode_override=mode_override, - name=name) - - enqueue_tpu_embedding_integer_batch.__doc__ = ( - gen_tpu_ops.enqueue_tpu_embedding_integer_batch.__doc__) - - # pylint: disable=protected-access - def enqueue_tpu_embedding_sparse_batch(sample_indices, - embedding_indices, - aggregation_weights, - device_ordinal, - combiners=None, - mode_override=None, - name=None): - """A placeholder op for enqueueing embedding IDs to the TPU. - - Args: - sample_indices: A list of rank 1 Tensors specifying the training example - and feature to which the corresponding embedding_indices and - aggregation_weights values belong. sample_indices[i] must equal b * nf + - f, where nf is the number of features from the corresponding table, f is - in [0, nf), and b is in [0, batch size). - embedding_indices: A list of rank 1 Tensors, indices into the embedding - tables. - aggregation_weights: A list of rank 1 Tensors containing per sample -- - i.e. per (training example, feature) -- aggregation weights. - device_ordinal: The TPU device to use. Should be >= 0 and less than the - number of TPU cores in the task on which the node is placed. - combiners: A list of string scalars, one for each embedding table that - specify how to normalize the embedding activations after weighted - summation. Supported combiners are 'mean', 'sum', or 'sqrtn'. It is - invalid to have the sum of the weights be 0 for 'mean' or the sum of the - squared weights be 0 for 'sqrtn'. If combiners isn't passed, the default - is to use 'sum' for all tables (optional). - mode_override: A string input that overrides the mode specified in the - TPUEmbeddingConfiguration. Supported values are {'unspecified', - 'inference', 'training', 'backward_pass_only'}. When set to - 'unspecified', the mode set in TPUEmbeddingConfiguration is used, - otherwise mode_override is used (optional). - name: A name for the operation (optional). - - Returns: - An EnqueueTPUEmbeddingSparseBatch operation. - """ - if mode_override is None: - mode_override = "unspecified" - return gen_tpu_ops.enqueue_tpu_embedding_sparse_batch( - sample_indices=sample_indices, - embedding_indices=embedding_indices, - aggregation_weights=aggregation_weights, - device_ordinal=device_ordinal, - combiners=combiners, - mode_override=mode_override, - name=name) - - enqueue_tpu_embedding_sparse_batch.__doc__ = ( - gen_tpu_ops.enqueue_tpu_embedding_sparse_batch.__doc__) - - # pylint: disable=protected-access - def enqueue_tpu_embedding_sparse_tensor_batch(sample_indices, - embedding_indices, - aggregation_weights, - table_ids, - device_ordinal, - combiners=None, - mode_override=None, - name=None): - """A placeholder op for enqueueing embedding IDs to the TPU. - - Args: - sample_indices: A list of rank 1 Tensors specifying the training example - to which the corresponding embedding_indices and aggregation_weights - values belong. It corresponds to sp_ids.indices[:,0] in - embedding_lookup_sparse(). - embedding_indices: A list of rank 1 Tensors, indices into the embedding - tables. It corresponds to sp_ids.values in embedding_lookup_sparse(). - aggregation_weights: A list of rank 1 Tensors containing per training - example aggregation weights. It corresponds to sp_weights.values in - embedding_lookup_sparse(). - table_ids: A list of integers specifying the identifier of the embedding - table (offset of TableDescriptor in the TPUEmbeddingConfiguration) to - lookup the corresponding input. The ith input is looked up using - table_ids[i]. The size of the table_ids list must be equal to that of - sample_indices, embedding_indices and aggregation_weights. - device_ordinal: The TPU device to use. Should be >= 0 and less than the - number of TPU cores in the task on which the node is placed. - combiners: A list of string scalars, one for each embedding table that - specify how to normalize the embedding activations after weighted - summation. Supported combiners are 'mean', 'sum', or 'sqrtn'. It is - invalid to have the sum of the weights be 0 for 'mean' or the sum of the - squared weights be 0 for 'sqrtn'. If combiners isn't passed, the default - is to use 'sum' for all tables (optional). - mode_override: A string input that overrides the mode specified in the - TPUEmbeddingConfiguration. Supported values are {'unspecified', - 'inference', 'training', 'backward_pass_only'}. When set to - 'unspecified', the mode set in TPUEmbeddingConfiguration is used, - otherwise mode_override is used (optional). - name: A name for the operation (optional). - - Returns: - An EnqueueTPUEmbeddingSparseTensorBatch operation. - """ - if mode_override is None: - mode_override = "unspecified" - return gen_tpu_ops.enqueue_tpu_embedding_sparse_tensor_batch( - sample_indices=sample_indices, - embedding_indices=embedding_indices, - aggregation_weights=aggregation_weights, - table_ids=table_ids, - device_ordinal=device_ordinal, - combiners=combiners, - mode_override=mode_override, - name=name) - - enqueue_tpu_embedding_sparse_tensor_batch.__doc__ = ( - gen_tpu_ops.enqueue_tpu_embedding_sparse_tensor_batch.__doc__) - -else: - # We have already built the appropriate libraries into the binary via CMake - # if we have built contrib, so we don't need this - pass + return gen_tpu_ops.infeed_dequeue_tuple(dtypes, shapes, name=name) +# pylint: enable=redefined-outer-name + + +# pylint: disable=protected-access +def send_tpu_embedding_gradients(inputs, + config, + learning_rates=None, + name=None): + """A placeholder op for feeding per-sample gradients to the embedding layer. + + Args: + inputs: A TensorList of gradients with which to update embedding tables. + This argument has the same length and shapes as the return value of + RecvTPUEmbeddingActivations, but contains gradients of the model's + loss with respect to the embedding activations. The embedding tables + are updated from these gradients via the optimizers specified in the + TPU embedding configuration given to tpu.initialize_system. + config: Serialized TPUEmbeddingConfiguration proto. + learning_rates: A TensorList of float32 scalars, one for each dynamic + learning rate tag: see the comments in + //third_party/tensorflow/core/protobuf/tpu/ + optimization_parameters.proto. + Multiple tables can share the same dynamic learning rate tag as + specified in the configuration. If the learning rates for all tables + are constant, this list should be empty. + name: A name for the operation (optional). + + Returns: + A SendTPUEmbeddingGradients operation. + """ + if learning_rates is None: + learning_rates = [] + return gen_tpu_ops.send_tpu_embedding_gradients( + inputs=inputs, learning_rates=learning_rates, config=config, name=name) + + +send_tpu_embedding_gradients.__doc__ = ( + gen_tpu_ops.send_tpu_embedding_gradients.__doc__) + + +# pylint: disable=protected-access +def enqueue_tpu_embedding_integer_batch(batch, + device_ordinal, + mode_override=None, + name=None): + """A placeholder op for enqueueing embedding IDs to the TPU. + + Args: + batch: A list of 1D tensors, one for each embedding table, containing the + indices into the tables. + device_ordinal: The TPU device to use. Should be >= 0 and less than the + number of TPU cores in the task on which the node is placed. + mode_override: A string input that overrides the mode specified in the + TPUEmbeddingConfiguration. Supported values are {'unspecified', + 'inference', 'training', 'backward_pass_only'}. When set to + 'unspecified', the mode set in TPUEmbeddingConfiguration is used, + otherwise mode_override is used (optional). + name: A name for the operation (optional). + + Returns: + An EnqueueTPUEmbeddingIntegerBatch operation. + """ + if mode_override is None: + mode_override = "unspecified" + return gen_tpu_ops.enqueue_tpu_embedding_integer_batch( + batch=batch, + device_ordinal=device_ordinal, + mode_override=mode_override, + name=name) + + +enqueue_tpu_embedding_integer_batch.__doc__ = ( + gen_tpu_ops.enqueue_tpu_embedding_integer_batch.__doc__) + + +# pylint: disable=protected-access +def enqueue_tpu_embedding_sparse_batch(sample_indices, + embedding_indices, + aggregation_weights, + device_ordinal, + combiners=None, + mode_override=None, + name=None): + """A placeholder op for enqueueing embedding IDs to the TPU. + + Args: + sample_indices: A list of rank 1 Tensors specifying the training example + and feature to which the corresponding embedding_indices and + aggregation_weights values belong. sample_indices[i] must equal b * nf + + f, where nf is the number of features from the corresponding table, f is + in [0, nf), and b is in [0, batch size). + embedding_indices: A list of rank 1 Tensors, indices into the embedding + tables. + aggregation_weights: A list of rank 1 Tensors containing per sample -- + i.e. per (training example, feature) -- aggregation weights. + device_ordinal: The TPU device to use. Should be >= 0 and less than the + number of TPU cores in the task on which the node is placed. + combiners: A list of string scalars, one for each embedding table that + specify how to normalize the embedding activations after weighted + summation. Supported combiners are 'mean', 'sum', or 'sqrtn'. It is + invalid to have the sum of the weights be 0 for 'mean' or the sum of the + squared weights be 0 for 'sqrtn'. If combiners isn't passed, the default + is to use 'sum' for all tables (optional). + mode_override: A string input that overrides the mode specified in the + TPUEmbeddingConfiguration. Supported values are {'unspecified', + 'inference', 'training', 'backward_pass_only'}. When set to + 'unspecified', the mode set in TPUEmbeddingConfiguration is used, + otherwise mode_override is used (optional). + name: A name for the operation (optional). + + Returns: + An EnqueueTPUEmbeddingSparseBatch operation. + """ + if mode_override is None: + mode_override = "unspecified" + return gen_tpu_ops.enqueue_tpu_embedding_sparse_batch( + sample_indices=sample_indices, + embedding_indices=embedding_indices, + aggregation_weights=aggregation_weights, + device_ordinal=device_ordinal, + combiners=combiners, + mode_override=mode_override, + name=name) + + +enqueue_tpu_embedding_sparse_batch.__doc__ = ( + gen_tpu_ops.enqueue_tpu_embedding_sparse_batch.__doc__) + + +# pylint: disable=protected-access +def enqueue_tpu_embedding_sparse_tensor_batch(sample_indices, + embedding_indices, + aggregation_weights, + table_ids, + device_ordinal, + combiners=None, + mode_override=None, + name=None): + """A placeholder op for enqueueing embedding IDs to the TPU. + + Args: + sample_indices: A list of rank 1 Tensors specifying the training example + to which the corresponding embedding_indices and aggregation_weights + values belong. It corresponds to sp_ids.indices[:,0] in + embedding_lookup_sparse(). + embedding_indices: A list of rank 1 Tensors, indices into the embedding + tables. It corresponds to sp_ids.values in embedding_lookup_sparse(). + aggregation_weights: A list of rank 1 Tensors containing per training + example aggregation weights. It corresponds to sp_weights.values in + embedding_lookup_sparse(). + table_ids: A list of integers specifying the identifier of the embedding + table (offset of TableDescriptor in the TPUEmbeddingConfiguration) to + lookup the corresponding input. The ith input is looked up using + table_ids[i]. The size of the table_ids list must be equal to that of + sample_indices, embedding_indices and aggregation_weights. + device_ordinal: The TPU device to use. Should be >= 0 and less than the + number of TPU cores in the task on which the node is placed. + combiners: A list of string scalars, one for each embedding table that + specify how to normalize the embedding activations after weighted + summation. Supported combiners are 'mean', 'sum', or 'sqrtn'. It is + invalid to have the sum of the weights be 0 for 'mean' or the sum of the + squared weights be 0 for 'sqrtn'. If combiners isn't passed, the default + is to use 'sum' for all tables (optional). + mode_override: A string input that overrides the mode specified in the + TPUEmbeddingConfiguration. Supported values are {'unspecified', + 'inference', 'training', 'backward_pass_only'}. When set to + 'unspecified', the mode set in TPUEmbeddingConfiguration is used, + otherwise mode_override is used (optional). + name: A name for the operation (optional). + + Returns: + An EnqueueTPUEmbeddingSparseTensorBatch operation. + """ + if mode_override is None: + mode_override = "unspecified" + return gen_tpu_ops.enqueue_tpu_embedding_sparse_tensor_batch( + sample_indices=sample_indices, + embedding_indices=embedding_indices, + aggregation_weights=aggregation_weights, + table_ids=table_ids, + device_ordinal=device_ordinal, + combiners=combiners, + mode_override=mode_override, + name=name) + + +enqueue_tpu_embedding_sparse_tensor_batch.__doc__ = ( + gen_tpu_ops.enqueue_tpu_embedding_sparse_tensor_batch.__doc__) -- GitLab From fbb9d4ea863c051ac6748d95166c321fe4bc7cdf Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 21 Feb 2019 18:32:10 -0800 Subject: [PATCH 311/766] Change the Keras model_iteration() to correctly infer the size of validation datasets when validation_steps is None. PiperOrigin-RevId: 235112144 --- tensorflow/python/keras/BUILD | 14 +++++ .../python/keras/engine/training_arrays.py | 10 +++ .../keras/engine/training_arrays_test.py | 61 +++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 tensorflow/python/keras/engine/training_arrays_test.py diff --git a/tensorflow/python/keras/BUILD b/tensorflow/python/keras/BUILD index 931a5d6785..8743dbaa13 100755 --- a/tensorflow/python/keras/BUILD +++ b/tensorflow/python/keras/BUILD @@ -1116,6 +1116,20 @@ tf_py_test( shard_count = 4, ) +tf_py_test( + name = "training_arrays_test", + size = "small", + srcs = ["engine/training_arrays_test.py"], + additional_deps = [ + ":keras", + ":layers", + "@absl_py//absl/testing:parameterized", + "//third_party/py/numpy", + "//tensorflow/python/data/ops:dataset_ops", + "//tensorflow/python:client_testlib", + ], +) + tf_py_test( name = "training_generator_test", size = "medium", diff --git a/tensorflow/python/keras/engine/training_arrays.py b/tensorflow/python/keras/engine/training_arrays.py index e609f5d198..b6fe5f68e5 100644 --- a/tensorflow/python/keras/engine/training_arrays.py +++ b/tensorflow/python/keras/engine/training_arrays.py @@ -176,6 +176,16 @@ def model_iteration(model, # to properly reinitialize and reuse in multiple validation passes. val_iterator = None if isinstance(val_inputs, (dataset_ops.DatasetV1, dataset_ops.DatasetV2)): + if validation_steps is None: + # Because we pass an iterator feed instead of a Dataset to the eval + # model_iteration() call, it will not trigger the dataset-input path + # that determines the number of steps required. To avoid this issue, + # set validation_steps here if validation_steps is None. + validation_steps = training_utils.infer_steps_for_dataset( + val_inputs, + validation_steps, + epochs=epochs, + steps_name='validation_steps') val_iterator = _get_iterator(val_inputs, model._distribution_strategy) val_inputs = _prepare_feed_values( model, val_iterator, val_targets, val_sample_weights, ModeKeys.TEST) diff --git a/tensorflow/python/keras/engine/training_arrays_test.py b/tensorflow/python/keras/engine/training_arrays_test.py new file mode 100644 index 0000000000..daa4735c83 --- /dev/null +++ b/tensorflow/python/keras/engine/training_arrays_test.py @@ -0,0 +1,61 @@ +# Copyright 2019 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 model.fit calls with a Dataset object passed as validation_data.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np + +from tensorflow.python.data.ops import dataset_ops +from tensorflow.python.keras import keras_parameterized +from tensorflow.python.keras import testing_utils +from tensorflow.python.keras.layers import core +from tensorflow.python.platform import test + + +@keras_parameterized.run_with_all_model_types +@keras_parameterized.run_all_keras_modes +class ValidationDatasetNoLimitTest(keras_parameterized.TestCase): + + def create_dataset(self, num_samples, batch_size): + input_data = np.random.rand(num_samples, 1) + expected_data = input_data * 3 + dataset = dataset_ops.Dataset.from_tensor_slices((input_data, + expected_data)) + return dataset.shuffle(10 * batch_size).batch(batch_size) + + def test_validation_dataset_with_no_step_arg(self): + # Create a model that learns y=Mx. + layers = [core.Dense(1)] + model = testing_utils.get_model_from_layers(layers, input_shape=(1,)) + model.compile(loss="mse", optimizer="adam", metrics=["mean_absolute_error"]) + + train_dataset = self.create_dataset(num_samples=200, batch_size=10) + eval_dataset = self.create_dataset(num_samples=50, batch_size=25) + + history = model.fit(x=train_dataset, validation_data=eval_dataset, epochs=2) + evaluation = model.evaluate(x=eval_dataset) + + # If the fit call used the entire dataset, then the final val MAE error + # from the fit history should be equal to the final element in the output + # of evaluating the model on the same eval dataset. + self.assertAlmostEqual(history.history["val_mean_absolute_error"][-1], + evaluation[-1]) + + +if __name__ == "__main__": + test.main() -- GitLab From 1babcf8125f60b3ff05c6fd4c6fa73e33d9c510f Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 21 Feb 2019 18:32:15 -0800 Subject: [PATCH 312/766] objc_library will eventually merge with cc_library, for this all Apple resources will now be passed as 'data' instead of on the other attributes. END_PUBLIC For more information on the resources cleanup and rules, see: go/apple-rules:resources BEGIN_PUBLIC The "resources", "asset_catalogs", "bundles", "datamodels", "storyboards", "strings", and "xibs" attributes are directly moved to the "data" attribute. Any "structured_resources" are moved to a new apple_resource_group instance, and that is added to "data". PiperOrigin-RevId: 235112154 --- tensorflow/lite/experimental/objc/BUILD.apple | 2 +- tensorflow/lite/experimental/swift/BUILD.apple | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tensorflow/lite/experimental/objc/BUILD.apple b/tensorflow/lite/experimental/objc/BUILD.apple index 3317c33d9d..e53de173b2 100644 --- a/tensorflow/lite/experimental/objc/BUILD.apple +++ b/tensorflow/lite/experimental/objc/BUILD.apple @@ -98,7 +98,7 @@ objc_library( "tests/*.h", ]), copts = TEST_COPTS, - resources = [ + data = [ "//tensorflow/lite:testdata/add.bin", "//tensorflow/lite:testdata/add_quantized.bin", ], diff --git a/tensorflow/lite/experimental/swift/BUILD.apple b/tensorflow/lite/experimental/swift/BUILD.apple index 35875bbbe9..7528b42751 100644 --- a/tensorflow/lite/experimental/swift/BUILD.apple +++ b/tensorflow/lite/experimental/swift/BUILD.apple @@ -59,7 +59,7 @@ swift_library( objc_library( name = "TestResources", - resources = [ + data = [ "//tensorflow/lite:testdata/add.bin", "//tensorflow/lite:testdata/add_quantized.bin", "//tensorflow/lite:testdata/multi_add.bin", @@ -98,7 +98,7 @@ swift_library( objc_library( name = "TensorFlowLiteAppResources", - storyboards = glob([ + data = glob([ "TestApps/TensorFlowLiteApp/TensorFlowLiteApp/Base.lproj/*.storyboard", ]), tags = DEFAULT_TAGS, -- GitLab From 31df1ce7dee077a5acaba2ddd43959665a8ae323 Mon Sep 17 00:00:00 2001 From: Stephan Lee Date: Thu, 21 Feb 2019 18:37:46 -0800 Subject: [PATCH 313/766] Write out the Keras model configuration in TensorBoard callback. PiperOrigin-RevId: 235112772 --- tensorflow/python/keras/callbacks.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tensorflow/python/keras/callbacks.py b/tensorflow/python/keras/callbacks.py index 79bafb9559..817d47a1ad 100644 --- a/tensorflow/python/keras/callbacks.py +++ b/tensorflow/python/keras/callbacks.py @@ -1209,6 +1209,8 @@ class TensorBoard(Callback): with self._train_writer.as_default(): with summary_ops_v2.always_record_summaries(): summary_ops_v2.graph(K.get_graph()) + if self.model._is_graph_network: # pylint: disable=protected-access + summary_ops_v2.keras_model('keras', self.model, step=0) def _close_writers(self): """Close all remaining open file writers owned by this callback. -- GitLab From 52c402f79a5913bb21fc991cf7ef7841c95b7886 Mon Sep 17 00:00:00 2001 From: Yuefeng Zhou Date: Thu, 21 Feb 2019 18:57:01 -0800 Subject: [PATCH 314/766] Make collective ops work with only one replica. PiperOrigin-RevId: 235114715 --- tensorflow/python/distribute/cross_device_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/distribute/cross_device_utils.py b/tensorflow/python/distribute/cross_device_utils.py index a28628dae4..612a958ebb 100644 --- a/tensorflow/python/distribute/cross_device_utils.py +++ b/tensorflow/python/distribute/cross_device_utils.py @@ -350,7 +350,7 @@ def build_collective_reduce(input_tensors, """ group_size = len(input_tensors) * num_workers if group_size < 2: - raise ValueError('num_workers * len(input_tensors) must be 2 or greater') + return input_tensors devices = [t.device for t in input_tensors] num_devices = len(devices) group_key = collective_keys.get_group_key(devices) -- GitLab From 3fc470095db98116bb34f28c4a2f2f75627bad63 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 21 Feb 2019 19:21:37 -0800 Subject: [PATCH 315/766] Fix sort dict keys for python3. PiperOrigin-RevId: 235117320 --- tensorflow/lite/python/op_hint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/python/op_hint.py b/tensorflow/lite/python/op_hint.py index f107abab90..90c87503f4 100644 --- a/tensorflow/lite/python/op_hint.py +++ b/tensorflow/lite/python/op_hint.py @@ -1133,7 +1133,7 @@ def _get_correct_mapping(original_index, nodes): # If it is -1, return the last index. if original_index == -1: node_indices = nodes.keys() - node_indices.sort() + node_indices = sorted(node_indices) return node_indices[-1] else: return original_index -- GitLab From 796ce572939922ccbacf5b7fcb39195904de0f95 Mon Sep 17 00:00:00 2001 From: Rohan Jain Date: Thu, 21 Feb 2019 19:41:00 -0800 Subject: [PATCH 316/766] Creating a VocabularyTable, very similar to IdTableFromHashBuckets except for it doesn't take a table as input and takes an initializer. Also oov_buckets should always be > 0 for this table. PiperOrigin-RevId: 235119219 --- .../python/learn/estimators/estimator_test.py | 12 +- .../python/kernel_tests/lookup_ops_test.py | 276 ++++++++++++-- tensorflow/python/ops/lookup_ops.py | 354 +++++++++++++----- 3 files changed, 521 insertions(+), 121 deletions(-) diff --git a/tensorflow/contrib/learn/python/learn/estimators/estimator_test.py b/tensorflow/contrib/learn/python/learn/estimators/estimator_test.py index 8a461a0bd7..cbcae338a0 100644 --- a/tensorflow/contrib/learn/python/learn/estimators/estimator_test.py +++ b/tensorflow/contrib/learn/python/learn/estimators/estimator_test.py @@ -1181,14 +1181,14 @@ class EstimatorTest(test.TestCase): ] self.assertItemsEqual([expected_vocab_file], assets) graph_ops = [x.name for x in graph.get_operations()] - self.assertTrue('input_example_tensor' in graph_ops) - self.assertTrue('ParseExample/ParseExample' in graph_ops) - self.assertTrue('linear/linear/feature/matmul' in graph_ops) + self.assertIn('input_example_tensor', graph_ops) + self.assertIn('ParseExample/ParseExample', graph_ops) + self.assertIn('linear/linear/feature/matmul', graph_ops) # Since there were no transforms, both save ops are still present. - self.assertTrue('save/SaveV2/tensor_names' in graph_ops) - self.assertTrue('save_1/SaveV2/tensor_names' in graph_ops) + self.assertIn('save/SaveV2/tensor_names', graph_ops) + self.assertIn('save_1/SaveV2/tensor_names', graph_ops) # Since there were no transforms, the hash table lookup is still there. - self.assertTrue('hash_table_Lookup' in graph_ops) + self.assertIn('hash_table_Lookup/LookupTableFindV2', graph_ops) # Restore, to validate that the export was well-formed. # tag_2, tag_3 was subjected to strip_unused_nodes. diff --git a/tensorflow/python/kernel_tests/lookup_ops_test.py b/tensorflow/python/kernel_tests/lookup_ops_test.py index d7980db3db..baa1550fb4 100644 --- a/tensorflow/python/kernel_tests/lookup_ops_test.py +++ b/tensorflow/python/kernel_tests/lookup_ops_test.py @@ -587,17 +587,17 @@ class KeyValueTensorInitializerTest(test.TestCase): dtypes.int64) self.assertEqual("", init1._shared_name) table1 = lookup_ops.HashTable(init1, default_value=-1) - self.assertEquals("hash_table", table1.name) - self.assertEquals("table_scope/hash_table", - table1.resource_handle.op.name) + self.assertEqual("hash_table", table1.name) + self.assertEqual("table_scope/hash_table", + table1.resource_handle.op.name) init2 = lookup_ops.KeyValueTensorInitializer( ("brain", "salad", "surgery"), (0, 1, 2), dtypes.string, dtypes.int64) self.assertEqual("", init2._shared_name) table2 = lookup_ops.HashTable(init2, default_value=-1) - self.assertEquals("hash_table_1", table2.name) - self.assertEquals("table_scope/hash_table_1", - table2.resource_handle.op.name) + self.assertEqual("hash_table_1", table2.name) + self.assertEqual("table_scope/hash_table_1", + table2.resource_handle.op.name) def test_int64(self): with ops.Graph().as_default(), self.cached_session(): @@ -1056,7 +1056,7 @@ class InitializeTableFromFileOpTest(test.TestCase): # Initialize from file. self.evaluate(table1.initializer) - self.assertEquals(vocab_size, self.evaluate(table1.size())) + self.assertEqual(vocab_size, self.evaluate(table1.size())) vocabulary_file2 = self._createVocabFile("one_column7.txt") vocab_size = 5 @@ -1086,7 +1086,7 @@ class InitializeTableFromFileOpTest(test.TestCase): # Smaller vocab size reads only vocab_size records. self.evaluate(table3.initializer) - self.assertEquals(vocab_size, self.evaluate(table3.size())) + self.assertEqual(vocab_size, self.evaluate(table3.size())) @test_util.run_v1_only("placeholder usage") def testFeedVocabularyName(self): @@ -1166,7 +1166,7 @@ class InitializeTableFromFileOpTest(test.TestCase): out = table.lookup(input_values) self.assertAllEqual([b"brain", b"salad", b"surgery", b"UNK"], self.evaluate(out)) - self.assertEquals(vocab_size, self.evaluate(table.size())) + self.assertEqual(vocab_size, self.evaluate(table.size())) def testStringToIdTable(self): vocab_file = self._createVocabFile("feat_to_id_2.txt") @@ -1183,7 +1183,7 @@ class InitializeTableFromFileOpTest(test.TestCase): out = table.lookup(input_string) self.assertAllEqual([0, 1, 2, -1], self.evaluate(out)) - self.assertEquals(vocab_size, self.evaluate(table.size())) + self.assertEqual(vocab_size, self.evaluate(table.size())) def testInt64ToIdTable(self): vocab_file = self._createVocabFile( @@ -1200,7 +1200,7 @@ class InitializeTableFromFileOpTest(test.TestCase): out = table.lookup( constant_op.constant((42, 1, -1000, 11), dtype=dtypes.int64)) self.assertAllEqual((0, 1, 2, -1), self.evaluate(out)) - self.assertEquals(vocab_size, self.evaluate(table.size())) + self.assertEqual(vocab_size, self.evaluate(table.size())) class IdTableWithHashBucketsTest(test.TestCase): @@ -1230,7 +1230,7 @@ class IdTableWithHashBucketsTest(test.TestCase): out = table.lookup(input_string) self.assertAllEqual([0, 1, 2, 3], self.evaluate(out)) - self.assertEquals(vocab_size + oov_buckets, table.size().eval()) + self.assertEqual(vocab_size + oov_buckets, table.size().eval()) @test_util.run_deprecated_v1 def testInt32IdTableWithHashBuckets(self): @@ -1253,7 +1253,7 @@ class IdTableWithHashBucketsTest(test.TestCase): out = table.lookup(values) self.assertAllEqual([0, 1, 2, 3], self.evaluate(out)) - self.assertEquals(vocab_size + oov_buckets, table.size().eval()) + self.assertEqual(vocab_size + oov_buckets, table.size().eval()) @test_util.run_deprecated_v1 def testInt64IdTableWithHashBuckets(self): @@ -1274,7 +1274,7 @@ class IdTableWithHashBucketsTest(test.TestCase): out = table.lookup(values) self.assertAllEqual([0, 1, 2, 3], self.evaluate(out)) - self.assertEquals(vocab_size + oov_buckets, table.size().eval()) + self.assertEqual(vocab_size + oov_buckets, table.size().eval()) @test_util.run_deprecated_v1 def testStringIdTableWithOnlyHashBucket(self): @@ -1296,7 +1296,7 @@ class IdTableWithHashBucketsTest(test.TestCase): 4 # fingerprint("surgery") mod 5 ], self.evaluate(out)) - self.assertEquals(oov_buckets, table.size().eval()) + self.assertEqual(oov_buckets, table.size().eval()) @test_util.run_deprecated_v1 def testInt32IdTableWithOnlyHashBucket(self): @@ -1319,7 +1319,7 @@ class IdTableWithHashBucketsTest(test.TestCase): 2 # fingerprint("-1000") mod 5 ], self.evaluate(out)) - self.assertEquals(oov_buckets, table.size().eval()) + self.assertEqual(oov_buckets, table.size().eval()) def testFloat64IdTableWithOnlyHashBucket(self): with self.cached_session(): @@ -1367,8 +1367,8 @@ class IdTableWithHashBucketsTest(test.TestCase): out1, out2 = self.evaluate([out1, out2]) self.assertAllEqual([5, 0, 1, 2, 5], out1) self.assertAllEqual([5, 0, 1, 2, 3], out2) - self.assertEquals(vocab_size + oov_buckets, table1.size().eval()) - self.assertEquals(vocab_size + oov_buckets, table2.size().eval()) + self.assertEqual(vocab_size + oov_buckets, table1.size().eval()) + self.assertEqual(vocab_size + oov_buckets, table2.size().eval()) test_util.assert_ops_in_graph({ "table1_Lookup/hash_bucket": "StringToHashBucketFast", "table2_Lookup/hash_bucket": "StringToHashBucketStrong", @@ -1395,7 +1395,7 @@ class IdTableWithHashBucketsTest(test.TestCase): out1 = table1.lookup(input_string_1) self.assertAllEqual([0, 1, 2, 3], self.evaluate(out1)) - self.assertEquals(vocab_size + oov_buckets, table1.size().eval()) + self.assertEqual(vocab_size + oov_buckets, table1.size().eval()) with self.cached_session(): default_value = -1 @@ -1415,7 +1415,7 @@ class IdTableWithHashBucketsTest(test.TestCase): out2 = table2.lookup(input_string_2) self.assertAllEqual([3, 1, 3], self.evaluate(out2)) - self.assertEquals(vocab_size + oov_buckets, table2.size().eval()) + self.assertEqual(vocab_size + oov_buckets, table2.size().eval()) @test_util.run_deprecated_v1 def testIdTableWithHashBucketsWithMultipleInitializersDifferentDefault(self): @@ -1449,8 +1449,8 @@ class IdTableWithHashBucketsTest(test.TestCase): out1, out2 = self.evaluate([out1, out2]) self.assertAllEqual([0, 1, 2, -1], out1) self.assertAllEqual([-2, 1, -2], out2) - self.assertEquals(vocab_size + oov_buckets, table1.size().eval()) - self.assertEquals(vocab_size + oov_buckets, table2.size().eval()) + self.assertEqual(vocab_size + oov_buckets, table1.size().eval()) + self.assertEqual(vocab_size + oov_buckets, table2.size().eval()) @test_util.run_deprecated_v1 def testSparseTensor(self): @@ -2885,5 +2885,237 @@ class MutableDenseHashTableBenchmark(MutableHashTableBenchmark): deleted_key=-2) +@test_util.run_all_in_graph_and_eager_modes +class StaticVocabularyTableTest(test.TestCase): + + def _createVocabFile(self, basename, values=("brain", "salad", "surgery")): + vocabulary_file = os.path.join(self.get_temp_dir(), basename) + with open(vocabulary_file, "w") as f: + f.write("\n".join(values) + "\n") + return vocabulary_file + + def testStringStaticVocabularyTable(self): + vocab_file = self._createVocabFile("feat_to_id_1.txt") + with self.cached_session(): + vocab_size = 3 + oov_buckets = 1 + table = lookup_ops.StaticVocabularyTable( + lookup_ops.TextFileIdTableInitializer( + vocab_file, vocab_size=vocab_size), oov_buckets) + + self.evaluate(table.initializer) + + input_string = constant_op.constant(["brain", "salad", "surgery", "UNK"]) + + out = table.lookup(input_string) + self.assertAllEqual([0, 1, 2, 3], self.evaluate(out)) + self.assertEqual(vocab_size + oov_buckets, self.evaluate(table.size())) + + def testInt32StaticVocabularyTable(self): + vocab_file = self._createVocabFile("feat_to_id_2.txt", ("42", "1", "-1000")) + with self.cached_session(): + vocab_size = 3 + oov_buckets = 1 + table = lookup_ops.StaticVocabularyTable( + lookup_ops.TextFileIdTableInitializer( + vocab_file, vocab_size=vocab_size, key_dtype=dtypes.int64), + oov_buckets, + lookup_key_dtype=dtypes.int32) + + self.evaluate(table.initializer) + + values = constant_op.constant((42, 1, -1000, 11), dtype=dtypes.int32) + + out = table.lookup(values) + self.assertAllEqual([0, 1, 2, 3], self.evaluate(out)) + self.assertEqual(vocab_size + oov_buckets, self.evaluate(table.size())) + + def testInt64StaticVocabularyTable(self): + vocab_file = self._createVocabFile("feat_to_id_3.txt", ("42", "1", "-1000")) + with self.cached_session(): + vocab_size = 3 + oov_buckets = 1 + table = lookup_ops.StaticVocabularyTable( + lookup_ops.TextFileIdTableInitializer( + vocab_file, vocab_size=vocab_size, key_dtype=dtypes.int64), + oov_buckets) + + self.evaluate(table.initializer) + + values = constant_op.constant((42, 1, -1000, 11), dtype=dtypes.int64) + + out = table.lookup(values) + self.assertAllEqual([0, 1, 2, 3], self.evaluate(out)) + self.assertEqual(vocab_size + oov_buckets, self.evaluate(table.size())) + + def testStringStaticVocabularyTableNoInitializer(self): + with self.cached_session(): + oov_buckets = 5 + + # Set a table that only uses hash buckets, for each input value returns + # an id calculated by fingerprint("input") mod oov_buckets. + table = lookup_ops.StaticVocabularyTable(None, oov_buckets) + self.evaluate(table.initializer) + + values = constant_op.constant(("brain", "salad", "surgery")) + + out = table.lookup(values) + self.assertAllEqual( + [ + 3, # fingerprint("brain") mod 5. + 1, # fingerprint("salad") mod 5. + 4 # fingerprint("surgery") mod 5 + ], + self.evaluate(out)) + self.assertEqual(oov_buckets, self.evaluate(table.size())) + + def testStaticVocabularyTableWithMultipleInitializers(self): + vocab_file = self._createVocabFile("feat_to_id_4.txt") + with self.cached_session(): + vocab_size = 3 + oov_buckets = 3 + + init = lookup_ops.TextFileIdTableInitializer( + vocab_file, vocab_size=vocab_size) + table1 = lookup_ops.StaticVocabularyTable( + init, oov_buckets, name="table1") + + table2 = lookup_ops.StaticVocabularyTable( + init, oov_buckets, name="table2") + + self.evaluate(lookup_ops.tables_initializer()) + + input_string = constant_op.constant( + ["fruit", "brain", "salad", "surgery", "UNK"]) + + out1 = table1.lookup(input_string) + out2 = table2.lookup(input_string) + + out1, out2 = self.evaluate([out1, out2]) + self.assertAllEqual([5, 0, 1, 2, 5], out1) + self.assertAllEqual([5, 0, 1, 2, 5], out2) + self.assertEqual(vocab_size + oov_buckets, self.evaluate(table1.size())) + self.assertEqual(vocab_size + oov_buckets, self.evaluate(table2.size())) + + def testStaticVocabularyTableInitializationAcrossSessions(self): + vocab_file = self._createVocabFile("feat_to_id_5.txt") + with self.cached_session(): + vocab_size = 3 + oov_buckets = 1 + table1 = lookup_ops.StaticVocabularyTable( + lookup_ops.TextFileIdTableInitializer( + vocab_file, vocab_size=vocab_size), oov_buckets) + + self.evaluate(table1.initializer) + + input_string_1 = constant_op.constant( + ["brain", "salad", "surgery", "UNK"]) + + out1 = table1.lookup(input_string_1) + + self.assertAllEqual([0, 1, 2, 3], self.evaluate(out1)) + self.assertEqual(vocab_size + oov_buckets, self.evaluate(table1.size())) + + with self.cached_session(): + vocab_size = 3 + oov_buckets = 1 + + # Underlying lookup table already initialized in previous session. + # No need to initialize table2 + table2 = lookup_ops.StaticVocabularyTable( + lookup_ops.TextFileIdTableInitializer( + vocab_file, vocab_size=vocab_size), oov_buckets) + + input_string_2 = constant_op.constant(["fruit", "salad", "UNK"]) + + out2 = table2.lookup(input_string_2) + + self.assertAllEqual([3, 1, 3], self.evaluate(out2)) + self.assertEqual(vocab_size + oov_buckets, self.evaluate(table2.size())) + + def testSparseTensor(self): + vocab_file = self._createVocabFile("feat_to_id_7.txt") + input_indices = [[0, 0], [0, 1], [2, 0], [2, 2], [3, 0]] + input_shape = [4, 4] + with self.cached_session() as sess: + sp_features = sparse_tensor.SparseTensor( + constant_op.constant(input_indices, dtypes.int64), + constant_op.constant(["brain", "salad", "brain", "surgery", "tarkus"], + dtypes.string), + constant_op.constant(input_shape, dtypes.int64)) + + table = lookup_ops.StaticVocabularyTable( + lookup_ops.TextFileIdTableInitializer(vocab_file, vocab_size=3), 1) + self.evaluate(table.initializer) + + sp_ids = table.lookup(sp_features) + + self.assertAllEqual([5], sp_ids.values._shape_as_list()) + + sp_ids_ind, sp_ids_val, sp_ids_shape = sess.run( + [sp_ids.indices, sp_ids.values, sp_ids.dense_shape]) + + self.assertAllEqual(input_indices, sp_ids_ind) + self.assertAllEqual([0, 1, 0, 2, 3], sp_ids_val) + self.assertAllEqual(input_shape, sp_ids_shape) + + def testInt32SparseTensor(self): + input_indices = [[0, 0], [0, 1], [2, 0], [2, 2], [3, 0]] + input_shape = [4, 4] + with self.cached_session() as sess: + sp_features = sparse_tensor.SparseTensor( + constant_op.constant(input_indices, dtypes.int64), + constant_op.constant([42, 1, 42, -1000, 11], dtypes.int32), + constant_op.constant(input_shape, dtypes.int64)) + + table = lookup_ops.StaticVocabularyTable( + lookup_ops.KeyValueTensorInitializer((42, 1, -1000), (0, 1, 2), + dtypes.int64, dtypes.int64), + 1, + lookup_key_dtype=dtypes.int32) + self.evaluate(table.initializer) + + sp_ids = table.lookup(sp_features) + + self.assertAllEqual([5], sp_ids.values._shape_as_list()) + + sp_ids_ind, sp_ids_val, sp_ids_shape = sess.run( + [sp_ids.indices, sp_ids.values, sp_ids.dense_shape]) + + self.assertAllEqual(input_indices, sp_ids_ind) + self.assertAllEqual([0, 1, 0, 2, 3], sp_ids_val) + self.assertAllEqual(input_shape, sp_ids_shape) + + def testInt64SparseTensor(self): + input_indices = [[0, 0], [0, 1], [2, 0], [2, 2], [3, 0]] + input_shape = [4, 4] + with self.cached_session() as sess: + sp_features = sparse_tensor.SparseTensor( + constant_op.constant(input_indices, dtypes.int64), + constant_op.constant([42, 1, 42, -1000, 11], dtypes.int64), + constant_op.constant(input_shape, dtypes.int64)) + + table = lookup_ops.StaticVocabularyTable( + lookup_ops.KeyValueTensorInitializer((42, 1, -1000), (0, 1, 2), + dtypes.int64, dtypes.int64), 1) + self.evaluate(table.initializer) + + sp_ids = table.lookup(sp_features) + + self.assertAllEqual([5], sp_ids.values._shape_as_list()) + + sp_ids_ind, sp_ids_val, sp_ids_shape = sess.run( + [sp_ids.indices, sp_ids.values, sp_ids.dense_shape]) + + self.assertAllEqual(input_indices, sp_ids_ind) + self.assertAllEqual([0, 1, 0, 2, 3], sp_ids_val) + self.assertAllEqual(input_shape, sp_ids_shape) + + def testStaticVocabularyTableNoInnerTable(self): + with self.cached_session(): + table = lookup_ops.StaticVocabularyTable(None, num_oov_buckets=1) + self.assertIsNone(table.resource_handle) + + if __name__ == "__main__": test.main() diff --git a/tensorflow/python/ops/lookup_ops.py b/tensorflow/python/ops/lookup_ops.py index 8f200c6e00..d991a47b55 100644 --- a/tensorflow/python/ops/lookup_ops.py +++ b/tensorflow/python/ops/lookup_ops.py @@ -197,10 +197,8 @@ class InitializableLookupTableBase(LookupInterface): Returns: A scalar tensor containing the number of elements in this table. """ - with ops.name_scope(name, "%s_Size" % self.name, - [self.resource_handle]) as scope: - return gen_lookup_ops.lookup_table_size_v2( - self.resource_handle, name=scope) + with ops.name_scope(name, "%s_Size" % self.name, [self.resource_handle]): + return gen_lookup_ops.lookup_table_size_v2(self.resource_handle) def lookup(self, keys, name=None): """Looks up `keys` in a table, outputs the corresponding values. @@ -228,9 +226,9 @@ class InitializableLookupTableBase(LookupInterface): with ops.name_scope( name, "%s_Lookup" % self.name, - (self.resource_handle, key_tensor, self._default_value)) as scope: + (self.resource_handle, key_tensor, self._default_value)): values = gen_lookup_ops.lookup_table_find_v2( - self.resource_handle, key_tensor, self._default_value, name=scope) + self.resource_handle, key_tensor, self._default_value) values.set_shape(key_tensor.get_shape()) if isinstance(keys, sparse_tensor.SparseTensor): @@ -304,10 +302,9 @@ class HashTable(InitializableLookupTableBase): A pair of tensors with the first tensor containing all keys and the second tensors containing all values in the table. """ - with ops.name_scope(name, "%s_Export" % self.name, - [self.resource_handle]) as name: + with ops.name_scope(name, "%s_Export" % self.name, [self.resource_handle]): exported_keys, exported_values = gen_lookup_ops.lookup_table_export_v2( - self.resource_handle, self._key_dtype, self._value_dtype, name=name) + self.resource_handle, self._key_dtype, self._value_dtype) exported_values.set_shape(exported_keys.get_shape().concatenate( self._value_shape)) @@ -348,6 +345,7 @@ class TableInitializerBase(trackable_base.Trackable): if context.executing_eagerly(): # Ensure a unique name when eager execution is enabled to avoid spurious # sharing issues. + # TODO(rohanj): Use context.shared_name() instead. shared_name += str(ops.uid()) return shared_name @@ -365,11 +363,15 @@ class KeyValueTensorInitializer(TableInitializerBase): value_dtype: The `values` data type. Used when `values` is a python array. name: A name for the operation (optional). """ - with ops.name_scope(name, "key_value_init", [keys, values]) as scope: - self._keys = ops.convert_to_tensor(keys, dtype=key_dtype, name="keys") - self._values = ops.convert_to_tensor( - values, dtype=value_dtype, name="values") - self._name = scope + self._keys = ops.convert_to_tensor(keys, dtype=key_dtype, name="keys") + self._values = ops.convert_to_tensor( + values, dtype=value_dtype, name="values") + self._name = name if name is not None else "key_value_init" + if context.executing_eagerly(): + # Ensure a unique name when eager execution is enabled to avoid spurious + # sharing issues. + # TODO(rohanj): Use context.shared_name() instead. + self._name += str(ops.uid()) super(KeyValueTensorInitializer, self).__init__(self._keys.dtype, self._values.dtype) @@ -389,19 +391,14 @@ class KeyValueTensorInitializer(TableInitializerBase): """ _check_table_dtypes(table, self._keys.dtype, self._values.dtype) with ops.name_scope( - self._name, values=(table.resource_handle, self._keys, - self._values)) as scope: - if context.executing_eagerly(): - # Ensure a unique name when eager execution is enabled to avoid spurious - # sharing issues. - scope += str(ops.uid()) + self._name, values=(table.resource_handle, self._keys, self._values)): if fwd_compat.forward_compatible(2018, 9, 19): init_op = gen_lookup_ops.lookup_table_import_v2( - table.resource_handle, self._keys, self._values, name=scope) + table.resource_handle, self._keys, self._values) else: # To maintain forward compatibiltiy, use the old implementation. - init_op = gen_lookup_ops.initialize_table_v2( - table.resource_handle, self._keys, self._values, name=scope) + init_op = gen_lookup_ops.initialize_table_v2(table.resource_handle, + self._keys, self._values) ops.add_to_collection(ops.GraphKeys.TABLE_INITIALIZERS, init_op) return init_op @@ -563,18 +560,12 @@ class TextFileInitializer(TableInitializerBase): key and value data types. """ _check_table_dtypes(table, self.key_dtype, self.value_dtype) - with ops.name_scope(self._name, "text_file_init", - (table.resource_handle,)) as scope: + with ops.name_scope(self._name, "text_file_init", (table.resource_handle,)): filename = ops.convert_to_tensor( self._filename, dtypes.string, name="asset_filepath") init_op = gen_lookup_ops.initialize_table_from_text_file_v2( - table.resource_handle, - filename, - self._key_index, - self._value_index, - -1 if self._vocab_size is None else self._vocab_size, - self._delimiter, - name=scope) + table.resource_handle, filename, self._key_index, self._value_index, + -1 if self._vocab_size is None else self._vocab_size, self._delimiter) ops.add_to_collection(ops.GraphKeys.TABLE_INITIALIZERS, init_op) # If the filename tensor is anything other than a string constant (e.g., if # it is a placeholder) then it does not make sense to track it as an asset. @@ -888,9 +879,9 @@ class IdTableWithHashBuckets(LookupInterface): def size(self, name=None): """Compute the number of elements in this table.""" - with ops.name_scope(name, "%s_Size" % self.name) as scope: + with ops.name_scope(name, "%s_Size" % self.name): if self._table: - tsize = self._table.size(scope) + tsize = self._table.size() else: tsize = ops.convert_to_tensor(0, dtype=dtypes.int64) return tsize + self._num_oov_buckets @@ -936,7 +927,7 @@ class IdTableWithHashBuckets(LookupInterface): ids = self._table.lookup(values, name=name) else: # TODO(yleon): Consider moving this functionality to its own kernel. - with ops.name_scope(name, "%s_Lookup" % self.name) as scope: + with ops.name_scope(name, "%s_Lookup" % self.name): str_to_hash_bucket = self._get_string_to_hash_bucket_fn( self._hasher_spec) buckets = str_to_hash_bucket( @@ -947,7 +938,7 @@ class IdTableWithHashBuckets(LookupInterface): ids = self._table.lookup(values) buckets = math_ops.add(buckets, self._table.size()) is_id_non_default = math_ops.not_equal(ids, self._table.default_value) - ids = array_ops.where(is_id_non_default, ids, buckets, name=scope) + ids = array_ops.where(is_id_non_default, ids, buckets) else: ids = buckets if isinstance(keys, sparse_tensor.SparseTensor): @@ -955,6 +946,191 @@ class IdTableWithHashBuckets(LookupInterface): return ids +class StaticVocabularyTable(LookupInterface): + """String to Id table wrapper that assigns out-of-vocabulary keys to buckets. + + For example, if an instance of `StaticVocabularyTable` is initialized with a + string-to-id initializer that maps: + + * `emerson -> 0` + * `lake -> 1` + * `palmer -> 2` + + The `Vocabulary` object will performs the following mapping: + + * `emerson -> 0` + * `lake -> 1` + * `palmer -> 2` + * ` -> bucket_id`, where bucket_id will be between `3` and + `3 + num_oov_buckets - 1`, calculated by: + `hash() % num_oov_buckets + vocab_size` + + If input_tensor is `["emerson", "lake", "palmer", "king", "crimson"]`, + the lookup result is `[0, 1, 2, 4, 7]`. + + If `initializer` is None, only out-of-vocabulary buckets are used. + + Example usage: + + ```python + num_oov_buckets = 3 + input_tensor = tf.constant(["emerson", "lake", "palmer", "king", "crimnson"]) + table = tf.lookup.StaticVocabularyTable( + tf.TextFileIdTableInitializer(filename), num_oov_buckets) + out = table.lookup(input_tensor). + table.init.run() + print(out.eval()) + ``` + + The hash function used for generating out-of-vocabulary buckets ID is + Fingerprint64. + """ + + def __init__(self, + initializer, + num_oov_buckets, + lookup_key_dtype=None, + name=None): + """Construct a `StaticVocabularyTable` object. + + Args: + initializer: A TableInitializerBase object that contains the data used to + initialize the table. If None, then we only use out-of-vocab buckets. + num_oov_buckets: Number of buckets to use for out-of-vocabulary keys. Must + be greater than zero. + lookup_key_dtype: Data type of keys passed to `lookup`. Defaults to + `initializer.key_dtype` if `initializer` is specified, otherwise + `tf.string`. Must be string or integer, and must be castable to + `initializer.key_dtype`. + name: A name for the operation (optional). + + Raises: + ValueError: when `num_oov_buckets` is not positive. + TypeError: when lookup_key_dtype or initializer.key_dtype are not + integer or string. Also when initializer.value_dtype != int64. + """ + if num_oov_buckets <= 0: + raise ValueError("oov_buckets must be > 0.") + # If a name ends with a '/' it is a "name scope", remove all trailing '/' + # characters to use as table name. + if name: + name = name.rstrip("/") + if initializer: + if lookup_key_dtype is None: + lookup_key_dtype = initializer.key_dtype + supported_table_key_dtypes = (dtypes.int64, dtypes.string) + if initializer.key_dtype not in supported_table_key_dtypes: + raise TypeError("Invalid key dtype, expected one of %s, but got %s." % + (supported_table_key_dtypes, initializer.key_dtype)) + if initializer.key_dtype.is_integer != lookup_key_dtype.is_integer: + raise TypeError( + "Invalid key dtype, expected %s but got %s." % + ("integer" if lookup_key_dtype.is_integer else "non-integer", + initializer.key_dtype)) + if initializer.value_dtype != dtypes.int64: + raise TypeError("Invalid value dtype, expected %s but got %s." % + (dtypes.int64, initializer.value_dtype)) + self._table = HashTable(initializer, default_value=-1) + name = name or self._table.name + else: + lookup_key_dtype = dtypes.string + self._table = None + name = name or "hash_bucket" + if (not lookup_key_dtype.is_integer) and (dtypes.string != + lookup_key_dtype): + raise TypeError("Invalid key_dtype, expected integer or string, got %s." % + lookup_key_dtype) + self._num_oov_buckets = num_oov_buckets + + self._table_name = None + if name is not None: + self._table_name = name.split("/")[-1] + super(StaticVocabularyTable, self).__init__(lookup_key_dtype, dtypes.int64) + + def create_resource(self): + if self._table is not None: + return self._table.create_resource() + return None + + def initialize(self): + if self._table is not None: + return self._table.initialize() + with ops.name_scope(None, "init"): + return control_flow_ops.no_op() + + @property + def initializer(self): + if self._table is not None: + return self._table._init_op # pylint: disable=protected-access + with ops.name_scope(None, "init"): + return control_flow_ops.no_op() + + @property + @deprecated("2018-12-15", "Use `initializer` instead.") + def init(self): + return self.initializer + + @property + def resource_handle(self): + if self._table is not None: + return self._table.resource_handle + return None + + @property + def name(self): + return self._table_name + + def size(self, name=None): + """Compute the number of elements in this table.""" + with ops.name_scope(name, "%s_Size" % self.name): + if self._table: + tsize = self._table.size() + else: + tsize = ops.convert_to_tensor(0, dtype=dtypes.int64) + return tsize + self._num_oov_buckets + + def lookup(self, keys, name=None): + """Looks up `keys` in the table, outputs the corresponding values. + + It assigns out-of-vocabulary keys to buckets based in their hashes. + + Args: + keys: Keys to look up. May be either a `SparseTensor` or dense `Tensor`. + name: Optional name for the op. + + Returns: + A `SparseTensor` if keys are sparse, otherwise a dense `Tensor`. + + Raises: + TypeError: when `keys` doesn't match the table key data type. + """ + if keys.dtype.base_dtype != self._key_dtype: + raise TypeError("Signature mismatch. Keys must be dtype %s, got %s." % + (self._key_dtype, keys.dtype)) + values = keys + if isinstance(keys, sparse_tensor.SparseTensor): + values = keys.values + if self._table and (self._table.key_dtype.base_dtype == dtypes.int64): + values = math_ops.to_int64(values) + + # TODO(yleon): Consider moving this functionality to its own kernel. + with ops.name_scope(name, "%s_Lookup" % self.name): + buckets = string_ops.string_to_hash_bucket_fast( + _as_string(values), + num_buckets=self._num_oov_buckets, + name="hash_bucket") + if self._table: + ids = self._table.lookup(values) + buckets = math_ops.add(buckets, self._table.size()) + is_id_non_default = math_ops.not_equal(ids, self._table.default_value) + ids = array_ops.where(is_id_non_default, ids, buckets) + else: + ids = buckets + if isinstance(keys, sparse_tensor.SparseTensor): + return sparse_tensor.SparseTensor(keys.indices, ids, keys.dense_shape) + return ids + + def index_table_from_file(vocabulary_file=None, num_oov_buckets=0, vocab_size=None, @@ -1051,9 +1227,9 @@ def index_table_from_file(vocabulary_file=None, if (not key_dtype.is_integer) and (dtypes.string != key_dtype.base_dtype): raise TypeError("Only integer and string keys are supported.") - with ops.name_scope(name, "string_to_index") as feat_to_id_scope: + with ops.name_scope(name, "string_to_index"): table = None - with ops.name_scope(None, "hash_table") as hash_table_scope: + with ops.name_scope(None, "hash_table"): init = TextFileIdTableInitializer( vocabulary_file, vocab_size=vocab_size, @@ -1063,13 +1239,12 @@ def index_table_from_file(vocabulary_file=None, value_column_index=value_column_index, delimiter=delimiter) - table = HashTable(init, default_value, name=hash_table_scope) + table = HashTable(init, default_value) if num_oov_buckets: table = IdTableWithHashBuckets( table, num_oov_buckets=num_oov_buckets, hasher_spec=hasher_spec, - name=feat_to_id_scope, key_dtype=key_dtype) return table @@ -1142,7 +1317,7 @@ def index_table_from_tensor(vocabulary_list, if (not dtype.is_integer) and (dtypes.string != dtype.base_dtype): raise TypeError("Only integer and string keys are supported.") - with ops.name_scope(name, "string_to_index") as feat_to_id_scope: + with ops.name_scope(name, "string_to_index"): keys = ops.convert_to_tensor(vocabulary_list) if keys.dtype.is_integer != dtype.is_integer: raise ValueError("Expected %s, got %s." % @@ -1153,7 +1328,7 @@ def index_table_from_tensor(vocabulary_list, num_elements = array_ops.size(keys) values = math_ops.to_int64(math_ops.range(num_elements)) - with ops.name_scope(None, "hash_table") as hash_table_scope: + with ops.name_scope(None, "hash_table"): table_keys = math_ops.to_int64(keys) if keys.dtype.is_integer else keys init = KeyValueTensorInitializer( table_keys, @@ -1161,13 +1336,12 @@ def index_table_from_tensor(vocabulary_list, table_keys.dtype.base_dtype, dtypes.int64, name="table_init") - table = HashTable(init, default_value, name=hash_table_scope) + table = HashTable(init, default_value) if num_oov_buckets: table = IdTableWithHashBuckets( table, num_oov_buckets=num_oov_buckets, hasher_spec=hasher_spec, - name=feat_to_id_scope, key_dtype=dtype) return table @@ -1249,7 +1423,7 @@ def index_to_string_table_from_file(vocabulary_file, if vocab_size is not None and vocab_size < 1: raise ValueError("vocab_size must be greater than 0, got %d." % vocab_size) - with ops.name_scope(name, "index_to_string") as scope: + with ops.name_scope(name, "index_to_string"): init = TextFileStringTableInitializer( vocabulary_file, vocab_size=vocab_size, @@ -1259,7 +1433,7 @@ def index_to_string_table_from_file(vocabulary_file, delimiter=delimiter) # TODO(yleon): Use a more effienct structure. - return HashTable(init, default_value, name=scope) + return HashTable(init, default_value) def index_to_string_table_from_tensor(vocabulary_list, @@ -1312,7 +1486,7 @@ def index_to_string_table_from_tensor(vocabulary_list, if vocabulary_list is None: raise ValueError("vocabulary_list must be specified.") - with ops.name_scope(name, "index_to_string") as scope: + with ops.name_scope(name, "index_to_string"): vocabulary_list = ops.convert_to_tensor(vocabulary_list, dtypes.string) num_elements = array_ops.size(vocabulary_list) keys = math_ops.to_int64(math_ops.range(num_elements)) @@ -1320,7 +1494,7 @@ def index_to_string_table_from_tensor(vocabulary_list, init = KeyValueTensorInitializer( keys, vocabulary_list, dtypes.int64, dtypes.string, name="table_init") # TODO(yleon): Use a more effienct structure. - return HashTable(init, default_value, name=scope) + return HashTable(init, default_value) class MutableHashTable(LookupInterface): @@ -1380,6 +1554,7 @@ class MutableHashTable(LookupInterface): # shared_name attribute value (but is better than the alternative of # sharing everything by default when executing eagerly; hopefully creating # tables in a loop is uncommon). + # TODO(rohanj): Use context.shared_name() instead. self._shared_name = "table_%d" % (ops.uid(),) super(MutableHashTable, self).__init__(key_dtype, value_dtype) @@ -1429,11 +1604,9 @@ class MutableHashTable(LookupInterface): Returns: A scalar tensor containing the number of elements in this table. """ - with ops.name_scope(name, "%s_Size" % self.name, - [self.resource_handle]) as name: + with ops.name_scope(name, "%s_Size" % self.name, [self.resource_handle]): with ops.colocate_with(self.resource_handle): - return gen_lookup_ops.lookup_table_size_v2( - self.resource_handle, name=name) + return gen_lookup_ops.lookup_table_size_v2(self.resource_handle) def remove(self, keys, name=None): """Removes `keys` and its associated values from the table. @@ -1455,12 +1628,9 @@ class MutableHashTable(LookupInterface): raise TypeError("Signature mismatch. Keys must be dtype %s, got %s." % (self._key_dtype, keys.dtype)) - with ops.name_scope( - name, "%s_lookup_table_remove" % self.name, - (self.resource_handle, keys, self._default_value)) as name: - # pylint: disable=protected-access - op = gen_lookup_ops.lookup_table_remove_v2( - self.resource_handle, keys, name=name) + with ops.name_scope(name, "%s_lookup_table_remove" % self.name, + (self.resource_handle, keys, self._default_value)): + op = gen_lookup_ops.lookup_table_remove_v2(self.resource_handle, keys) return op @@ -1481,13 +1651,12 @@ class MutableHashTable(LookupInterface): Raises: TypeError: when `keys` do not match the table data types. """ - with ops.name_scope( - name, "%s_lookup_table_find" % self.name, - (self.resource_handle, keys, self._default_value)) as name: + with ops.name_scope(name, "%s_lookup_table_find" % self.name, + (self.resource_handle, keys, self._default_value)): keys = ops.convert_to_tensor(keys, dtype=self._key_dtype, name="keys") with ops.colocate_with(self.resource_handle): - values = gen_lookup_ops.lookup_table_find_v2( - self.resource_handle, keys, self._default_value, name=name) + values = gen_lookup_ops.lookup_table_find_v2(self.resource_handle, keys, + self._default_value) return values def insert(self, keys, values, name=None): @@ -1508,13 +1677,13 @@ class MutableHashTable(LookupInterface): types. """ with ops.name_scope(name, "%s_lookup_table_insert" % self.name, - [self.resource_handle, keys, values]) as name: + [self.resource_handle, keys, values]): keys = ops.convert_to_tensor(keys, self._key_dtype, name="keys") values = ops.convert_to_tensor(values, self._value_dtype, name="values") with ops.colocate_with(self.resource_handle): # pylint: disable=protected-access - op = gen_lookup_ops.lookup_table_insert_v2( - self.resource_handle, keys, values, name=name) + op = gen_lookup_ops.lookup_table_insert_v2(self.resource_handle, keys, + values) return op def export(self, name=None): @@ -1528,10 +1697,10 @@ class MutableHashTable(LookupInterface): second tensors containing all values in the table. """ with ops.name_scope(name, "%s_lookup_table_export_values" % self.name, - [self.resource_handle]) as name: + [self.resource_handle]): with ops.colocate_with(self.resource_handle): exported_keys, exported_values = gen_lookup_ops.lookup_table_export_v2( - self.resource_handle, self._key_dtype, self._value_dtype, name=name) + self.resource_handle, self._key_dtype, self._value_dtype) return exported_keys, exported_values def _gather_saveables_for_checkpoint(self): @@ -1550,12 +1719,13 @@ class MutableHashTable(LookupInterface): # pylint: disable=protected-access super(MutableHashTable._Saveable, self).__init__(table, specs, name) - def restore(self, restored_tensors, restored_shapes): + def restore(self, restored_tensors, restored_shapes, name=None): del restored_shapes # unused # pylint: disable=protected-access - with ops.colocate_with(self.op.resource_handle): - return gen_lookup_ops.lookup_table_import_v2( - self.op.resource_handle, restored_tensors[0], restored_tensors[1]) + with ops.name_scope(name, "%s_table_restore" % self.name): + with ops.colocate_with(self.op.resource_handle): + return gen_lookup_ops.lookup_table_import_v2( + self.op.resource_handle, restored_tensors[0], restored_tensors[1]) class MutableDenseHashTable(LookupInterface): @@ -1641,6 +1811,7 @@ class MutableDenseHashTable(LookupInterface): # shared_name attribute value (but is better than the alternative of # sharing everything by default when executing eagerly; hopefully creating # tables in a loop is uncommon). + # TODO(rohanj): Use context.shared_name() instead. self._shared_name = "table_%d" % (ops.uid(),) super(MutableDenseHashTable, self).__init__(key_dtype, value_dtype) @@ -1683,11 +1854,9 @@ class MutableDenseHashTable(LookupInterface): Returns: A scalar tensor containing the number of elements in this table. """ - with ops.name_scope(name, "%s_Size" % self.name, - [self.resource_handle]) as name: + with ops.name_scope(name, "%s_Size" % self.name, [self.resource_handle]): with ops.colocate_with(self.resource_handle): - return gen_lookup_ops.lookup_table_size_v2( - self.resource_handle, name=name) + return gen_lookup_ops.lookup_table_size_v2(self.resource_handle) def lookup(self, keys, name=None): """Looks up `keys` in a table, outputs the corresponding values. @@ -1707,11 +1876,11 @@ class MutableDenseHashTable(LookupInterface): TypeError: when `keys` do not match the table data types. """ with ops.name_scope(name, "%s_lookup_table_find" % self.name, - [self.resource_handle, keys]) as name: + [self.resource_handle, keys]): keys = ops.convert_to_tensor(keys, dtype=self._key_dtype, name="keys") with ops.colocate_with(self.resource_handle): - values = gen_lookup_ops.lookup_table_find_v2( - self.resource_handle, keys, self._default_value, name=name) + values = gen_lookup_ops.lookup_table_find_v2(self.resource_handle, keys, + self._default_value) return values @@ -1733,13 +1902,13 @@ class MutableDenseHashTable(LookupInterface): types. """ with ops.name_scope(name, "%s_lookup_table_insert" % self.name, - [self.resource_handle, keys, values]) as name: + [self.resource_handle, keys, values]): keys = ops.convert_to_tensor(keys, dtype=self._key_dtype, name="keys") values = ops.convert_to_tensor( values, dtype=self._value_dtype, name="values") with ops.colocate_with(self.resource_handle): - op = gen_lookup_ops.lookup_table_insert_v2( - self.resource_handle, keys, values, name=name) + op = gen_lookup_ops.lookup_table_insert_v2(self.resource_handle, keys, + values) return op def remove(self, keys, name=None): @@ -1762,12 +1931,10 @@ class MutableDenseHashTable(LookupInterface): raise TypeError("Signature mismatch. Keys must be dtype %s, got %s." % (self._key_dtype, keys.dtype)) - with ops.name_scope( - name, "%s_lookup_table_remove" % self.name, - (self.resource_handle, keys, self._default_value)) as name: + with ops.name_scope(name, "%s_lookup_table_remove" % self.name, + (self.resource_handle, keys, self._default_value)): # pylint: disable=protected-access - op = gen_lookup_ops.lookup_table_remove_v2( - self.resource_handle, keys, name=name) + op = gen_lookup_ops.lookup_table_remove_v2(self.resource_handle, keys) return op @@ -1782,10 +1949,10 @@ class MutableDenseHashTable(LookupInterface): second tensors containing all values in the table. """ with ops.name_scope(name, "%s_lookup_table_export_values" % self.name, - [self.resource_handle]) as name: + [self.resource_handle]): with ops.colocate_with(self.resource_handle): exported_keys, exported_values = gen_lookup_ops.lookup_table_export_v2( - self.resource_handle, self._key_dtype, self._value_dtype, name=name) + self.resource_handle, self._key_dtype, self._value_dtype) return exported_keys, exported_values @@ -1807,12 +1974,13 @@ class MutableDenseHashTable(LookupInterface): # pylint: disable=protected-access super(MutableDenseHashTable._Saveable, self).__init__(table, specs, name) - def restore(self, restored_tensors, restored_shapes): + def restore(self, restored_tensors, restored_shapes, name=None): del restored_shapes # unused # pylint: disable=protected-access - with ops.colocate_with(self.op.resource_handle): - return gen_lookup_ops.lookup_table_import_v2( - self.op.resource_handle, restored_tensors[0], restored_tensors[1]) + with ops.name_scope(name, "%s_table_restore" % self.name): + with ops.colocate_with(self.op.resource_handle): + return gen_lookup_ops.lookup_table_import_v2( + self.op.resource_handle, restored_tensors[0], restored_tensors[1]) ops.NotDifferentiable("LookupTableFind") -- GitLab From b2f602ef8845ab59e3303b5c93fb2516b6adc03b Mon Sep 17 00:00:00 2001 From: Siju Date: Fri, 22 Feb 2019 09:31:29 +0530 Subject: [PATCH 317/766] Update xla_compilation_device.cc aligment->alignment --- tensorflow/compiler/tf2xla/xla_compilation_device.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/compiler/tf2xla/xla_compilation_device.cc b/tensorflow/compiler/tf2xla/xla_compilation_device.cc index 5bd0277c05..f98d07d196 100644 --- a/tensorflow/compiler/tf2xla/xla_compilation_device.cc +++ b/tensorflow/compiler/tf2xla/xla_compilation_device.cc @@ -42,7 +42,7 @@ class XlaCompilationAllocator : public Allocator { void* AllocateRaw(size_t alignment, size_t num_bytes) override { // Regardless of the size requested, always allocates an XlaExpression. - // Respects the aligment request because there is alignment checking even + // Respects the alignment request because there is alignment checking even // for Tensors whose data is never accessed. void* p = port::AlignedMalloc(sizeof(XlaExpression), alignment); XlaExpression* expression = reinterpret_cast(p); -- GitLab From a17a64d0b76e74a0d2f79a39f3f329e0826f2d8c Mon Sep 17 00:00:00 2001 From: Saurabh Saxena Date: Thu, 21 Feb 2019 20:14:29 -0800 Subject: [PATCH 318/766] Loosens constraints on maximum_iterations in while_v2 for XLA. maximum_iterations no longer needs to be a TF-graph-compile-time constant but should be a compile-time constant when building the XLA graph (when values of placeholders are also available). There are broadly 2 parts of this change. 1. while_v2: This removes the _maximum_iterations attr from the functional While op generated by while_v2 and makes it an input instead. This frees maximum_iterations from being a graph-building time constant. Its index in the list of op inputs is preserved in both the forward and backwards graph so it should be ok for n'th order derivatives. 2. The XLA changes are needed to propagate constants inside the loop body e.g. when building the gradient of a While inside a while_loop. They are 2-fold: a. This updates the const_analysis pass to inspect the cond and body functions of While for compile time constant requirements. b. When compiling the XLA While op in while_op.cc if there are inputs that are compile time constants and the corresponding loop variables are loop invariants, the constants get propagated into the loop body. Makes XlaCompiler::FindFunctionBody public to allow accessing the body of the While in while_op.cc. If the body has been specialized using the PropagateConstIntoFunctionalNodes pass the rewritten function only exists in XlaCompiler::local_flib_def_. PiperOrigin-RevId: 235122516 --- .../compiler/jit/create_xla_launch_op.cc | 5 +- .../jit/encapsulate_subgraphs_pass.cc | 5 +- .../compiler/jit/mark_for_compilation_pass.cc | 2 +- .../compiler/jit/partially_decluster_pass.cc | 32 ++- .../jit/partially_decluster_pass_test.cc | 8 + tensorflow/compiler/tests/while_test.py | 107 ++++++++- tensorflow/compiler/tf2xla/const_analysis.cc | 101 ++++++++- tensorflow/compiler/tf2xla/const_analysis.h | 12 +- .../compiler/tf2xla/const_analysis_test.cc | 16 +- tensorflow/compiler/tf2xla/graph_compiler.cc | 7 +- tensorflow/compiler/tf2xla/kernels/BUILD | 1 + .../compiler/tf2xla/kernels/while_op.cc | 210 ++++++++++++++++-- tensorflow/compiler/tf2xla/xla_compiler.h | 2 +- tensorflow/python/keras/BUILD | 15 ++ .../time_distributed_learning_phase_test.py | 43 ++++ .../python/keras/layers/wrappers_test.py | 11 - .../kernel_tests/control_flow_ops_py_test.py | 22 +- .../python/kernel_tests/while_v2_test.py | 24 +- tensorflow/python/ops/while_v2.py | 143 +++++------- .../ops/while_v2_indexed_slices_rewriter.py | 4 +- 20 files changed, 597 insertions(+), 173 deletions(-) create mode 100644 tensorflow/python/keras/layers/time_distributed_learning_phase_test.py diff --git a/tensorflow/compiler/jit/create_xla_launch_op.cc b/tensorflow/compiler/jit/create_xla_launch_op.cc index 6f1ff85f24..7e4c8466d8 100644 --- a/tensorflow/compiler/jit/create_xla_launch_op.cc +++ b/tensorflow/compiler/jit/create_xla_launch_op.cc @@ -126,8 +126,9 @@ Status GetBodyAndConstantsAndResources(FunctionLibraryRuntime* flr, const DataTypeVector& arg_types = (*fbody)->arg_types; std::vector const_args(arg_types.size()); // If we can't analyze the const args. Bail out. - TF_RETURN_IF_ERROR(BackwardsConstAnalysis( - *((*fbody)->graph), &const_args, /*compile_time_const_nodes=*/nullptr)); + TF_RETURN_IF_ERROR( + BackwardsConstAnalysis(*((*fbody)->graph), &const_args, + /*compile_time_const_nodes=*/nullptr, flr)); for (int i = 0; i < const_args.size(); ++i) { if (const_args[i]) { diff --git a/tensorflow/compiler/jit/encapsulate_subgraphs_pass.cc b/tensorflow/compiler/jit/encapsulate_subgraphs_pass.cc index d0d7a3f378..1e69e61a17 100644 --- a/tensorflow/compiler/jit/encapsulate_subgraphs_pass.cc +++ b/tensorflow/compiler/jit/encapsulate_subgraphs_pass.cc @@ -2575,8 +2575,9 @@ Status EncapsulateSubgraphsPass::Run( const int num_args = input_permutation->size(); std::vector const_args(num_args); - TF_RETURN_IF_ERROR(BackwardsConstAnalysis( - **subgraph, &const_args, /*compile_time_const_nodes=*/nullptr)); + TF_RETURN_IF_ERROR( + BackwardsConstAnalysis(**subgraph, &const_args, + /*compile_time_const_nodes=*/nullptr, flr)); DataTypeVector arg_types(num_args); TF_RETURN_IF_ERROR(GetArgTypes(**subgraph, &arg_types)); diff --git a/tensorflow/compiler/jit/mark_for_compilation_pass.cc b/tensorflow/compiler/jit/mark_for_compilation_pass.cc index d9a83049d6..fd778490c3 100644 --- a/tensorflow/compiler/jit/mark_for_compilation_pass.cc +++ b/tensorflow/compiler/jit/mark_for_compilation_pass.cc @@ -441,7 +441,7 @@ Status FindCompilationCandidates( std::vector compile_time_const_nodes(graph.num_node_ids(), false); TF_RETURN_IF_ERROR( BackwardsConstAnalysis(graph, /*compile_time_const_arg_indices=*/nullptr, - &compile_time_const_nodes)); + &compile_time_const_nodes, lib_runtime)); int64& fuel = GetMarkForCompilationPassFlags()->tf_xla_clustering_fuel; diff --git a/tensorflow/compiler/jit/partially_decluster_pass.cc b/tensorflow/compiler/jit/partially_decluster_pass.cc index e1fd2aaee2..202e0323bf 100644 --- a/tensorflow/compiler/jit/partially_decluster_pass.cc +++ b/tensorflow/compiler/jit/partially_decluster_pass.cc @@ -20,9 +20,13 @@ limitations under the License. #include "tensorflow/compiler/jit/xla_cluster_util.h" #include "tensorflow/compiler/tf2xla/const_analysis.h" #include "tensorflow/compiler/tf2xla/xla_op_registry.h" +#include "tensorflow/core/common_runtime/function.h" +#include "tensorflow/core/framework/function.h" #include "tensorflow/core/framework/memory_types.h" #include "tensorflow/core/framework/node_def.pb.h" #include "tensorflow/core/framework/op_kernel.h" +#include "tensorflow/core/lib/core/errors.h" +#include "tensorflow/core/public/version.h" namespace tensorflow { namespace { @@ -274,10 +278,18 @@ Status MustCompileNode(const Node* n, bool* must_compile) { // regress performance in any significant manner. We will have to revisit this // algorith with a more complex cost model if this assumption turns out to be // incorrect. -Status PartiallyDeclusterGraph(Graph* graph) { +Status PartiallyDeclusterGraph(Graph* graph, + const FunctionLibraryDefinition* flib_def, + Env* env) { std::vector compile_time_const_nodes(graph->num_node_ids()); - TF_RETURN_IF_ERROR(BackwardsConstAnalysis( - *graph, nullptr, &compile_time_const_nodes, IsIntraClusterEdge)); + OptimizerOptions opts; + auto pflr = absl::make_unique( + nullptr, env, TF_GRAPH_DEF_VERSION, flib_def, opts); + FunctionLibraryRuntime* lib_runtime = + pflr->GetFLR(ProcessFunctionLibraryRuntime::kDefaultFLRDevice); + TF_RETURN_IF_ERROR(BackwardsConstAnalysis(*graph, nullptr, + &compile_time_const_nodes, + lib_runtime, IsIntraClusterEdge)); std::vector rpo; GetReversePostOrder(*graph, &rpo, /*stable_comparator=*/NodeComparatorName(), @@ -341,7 +353,19 @@ Status PartiallyDeclusterPass::Run( TF_RETURN_IF_ERROR( reduce_device_to_host_copies::PartiallyDeclusterGraph(graph)); - TF_RETURN_IF_ERROR(reduce_recompilation::PartiallyDeclusterGraph(graph)); + if (options.flib_def == nullptr) { + return errors::InvalidArgument( + "GraphOptimizationPassOptions::flib_def must be set for " + "PartiallyDeclusterPass."); + } + if (options.session_options == nullptr || + options.session_options->env == nullptr) { + return errors::InvalidArgument( + "GraphOptimizationPassOptions::session_options::env must be set for " + "PartiallyDeclusterPass."); + } + TF_RETURN_IF_ERROR(reduce_recompilation::PartiallyDeclusterGraph( + graph, options.flib_def, options.session_options->env)); return Status::OK(); } diff --git a/tensorflow/compiler/jit/partially_decluster_pass_test.cc b/tensorflow/compiler/jit/partially_decluster_pass_test.cc index 1d81a8f4fc..3494d0ee7e 100644 --- a/tensorflow/compiler/jit/partially_decluster_pass_test.cc +++ b/tensorflow/compiler/jit/partially_decluster_pass_test.cc @@ -27,6 +27,8 @@ limitations under the License. #include "tensorflow/compiler/tf2xla/cc/ops/xla_ops.h" #include "tensorflow/compiler/tf2xla/xla_op_kernel.h" #include "tensorflow/compiler/tf2xla/xla_op_registry.h" +#include "tensorflow/core/framework/function.h" +#include "tensorflow/core/framework/function.pb.h" #include "tensorflow/core/framework/node_def_util.h" #include "tensorflow/core/framework/op.h" #include "tensorflow/core/graph/algorithm.h" @@ -90,6 +92,12 @@ Status PartiallyDecluster(std::unique_ptr* graph) { GraphOptimizationPassOptions opt_options; opt_options.graph = graph; + FunctionDefLibrary fdef_lib; + FunctionLibraryDefinition flib_def(OpRegistry::Global(), fdef_lib); + opt_options.flib_def = &flib_def; + SessionOptions session_options; + session_options.env = Env::Default(); + opt_options.session_options = &session_options; PartiallyDeclusterPass pass; return pass.Run(opt_options); } diff --git a/tensorflow/compiler/tests/while_test.py b/tensorflow/compiler/tests/while_test.py index 4ee144beb7..55d1f85370 100644 --- a/tensorflow/compiler/tests/while_test.py +++ b/tensorflow/compiler/tests/while_test.py @@ -18,6 +18,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +import os import numpy as np from tensorflow.compiler.tests import xla_test @@ -25,7 +26,10 @@ from tensorflow.compiler.tf2xla.python import xla from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes from tensorflow.python.framework import function +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 gradients_impl from tensorflow.python.platform import test @@ -125,6 +129,105 @@ class WhileTest(xla_test.XLATestCase): result = sess.run(loop_outputs, {init_index: 0}) self.assertAllClose(result, [10, 7], rtol=1e-3) - -if __name__ == '__main__': + def _testMaxItersSimple(self): + if is_compile_on_demand(): + self.skipTest("list_ops are not supported in cpu_ondemand") + with self.cached_session() as sess, self.test_scope(): + xla_context = control_flow_ops.XLAControlFlowContext() + xla_context.Enter() + v = constant_op.constant(1.0) + p = array_ops.placeholder(dtype=dtypes.int32) + + def create_while_loop(): + iterations = array_ops.size(p, name="iterations") + r = control_flow_ops.while_loop( + lambda *_: True, + lambda i, x: (i + 1, v * x), (0, 1.0), + maximum_iterations=iterations, + name="outer") + return array_ops.identity(r[1]) + + output = create_while_loop() + output = gradients_impl.gradients(output, v)[0] + + result = sess.run(output, feed_dict={p: [0, 0, 0]}) + print(result) + xla_context.Exit() + + def testMaxItersSimple(self): + self.skipTest("Fails with v1 control flow") + # This fails with old control. + # self._testMaxItersSimple() + + @test_util.enable_control_flow_v2 + def testMaxItersSimpleV2(self): + self._testMaxItersSimple() + + def _testNestedWhileLoopWithMaxItersFromOuterContext(self): + if is_compile_on_demand(): + self.skipTest("list_ops are not supported in cpu_ondemand") + with self.cached_session() as sess, self.test_scope(): + xla_context = control_flow_ops.XLAControlFlowContext() + xla_context.Enter() + v = constant_op.constant(1.0) + p = array_ops.placeholder(dtype=dtypes.int32) + + def mid_body_builder(iterations): + + def mid_body(i, x): + r = control_flow_ops.while_loop( + lambda *_: True, + lambda i, x: (i + 1, v * x), (0, x), + maximum_iterations=iterations, + name="inner") + return (i + 1, gradients_impl.gradients(x + r[1], v)[0]) + + return mid_body + + def outer_body(i, x): + iterations = array_ops.size(p, name="iterations") + return (i + 1, x + control_flow_ops.while_loop( + lambda *_: True, + mid_body_builder(iterations), (0, x), + maximum_iterations=iterations, + name="mid")[1]) + + def create_while_loop(): + r = control_flow_ops.while_loop( + lambda *_: True, + outer_body, (0, 1.0), + maximum_iterations=5, + name="outer") + return array_ops.identity(r[1]) + + # p:placeholder + # j = 0 + # i, x = 0, 1. + # while j++ < 5: + # i1, x1 = 0, x + # while i1++ < len(p): + # i2, x2 = 0, x1 + # while i2++ < len(p): + # x2 = v * x2 + # x1 = grad(x1 + x2, v) + # x = x1 + # output = x + output = create_while_loop() + sess.run(output, feed_dict={p: [0, 0, 0]}) + xla_context.Exit() + + def testNestedWhileLoopWithMaxItersFromOuterContext(self): + self._testNestedWhileLoopWithMaxItersFromOuterContext() + + @test_util.enable_control_flow_v2 + def testNestedWhileLoopWithMaxItersFromOuterContextV2(self): + self._testNestedWhileLoopWithMaxItersFromOuterContext() + + +def is_compile_on_demand(): + return ("TF_XLA_FLAGS" in os.environ and + "tf_xla_compile_on_demand" in os.environ["TF_XLA_FLAGS"]) + + +if __name__ == "__main__": test.main() diff --git a/tensorflow/compiler/tf2xla/const_analysis.cc b/tensorflow/compiler/tf2xla/const_analysis.cc index a57095f91e..6aff436da4 100644 --- a/tensorflow/compiler/tf2xla/const_analysis.cc +++ b/tensorflow/compiler/tf2xla/const_analysis.cc @@ -20,15 +20,26 @@ limitations under the License. #include "absl/algorithm/container.h" #include "tensorflow/compiler/tf2xla/xla_op_registry.h" +#include "tensorflow/compiler/xla/status_macros.h" +#include "tensorflow/core/common_runtime/function.h" +#include "tensorflow/core/framework/attr_value.pb.h" +#include "tensorflow/core/framework/function.h" #include "tensorflow/core/framework/node_def_util.h" #include "tensorflow/core/graph/algorithm.h" +#include "tensorflow/core/lib/core/errors.h" namespace tensorflow { + +Status GetCompileTimeConstInputs(const Node* node, + std::vector* const_input_idxs, + FunctionLibraryRuntime* flib_runtime); + // Backwards dataflow analysis that finds arguments to a graph that must be // compile-time constants. Status BackwardsConstAnalysis(const Graph& g, std::vector* compile_time_const_arg_indices, std::vector* compile_time_const_nodes, + FunctionLibraryRuntime* flib_runtime, std::function edge_filter) { std::vector compile_time_const_nodes_impl; if (compile_time_const_nodes) { @@ -61,7 +72,18 @@ Status BackwardsConstAnalysis(const Graph& g, } for (const Edge* pred : node->in_edges()) { if (!pred->IsControlEdge() && edge_filter(*pred)) { - (*compile_time_const_nodes)[pred->src()->id()] = true; + // If the src node of the `pred` is an IdentityN do not mark it as a + // compile-time const. Only mark the corresponding input to the + // IdentityN node as a const. + // Note: XLA IdentityN op simply forwards its inputs so this is safe. + while (edge_filter(*pred) && + pred->src()->type_string() == "IdentityN") { + status = pred->src()->input_edge(pred->src_output(), &pred); + if (!status.ok()) return; + } + if (edge_filter(*pred)) { + (*compile_time_const_nodes)[pred->src()->id()] = true; + } } } return; @@ -69,17 +91,29 @@ Status BackwardsConstAnalysis(const Graph& g, // Mark any compile-time constant operator arguments as const. std::vector const_input_idxs; - status = XlaOpRegistry::CompileTimeConstantInputs( - node->def(), node->op_def(), &const_input_idxs); + status = GetCompileTimeConstInputs(node, &const_input_idxs, flib_runtime); if (!status.ok()) { return; } for (Edge const* edge : node->in_edges()) { - if (absl::c_binary_search(const_input_idxs, edge->dst_input()) && + if (!edge->IsControlEdge() && + absl::c_binary_search(const_input_idxs, edge->dst_input()) && edge_filter(*edge)) { - (*compile_time_const_nodes)[edge->src()->id()] = true; + // Do not mark IdentityN nodes as compile-time const. + // If the src node of the `pred` is an IdentityN do not mark it as a + // compile-time const. Only mark the corresponding input to the + // IdentityN node as a const. + // Note: XLA IdentityN op simply forwards its inputs so this is safe. + while (edge_filter(*edge) && + edge->src()->type_string() == "IdentityN") { + status = edge->src()->input_edge(edge->src_output(), &edge); + if (!status.ok()) return; + } + if (edge_filter(*edge)) { + (*compile_time_const_nodes)[edge->src()->id()] = true; + } } } }; @@ -91,4 +125,61 @@ Status BackwardsConstAnalysis(const Graph& g, return status; } +Status GetCompileTimeConstInputs(const Node* node, + std::vector* const_input_idxs, + FunctionLibraryRuntime* flib_runtime) { + if (node->type_string() != "While") { + return XlaOpRegistry::CompileTimeConstantInputs(node->def(), node->op_def(), + const_input_idxs); + } + // For While nodes, recurse into the body and cond graphs. + // TODO(b/124403063): Implement similar functionality for cond nodes and other + // functional ops. + NameAttrList cond_function; + TF_RETURN_IF_ERROR(GetNodeAttr(node->def(), "cond", &cond_function)); + NameAttrList body_function; + TF_RETURN_IF_ERROR(GetNodeAttr(node->def(), "body", &body_function)); + FunctionLibraryRuntime::Handle cond_handle; + FunctionLibraryRuntime::Handle body_handle; + TF_RETURN_IF_ERROR(flib_runtime->Instantiate( + cond_function.name(), AttrSlice(&cond_function.attr()), &cond_handle)); + TF_RETURN_IF_ERROR(flib_runtime->Instantiate( + body_function.name(), AttrSlice(&body_function.attr()), &body_handle)); + const FunctionBody* fcond = flib_runtime->GetFunctionBody(cond_handle); + const FunctionBody* fbody = flib_runtime->GetFunctionBody(body_handle); + TF_RET_CHECK(fcond); + TF_RET_CHECK(fbody); + int num_inputs = fbody->fdef.signature().input_arg_size(); + + // Stores which of the loop inputs are expected to be compile time constants. + std::vector compile_time_const_arg_indices(num_inputs); + TF_RETURN_IF_ERROR(BackwardsConstAnalysis( + *(fcond->graph), &compile_time_const_arg_indices, + /*compile_time_const_nodes=*/nullptr, flib_runtime)); + TF_RETURN_IF_ERROR(BackwardsConstAnalysis( + *(fbody->graph), &compile_time_const_arg_indices, + /*compile_time_const_nodes=*/nullptr, flib_runtime)); + for (int i = 0; i < num_inputs; i++) { + if (compile_time_const_arg_indices[i]) { + // Check that this input is actually a loop invariant. + // NOTE(srbs): Ideally this should raise an error if the loop body + // requires the input at this index to be a compile time const but it is + // not a loop invariant. However, that causes problems because const + // analysis is performed for the entire graph (in the + // MarkForCompilationPass for example) and not just for the ops + // that will actually be run using XLA kernels. So we silently return here + // and let the error be raised during the actual compilation of the + // XLA graph. + Node* arg_i = fbody->arg_nodes[i]; + Node* ret_i = fbody->ret_nodes[i]; + const Node* ret_i_input_0; + TF_RETURN_IF_ERROR(ret_i->input_node(0, &ret_i_input_0)); + if (ret_i_input_0->id() == arg_i->id()) { + const_input_idxs->push_back(i); + } + } + } + return Status::OK(); +} + } // namespace tensorflow diff --git a/tensorflow/compiler/tf2xla/const_analysis.h b/tensorflow/compiler/tf2xla/const_analysis.h index 49b3c6d413..1663cbff41 100644 --- a/tensorflow/compiler/tf2xla/const_analysis.h +++ b/tensorflow/compiler/tf2xla/const_analysis.h @@ -34,11 +34,13 @@ namespace tensorflow { // `compile_time_const_nodes`, if `compile_time_const_nodes` is not null. // // Only propagate const-ness along edges for which `edge_filter` returns true. -Status BackwardsConstAnalysis(const Graph& g, - std::vector* compile_time_const_arg_indices, - std::vector* compile_time_const_nodes, - std::function edge_filter = - [](const Edge& e) { return true; }); +Status BackwardsConstAnalysis( + const Graph& g, std::vector* compile_time_const_arg_indices, + std::vector* compile_time_const_nodes, + FunctionLibraryRuntime* flib_runtime, + std::function edge_filter = [](const Edge& e) { + return true; + }); } // namespace tensorflow diff --git a/tensorflow/compiler/tf2xla/const_analysis_test.cc b/tensorflow/compiler/tf2xla/const_analysis_test.cc index 40c6d0e017..ed5f004550 100644 --- a/tensorflow/compiler/tf2xla/const_analysis_test.cc +++ b/tensorflow/compiler/tf2xla/const_analysis_test.cc @@ -44,8 +44,8 @@ TEST(ConstAnalysisTest, Basics) { std::vector const_args(4, false); std::vector const_nodes(root.graph()->num_node_ids(), false); - TF_ASSERT_OK( - BackwardsConstAnalysis(*root.graph(), &const_args, &const_nodes)); + TF_ASSERT_OK(BackwardsConstAnalysis(*root.graph(), &const_args, &const_nodes, + /*flib_runtime=*/nullptr)); // Arg 0 doesn't need to be constant since the graph only uses its shape. // Arg 1 must be constant because it flows to the shape argument of a Reshape. @@ -82,7 +82,8 @@ TEST(ConstAnalysisTest, TopologicalOrder) { std::vector const_args(3, false); TF_ASSERT_OK(BackwardsConstAnalysis(graph, &const_args, - /*compile_time_const_nodes=*/nullptr)); + /*compile_time_const_nodes=*/nullptr, + /*flib_runtime=*/nullptr)); EXPECT_EQ(const_args, std::vector({true, true, false})); } @@ -103,7 +104,8 @@ TEST(ConstAnalysisTest, DontFollowControlDependencies) { std::vector const_args(2, false); TF_ASSERT_OK(BackwardsConstAnalysis(graph, &const_args, - /*compile_time_const_nodes=*/nullptr)); + /*compile_time_const_nodes=*/nullptr, + /*flib_runtime=*/nullptr)); EXPECT_EQ(const_args, std::vector({false, true})); } @@ -128,7 +130,8 @@ TEST(ConstAnalysisTest, RespectExplicitAttr_0) { std::vector const_args(2, false); TF_ASSERT_OK(BackwardsConstAnalysis(graph, &const_args, - /*compile_time_const_nodes=*/nullptr)); + /*compile_time_const_nodes=*/nullptr, + /*flib_runtime=*/nullptr)); EXPECT_EQ(const_args, std::vector({false, false})); } @@ -152,7 +155,8 @@ TEST(ConstAnalysisTest, RespectExplicitAttr_1) { std::vector const_args(1, false); TF_ASSERT_OK(BackwardsConstAnalysis(graph, &const_args, - /*compile_time_const_nodes=*/nullptr)); + /*compile_time_const_nodes=*/nullptr, + /*flib_runtime=*/nullptr)); EXPECT_EQ(const_args, std::vector({true})); } diff --git a/tensorflow/compiler/tf2xla/graph_compiler.cc b/tensorflow/compiler/tf2xla/graph_compiler.cc index 5e4699bbb6..b3cb23003e 100644 --- a/tensorflow/compiler/tf2xla/graph_compiler.cc +++ b/tensorflow/compiler/tf2xla/graph_compiler.cc @@ -56,9 +56,9 @@ Status PrepareArguments(XlaOpKernelContext* ctx, Graph* graph, auto client = ctx->compiler()->client(); std::vector arg_must_be_compile_time_constant(expressions.size()); - TF_RETURN_IF_ERROR( - BackwardsConstAnalysis(*graph, &arg_must_be_compile_time_constant, - /*compile_time_const_nodes=*/nullptr)); + TF_RETURN_IF_ERROR(BackwardsConstAnalysis( + *graph, &arg_must_be_compile_time_constant, + /*compile_time_const_nodes=*/nullptr, ctx->function_library())); args->resize(expressions.size()); for (int i = 0; i < args->size(); ++i) { @@ -284,6 +284,7 @@ void GraphCompiler::PartiallySetupParams(OpKernelContext::Params* params) { params->inputs = &tensor_inputs_; params->step_container = step_container_; params->resource_manager = device_->resource_manager(); + params->function_library = flib_; } } // namespace tensorflow diff --git a/tensorflow/compiler/tf2xla/kernels/BUILD b/tensorflow/compiler/tf2xla/kernels/BUILD index 343568b239..316a662751 100644 --- a/tensorflow/compiler/tf2xla/kernels/BUILD +++ b/tensorflow/compiler/tf2xla/kernels/BUILD @@ -251,6 +251,7 @@ tf_kernel_library( "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:protos_all_cc", + "@com_google_absl//absl/strings", ], ) diff --git a/tensorflow/compiler/tf2xla/kernels/while_op.cc b/tensorflow/compiler/tf2xla/kernels/while_op.cc index f49da9683b..1943793688 100644 --- a/tensorflow/compiler/tf2xla/kernels/while_op.cc +++ b/tensorflow/compiler/tf2xla/kernels/while_op.cc @@ -15,6 +15,7 @@ limitations under the License. #include "tensorflow/compiler/tf2xla/kernels/while_op.h" +#include "absl/strings/str_split.h" #include "tensorflow/compiler/tf2xla/shape_util.h" #include "tensorflow/compiler/tf2xla/side_effect_util.h" #include "tensorflow/compiler/tf2xla/type_util.h" @@ -25,6 +26,7 @@ limitations under the License. #include "tensorflow/compiler/xla/client/xla_builder.h" #include "tensorflow/compiler/xla/client/xla_computation.h" #include "tensorflow/compiler/xla/literal.h" +#include "tensorflow/core/framework/attr_value.pb.h" #include "tensorflow/core/framework/function.h" #include "tensorflow/core/framework/op_kernel.h" @@ -89,6 +91,151 @@ Status MakeXlaCompilerArgumentsFromInputs( return Status::OK(); } +// Populates loop invariant indices to true in `loop_invariants`. +void GetLoopInvariants(XlaOpKernelContext* ctx, + const NameAttrList& body_name_attr, + std::vector* const loop_invariants) { + const FunctionBody* body; + OP_REQUIRES_OK(ctx, ctx->compiler()->FindFunctionBody(body_name_attr, &body)); + for (int i = 0; i < body->ret_nodes.size(); i++) { + const Node* arg = body->arg_nodes[i]; + const Node* ret = body->ret_nodes[i]; + const Node* ret_input_0; + OP_REQUIRES_OK(ctx, ret->input_node(0, &ret_input_0)); + (*loop_invariants)[i] = ret_input_0->id() == arg->id(); + } +} + +// Converts entries in `args` which are loop invariants and have compile +// time constant inputs to constants so that they can be propagated in the loop +// body. +Status ConvertLoopInvariantsToConst( + XlaOpKernelContext* ctx, const NameAttrList& body_name_attr, + std::vector* args, + std::vector* compile_time_const_arg_indices, + int* num_compile_time_const_args, xla::Client* client) { + std::vector loop_invariants(ctx->num_inputs()); + GetLoopInvariants(ctx, body_name_attr, &loop_invariants); + for (int i = 0; i < ctx->num_inputs(); i++) { + XlaCompiler::Argument& arg = (*args)[i]; + const XlaExpression& expression = ctx->InputExpression(i); + // If this is a loop invariant and the input tensor is a compile time + // constant build a kConstant type argument. + if (arg.kind != XlaCompiler::Argument::kResource && loop_invariants[i]) { + // NOTE: We can not simple check that this is Kind::kConstant because + // this could be the output of a MetadataOnly op e.g. Size. + xla::StatusOr> maybe_constant = + expression.ResolveConstant(client); + if (maybe_constant.ok() && maybe_constant.ValueOrDie().has_value()) { + arg.kind = XlaCompiler::Argument::kConstant; + arg.type = expression.dtype(); + arg.constant_value = std::move(maybe_constant.ValueOrDie().value()); + arg.shape = expression.GetShape().ValueOrDie(); + compile_time_const_arg_indices->at(i) = true; + (*num_compile_time_const_args)++; + } + } + } + return Status::OK(); +} + +Status VerifyBodyInputAndOutputShapeMatch( + XlaOpKernelContext* ctx, + const std::vector& compile_time_const_arg_indices, + const XlaCompiler::CompilationResult& body, bool has_token_input_output) { + xla::Shape body_input_shape = body.xla_input_shapes[0]; + xla::Shape body_output_shape; + body_output_shape.set_element_type(xla::TUPLE); + for (int i = 0; i < ctx->num_outputs(); i++) { + if (!compile_time_const_arg_indices[i]) { + *(body_output_shape.add_tuple_shapes()) = + body.xla_output_shape.tuple_shapes(i); + } + } + // If `body` has a token output, append its shape to `body_output_shape`. + if (has_token_input_output) { + *(body_output_shape.add_tuple_shapes()) = + body.xla_output_shape.tuple_shapes(ctx->num_inputs()); + } + if (!xla::ShapeUtil::Compatible(body_input_shape, body_output_shape)) { + return errors::InvalidArgument( + "Input and output shapes of loop body do not match: ", + xla::ShapeUtil::HumanString(body_input_shape), " vs. ", + xla::ShapeUtil::HumanString(body_output_shape)); + } + return Status::OK(); +} + +xla::StatusOr BuildWrappedCond( + XlaOpKernelContext* ctx, const XlaCompiler::CompilationResult& cond) { + xla::Shape cond_input_shape = cond.xla_input_shapes[0]; + std::unique_ptr cb = + ctx->builder()->CreateSubBuilder("cond_wrapper"); + auto inputs = xla::Parameter(cb.get(), 0, cond_input_shape, "inputs"); + auto outputs = xla::Call(cb.get(), *cond.computation, {inputs}); + xla::GetTupleElement(outputs, 0); + return cb->Build(); +} + +xla::StatusOr BuildWrappedBody( + XlaOpKernelContext* ctx, const XlaCompiler::CompilationResult& body, + const std::vector& compile_time_const_arg_indices, + bool has_token_input_output) { + xla::XlaComputation body_wrapper; + std::unique_ptr cb = + ctx->builder()->CreateSubBuilder("body_wrapper"); + xla::Shape body_input_shape = body.xla_input_shapes[0]; + auto inputs = xla::Parameter(cb.get(), 0, body_input_shape, "inputs"); + // Call the original body function which has mismatched inputs and outputs + // and strip the compile time consts from the list of outputs. While requires + // the inputs and outputs of its body function to match. + auto outputs = xla::Call(cb.get(), *body.computation, {inputs}); + std::vector non_compile_time_const_outputs; + for (int i = 0; i < compile_time_const_arg_indices.size(); i++) { + if (!compile_time_const_arg_indices[i]) { + non_compile_time_const_outputs.push_back( + xla::GetTupleElement(outputs, i)); + } + } + // If `body` has a token output, append it to + // `non_compile_time_const_outputs`. + if (has_token_input_output) { + non_compile_time_const_outputs.push_back( + xla::GetTupleElement(outputs, ctx->num_outputs())); + } + xla::Tuple(cb.get(), non_compile_time_const_outputs); + return cb->Build(); +} + +xla::XlaOp BuildWhile(XlaOpKernelContext* ctx, + const xla::XlaComputation& wrapped_cond, + const xla::XlaComputation& wrapped_body, + const xla::XlaOp& initial_values, + const std::vector& input_mapping, + const std::vector& compile_time_const_arg_indices, + int num_compile_time_const_args, + bool has_token_input_output) { + xla::XlaOp while_result = + xla::While(wrapped_cond, wrapped_body, initial_values); + std::vector padded_while_outputs(ctx->num_outputs()); + int while_result_index = 0; + for (int i = 0; i < ctx->num_inputs(); i++) { + if (!compile_time_const_arg_indices[i]) { + padded_while_outputs[input_mapping[while_result_index]] = + xla::GetTupleElement(while_result, while_result_index); + while_result_index++; + } else { + padded_while_outputs[i] = ctx->Input(i); + } + } + // If `body` has a token output, append it to `padded_while_outputs`. + if (has_token_input_output) { + padded_while_outputs.push_back(xla::GetTupleElement( + while_result, ctx->num_inputs() - num_compile_time_const_args)); + } + return xla::Tuple(ctx->builder(), padded_while_outputs); +} + } // anonymous namespace XlaWhileOp::XlaWhileOp(OpKernelConstruction* ctx) : XlaOpKernel(ctx) { @@ -117,6 +264,23 @@ void XlaWhileOp::Compile(XlaOpKernelContext* ctx) { xla::XlaBuilder* builder = ctx->builder(); XlaCompiler* compiler = ctx->compiler(); + // Indices of loop vars which satisfy the following conditions: + // 1. They are loop invariants. + // 2. The op inputs at these indices are compile time constants. + // + // These compile time consts do not appear as _Args in the cond/body functions + // and are replaced by kConstant nodes instead. As as result, the compiled + // body function does not have matching input and output shape. We fix this + // by rewriting the body computation (see body_wrapper below) to output + // just the non compile-time-const values and later pad up the while output + // with the const args. + std::vector compile_time_const_arg_indices(ctx->num_inputs()); + int num_compile_time_const_args = 0; + OP_REQUIRES_OK(ctx, ConvertLoopInvariantsToConst( + ctx, body_name_attr_, &arguments, + &compile_time_const_arg_indices, + &num_compile_time_const_args, compiler->client())); + VLOG(1) << "Compiling body"; // All resource that are inputs to the loop's body must also be @@ -232,12 +396,13 @@ void XlaWhileOp::Compile(XlaOpKernelContext* ctx) { "Input shapes of loop body and condition do not match: ", xla::ShapeUtil::HumanString(body_input_shape), " vs. ", xla::ShapeUtil::HumanString(cond_input_shape))); - OP_REQUIRES( - ctx, xla::ShapeUtil::Compatible(body_input_shape, body.xla_output_shape), - errors::InvalidArgument( - "Input and output shapes of loop body do not match: ", - xla::ShapeUtil::HumanString(body_input_shape), " vs. ", - xla::ShapeUtil::HumanString(body.xla_output_shape))); + + // Check that the shape of the body outputs excluding the compile time const + // args (which are pruned from the body outputs in body_wapper) matches the + // shape of the inputs. + OP_REQUIRES_OK(ctx, VerifyBodyInputAndOutputShapeMatch( + ctx, compile_time_const_arg_indices, body, + has_token_input_output_)); xla::Shape expected_cond_output_shape_without_side_effect = xla::ShapeUtil::MakeTupleShape( @@ -275,7 +440,7 @@ void XlaWhileOp::Compile(XlaOpKernelContext* ctx) { OP_REQUIRES_OK(ctx, ctx->GetResourceInput(input_num, &resource)); OP_REQUIRES_OK(ctx, resource->Pack(&inputs[i], builder)); } else { - inputs[i] = ctx->Input(i); + inputs[i] = ctx->Input(input_num); } } @@ -284,26 +449,27 @@ void XlaWhileOp::Compile(XlaOpKernelContext* ctx) { VLOG(1) << "Building while loop"; // Wraps the condition in a computation that unpacks the output tuple. - xla::XlaComputation cond_wrapper; - { - std::unique_ptr cb = - builder->CreateSubBuilder("cond_wrapper"); - auto inputs = xla::Parameter(cb.get(), 0, cond_input_shape, "inputs"); - auto outputs = xla::Call(cb.get(), *cond.computation, {inputs}); - xla::GetTupleElement(outputs, 0); - xla::StatusOr result = cb->Build(); - OP_REQUIRES_OK(ctx, result.status()); - cond_wrapper = std::move(result.ValueOrDie()); - } - - xla::XlaOp while_result = xla::While(cond_wrapper, *body.computation, init); + xla::StatusOr cond_result = BuildWrappedCond(ctx, cond); + OP_REQUIRES_OK(ctx, cond_result.status()); + xla::XlaComputation wrapped_cond = std::move(cond_result.ValueOrDie()); + + // Remove compile time const args from the list of body outputs. + xla::StatusOr body_result = BuildWrappedBody( + ctx, body, compile_time_const_arg_indices, has_token_input_output_); + OP_REQUIRES_OK(ctx, body_result.status()); + xla::XlaComputation wrapped_body = std::move(body_result.ValueOrDie()); + + // Builds the While op and pads its output with the compile time const args. + xla::XlaOp while_result = + BuildWhile(ctx, wrapped_cond, wrapped_body, init, body.input_mapping, + compile_time_const_arg_indices, num_compile_time_const_args, + has_token_input_output_); // Sets non-variable outputs and determine when resource variables start. int resource_index = 0; for (int i = 0; i < ctx->num_outputs(); ++i) { if (ctx->input_type(i) != DT_RESOURCE) { - ctx->SetOutput(body.input_mapping[i], - xla::GetTupleElement(while_result, i)); + ctx->SetOutput(i, xla::GetTupleElement(while_result, i)); ++resource_index; } else { break; diff --git a/tensorflow/compiler/tf2xla/xla_compiler.h b/tensorflow/compiler/tf2xla/xla_compiler.h index ad3144b41b..f6fd877d9c 100644 --- a/tensorflow/compiler/tf2xla/xla_compiler.h +++ b/tensorflow/compiler/tf2xla/xla_compiler.h @@ -416,11 +416,11 @@ class XlaCompiler { Status SetNodeToken(const string& node_name, const xla::XlaOp& op); xla::StatusOr GetNodeToken(const string& node_name); - private: // Sets the function body `fbody` to the one registered as `function`. Status FindFunctionBody(const NameAttrList& function, const FunctionBody** fbody); + private: // Returns the optimized graph object in this function body. std::unique_ptr GetGraph(const FunctionBody* fbody); diff --git a/tensorflow/python/keras/BUILD b/tensorflow/python/keras/BUILD index 8743dbaa13..917acf1561 100755 --- a/tensorflow/python/keras/BUILD +++ b/tensorflow/python/keras/BUILD @@ -858,6 +858,21 @@ tf_py_test( ], ) +tf_py_test( + name = "time_distributed_learning_phase_test", + size = "small", + srcs = ["layers/time_distributed_learning_phase_test.py"], + additional_deps = [ + ":keras", + "//third_party/py/numpy", + "//tensorflow/python:client_testlib", + ], + tags = [ + "noasan", # http://b/78599823 + "notsan", + ], +) + tf_py_test( name = "scikit_learn_test", size = "small", diff --git a/tensorflow/python/keras/layers/time_distributed_learning_phase_test.py b/tensorflow/python/keras/layers/time_distributed_learning_phase_test.py new file mode 100644 index 0000000000..2c38f25d33 --- /dev/null +++ b/tensorflow/python/keras/layers/time_distributed_learning_phase_test.py @@ -0,0 +1,43 @@ +# Copyright 2019 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 layer wrappers.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np + +from tensorflow.python import keras +from tensorflow.python.platform import test + + +# TODO(b/125513261): Move this back into wrappers_test.py. +class TimeDistributedLearningPhaseTest(test.TestCase): + + def test_TimeDistributed_learning_phase(self): + with self.cached_session(): + # test layers that need learning_phase to be set + np.random.seed(1234) + x = keras.layers.Input(shape=(3, 2)) + y = keras.layers.TimeDistributed(keras.layers.Dropout(.999))( + x, training=True) + model = keras.models.Model(x, y) + y = model.predict(np.random.random((10, 3, 2))) + self.assertAllClose(np.mean(y), 0., atol=1e-1, rtol=1e-1) + + +if __name__ == '__main__': + test.main() diff --git a/tensorflow/python/keras/layers/wrappers_test.py b/tensorflow/python/keras/layers/wrappers_test.py index 8fa0e7bdac..8fa0fd7de2 100644 --- a/tensorflow/python/keras/layers/wrappers_test.py +++ b/tensorflow/python/keras/layers/wrappers_test.py @@ -155,17 +155,6 @@ class TimeDistributedTest(test.TestCase): model.compile(optimizer='rmsprop', loss='mse') self.assertEqual(len(model.losses), 1) - def test_TimeDistributed_learning_phase(self): - with self.cached_session(): - # test layers that need learning_phase to be set - np.random.seed(1234) - x = keras.layers.Input(shape=(3, 2)) - y = keras.layers.TimeDistributed(keras.layers.Dropout(.999))( - x, training=True) - model = keras.models.Model(x, y) - y = model.predict(np.random.random((10, 3, 2))) - self.assertAllClose(np.mean(y), 0., atol=1e-1, rtol=1e-1) - def test_TimeDistributed_batchnorm(self): with self.cached_session(): # test that wrapped BN updates still work. diff --git a/tensorflow/python/kernel_tests/control_flow_ops_py_test.py b/tensorflow/python/kernel_tests/control_flow_ops_py_test.py index 982ead7e94..1b13546b03 100644 --- a/tensorflow/python/kernel_tests/control_flow_ops_py_test.py +++ b/tensorflow/python/kernel_tests/control_flow_ops_py_test.py @@ -1411,7 +1411,6 @@ class ControlFlowTest(test.TestCase): r"while loop context '' \(currently defined in 'cond/.+'\)"): _ = gradients_impl.gradients(loop, v) - @test_util.disable_control_flow_v2("b/123601232") @test_util.run_v1_only("b/120545219") def testNestedWhileLoopWithMaxItersFromOuterContextInXLAContext(self): v = constant_op.constant(1.0) @@ -1456,12 +1455,14 @@ class ControlFlowTest(test.TestCase): with self.session(use_gpu=False) as sess: opts = config_pb2.RunOptions(trace_level=config_pb2.RunOptions.FULL_TRACE) + run_metadata_without_xla_context = config_pb2.RunMetadata() run_metadata = config_pb2.RunMetadata() final_value_without_xla_context = sess.run( - final_without_xla_context, feed_dict={ - p: [0, 0, 0] - }) + final_without_xla_context, + feed_dict={p: [0, 0, 0]}, + options=opts, + run_metadata=run_metadata_without_xla_context) final_value_with_xla_context = sess.run( final_with_xla_context, @@ -1469,9 +1470,18 @@ class ControlFlowTest(test.TestCase): options=opts, run_metadata=run_metadata) - node_stats = run_metadata.step_stats.dev_stats[0].node_stats + if control_flow_util.ENABLE_CONTROL_FLOW_V2: + # With while_v2 on xla, run_metadata only contains the unlowered While + # op so node_stats does not have statistics for the pushes. So as a + # loose check we check the pushes in the lowered version. + node_stats = run_metadata_without_xla_context.step_stats.dev_stats[ + 0].node_stats + stack_push_op = "TensorListPushBack" + else: + node_stats = run_metadata.step_stats.dev_stats[0].node_stats + stack_push_op = "StackPushV2" stack_push_count = len( - [x for x in node_stats if x.node_name.endswith("StackPushV2")]) + [x for x in node_stats if x.node_name.endswith(stack_push_op)]) # Pushes to the stack = product of maximum_iterations values; # the last two "3"s comes from size(p), when p == [0, 0, 0]. self.assertEqual(stack_push_count, 5 * 3 * 3) diff --git a/tensorflow/python/kernel_tests/while_v2_test.py b/tensorflow/python/kernel_tests/while_v2_test.py index 2c6d275900..f406624fc6 100644 --- a/tensorflow/python/kernel_tests/while_v2_test.py +++ b/tensorflow/python/kernel_tests/while_v2_test.py @@ -283,8 +283,8 @@ class WhileV2Test(test.TestCase, parameterized.TestCase): while_op = op body_graph = while_v2._get_graph(while_op, "body") - # body_graph.inputs: [counter_arg, x_arg, tl_arg, *accumulators] - x_input_t = body_graph.inputs[1] + x_input_index = [i for i, inp in enumerate(while_op.inputs) if inp == x][0] + x_input_t = body_graph.inputs[x_input_index] accumulator_count = len( [c for c in x_input_t.consumers() if c.type == "TensorListPushBack"]) self.assertEqual(accumulator_count, 1) @@ -331,12 +331,13 @@ class WhileV2Test(test.TestCase, parameterized.TestCase): while_op = ret[0].op.inputs[0].op # Gradient pass. grad = gradients_impl.gradients(ret[0], x) + # Note: There is an Identity b/w grad[0] and the While op. grad_while_op = grad[0].op.inputs[0].op # Get the TensorList output of While op containing the accumulated values # of y. - # while_op.inputs: [counter_arg, x_arg, y_arg, *accumulators] - output = GetAccumulatorForInputAtIndex(while_op, 2) + x_input_index = [i for i, inp in enumerate(while_op.inputs) if x == inp][0] + output = GetAccumulatorForInputAtIndex(while_op, x_input_index) _, val = list_ops.tensor_list_pop_back(output, element_dtype=dtypes.float32) MatchShape(val.shape) @@ -347,8 +348,9 @@ class WhileV2Test(test.TestCase, parameterized.TestCase): # Get the TensorList output of gradient While op containing the accumulated # values of grad_x (note that grad_x is needed by the second derivative). # grad_while_op.inputs: - # [counter_arg, total_iters_arg, grad_x_arg, grad_y_arg, *other_args] - grad_output = GetAccumulatorForInputAtIndex(grad_while_op, 2) + grad_output_index = grad_while_op.outputs.index(grad[0].op.inputs[0]) + grad_output = GetAccumulatorForInputAtIndex(grad_while_op, + grad_output_index) _, val = list_ops.tensor_list_pop_back(grad_output, element_dtype=dtypes.float32) MatchShape(val.shape) @@ -447,17 +449,17 @@ class WhileV2Test(test.TestCase, parameterized.TestCase): [x])[0] while_op = output.op.inputs[0].op self.assertEqual(while_op.type, "While") - # outputs = [loop_counter, x] - self.assertLen(while_op.outputs, 2) + # outputs = [loop_counter, max_iters, x] + self.assertLen(while_op.outputs, 3) gradients_impl.gradients(output, x) # while_op should have been rewritten to output 2.0 intermediate. - # outputs = [loop_counter, x, 2.0_accumulator, x_accumulator] - self.assertLen(while_op.outputs, 4) + # outputs = [loop_counter, max_iters, x, 2.0_accumulator, x_accumulator] + self.assertLen(while_op.outputs, 5) gradients_impl.gradients(output, x) # Computing the gradient again shouldn't rewrite while_op again. - self.assertLen(while_op.outputs, 4) + self.assertLen(while_op.outputs, 5) def ScalarShape(): diff --git a/tensorflow/python/ops/while_v2.py b/tensorflow/python/ops/while_v2.py index 68f1cbae0a..4a42e94748 100644 --- a/tensorflow/python/ops/while_v2.py +++ b/tensorflow/python/ops/while_v2.py @@ -23,7 +23,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.core.framework import attr_value_pb2 from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes from tensorflow.python.framework import func_graph as func_graph_module @@ -31,7 +30,6 @@ from tensorflow.python.framework import function_def_to_graph from tensorflow.python.framework import ops from tensorflow.python.framework import tensor_shape from tensorflow.python.framework import tensor_spec -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 control_flow_util @@ -64,7 +62,6 @@ def while_loop(cond, name=None, return_same_structure=True): """Like tf.while_loop, except emits a single While op.""" - maximum_iterations = _validate_and_convert_to_tensor(maximum_iterations) # Keep the original loop_vars around to know which args were TensorArrays. orig_loop_vars = loop_vars # Cache its length since we use it at multiple places below. @@ -88,24 +85,25 @@ def while_loop(cond, with ops.name_scope(None): cond_name = util.unique_fn_name(scope, "cond") body_name = util.unique_fn_name(scope, "body") - + maximum_iterations_loop_var = _build_maximum_iterations_loop_var( + maximum_iterations) loop_counter = constant_op.constant( 0, - dtype=maximum_iterations.dtype + dtype=maximum_iterations_loop_var.dtype if maximum_iterations is not None else None, name="loop_counter") # Add loop counter needed for computing gradients. - loop_vars = [loop_counter] + loop_vars + loop_vars = [loop_counter, maximum_iterations_loop_var] + loop_vars - shape_invariants = type(shape_invariants)([tensor_shape.scalar() - ]) + shape_invariants + shape_invariants = type(shape_invariants)( + [tensor_shape.scalar(), tensor_shape.scalar()]) + shape_invariants # Automatic control dependencies are added in defuns, but not in v1 # graphs. Propagate that behavior here. add_control_dependencies = ops.get_default_graph()._add_control_dependencies # Build a `cond` wrapper that can handle the extra counter loop_var. - def wrapped_cond(loop_counter, *args): + def wrapped_cond(loop_counter, maximum_iterations_arg, *args): # Convert the flow variables in `args` to TensorArrays. `args` should # already have the same structure as `orig_loop_vars` but currently there # is no nest.zip so we call `_pack_sequence_as` which flattens both @@ -115,7 +113,7 @@ def while_loop(cond, return cond(*_pack_sequence_as(orig_loop_vars, args)) else: return math_ops.logical_and( - loop_counter < maximum_iterations, + loop_counter < maximum_iterations_arg, cond(*_pack_sequence_as(orig_loop_vars, args))) # NOTE(skyewm): we set collections to the outer graph's collections for @@ -130,11 +128,12 @@ def while_loop(cond, cond_name, collections=ops.get_default_graph()._collections), # pylint: disable=protected-access add_control_dependencies=add_control_dependencies) - def wrapped_body(loop_counter, *args): + def wrapped_body(loop_counter, maximum_iterations_arg, *args): """Loop body augmented with counter update. Args: loop_counter: Loop counter which needs to be incremented in the body. + maximum_iterations_arg: Maximum iterations of the loop. *args: List of args Returns: @@ -161,7 +160,7 @@ def while_loop(cond, # TODO(srbs): Update lowering code to create _Enter nodes with # is_constant=True for inputs that are directly passed to outputs. - return [loop_counter + 1] + list(outputs) + return [loop_counter + 1, maximum_iterations_arg] + list(outputs) body_graph = func_graph_module.func_graph_from_py_func( body_name, @@ -195,10 +194,16 @@ def while_loop(cond, # shape invariants, or the shapes of the loop vars if the invariants are not # specified. num_flattened_outputs = len(nest.flatten(orig_loop_vars)) + # First var is loop counter and second var is maximum_iterations. + first_loop_var_index = 2 _check_shapes_compat( - body_graph.outputs[1:1 + num_flattened_outputs], - nest.flatten(shape_invariants[1:1 + len_orig_loop_vars]), - nest.flatten(loop_vars[1:1 + len_orig_loop_vars])) + body_graph.outputs[first_loop_var_index:first_loop_var_index + + num_flattened_outputs], + nest.flatten( + shape_invariants[first_loop_var_index:first_loop_var_index + + len_orig_loop_vars]), + nest.flatten(loop_vars[first_loop_var_index:first_loop_var_index + + len_orig_loop_vars])) flattened_loop_vars = nest.flatten(loop_vars) _check_num_inputs_outputs(cond_graph, body_graph, len(flattened_loop_vars)) @@ -213,7 +218,6 @@ def while_loop(cond, _copy_handle_data(body_graph.outputs, outputs) util.maybe_set_lowering_attr(outputs[0].op) - _maybe_set_maximum_iterations_attr(outputs[0].op, maximum_iterations) # Return identities for each output of the While op, rather than the output # of the While op directly. This makes pruning work if the output of @@ -223,9 +227,9 @@ def while_loop(cond, # identity op will end up with only the appropriate exit op as input. outputs = tuple(array_ops.identity(t) for t in outputs) - # First var is loop counter. - outputs = _pack_sequence_as(orig_loop_vars, - outputs[1:1 + num_flattened_outputs]) + outputs = _pack_sequence_as( + orig_loop_vars, outputs[first_loop_var_index:first_loop_var_index + + num_flattened_outputs]) if return_same_structure: return outputs @@ -249,11 +253,8 @@ def _WhileGrad(op, *grads): # pylint: disable=invalid-name body_graph = _get_graph(while_op, "body") orig_num_params = len(body_graph.outputs) - maximum_iterations = op.get_attr( - "_maximum_iterations") if _is_in_xla_context() else None + maximum_iterations = op.inputs[1] parallel_iterations = op.get_attr("parallel_iterations") - assert not _is_in_xla_context() or maximum_iterations is not None - maximum_iterations = _validate_and_convert_to_tensor(maximum_iterations) grads = [_preprocess_grad(grad, body_out, while_out) for grad, body_out, while_out @@ -298,8 +299,9 @@ def _WhileGrad(op, *grads): # pylint: disable=invalid-name loop_vars = while_v2_indexed_slices_rewriter.rewrite_grad_indexed_slices( grads, body_grad_graph, loop_vars, while_op.inputs) - def grad_cond(counter, max_iters, *unused_args): - return counter < max_iters + def grad_cond(counter, unused_maximum_iterations_arg, forward_loop_iters, + *unused_args): + return counter < forward_loop_iters grad_cond_name = util.unique_grad_fn_name(op.get_attr("cond").name) cond_grad_graph = func_graph_module.func_graph_from_py_func( @@ -319,7 +321,6 @@ def _WhileGrad(op, *grads): # pylint: disable=invalid-name _copy_handle_data(body_grad_graph.outputs, outputs) util.maybe_set_lowering_attr(grad_op) - _maybe_set_maximum_iterations_attr(grad_op, maximum_iterations) # See comment in while_loop. outputs = [array_ops.identity(t) for t in outputs] @@ -387,48 +388,6 @@ def _is_trainable(tensor): return True -def _validate_and_convert_to_tensor(maximum_iterations): - """Checks that `maximum_iterations` is valid. - - In XLA context, `maximum_iterations` is required and must be statically - inferable, e.g. output tensor of a Const node. - - Args: - maximum_iterations: The maximum_iterations passed to while_loop. - - Returns: - A scalar valued tensor of type int32 or None. - - Raises: - ValueError: If `maximum_iterations` is invalid. - """ - if maximum_iterations is None: - return None - - if _is_in_xla_context() and isinstance(maximum_iterations, ops.Tensor): - # Get the constant value from the `maximum_iterations` tensor to avoid - # capturing a Const tensor from outside this graph. - value = tensor_util.constant_value(maximum_iterations) - if value is None: - # XLA requires maximum_iterations to be statically known (e.g. a - # constant value or known shape dimension) when intermediate values - # from the forward pass are needed in the gradients pass. However, - # maximum_iterations may not be required if the gradient isn't built - # or no intermediates are required, thus we return the tensor as is. - return maximum_iterations - - maximum_iterations = value - - # EmptyTensorList expects `max_num_elements` to be of type int32. - maximum_iterations = ops.convert_to_tensor( - maximum_iterations, dtype=dtypes.int32, name="maximum_iterations") - if maximum_iterations.shape.ndims != 0: - raise ValueError("maximum_iterations must be a scalar, saw shape: %s" % - maximum_iterations.shape) - - return maximum_iterations - - # TODO(srbs): Pull this into common utils for cond_v2 and while_v2. def _get_graph(while_op, func_attr_name): """Returns `FuncGraph` for the given function attribute. @@ -458,7 +417,7 @@ def _get_graph(while_op, func_attr_name): def _create_grad_func(ys, xs, grads, cond_graph, body_graph, name, while_op, - max_iters): + maximum_iterations): """Builds and returns the gradient FuncGraph of `func_graph` and its args. The returned grad_func_graph must be called with the returned @@ -472,7 +431,7 @@ def _create_grad_func(ys, xs, grads, cond_graph, body_graph, name, while_op, body_graph: FuncGraph for the forward body function. name: Name of the returned gradient function. while_op: The forward While op. - max_iters: the maximum number of iterations, or None if no limit. + maximum_iterations: Tensor. The maximum number of iterations. Returns: 2-tuple of (grad_func_graph, args). @@ -483,7 +442,7 @@ def _create_grad_func(ys, xs, grads, cond_graph, body_graph, name, while_op, counter = constant_op.constant( 0, dtype=total_iters.dtype, name="grad_counter") - args = [counter, total_iters] + list(grads) + args = [counter, maximum_iterations, total_iters] + list(grads) # Note: The returned function does not have `args` in the list of # `external_captures`. grad_func_graph = func_graph_module.func_graph_from_py_func( @@ -491,7 +450,7 @@ def _create_grad_func(ys, xs, grads, cond_graph, body_graph, name, while_op, lambda *args: _grad_fn(ys, xs, args, body_graph), args, {}, func_graph=_WhileBodyGradFuncGraph(name, cond_graph, body_graph, - max_iters)) + maximum_iterations)) # Add the popped accumulators to the list of outputs. for internal_capture in grad_func_graph.internal_captures: @@ -521,13 +480,14 @@ def _grad_fn(ys, xs, args, func_graph): args: The input arguments. args[0] - Loop counter args[1] - Total number of iterations. - args[2:] - Incoming gradients for `ys`. + args[2] - maximum_iterations. + args[3:] - Incoming gradients for `ys`. func_graph: function.FuncGraph. The corresponding forward-pass function. Returns: The output gradient Tensors. """ - grad_ys = args[2:] + grad_ys = args[3:] # Build the gradient graph. Note that this builds the gradient computation of # func_graph in the current graph, which requires capturing tensors from @@ -542,8 +502,9 @@ def _grad_fn(ys, xs, args, func_graph): # is a tf.StopGradient in the loop body. assert all(g is not None for g in grad_outs) counter = args[0] - total_iters = args[1] - return [counter + 1, total_iters] + grad_outs + maximum_iterations = args[1] + total_iters = args[2] + return [counter + 1, maximum_iterations, total_iters] + grad_outs def _resolve_grad_captures(body_graph, body_grad_graph, while_op): @@ -600,9 +561,10 @@ def _get_structured_grad_output(outputs, grads, body_grad_graph): """ result = [] # outputs[0] is the loop counter. - # outputs[1] is the total number of loop iterations. - outputs_idx = 2 - structured_outputs_idx = 2 + # outputs[1] is maximum_iterations. + # outputs[2] is the total number of loop iterations. + outputs_idx = 3 + structured_outputs_idx = 3 for g in grads: # Set None as the output gradient for tensors with None input gradient. if g is None: @@ -724,7 +686,8 @@ class _WhileBodyGradFuncGraph(util.WhileBodyFuncGraph): values of this dict need to be added to the list of outputs. """ - def __init__(self, name, forward_cond_graph, forward_body_graph, max_iters): + def __init__(self, name, forward_cond_graph, forward_body_graph, + maximum_iterations): super(_WhileBodyGradFuncGraph, self).__init__(name) self.empty_tensor_lists = [] self.popped_tensor_lists = {} @@ -732,7 +695,7 @@ class _WhileBodyGradFuncGraph(util.WhileBodyFuncGraph): self._forward_graph = forward_body_graph # FuncGraph for the cond of the forward While op. self._forward_cond_graph = forward_cond_graph - self._maximum_iterations = max_iters + self._maximum_iterations = maximum_iterations # Dict from forward intermediate tensor to its indirectly captured tensor # in this graph. Indirect capturing happens in two ways: # 1. For non-resource tensors we capture their accumulators from the forward @@ -965,15 +928,6 @@ def _copy_handle_data(src_tensors, tgt_tensors): custom_gradient.copy_handle_data(src_t, tgt_t) -def _maybe_set_maximum_iterations_attr(op, maximum_iterations): - if maximum_iterations is not None and control_flow_util.IsInXLAContext(op): - # Store the maximum_iterations to use in the gradient pass. - op._set_attr( # pylint: disable=protected-access - "_maximum_iterations", - attr_value_pb2.AttrValue( - i=tensor_util.constant_value(maximum_iterations))) - - # TODO(srbs): This method should be in control_flow_util but that introduces # a circular dependency ops -> control_flow_util -> ops. def _is_in_xla_context(): @@ -1024,4 +978,13 @@ def _build_signature(loop_vars, shape_invariants): ]) +def _build_maximum_iterations_loop_var(maximum_iterations): + if maximum_iterations is None: + # Default value for max_num_elements to EmptyTensorList meaning that the + # list size is unbounded. + maximum_iterations = -1 + # EmptyTensorList expects `max_num_elements` to be of type int32. + return ops.convert_to_tensor( + maximum_iterations, dtype=dtypes.int32, name="maximum_iterations") + # pylint: enable=protected-access diff --git a/tensorflow/python/ops/while_v2_indexed_slices_rewriter.py b/tensorflow/python/ops/while_v2_indexed_slices_rewriter.py index 30e9709d70..9951cf74cc 100644 --- a/tensorflow/python/ops/while_v2_indexed_slices_rewriter.py +++ b/tensorflow/python/ops/while_v2_indexed_slices_rewriter.py @@ -63,8 +63,8 @@ def rewrite_grad_indexed_slices(grads, body_grad_graph, loop_vars, # body_grad_graph.outputs, which contains flattened composite tensors. inputs_with_grads = [t for g, t in zip(grads, forward_inputs) if g is not None] - # Skip loop counter and total number of loop iterations. - structured_outputs = body_grad_graph.structured_outputs[2:] + # Skip loop counter, maximum_iterations and total number of loop iterations. + structured_outputs = body_grad_graph.structured_outputs[3:] for forward_input, output in zip(inputs_with_grads, structured_outputs): if not isinstance(output, ops.IndexedSlices): continue -- GitLab From 18f10890c0ea39b8da852523f4f68c62a4ac8b43 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 21 Feb 2019 20:31:33 -0800 Subject: [PATCH 319/766] Address TODO to support materialization of Fill nodes that are fed from constants stored in raw serialized format. PiperOrigin-RevId: 235123971 --- tensorflow/core/grappler/optimizers/BUILD | 1 + .../grappler/optimizers/constant_folding.cc | 24 ++++++++---- .../optimizers/constant_folding_test.cc | 38 ++++++++++++++++++- 3 files changed, 54 insertions(+), 9 deletions(-) diff --git a/tensorflow/core/grappler/optimizers/BUILD b/tensorflow/core/grappler/optimizers/BUILD index cdf6180ff5..670e0f04dc 100644 --- a/tensorflow/core/grappler/optimizers/BUILD +++ b/tensorflow/core/grappler/optimizers/BUILD @@ -123,6 +123,7 @@ tf_cc_test( "//tensorflow/core:core_cpu_internal", "//tensorflow/core:direct_session", "//tensorflow/core:lib", + "//tensorflow/core:lib_internal", "//tensorflow/core:protos_all_cc", "//tensorflow/core:test", "//tensorflow/core:test_main", diff --git a/tensorflow/core/grappler/optimizers/constant_folding.cc b/tensorflow/core/grappler/optimizers/constant_folding.cc index 37fa7d9a3e..ae3aeb748c 100644 --- a/tensorflow/core/grappler/optimizers/constant_folding.cc +++ b/tensorflow/core/grappler/optimizers/constant_folding.cc @@ -29,6 +29,7 @@ limitations under the License. #include "tensorflow/core/framework/op_def.pb.h" #include "tensorflow/core/framework/tensor.pb.h" #include "tensorflow/core/framework/tensor_shape.pb.h" +#include "tensorflow/core/framework/tensor_util.h" #include "tensorflow/core/framework/types.h" #include "tensorflow/core/framework/versions.pb.h" #include "tensorflow/core/grappler/clusters/cluster.h" @@ -791,16 +792,25 @@ Status ConstantFolding::MaterializeConstantValuedNode( } } TF_RETURN_IF_ERROR(CheckAttrExists(*input_node, "value")); + + // Copy the input tensor to the fill node, set the output shape and data + // type, and change the node type to Const. + TensorProto* tensor = (*node->mutable_attr())["value"].mutable_tensor(); const TensorProto& input_tensor = input_node->attr().at("value").tensor(); - // TODO(rmlarsen): Handle the case where the value is stored in - // tensor_content. if (!input_tensor.tensor_content().empty()) { - return Status::OK(); + // Convert the value to repeated field format, so we can use the + // decompression mechanism to store only a single value in the constant + // node, even if the shape specified in the original Fill is large. + Tensor t; + if (!t.FromProto(input_tensor)) { + return errors::InvalidArgument( + "Could not construct Tensor form TensorProto in node: ", + input_node->name()); + } + t.AsProtoField(tensor); + } else { + *tensor = input_tensor; } - TensorProto* tensor = (*node->mutable_attr())["value"].mutable_tensor(); - // Copy the input tensor to the fill node, set the output shape, and - // change the nodd type to Const. - *tensor = input_tensor; *(tensor->mutable_tensor_shape()) = output_shape; (*node->mutable_attr())["dtype"].set_type(output_dtype); node->mutable_attr()->erase("T"); diff --git a/tensorflow/core/grappler/optimizers/constant_folding_test.cc b/tensorflow/core/grappler/optimizers/constant_folding_test.cc index 76e149d0ae..afca3d2257 100644 --- a/tensorflow/core/grappler/optimizers/constant_folding_test.cc +++ b/tensorflow/core/grappler/optimizers/constant_folding_test.cc @@ -26,6 +26,7 @@ limitations under the License. #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/tensor_coding.h" namespace tensorflow { namespace grappler { @@ -3650,8 +3651,7 @@ TEST_F(ConstantFoldingTest, MaterializeConstantValuedNode) { auto x_t = GenerateRandomTensor(TensorShape({1, 2, 3, 4})); auto tensors_expected = EvaluateNodes(item.graph, item.fetch, {{"x", x_t}}); - ConstantFolding optimizer(/*opt_level=*/RewriterConfig::AGGRESSIVE, - /*cpu_device=*/nullptr); + ConstantFolding optimizer(/*cpu_device=*/nullptr); GraphDef output; Status status = optimizer.Optimize(/*cluster=*/nullptr, item, &output); TF_EXPECT_OK(status); @@ -3683,6 +3683,40 @@ TEST_F(ConstantFoldingTest, MaterializeConstantValuedNode) { } } +TEST_F(ConstantFoldingTest, MaterializeConstantValuedNodeHugeFill) { + tensorflow::Scope scope = tensorflow::Scope::NewRootScope(); + Output value = ops::Const(scope.WithOpName("value"), 42, {}); + Output fill_huge = ops::Fill(scope.WithOpName("fill_huge"), + {1024, 1024, 1024, 1024, 1024}, value); + + GrapplerItem item; + TF_CHECK_OK(scope.ToGraphDef(&item.graph)); + // Manually convert the input value format to tensor_content to test this + // case. + NodeDef* node = item.graph.mutable_node(0); + ASSERT_EQ(node->name(), "value"); + TensorProto* t = (*node->mutable_attr())["value"].mutable_tensor(); + t->clear_int_val(); + int val = 42; + port::CopyFromArray(t->mutable_tensor_content(), + reinterpret_cast(&val), sizeof(int)); + item.fetch = {"fill_huge"}; + ConstantFolding optimizer(/*cpu_device=*/nullptr); + GraphDef output; + Status status = optimizer.Optimize(/*cluster=*/nullptr, item, &output); + TF_EXPECT_OK(status); + + EXPECT_EQ(output.node_size(), 3); + for (const auto& node : output.node()) { + EXPECT_EQ(node.op(), "Const"); + if (node.name() == "fill_huge") { + ASSERT_EQ(node.input_size(), 2); + EXPECT_EQ(node.input(0)[0], '^'); + EXPECT_EQ(node.input(1)[0], '^'); + } + } +} + TEST_F(ConstantFoldingTest, BitcastDenormalFloats) { tensorflow::Scope scope = tensorflow::Scope::NewRootScope(); -- GitLab From 5295cfc0966aa497ac34bd3affa7af34ed3476e9 Mon Sep 17 00:00:00 2001 From: Dan Moldovan Date: Thu, 21 Feb 2019 20:38:23 -0800 Subject: [PATCH 320/766] Handle the case when tf.function's target is a tf_decorator method that was hot-swapped after tf.function was applied. PiperOrigin-RevId: 235124593 --- tensorflow/python/util/tf_decorator.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tensorflow/python/util/tf_decorator.py b/tensorflow/python/util/tf_decorator.py index f5ce5bd42a..6c71f70c4e 100644 --- a/tensorflow/python/util/tf_decorator.py +++ b/tensorflow/python/util/tf_decorator.py @@ -59,6 +59,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +import inspect import traceback as _traceback @@ -154,7 +155,18 @@ def rewrap(decorator_func, previous_target, new_target): return target.decorated_target = new_target - innermost_decorator.__wrapped__ = new_target + + if inspect.ismethod(innermost_decorator): + # Bound methods can't be assigned attributes. Thankfully, they seem to + # be just proxies for their unbound counterpart, and we can modify that. + if hasattr(innermost_decorator, '__func__'): + innermost_decorator.__func__.__wrapped__ = new_target + elif hasattr(innermost_decorator, 'im_func'): + innermost_decorator.im_func.__wrapped__ = new_target + else: + innermost_decorator.__wrapped__ = new_target + else: + innermost_decorator.__wrapped__ = new_target def unwrap(maybe_tf_decorator): -- GitLab From 2b8badc120ce8f852fb5a4cc5d646e38c54b6b3c Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 21 Feb 2019 21:17:05 -0800 Subject: [PATCH 321/766] fixes for py3.7 builds PiperOrigin-RevId: 235128104 --- tensorflow/python/eager/tensor_test.py | 4 ++-- tensorflow/tools/docs/parser.py | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tensorflow/python/eager/tensor_test.py b/tensorflow/python/eager/tensor_test.py index 0d8845bd96..23fb983767 100644 --- a/tensorflow/python/eager/tensor_test.py +++ b/tensorflow/python/eager/tensor_test.py @@ -66,11 +66,11 @@ class TFETensorTest(test_util.TensorFlowTestCase): device = ctx.device_name # Missing context. with self.assertRaisesRegexp( - TypeError, r"Required argument 'context' \(pos 2\) not found"): + TypeError, r".*argument 'context' \(pos 2\).*"): ops.EagerTensor(1, device=device) # Missing device. with self.assertRaisesRegexp( - TypeError, r"Required argument 'device' \(pos 3\) not found"): + TypeError, r".*argument 'device' \(pos 3\).*"): ops.EagerTensor(1, context=handle) # Bad dtype type. with self.assertRaisesRegexp(TypeError, diff --git a/tensorflow/tools/docs/parser.py b/tensorflow/tools/docs/parser.py index ff32d08951..d87f9585f2 100644 --- a/tensorflow/tools/docs/parser.py +++ b/tensorflow/tools/docs/parser.py @@ -502,7 +502,10 @@ def _gen_pairs(items): assert len(items) % 2 == 0 items = iter(items) while True: - yield next(items), next(items) + try: + yield next(items), next(items) + except StopIteration: + return class _FunctionDetail( -- GitLab From 05ef7b7025091768f5ad28d55ee55bb3530188a4 Mon Sep 17 00:00:00 2001 From: Albin Joy Date: Fri, 22 Feb 2019 11:00:50 +0530 Subject: [PATCH 322/766] Added comments to get better understanding --- tensorflow/core/grappler/optimizers/layout_optimizer.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tensorflow/core/grappler/optimizers/layout_optimizer.cc b/tensorflow/core/grappler/optimizers/layout_optimizer.cc index e091e1a019..05a8293284 100644 --- a/tensorflow/core/grappler/optimizers/layout_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/layout_optimizer.cc @@ -2048,6 +2048,11 @@ class DataLayoutOptimizer : GraphProcessor { // only needs to be performed if at least one node in the previous pass is // expanded. if (graph_->node_size() > node_size_original) { + // Creating Const nodes holding the permutation used by added Transposes + // of nodes and ensure both are in the same frame. + NodeDef* n = AddNodePermNHWCToNCHW(); + n = AddNodePermNCHWToNHWC(); + n = n; // Avoid compilation warning std::set ops_format_agnostic = GetOpsFormatAgnostic(); for (int i = 0; i < graph_->node_size(); i++) { if (ops_format_agnostic.find(graph_->node(i).op()) != -- GitLab From b3dd648bdbefff0c4e6fe1c42bb3b3aa5df8cab7 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 21 Feb 2019 22:37:33 -0800 Subject: [PATCH 323/766] Replace angle bracket CUDA kernel launches ('<<<>>>') with API function calls ('cudaLaunchKernel'), through a helper function which also checks the return code in debug builds. PiperOrigin-RevId: 235135115 --- .../kernels/adjust_hsv_in_yiq_op_gpu.cu.cc | 7 +- .../mpi_collectives/kernels/ring.cu.cc | 15 ++-- tensorflow/contrib/mpi_collectives/ring.cu.cc | 15 ++-- .../kernels/reduce_slice_ops_gpu.cu.cc | 8 +- .../resampler/kernels/resampler_ops_gpu.cu.cc | 28 +++---- .../contrib/rnn/kernels/lstm_ops_gpu.cu.cc | 35 +++++---- .../seq2seq/kernels/beam_search_ops_gpu.cu.cc | 14 +--- .../inc_op_kernel.cu.cc | 7 +- tensorflow/core/BUILD | 5 +- .../core/kernels/adjust_hue_op_gpu.cu.cc | 6 +- .../kernels/adjust_saturation_op_gpu.cu.cc | 6 +- .../core/kernels/avgpooling_op_gpu.cu.cc | 10 +-- tensorflow/core/kernels/bias_op_gpu.cu.cc | 35 +++++---- .../core/kernels/bucketize_op_gpu.cu.cc | 8 +- .../core/kernels/check_numerics_op_gpu.cu.cc | 5 +- .../kernels/compare_and_bitpack_op_gpu.cu.cc | 6 +- tensorflow/core/kernels/conv_2d_gpu.h | 70 +++++++++-------- .../core/kernels/crop_and_resize_op_gpu.cu.cc | 10 ++- .../core/kernels/cwise_op_clip_gpu.cu.cc | 24 +++--- .../core/kernels/depthtospace_op_gpu.cu.cc | 46 ++++++------ .../core/kernels/depthwise_conv_op_gpu.h | 10 ++- .../core/kernels/determinant_op_gpu.cu.cc | 19 +++-- .../core/kernels/dilation_ops_gpu.cu.cc | 8 +- .../kernels/dynamic_partition_op_gpu.cu.cc | 13 ++-- .../core/kernels/dynamic_stitch_op_gpu.cu.cc | 6 +- .../core/kernels/gather_functor_gpu.cu.h | 20 ++--- .../core/kernels/gather_nd_op_gpu.cu.cc | 10 +-- .../kernels/inplace_ops_functor_gpu.cu.cc | 34 +++++---- .../core/kernels/matrix_set_diag_op_gpu.cu.cc | 16 ++-- ...arameterized_truncated_normal_op_gpu.cu.cc | 13 ++-- .../kernels/population_count_op_gpu.cu.cc | 6 +- tensorflow/core/kernels/random_op_gpu.cu.cc | 4 +- .../core/kernels/reduction_gpu_kernels.cu.h | 66 ++++++++-------- .../core/kernels/resize_bilinear_op_gpu.cu.cc | 26 ++++--- .../resize_nearest_neighbor_op_gpu.cu.cc | 9 ++- tensorflow/core/kernels/scan_ops_gpu.h | 37 ++++----- .../core/kernels/scatter_functor_gpu.cu.h | 16 ++-- .../core/kernels/scatter_nd_op_gpu.cu.cc | 11 ++- .../core/kernels/searchsorted_op_gpu.cu.cc | 16 ++-- .../kernels/segment_reduction_ops_gpu.cu.cc | 25 ++++--- tensorflow/core/kernels/softmax_op_gpu.cu.cc | 13 ++-- .../kernels/spacetobatch_functor_gpu.cu.cc | 9 ++- .../core/kernels/spacetodepth_op_gpu.cu.cc | 46 ++++++------ .../sparse_tensor_dense_matmul_op_gpu.cu.cc | 8 +- tensorflow/core/kernels/split_lib_gpu.cu.cc | 8 +- .../kernels/stateful_random_ops_gpu.cu.cc | 6 +- tensorflow/core/kernels/tile_functor_gpu.h | 6 +- tensorflow/core/kernels/topk_op_gpu.h | 4 +- .../core/kernels/transpose_functor_gpu.cu.cc | 8 +- tensorflow/core/kernels/where_op_gpu.cu.h | 6 +- .../core/util/cuda_kernel_helper_test.cu.cc | 75 ++++++++++--------- tensorflow/core/util/cuda_launch_config.h | 44 +++++++++++ .../adding_an_op/cuda_op_kernel.cu.cc | 3 +- .../builds/user_ops/cuda_op_kernel.cu.cc | 3 +- 54 files changed, 519 insertions(+), 445 deletions(-) diff --git a/tensorflow/contrib/image/kernels/adjust_hsv_in_yiq_op_gpu.cu.cc b/tensorflow/contrib/image/kernels/adjust_hsv_in_yiq_op_gpu.cu.cc index bbb3a3b18f..41cac40906 100644 --- a/tensorflow/contrib/image/kernels/adjust_hsv_in_yiq_op_gpu.cu.cc +++ b/tensorflow/contrib/image/kernels/adjust_hsv_in_yiq_op_gpu.cu.cc @@ -55,9 +55,10 @@ void AdjustHsvInYiqGPU::operator()(OpKernelContext* ctx, int channel_count, &tranformation_matrix)); // TODO(huangyp): It takes about 3.5 us to compute tranformation_matrix // with one thread. Improve its performance if necessary. - internal::compute_tranformation_matrix_cuda<<<1, 1, 0, cu_stream>>>( - delta_h, scale_s, scale_v, tranformation_matrix.flat().data(), - tranformation_matrix.flat().size()); + CudaLaunchKernel(internal::compute_tranformation_matrix_cuda, 1, 1, 0, + cu_stream, delta_h, scale_s, scale_v, + tranformation_matrix.flat().data(), + tranformation_matrix.flat().size()); // Call cuBlas C = A * B directly. auto no_transpose = se::blas::Transpose::kNoTranspose; auto a_ptr = diff --git a/tensorflow/contrib/mpi_collectives/kernels/ring.cu.cc b/tensorflow/contrib/mpi_collectives/kernels/ring.cu.cc index b04abde469..0f709a57a8 100644 --- a/tensorflow/contrib/mpi_collectives/kernels/ring.cu.cc +++ b/tensorflow/contrib/mpi_collectives/kernels/ring.cu.cc @@ -96,13 +96,14 @@ __global__ void elemwise_accum(T* out, const T* in, const size_t N) { // Synchronously accumulate tensors on the GPU, using a different stream than // the default and than TensorFlow to avoid synchronizing on operations // unrelated to the allreduce. -#define GENERATE_ACCUMULATE(type) \ - template <> \ - void AccumulateTensorData(type * dst, type * src, \ - size_t size) { \ - auto stream = CudaStreamForMPI(); \ - elemwise_accum<<<32, 256, 0, stream>>>(dst, src, size); \ - cudaStreamSynchronize(stream); \ +#define GENERATE_ACCUMULATE(type) \ + template <> \ + void AccumulateTensorData(type * dst, type * src, \ + size_t size) { \ + auto stream = CudaStreamForMPI(); \ + CudaLaunchKernel(elemwise_accum, 32, 256, 0, stream, dst, src, \ + size); \ + cudaStreamSynchronize(stream); \ }; GENERATE_ACCUMULATE(int); GENERATE_ACCUMULATE(long long); diff --git a/tensorflow/contrib/mpi_collectives/ring.cu.cc b/tensorflow/contrib/mpi_collectives/ring.cu.cc index 2f3eef366a..f8c7adf0a1 100644 --- a/tensorflow/contrib/mpi_collectives/ring.cu.cc +++ b/tensorflow/contrib/mpi_collectives/ring.cu.cc @@ -96,13 +96,14 @@ __global__ void elemwise_accum(T* out, const T* in, const size_t N) { // Synchronously accumulate tensors on the GPU, using a different stream than // the default and than TensorFlow to avoid synchronizing on operations // unrelated to the allreduce. -#define GENERATE_ACCUMULATE(type) \ - template <> \ - void AccumulateTensorData(type * dst, type * src, \ - size_t size) { \ - auto stream = CudaStreamForMPI(); \ - elemwise_accum<<<32, 256, 0, stream>>>(dst, src, size); \ - cudaStreamSynchronize(stream); \ +#define GENERATE_ACCUMULATE(type) \ + template <> \ + void AccumulateTensorData(type * dst, type * src, \ + size_t size) { \ + auto stream = CudaStreamForMPI(); \ + CudaLaunchKernel(elemwise_accum, 32, 256, 0, stream, dst, src, \ + size); \ + cudaStreamSynchronize(stream); \ }; GENERATE_ACCUMULATE(int); GENERATE_ACCUMULATE(long long); diff --git a/tensorflow/contrib/reduce_slice_ops/kernels/reduce_slice_ops_gpu.cu.cc b/tensorflow/contrib/reduce_slice_ops/kernels/reduce_slice_ops_gpu.cu.cc index 204b83f7f5..cdabcfe059 100644 --- a/tensorflow/contrib/reduce_slice_ops/kernels/reduce_slice_ops_gpu.cu.cc +++ b/tensorflow/contrib/reduce_slice_ops/kernels/reduce_slice_ops_gpu.cu.cc @@ -77,10 +77,10 @@ namespace functor { sizex, sizey, sizez, d, ReduceSliceDeviceKernel##reduceop, \ 0, 0); \ \ - ReduceSliceDeviceKernel##reduceop \ - <<>>( \ - config, indices_width, bound, beginning(), indices.data(), \ - data.data(), output.data()); \ + CudaLaunchKernel( \ + ReduceSliceDeviceKernel##reduceop, config.block_count, \ + config.thread_per_block, 0, d.stream(), config, indices_width, \ + bound, beginning(), indices.data(), data.data(), output.data()); \ } \ }; diff --git a/tensorflow/contrib/resampler/kernels/resampler_ops_gpu.cu.cc b/tensorflow/contrib/resampler/kernels/resampler_ops_gpu.cu.cc index 3c07051f68..ad0bec9572 100644 --- a/tensorflow/contrib/resampler/kernels/resampler_ops_gpu.cu.cc +++ b/tensorflow/contrib/resampler/kernels/resampler_ops_gpu.cu.cc @@ -119,10 +119,10 @@ struct Resampler2DFunctor { batch_size * num_sampling_points * data_channels; ::tensorflow::CudaLaunchConfig config = ::tensorflow::GetCudaLaunchConfig(output_data_size, d); - Resampler2DKernel - <<>>( - data, warp, output, batch_size, data_height, data_width, - data_channels, num_sampling_points); + CudaLaunchKernel(Resampler2DKernel, config.block_count, + config.thread_per_block, 0, d.stream(), data, warp, output, + batch_size, data_height, data_width, data_channels, + num_sampling_points); } }; @@ -254,22 +254,22 @@ struct ResamplerGrad2DFunctor { ::tensorflow::CudaLaunchConfig config = ::tensorflow::GetCudaLaunchConfig(grad_warp_size, d); - ::tensorflow:: - SetZero<<>>( - grad_warp_size, grad_warp); + ::tensorflow::CudaLaunchKernel(SetZero, config.block_count, + config.thread_per_block, 0, d.stream(), + grad_warp_size, grad_warp); config = ::tensorflow::GetCudaLaunchConfig(grad_data_size, d); - ::tensorflow:: - SetZero<<>>( - grad_data_size, grad_data); + ::tensorflow::CudaLaunchKernel(SetZero, config.block_count, + config.thread_per_block, 0, d.stream(), + grad_data_size, grad_data); const int resampler_output_size = batch_size * num_sampling_points * data_channels; config = ::tensorflow::GetCudaLaunchConfig(resampler_output_size, d); - ResamplerGrad2DKernel - <<>>( - data, warp, grad_output, grad_data, grad_warp, batch_size, - data_height, data_width, data_channels, num_sampling_points); + CudaLaunchKernel(ResamplerGrad2DKernel, config.block_count, + config.thread_per_block, 0, d.stream(), data, warp, + grad_output, grad_data, grad_warp, batch_size, data_height, + data_width, data_channels, num_sampling_points); } }; diff --git a/tensorflow/contrib/rnn/kernels/lstm_ops_gpu.cu.cc b/tensorflow/contrib/rnn/kernels/lstm_ops_gpu.cu.cc index 15ae95f13c..648d210ee1 100644 --- a/tensorflow/contrib/rnn/kernels/lstm_ops_gpu.cu.cc +++ b/tensorflow/contrib/rnn/kernels/lstm_ops_gpu.cu.cc @@ -242,8 +242,8 @@ void LSTMBlockCellFpropWithCUDA( const int block_dim = 128; const int grid_dim = Eigen::divup(batch_size * (cell_size + input_size), block_dim); - concat_xh<<>>( - xh.data(), x.data(), h_prev.data(), batch_size, cell_size, input_size); + CudaLaunchKernel(concat_xh, grid_dim, block_dim, 0, cu_stream, xh.data(), + x.data(), h_prev.data(), batch_size, cell_size, input_size); // states1 = xh * w typename TTypes::ConstMatrix const_xh(xh.data(), xh.dimensions()); @@ -261,15 +261,17 @@ void LSTMBlockCellFpropWithCUDA( Eigen::divup(cell_size, static_cast(block_dim_2d.y))); if (use_peephole) { - lstm_gates<<>>( - icfo.data(), b.data(), cs_prev.data(), wci.data(), wcf.data(), - wco.data(), o.data(), h.data(), ci.data(), cs.data(), co.data(), - i.data(), f.data(), forget_bias, cell_clip, batch_size, cell_size); + CudaLaunchKernel(lstm_gates, grid_dim_2d, block_dim_2d, 0, + cu_stream, icfo.data(), b.data(), cs_prev.data(), + wci.data(), wcf.data(), wco.data(), o.data(), h.data(), + ci.data(), cs.data(), co.data(), i.data(), f.data(), + forget_bias, cell_clip, batch_size, cell_size); } else { - lstm_gates<<>>( - icfo.data(), b.data(), cs_prev.data(), wci.data(), wcf.data(), - wco.data(), o.data(), h.data(), ci.data(), cs.data(), co.data(), - i.data(), f.data(), forget_bias, cell_clip, batch_size, cell_size); + CudaLaunchKernel(lstm_gates, grid_dim_2d, block_dim_2d, 0, + cu_stream, icfo.data(), b.data(), cs_prev.data(), + wci.data(), wcf.data(), wco.data(), o.data(), h.data(), + ci.data(), cs.data(), co.data(), i.data(), f.data(), + forget_bias, cell_clip, batch_size, cell_size); } } @@ -374,12 +376,13 @@ void LSTMBlockCellBpropWithCUDA( dim3 grid_dim_2d(Eigen::divup(batch_size, static_cast(block_dim_2d.x)), Eigen::divup(cell_size, static_cast(block_dim_2d.y))); - lstm_gates_bprop<<>>( - cs_prev.data(), h_prev.data(), w.data(), wci.data(), wcf.data(), - wco.data(), b.data(), i.data(), cs.data(), f.data(), o.data(), ci.data(), - co.data(), cs_grad.data(), h_grad.data(), do_.data(), dcs.data(), - dci.data(), df.data(), di.data(), dicfo.data(), cs_prev_grad.data(), - batch_size, cell_size, use_peephole); + CudaLaunchKernel(lstm_gates_bprop, grid_dim_2d, block_dim_2d, 0, cu_stream, + cs_prev.data(), h_prev.data(), w.data(), wci.data(), + wcf.data(), wco.data(), b.data(), i.data(), cs.data(), + f.data(), o.data(), ci.data(), co.data(), cs_grad.data(), + h_grad.data(), do_.data(), dcs.data(), dci.data(), df.data(), + di.data(), dicfo.data(), cs_prev_grad.data(), batch_size, + cell_size, use_peephole); if (use_peephole) { Eigen::array p_shape({1, cell_size}); diff --git a/tensorflow/contrib/seq2seq/kernels/beam_search_ops_gpu.cu.cc b/tensorflow/contrib/seq2seq/kernels/beam_search_ops_gpu.cu.cc index bc28d492fe..4836f80c9f 100644 --- a/tensorflow/contrib/seq2seq/kernels/beam_search_ops_gpu.cu.cc +++ b/tensorflow/contrib/seq2seq/kernels/beam_search_ops_gpu.cu.cc @@ -91,16 +91,10 @@ struct GatherTree { beams.device(d) = beams.constant(end_token); CudaLaunchConfig config = GetCudaLaunchConfig(batch_size * beam_width, d); - // clang-format off - GatherTreeOpKernel - <<>>( - batch_size, max_time, beam_width, - step_ids.data(), - parent_ids.data(), - max_sequence_length.data(), - end_token, - beams.data()); - // clang-format on + CudaLaunchKernel(GatherTreeOpKernel, config.block_count, + config.thread_per_block, 0, d.stream(), batch_size, + max_time, beam_width, step_ids.data(), parent_ids.data(), + max_sequence_length.data(), end_token, beams.data()); } }; diff --git a/tensorflow/contrib/tensorrt/custom_plugin_examples/inc_op_kernel.cu.cc b/tensorflow/contrib/tensorrt/custom_plugin_examples/inc_op_kernel.cu.cc index 11335d7da6..858380bb91 100644 --- a/tensorflow/contrib/tensorrt/custom_plugin_examples/inc_op_kernel.cu.cc +++ b/tensorflow/contrib/tensorrt/custom_plugin_examples/inc_op_kernel.cu.cc @@ -21,9 +21,10 @@ limitations under the License. #include #define EIGEN_USE_GPU -#include "tensorflow/core/framework/op_kernel.h" #include "cuda/include/cuda_runtime_api.h" +#include "tensorflow/core/framework/op_kernel.h" #include "tensorflow/core/platform/stream_executor.h" +#include "tensorflow/core/util/cuda_launch_config.h" namespace tensorflow { namespace tensorrt { @@ -38,8 +39,8 @@ void IncrementKernel(const float* d_input, float inc, float* d_output, int threads_per_block = 256; int blocks_per_grid = (count + threads_per_block - 1) / threads_per_block; - VecInc<<>>(d_input, inc, - d_output, count); + CudaLaunchKernel(VecInc, threads_per_block, blocks_per_grid, 0, stream, + d_input, inc, d_output, count); } // Note: this kernel definition is not needed in the plugin_test rule, but it is diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD index 06c108b38f..40800809e8 100644 --- a/tensorflow/core/BUILD +++ b/tensorflow/core/BUILD @@ -968,7 +968,10 @@ tf_cuda_library( "util/mkl_util.h", ]), visibility = ["//visibility:public"], - deps = [":framework_internal"], + deps = [ + ":framework_internal", + "@com_google_absl//absl/base", + ], ) cc_library( diff --git a/tensorflow/core/kernels/adjust_hue_op_gpu.cu.cc b/tensorflow/core/kernels/adjust_hue_op_gpu.cu.cc index c30085269c..ea22911617 100644 --- a/tensorflow/core/kernels/adjust_hue_op_gpu.cu.cc +++ b/tensorflow/core/kernels/adjust_hue_op_gpu.cu.cc @@ -35,9 +35,9 @@ void AdjustHueGPU::operator()(GPUDevice* device, const int threads_per_block = config.thread_per_block; const int block_count = (number_of_elements + threads_per_block - 1) / threads_per_block; - internal::adjust_hsv_nhwc - <<>>( - number_of_elements, input, output, delta, nullptr, nullptr); + CudaLaunchKernel(internal::adjust_hsv_nhwc, + block_count, threads_per_block, 0, stream, + number_of_elements, input, output, delta, nullptr, nullptr); } template struct AdjustHueGPU; diff --git a/tensorflow/core/kernels/adjust_saturation_op_gpu.cu.cc b/tensorflow/core/kernels/adjust_saturation_op_gpu.cu.cc index 6c70490d46..637120d9af 100644 --- a/tensorflow/core/kernels/adjust_saturation_op_gpu.cu.cc +++ b/tensorflow/core/kernels/adjust_saturation_op_gpu.cu.cc @@ -36,9 +36,9 @@ void AdjustSaturationGPU::operator()(GPUDevice* device, const int threads_per_block = config.thread_per_block; const int block_count = (number_of_elements + threads_per_block - 1) / threads_per_block; - internal::adjust_hsv_nhwc - <<>>( - number_of_elements, input, output, nullptr, scale, nullptr); + CudaLaunchKernel(internal::adjust_hsv_nhwc, + block_count, threads_per_block, 0, stream, + number_of_elements, input, output, nullptr, scale, nullptr); } template struct AdjustSaturationGPU; diff --git a/tensorflow/core/kernels/avgpooling_op_gpu.cu.cc b/tensorflow/core/kernels/avgpooling_op_gpu.cu.cc index 35511d5c31..8bf47b7ad9 100644 --- a/tensorflow/core/kernels/avgpooling_op_gpu.cu.cc +++ b/tensorflow/core/kernels/avgpooling_op_gpu.cu.cc @@ -91,11 +91,11 @@ bool RunAvePoolBackwardNHWC(const T* const top_diff, const int num, const GPUDevice& d) { int x_size = num * height * width * channels; CudaLaunchConfig config = GetCudaLaunchConfig(x_size, d); - AvePoolBackwardNHWC - <<>>( - config.virtual_thread_count, top_diff, num, height, width, channels, - pooled_height, pooled_width, kernel_h, kernel_w, stride_h, stride_w, - pad_t, pad_t, bottom_diff); + CudaLaunchKernel(AvePoolBackwardNHWC, config.block_count, + config.thread_per_block, 0, d.stream(), + config.virtual_thread_count, top_diff, num, height, width, + channels, pooled_height, pooled_width, kernel_h, kernel_w, + stride_h, stride_w, pad_t, pad_t, bottom_diff); return d.ok(); } diff --git a/tensorflow/core/kernels/bias_op_gpu.cu.cc b/tensorflow/core/kernels/bias_op_gpu.cu.cc index 006fa1dc71..21fefa9bb5 100644 --- a/tensorflow/core/kernels/bias_op_gpu.cu.cc +++ b/tensorflow/core/kernels/bias_op_gpu.cu.cc @@ -85,14 +85,15 @@ void BiasGPU::compute(const GPUDevice& d, const T* input, const T* bias, } CudaLaunchConfig config = GetCudaLaunchConfig(total_count, d); if (data_format == FORMAT_NHWC) { - BiasNHWCKernel - <<>>( - config.virtual_thread_count, input, bias, output, bias_size); + CudaLaunchKernel(BiasNHWCKernel, config.block_count, + config.thread_per_block, 0, d.stream(), + config.virtual_thread_count, input, bias, output, + bias_size); } else { - BiasNCHWKernel - <<>>( - config.virtual_thread_count, input, bias, output, bias_size, - image_size); + CudaLaunchKernel(BiasNCHWKernel, config.block_count, + config.thread_per_block, 0, d.stream(), + config.virtual_thread_count, input, bias, output, + bias_size, image_size); } } @@ -225,24 +226,22 @@ void BiasGradGPU::compute(const GPUDevice& d, const T* output_backprop, if (config.thread_per_block < kWarpSize) { config.thread_per_block = kWarpSize; } - BiasGradNCHW_SharedAtomics - <<>>( - output_backprop, bias_backprop, batch, bias_size, image_size, - group_size); + CudaLaunchKernel(BiasGradNCHW_SharedAtomics, config.block_count, + config.thread_per_block, 0, d.stream(), output_backprop, + bias_backprop, batch, bias_size, image_size, group_size); } } else { // Note that even if we don't have enough shared memory to fit the entire // output block, it is possible to process one group of elements at a time. // But for now, we simply fall back to the naive implementation. if (data_format == FORMAT_NHWC) { - BiasGradNHWC_Naive - <<>>( - total_count, output_backprop, bias_backprop, bias_size); + CudaLaunchKernel(BiasGradNHWC_Naive, config.block_count, + config.thread_per_block, 0, d.stream(), total_count, + output_backprop, bias_backprop, bias_size); } else { - BiasGradNCHW_Naive - <<>>( - total_count, output_backprop, bias_backprop, bias_size, - image_size); + CudaLaunchKernel(BiasGradNCHW_Naive, config.block_count, + config.thread_per_block, 0, d.stream(), total_count, + output_backprop, bias_backprop, bias_size, image_size); } } } diff --git a/tensorflow/core/kernels/bucketize_op_gpu.cu.cc b/tensorflow/core/kernels/bucketize_op_gpu.cu.cc index 551d77f495..4533d28c93 100644 --- a/tensorflow/core/kernels/bucketize_op_gpu.cu.cc +++ b/tensorflow/core/kernels/bucketize_op_gpu.cu.cc @@ -103,10 +103,10 @@ struct BucketizeFunctor { d.stream()>>>(input.size(), input.data(), boundaries_vector.size(), boundaries_array.data(), output.data()); } else { - BucketizeCustomKernel - <<>>( - input.size(), input.data(), boundaries_vector.size(), - boundaries_array.data(), output.data()); + CudaLaunchKernel(BucketizeCustomKernel, config.block_count, + config.thread_per_block, 0, d.stream(), input.size(), + input.data(), boundaries_vector.size(), + boundaries_array.data(), output.data()); } return Status::OK(); } diff --git a/tensorflow/core/kernels/check_numerics_op_gpu.cu.cc b/tensorflow/core/kernels/check_numerics_op_gpu.cu.cc index f9f10c1b42..c63c29431c 100644 --- a/tensorflow/core/kernels/check_numerics_op_gpu.cu.cc +++ b/tensorflow/core/kernels/check_numerics_op_gpu.cu.cc @@ -24,6 +24,7 @@ limitations under the License. #include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor" #include "tensorflow/core/platform/types.h" +#include "tensorflow/core/util/cuda_launch_config.h" namespace tensorflow { @@ -65,8 +66,8 @@ struct CheckNumericsLaunch { (d.getNumGpuMultiProcessors() * d.maxGpuThreadsPerMultiProcessor()) / block_size; - CheckNumericsKernel<<>>( - data, size, abnormal_detected); + CudaLaunchKernel(CheckNumericsKernel, num_blocks, block_size, 0, + d.stream(), data, size, abnormal_detected); } }; diff --git a/tensorflow/core/kernels/compare_and_bitpack_op_gpu.cu.cc b/tensorflow/core/kernels/compare_and_bitpack_op_gpu.cu.cc index 345405e3fe..e15eea1c9a 100644 --- a/tensorflow/core/kernels/compare_and_bitpack_op_gpu.cu.cc +++ b/tensorflow/core/kernels/compare_and_bitpack_op_gpu.cu.cc @@ -124,9 +124,9 @@ __global__ void CompareAndBitpackKernel(const int size, int64 total_count = output.size(); \ CudaLaunchConfig config = GetCudaLaunchConfig(total_count, d); \ \ - CompareAndBitpackKernel \ - <<>>( \ - total_count, threshold.data(), input.data(), output.data()); \ + CudaLaunchKernel(CompareAndBitpackKernel, config.block_count, \ + config.thread_per_block, 0, d.stream(), total_count, \ + threshold.data(), input.data(), output.data()); \ } TF_CALL_GPU_NUMBER_TYPES(DEFINE_GPU_SPECS) diff --git a/tensorflow/core/kernels/conv_2d_gpu.h b/tensorflow/core/kernels/conv_2d_gpu.h index 8d11757428..3e35acc0e5 100644 --- a/tensorflow/core/kernels/conv_2d_gpu.h +++ b/tensorflow/core/kernels/conv_2d_gpu.h @@ -437,9 +437,10 @@ struct TransformFilter { CHECK(dst_filter_format == FORMAT_OIHW) << "Unsupported output layout: " << ToString(dst_filter_format); - ShuffleInTensor3Simple - <<>>( - config.virtual_thread_count, in.data(), combined_dims, out.data()); + CudaLaunchKernel(ShuffleInTensor3Simple, config.block_count, + config.thread_per_block, 0, d.stream(), + config.virtual_thread_count, in.data(), combined_dims, + out.data()); } }; @@ -458,9 +459,10 @@ struct ReverseTransformFilter { combined_dims[2] *= in.dimension(i); } CudaLaunchConfig config = GetCudaLaunchConfig(out.size(), d); - ShuffleInTensor3Simple - <<>>( - config.virtual_thread_count, in.data(), combined_dims, out.data()); + CudaLaunchKernel(ShuffleInTensor3Simple, config.block_count, + config.thread_per_block, 0, d.stream(), + config.virtual_thread_count, in.data(), combined_dims, + out.data()); } }; @@ -488,15 +490,15 @@ struct PadInput { const Dimension padding_left_dim(padding_left); if (format == FORMAT_NHWC) { - PadInputCustomKernelNHWC - <<>>( - config.virtual_thread_count, in.data(), input_dims, out.data(), - output_dims, padding_left_dim); + CudaLaunchKernel(PadInputCustomKernelNHWC, config.block_count, + config.thread_per_block, 0, d.stream(), + config.virtual_thread_count, in.data(), input_dims, + out.data(), output_dims, padding_left_dim); } else if (format == FORMAT_NCHW) { - PadInputCustomKernelNCHW - <<>>( - config.virtual_thread_count, in.data(), input_dims, out.data(), - output_dims, padding_left_dim); + CudaLaunchKernel(PadInputCustomKernelNCHW, config.block_count, + config.thread_per_block, 0, d.stream(), + config.virtual_thread_count, in.data(), input_dims, + out.data(), output_dims, padding_left_dim); } else { LOG(FATAL) << "Invalid data format: " << format; } @@ -605,15 +607,17 @@ void LaunchBatchNarrowMatrixTransposeKernel( const T* input, const Dimension<3>& input_dims, T* output) { constexpr int NumThreads = TileLongSide; if (tile_size_i <= TileLongSide && tile_size_j <= TileShortSide) { - SwapDimension1And2InTensor3UsingTiles - <<>>(input, input_dims, - output); + CudaLaunchKernel( + SwapDimension1And2InTensor3UsingTiles, + total_tiles_count, NumThreads, 0, d.stream(), input, input_dims, + output); } else { - SwapDimension1And2InTensor3UsingTiles - <<>>(input, input_dims, - output); + CudaLaunchKernel( + SwapDimension1And2InTensor3UsingTiles, + total_tiles_count, NumThreads, 0, d.stream(), input, input_dims, + output); } } @@ -914,10 +918,12 @@ void RunSwapDimension1And2InTensor3(const GPUDevice& d, const T* input, int total_tiles_count = input_dims_in_tiles[0] * input_dims_in_tiles[1] * input_dims_in_tiles[2]; - SwapDimension1And2InTensor3UsingTiles - <<>>(input, input_dims, - output); + + CudaLaunchKernel( + SwapDimension1And2InTensor3UsingTiles, + total_tiles_count, kNumThreads, 0, d.stream(), input, input_dims, + output); } else if (narrow_matrix) { SwapDimension1And2InTensor3WithNarrowMatrices( @@ -925,9 +931,9 @@ void RunSwapDimension1And2InTensor3(const GPUDevice& d, const T* input, } else { int total_element_count = input_dims[0] * input_dims[1] * input_dims[2]; CudaLaunchConfig config = GetCudaLaunchConfig(total_element_count, d); - ShuffleInTensor3Simple - <<>>( - config.virtual_thread_count, input, input_dims, output); + CudaLaunchKernel(ShuffleInTensor3Simple, + config.block_count, config.thread_per_block, 0, d.stream(), + config.virtual_thread_count, input, input_dims, output); } } @@ -957,9 +963,9 @@ struct SwapDimension0And2InTensor3 { static_cast(combined_dims[2])}; size_t total_size = combined_dims[0] * combined_dims[1] * combined_dims[2]; CudaLaunchConfig config = GetCudaLaunchConfig(total_size, d); - ShuffleInTensor3Simple - <<>>( - config.virtual_thread_count, in, input_dims, out); + CudaLaunchKernel(ShuffleInTensor3Simple, + config.block_count, config.thread_per_block, 0, d.stream(), + config.virtual_thread_count, in, input_dims, out); } }; diff --git a/tensorflow/core/kernels/crop_and_resize_op_gpu.cu.cc b/tensorflow/core/kernels/crop_and_resize_op_gpu.cu.cc index 8ab08fb93a..7bedf5c3ea 100644 --- a/tensorflow/core/kernels/crop_and_resize_op_gpu.cu.cc +++ b/tensorflow/core/kernels/crop_and_resize_op_gpu.cu.cc @@ -406,8 +406,9 @@ struct CropAndResizeBackpropImage { total_count = batch * image_height * image_width * depth; if (total_count > 0) { config = GetCudaLaunchConfig(total_count, d); - SetZero<<>>( - config.virtual_thread_count, grads_image.data()); + CudaLaunchKernel(SetZero, config.block_count, config.thread_per_block, + 0, d.stream(), config.virtual_thread_count, + grads_image.data()); } // Configurate interpolation method. @@ -454,8 +455,9 @@ struct CropAndResizeBackpropBoxes { total_count = num_boxes * 4; if (total_count > 0) { config = GetCudaLaunchConfig(total_count, d); - SetZero<<>>( - config.virtual_thread_count, grads_boxes.data()); + CudaLaunchKernel(SetZero, config.block_count, + config.thread_per_block, 0, d.stream(), + config.virtual_thread_count, grads_boxes.data()); } // Accumulate. diff --git a/tensorflow/core/kernels/cwise_op_clip_gpu.cu.cc b/tensorflow/core/kernels/cwise_op_clip_gpu.cu.cc index 44dea7dee9..5069ed51bc 100644 --- a/tensorflow/core/kernels/cwise_op_clip_gpu.cu.cc +++ b/tensorflow/core/kernels/cwise_op_clip_gpu.cu.cc @@ -62,10 +62,10 @@ struct UnaryClipOp { typename TTypes::Flat &out_flat) const { CudaLaunchConfig config = GetCudaLaunchConfig(in0_flat.size(), d); - UnaryClipCustomKernel - <<>>( - in0_flat.size(), in0_flat.data(), in1_flat.data(), in2_flat.data(), - out_flat.data()); + CudaLaunchKernel(UnaryClipCustomKernel, config.block_count, + config.thread_per_block, 0, d.stream(), in0_flat.size(), + in0_flat.data(), in1_flat.data(), in2_flat.data(), + out_flat.data()); } }; @@ -78,10 +78,10 @@ struct BinaryRightClipOp { typename TTypes::Flat &out_flat) const { CudaLaunchConfig config = GetCudaLaunchConfig(in0_flat.size(), d); - BinaryRightClipCustomKernel - <<>>( - in0_flat.size(), in0_flat.data(), in1_flat.data(), in2_flat.data(), - out_flat.data()); + CudaLaunchKernel(BinaryRightClipCustomKernel, config.block_count, + config.thread_per_block, 0, d.stream(), in0_flat.size(), + in0_flat.data(), in1_flat.data(), in2_flat.data(), + out_flat.data()); } }; @@ -94,10 +94,10 @@ struct BinaryLeftClipOp { typename TTypes::Flat &out_flat) const { CudaLaunchConfig config = GetCudaLaunchConfig(in0_flat.size(), d); - BinaryLeftClipCustomKernel - <<>>( - in0_flat.size(), in0_flat.data(), in1_flat.data(), in2_flat.data(), - out_flat.data()); + CudaLaunchKernel(BinaryLeftClipCustomKernel, config.block_count, + config.thread_per_block, 0, d.stream(), in0_flat.size(), + in0_flat.data(), in1_flat.data(), in2_flat.data(), + out_flat.data()); } }; diff --git a/tensorflow/core/kernels/depthtospace_op_gpu.cu.cc b/tensorflow/core/kernels/depthtospace_op_gpu.cu.cc index 0656081177..22ef57b159 100644 --- a/tensorflow/core/kernels/depthtospace_op_gpu.cu.cc +++ b/tensorflow/core/kernels/depthtospace_op_gpu.cu.cc @@ -162,10 +162,11 @@ struct DepthToSpaceOpFunctor { return; } CudaLaunchConfig config = GetCudaLaunchConfig(total_count, d); - D2S_NHWC<<>>( - config.virtual_thread_count, input.data(), block_size, batch_size, - input_height, input_width, input_depth, output_height, output_width, - output_depth, output.data()); + CudaLaunchKernel(D2S_NHWC, config.block_count, config.thread_per_block, + 0, d.stream(), config.virtual_thread_count, input.data(), + block_size, batch_size, input_height, input_width, + input_depth, output_height, output_width, output_depth, + output.data()); } void operator()(const GPUDevice& d, typename TTypes::ConstTensor input, int block_size, typename TTypes::Tensor output) { @@ -197,23 +198,23 @@ struct DepthToSpaceOpFunctor { CudaLaunchConfig config = GetCudaLaunchConfig(total_count, d); switch (block_size) { case 2: - return D2S_NCHW_LOOP - <<>>( - total_count, input.data(), input_width, output_width, - output_depth_by_input_area, input_depth_by_input_area, - output.data()); + return CudaLaunchKernel(D2S_NCHW_LOOP, config.block_count, + config.thread_per_block, 0, d.stream(), + total_count, input.data(), input_width, + output_width, output_depth_by_input_area, + input_depth_by_input_area, output.data()); case 3: - return D2S_NCHW_LOOP - <<>>( - total_count, input.data(), input_width, output_width, - output_depth_by_input_area, input_depth_by_input_area, - output.data()); + return CudaLaunchKernel(D2S_NCHW_LOOP, config.block_count, + config.thread_per_block, 0, d.stream(), + total_count, input.data(), input_width, + output_width, output_depth_by_input_area, + input_depth_by_input_area, output.data()); case 4: - return D2S_NCHW_LOOP - <<>>( - total_count, input.data(), input_width, output_width, - output_depth_by_input_area, input_depth_by_input_area, - output.data()); + return CudaLaunchKernel(D2S_NCHW_LOOP, config.block_count, + config.thread_per_block, 0, d.stream(), + total_count, input.data(), input_width, + output_width, output_depth_by_input_area, + input_depth_by_input_area, output.data()); } } @@ -223,9 +224,10 @@ struct DepthToSpaceOpFunctor { return; } auto config = GetCudaLaunchConfig(total_count, d); - D2S_NCHW<<>>( - config.virtual_thread_count, input.data(), block_size, input_width, - output_depth * input_height, output.data()); + CudaLaunchKernel(D2S_NCHW, config.block_count, config.thread_per_block, + 0, d.stream(), config.virtual_thread_count, input.data(), + block_size, input_width, output_depth * input_height, + output.data()); } void operator()(const GPUDevice& d, typename TTypes::ConstTensor input, int block_size, typename TTypes::Tensor output) { diff --git a/tensorflow/core/kernels/depthwise_conv_op_gpu.h b/tensorflow/core/kernels/depthwise_conv_op_gpu.h index 098853e684..4776d5c26e 100644 --- a/tensorflow/core/kernels/depthwise_conv_op_gpu.h +++ b/tensorflow/core/kernels/depthwise_conv_op_gpu.h @@ -983,8 +983,9 @@ Status LaunchDepthwiseConv2dBackpropInputGPU(OpKernelContext* ctx, auto device = ctx->eigen_gpu_device(); CudaLaunchConfig config = GetCudaLaunchConfig(num_in_backprop, device, kernel, 0, 0); - kernel<<>>( - args, out_backprop, filter, in_backprop, num_in_backprop); + CudaLaunchKernel(kernel, config.block_count, config.thread_per_block, 0, + device.stream(), args, out_backprop, filter, in_backprop, + num_in_backprop); return Status::OK(); } @@ -1746,8 +1747,9 @@ Status LaunchDepthwiseConv2dBackpropFilterGPU( auto device = ctx->eigen_gpu_device(); CudaLaunchConfig config = GetCudaLaunchConfig(num_out_backprop, device, kernel, 0, 0); - kernel<<>>( - args, out_backprop, input, filter_backprop, num_out_backprop); + CudaLaunchKernel(kernel, config.block_count, config.thread_per_block, 0, + device.stream(), args, out_backprop, input, filter_backprop, + num_out_backprop); return Status::OK(); } diff --git a/tensorflow/core/kernels/determinant_op_gpu.cu.cc b/tensorflow/core/kernels/determinant_op_gpu.cu.cc index c866204c97..fae5850dc4 100644 --- a/tensorflow/core/kernels/determinant_op_gpu.cu.cc +++ b/tensorflow/core/kernels/determinant_op_gpu.cu.cc @@ -129,10 +129,12 @@ struct DeterminantFromPivotedLUFunctor { const int64 num_matrices = output.size(); const int64 n = lu_factor.dimension(2); CudaLaunchConfig config = GetCudaLaunchConfig(num_matrices, device); - DeterminantFromPivotedLUKernel - <<>>( - config.virtual_thread_count, n, lu_factor.data(), pivots, nullptr, - output.data()); + + CudaLaunchKernel( + DeterminantFromPivotedLUKernel, + config.block_count, config.thread_per_block, 0, device.stream(), + config.virtual_thread_count, n, lu_factor.data(), pivots, nullptr, + output.data()); } }; @@ -150,10 +152,11 @@ struct LogDeterminantFromPivotedLUFunctor { const int64 num_matrices = sign.size(); const int64 n = lu_factor.dimension(2); CudaLaunchConfig config = GetCudaLaunchConfig(num_matrices, device); - DeterminantFromPivotedLUKernel - <<>>( - config.virtual_thread_count, n, lu_factor.data(), pivots, - sign.data(), log_abs_det.data()); + CudaLaunchKernel( + DeterminantFromPivotedLUKernel, + config.block_count, config.thread_per_block, 0, device.stream(), + config.virtual_thread_count, n, lu_factor.data(), pivots, sign.data(), + log_abs_det.data()); } }; diff --git a/tensorflow/core/kernels/dilation_ops_gpu.cu.cc b/tensorflow/core/kernels/dilation_ops_gpu.cu.cc index c63806a7f6..80d77d9b74 100644 --- a/tensorflow/core/kernels/dilation_ops_gpu.cu.cc +++ b/tensorflow/core/kernels/dilation_ops_gpu.cu.cc @@ -230,8 +230,8 @@ struct DilationBackpropInput { // Initialize in_backprop with all zeros. total_count = batch * input_rows * input_cols * depth; config = GetCudaLaunchConfig(total_count, d); - SetZero<<>>( - total_count, in_backprop.data()); + CudaLaunchKernel(SetZero, config.block_count, config.thread_per_block, 0, + d.stream(), total_count, in_backprop.data()); // Accumulate. total_count = batch * output_rows * output_cols * depth; @@ -270,8 +270,8 @@ struct DilationBackpropFilter { // Initialize filter_backprop with all zeros. total_count = filter_rows * filter_cols * depth; config = GetCudaLaunchConfig(total_count, d); - SetZero<<>>( - total_count, filter_backprop.data()); + CudaLaunchKernel(SetZero, config.block_count, config.thread_per_block, 0, + d.stream(), total_count, filter_backprop.data()); // Accumulate. total_count = batch * output_rows * output_cols * depth; diff --git a/tensorflow/core/kernels/dynamic_partition_op_gpu.cu.cc b/tensorflow/core/kernels/dynamic_partition_op_gpu.cu.cc index 59f687bf9c..346823acec 100644 --- a/tensorflow/core/kernels/dynamic_partition_op_gpu.cu.cc +++ b/tensorflow/core/kernels/dynamic_partition_op_gpu.cu.cc @@ -79,9 +79,9 @@ template void RangeInit(const GPUDevice& d, const T start, const T delta, const int32 size, typename TTypes::Flat out) { CudaLaunchConfig config = GetCudaLaunchConfig(size, d); - RangeInitKernel - <<>>( - start, delta, size, out.data()); + CudaLaunchKernel(RangeInitKernel, config.block_count, + config.thread_per_block, 0, d.stream(), start, delta, size, + out.data()); } // Given *num_runs pairs (key, value), this function moves the value @@ -103,10 +103,9 @@ void CallGatherKernel(const GPUDevice& d, const T* params, const int32* indices, T* out, int64 gather_dim_size, int64 indices_size, int64 slice_size, int64 out_size) { CudaLaunchConfig config = GetCudaLaunchConfig(out_size, d); - GatherOpKernel - <<>>( - params, indices, out, gather_dim_size, indices_size, slice_size, - out_size); + CudaLaunchKernel(GatherOpKernel, config.block_count, + config.thread_per_block, 0, d.stream(), params, indices, out, + gather_dim_size, indices_size, slice_size, out_size); } struct IdentityOp { diff --git a/tensorflow/core/kernels/dynamic_stitch_op_gpu.cu.cc b/tensorflow/core/kernels/dynamic_stitch_op_gpu.cu.cc index 102cdc40d4..618038360d 100644 --- a/tensorflow/core/kernels/dynamic_stitch_op_gpu.cu.cc +++ b/tensorflow/core/kernels/dynamic_stitch_op_gpu.cu.cc @@ -57,9 +57,9 @@ void DynamicStitchGPUImpl(const Eigen::GpuDevice& gpu_device, const int32 output_size = first_dim_size * slice_size; auto config = GetCudaLaunchConfig(output_size, gpu_device); - DynamicStitchKernel - <<>>( - slice_size, output_size, input_indices, input_ptrs, output); + CudaLaunchKernel(DynamicStitchKernel, config.block_count, + config.thread_per_block, 0, gpu_device.stream(), slice_size, + output_size, input_indices, input_ptrs, output); } #define REGISTER_GPU(T) \ diff --git a/tensorflow/core/kernels/gather_functor_gpu.cu.h b/tensorflow/core/kernels/gather_functor_gpu.cu.h index 11ea63d730..01a400aa47 100644 --- a/tensorflow/core/kernels/gather_functor_gpu.cu.h +++ b/tensorflow/core/kernels/gather_functor_gpu.cu.h @@ -92,19 +92,15 @@ struct GatherFunctor { CudaLaunchConfig config = GetCudaLaunchConfig(out_size, d); if (is_axis_zero) { - // clang-format off - GatherOpKernel - <<>>( - params.data(), indices.data(), out.data(), gather_dim_size, - indices_size, slice_size, out_size); - // clang-format on + CudaLaunchKernel(GatherOpKernel, config.block_count, + config.thread_per_block, 0, d.stream(), params.data(), + indices.data(), out.data(), gather_dim_size, + indices_size, slice_size, out_size); } else { - // clang-format off - GatherOpKernel - <<>>( - params.data(), indices.data(), out.data(), gather_dim_size, - indices_size, slice_size, out_size); - // clang-format on + CudaLaunchKernel(GatherOpKernel, config.block_count, + config.thread_per_block, 0, d.stream(), params.data(), + indices.data(), out.data(), gather_dim_size, + indices_size, slice_size, out_size); } // TODO(fpmc): enable indices validation on GPU. // Right now checking for indicies out of bound in the kernel would diff --git a/tensorflow/core/kernels/gather_nd_op_gpu.cu.cc b/tensorflow/core/kernels/gather_nd_op_gpu.cu.cc index da8d2e9e3c..e6ff5e5a29 100644 --- a/tensorflow/core/kernels/gather_nd_op_gpu.cu.cc +++ b/tensorflow/core/kernels/gather_nd_op_gpu.cu.cc @@ -86,12 +86,10 @@ struct GatherNdSlice { } CudaLaunchConfig config = GetCudaLaunchConfig(out_size, d); - // clang-format off - GatherSliceOpKernel - <<>>( - Tparams.data(), Tindices.data(), Tout.data(), batch_strides, - batch_indices, indices_size, s_size, out_size); - // clang-format on + CudaLaunchKernel(GatherSliceOpKernel, config.block_count, + config.thread_per_block, 0, d.stream(), Tparams.data(), + Tindices.data(), Tout.data(), batch_strides, batch_indices, + indices_size, s_size, out_size); // TODO(ebrevdo): enable indices validation on GPU. // Right now checking for indices out of bound in the kernel would diff --git a/tensorflow/core/kernels/inplace_ops_functor_gpu.cu.cc b/tensorflow/core/kernels/inplace_ops_functor_gpu.cu.cc index ba9879691b..2eda23cfa7 100644 --- a/tensorflow/core/kernels/inplace_ops_functor_gpu.cu.cc +++ b/tensorflow/core/kernels/inplace_ops_functor_gpu.cu.cc @@ -49,9 +49,9 @@ Status DoParallelConcatUpdate(const Device& d, const Tensor& value, int32 loc, const int64 ncols = Toutput.dimension(1); const T* src = value.flat().data(); T* dst = output->flat().data(); - DoParallelConcatOpKernel - <<>>( - cfg.virtual_thread_count, nrows, ncols, loc, src, dst); + CudaLaunchKernel(DoParallelConcatOpKernel, cfg.block_count, + cfg.thread_per_block, 0, d.stream(), + cfg.virtual_thread_count, nrows, ncols, loc, src, dst); return Status::OK(); } @@ -117,19 +117,22 @@ void DoInplaceOp(const Device& d, InplaceOpType op, const Tensor& i, T* dst = y->flat().data(); switch (op) { case I_UPDATE: - DoInplaceOpKernel - <<>>( - cfg.virtual_thread_count, nrows, ncols, n, src, rowids, dst); + CudaLaunchKernel(DoInplaceOpKernel, cfg.block_count, + cfg.thread_per_block, 0, d.stream(), + cfg.virtual_thread_count, nrows, ncols, n, src, rowids, + dst); break; case I_ADD: - DoInplaceOpKernel - <<>>( - cfg.virtual_thread_count, nrows, ncols, n, src, rowids, dst); + CudaLaunchKernel(DoInplaceOpKernel, cfg.block_count, + cfg.thread_per_block, 0, d.stream(), + cfg.virtual_thread_count, nrows, ncols, n, src, rowids, + dst); break; case I_SUB: - DoInplaceOpKernel - <<>>( - cfg.virtual_thread_count, nrows, ncols, n, src, rowids, dst); + CudaLaunchKernel(DoInplaceOpKernel, cfg.block_count, + cfg.thread_per_block, 0, d.stream(), + cfg.virtual_thread_count, nrows, ncols, n, src, rowids, + dst); break; } } @@ -148,9 +151,10 @@ void DoInplaceOp(const Device& d, InplaceOpType op, const Tensor& i, const int32* rowids = i.flat().data(); bool* dst = y->flat().data(); if (op == I_UPDATE) { - DoInplaceOpKernel - <<>>( - cfg.virtual_thread_count, nrows, ncols, n, src, rowids, dst); + CudaLaunchKernel(DoInplaceOpKernel, cfg.block_count, + cfg.thread_per_block, 0, d.stream(), + cfg.virtual_thread_count, nrows, ncols, n, src, rowids, + dst); } } diff --git a/tensorflow/core/kernels/matrix_set_diag_op_gpu.cu.cc b/tensorflow/core/kernels/matrix_set_diag_op_gpu.cu.cc index 35037b8e14..47a3905743 100644 --- a/tensorflow/core/kernels/matrix_set_diag_op_gpu.cu.cc +++ b/tensorflow/core/kernels/matrix_set_diag_op_gpu.cu.cc @@ -73,16 +73,16 @@ struct MatrixSetDiag { if (input.data() == output.data()) { CudaLaunchConfig config = GetCudaLaunchConfig(batch_size * minsize, device); - MatrixSetDiagKernel - <<>>( - config.virtual_thread_count, m, n, minsize, diag.data(), - output.data()); + CudaLaunchKernel(MatrixSetDiagKernel, config.block_count, + config.thread_per_block, 0, device.stream(), + config.virtual_thread_count, m, n, minsize, diag.data(), + output.data()); } else { CudaLaunchConfig config = GetCudaLaunchConfig(batch_size * m * n, device); - MatrixCopyInputAndSetDiagKernel - <<>>( - config.virtual_thread_count, m, n, minsize, input.data(), - diag.data(), output.data()); + CudaLaunchKernel(MatrixCopyInputAndSetDiagKernel, + config.block_count, config.thread_per_block, 0, + device.stream(), config.virtual_thread_count, m, n, + minsize, input.data(), diag.data(), output.data()); } } }; diff --git a/tensorflow/core/kernels/parameterized_truncated_normal_op_gpu.cu.cc b/tensorflow/core/kernels/parameterized_truncated_normal_op_gpu.cu.cc index 45ae45e4f4..d8f5c5222e 100644 --- a/tensorflow/core/kernels/parameterized_truncated_normal_op_gpu.cu.cc +++ b/tensorflow/core/kernels/parameterized_truncated_normal_op_gpu.cu.cc @@ -242,13 +242,12 @@ struct TruncatedNormalFunctor { typename TTypes::Flat output) { const auto config = GetCudaLaunchConfig(num_elements, d); - TruncatedNormalKernel - <<>>( - gen, output.data(), num_batches, samples_per_batch, num_elements, - means.data(), means.dimension(0) == 1, stddevs.data(), - stddevs.dimension(0) == 1, minvals.data(), - minvals.dimension(0) == 1, maxvals.data(), - maxvals.dimension(0) == 1, kMaxIterations); + CudaLaunchKernel( + TruncatedNormalKernel, config.block_count, config.thread_per_block, + 0, d.stream(), gen, output.data(), num_batches, samples_per_batch, + num_elements, means.data(), means.dimension(0) == 1, stddevs.data(), + stddevs.dimension(0) == 1, minvals.data(), minvals.dimension(0) == 1, + maxvals.data(), maxvals.dimension(0) == 1, kMaxIterations); } }; diff --git a/tensorflow/core/kernels/population_count_op_gpu.cu.cc b/tensorflow/core/kernels/population_count_op_gpu.cu.cc index 27a687ba40..dca8c26b79 100644 --- a/tensorflow/core/kernels/population_count_op_gpu.cu.cc +++ b/tensorflow/core/kernels/population_count_op_gpu.cu.cc @@ -71,9 +71,9 @@ __global__ void PopulationCountKernel(const int size, const int64* input, const GPUDevice& d = c->eigen_device(); \ int64 total_count = input.size(); \ CudaLaunchConfig config = GetCudaLaunchConfig(total_count, d); \ - PopulationCountKernel \ - <<>>( \ - total_count, input.data(), output.data()); \ + CudaLaunchKernel(PopulationCountKernel, config.block_count, \ + config.thread_per_block, 0, d.stream(), total_count, \ + input.data(), output.data()); \ } TF_CALL_uint8(DEFINE_GPU_SPECS); diff --git a/tensorflow/core/kernels/random_op_gpu.cu.cc b/tensorflow/core/kernels/random_op_gpu.cu.cc index a560388523..24ecb3779b 100644 --- a/tensorflow/core/kernels/random_op_gpu.cu.cc +++ b/tensorflow/core/kernels/random_op_gpu.cu.cc @@ -59,8 +59,8 @@ void FillPhiloxRandom::operator()( (d.getNumGpuMultiProcessors() * d.maxGpuThreadsPerMultiProcessor()) / block_size; - FillPhiloxRandomKernelLaunch - <<>>(gen, data, size, dist); + CudaLaunchKernel(FillPhiloxRandomKernelLaunch, num_blocks, + block_size, 0, d.stream(), gen, data, size, dist); } // Explicit instantiation of the GPU distributions functors diff --git a/tensorflow/core/kernels/reduction_gpu_kernels.cu.h b/tensorflow/core/kernels/reduction_gpu_kernels.cu.h index e9900e5868..1cc695878f 100644 --- a/tensorflow/core/kernels/reduction_gpu_kernels.cu.h +++ b/tensorflow/core/kernels/reduction_gpu_kernels.cu.h @@ -511,8 +511,9 @@ void LaunchScalarReduction(OpKernelContext* ctx, OUT_T out, IN_T in, if (in_size <= 4096) { const int num_blocks = 1; const int num_threads = 256; - BlockReduceKernel - <<>>(in, out, in_size, op, init); + CudaLaunchKernel(BlockReduceKernel, + num_blocks, num_threads, 0, cu_stream, in, out, in_size, + op, init); return; } else if (in_size <= 1 << 18) { const int num_threads = 256; @@ -531,17 +532,17 @@ void LaunchScalarReduction(OpKernelContext* ctx, OUT_T out, IN_T in, DT_INT8, TensorShape({static_cast(num_blocks * sizeof(T))}), &temp_storage)); - BlockReduceKernel - <<>>( - in, (T*)temp_storage.flat().data(), in_size, op, init); + CudaLaunchKernel(BlockReduceKernel, num_blocks, + num_threads, 0, cu_stream, in, + (T*)temp_storage.flat().data(), in_size, op, init); // take care that we only reduce blocks that had some valid elements in them // TODO(eriche): CUB currently has a bug in HeadSegmentedReduce that // requires it to be used with a full warp. Can reduce 32 -> num_blocks // when this is fixed. - CleanupSegments<<<1, 32, 0, cu_stream>>>( - (T*)temp_storage.flat().data(), out, 1, 1, num_blocks, op, - init); + CudaLaunchKernel(CleanupSegments, 1, 32, 0, cu_stream, + (T*)temp_storage.flat().data(), out, 1, 1, + num_blocks, op, init); return; } @@ -576,8 +577,9 @@ void LaunchRowReduction(OpKernelContext* ctx, OUT_T out, IN_T in, int num_rows, const int warps_per_block = threads_per_block / 32; int num_blocks = (num_rows + warps_per_block - 1) / warps_per_block; - RowReduceKernel<<>>( - in, out, num_rows, num_cols, op, init); + CudaLaunchKernel(RowReduceKernel, num_blocks, + threads_per_block, 0, cu_stream, in, out, num_rows, + num_cols, op, init); return; } @@ -628,8 +630,9 @@ void LaunchColumnReduction_LTE16Cols(OpKernelContext* ctx, OUT_T out, IN_T in, } if (grid_dim.y == 1) { - ColumnReduceMax16ColumnsKernel<<>>( - in, out, extent_x, extent_y, op, init); + CudaLaunchKernel(ColumnReduceMax16ColumnsKernel, grid_dim, + block_dim, 0, cu_stream, in, out, extent_x, extent_y, op, + init); } else { Tensor temp_storage; OP_REQUIRES_OK(ctx, @@ -637,15 +640,16 @@ void LaunchColumnReduction_LTE16Cols(OpKernelContext* ctx, OUT_T out, IN_T in, TensorShape({static_cast( sizeof(T) * extent_y * grid_dim.y)}), &temp_storage)); - ColumnReduceMax16ColumnsKernel<<>>( - in, (T*)temp_storage.flat().data(), extent_x, extent_y, op, - init); + CudaLaunchKernel(ColumnReduceMax16ColumnsKernel, grid_dim, + block_dim, 0, cu_stream, in, + (T*)temp_storage.flat().data(), extent_x, extent_y, + op, init); dim3 new_grid_dim((grid_dim.y * extent_y + 31) / 32, 1, 1); dim3 num_threads(128, 1, 1); - CleanupSegments<<>>( - (T*)temp_storage.flat().data(), out, extent_x, extent_y, - grid_dim.y, op, init); + CudaLaunchKernel(CleanupSegments, new_grid_dim, num_threads, + 0, cu_stream, (T*)temp_storage.flat().data(), out, + extent_x, extent_y, grid_dim.y, op, init); } } @@ -664,8 +668,8 @@ void LaunchColumnReduction_LTE4096Cols(OpKernelContext* ctx, OUT_T out, IN_T in, } if (grid_dim.y == 1) { - ColumnReduceKernel<<>>( - in, out, extent_x, extent_y, op, init); + CudaLaunchKernel(ColumnReduceKernel, grid_dim, block_dim, + 0, cu_stream, in, out, extent_x, extent_y, op, init); } else { Tensor temp_storage; OP_REQUIRES_OK(ctx, @@ -674,15 +678,15 @@ void LaunchColumnReduction_LTE4096Cols(OpKernelContext* ctx, OUT_T out, IN_T in, sizeof(T) * extent_y * grid_dim.y)}), &temp_storage)); - ColumnReduceKernel<<>>( - in, (T*)temp_storage.flat().data(), extent_x, extent_y, op, - init); + CudaLaunchKernel(ColumnReduceKernel, grid_dim, block_dim, 0, + cu_stream, in, (T*)temp_storage.flat().data(), + extent_x, extent_y, op, init); dim3 new_grid_dim((grid_dim.y * extent_y + 31) / 32, 1, 1); dim3 num_threads(128, 1, 1); - CleanupSegments<<>>( - (T*)temp_storage.flat().data(), out, extent_x, extent_y, - grid_dim.y, op, init); + CudaLaunchKernel(CleanupSegments, new_grid_dim, block_dim, 0, + cu_stream, (T*)temp_storage.flat().data(), out, + extent_x, extent_y, grid_dim.y, op, init); } } @@ -700,8 +704,9 @@ void LaunchColumnReduction(OpKernelContext* ctx, OUT_T out, IN_T in, int threads_per_block = 128; int num_blocks = Eigen::divup(extent_y, threads_per_block); - ColumnReduceSimpleKernel<<>>( - in, out, 1, extent_x, extent_y, op); + CudaLaunchKernel(ColumnReduceSimpleKernel, num_blocks, + threads_per_block, 0, cu_stream, in, out, 1, extent_x, + extent_y, op); } } @@ -715,8 +720,9 @@ void Launch3DYReduction(OpKernelContext* ctx, OUT_T out, IN_T in, int extent_x, // TODO(eriche): this won't be very good in the case of small x // small z and large y. - ColumnReduceSimpleKernel<<>>( - in, out, extent_x, extent_y, extent_z, op); + CudaLaunchKernel(ColumnReduceSimpleKernel, num_blocks, + threads_per_block, 0, cu_stream, in, out, extent_x, extent_y, + extent_z, op); } template diff --git a/tensorflow/core/kernels/resize_bilinear_op_gpu.cu.cc b/tensorflow/core/kernels/resize_bilinear_op_gpu.cu.cc index f82c3fcd9f..d918d9193c 100644 --- a/tensorflow/core/kernels/resize_bilinear_op_gpu.cu.cc +++ b/tensorflow/core/kernels/resize_bilinear_op_gpu.cu.cc @@ -164,11 +164,11 @@ struct ResizeBilinear { if (total_count == 0) return; CudaLaunchConfig config = GetCudaLaunchConfig(total_count, d); - ResizeBilinearKernel - <<>>( - config.virtual_thread_count, images.data(), height_scale, - width_scale, batch, in_height, in_width, channels, out_height, - out_width, output.data()); + CudaLaunchKernel(ResizeBilinearKernel, config.block_count, + config.thread_per_block, 0, d.stream(), + config.virtual_thread_count, images.data(), height_scale, + width_scale, batch, in_height, in_width, channels, + out_height, out_width, output.data()); } }; @@ -194,17 +194,19 @@ struct ResizeBilinearGrad { total_count = batch * original_height * original_width * channels; if (total_count == 0) return; config = GetCudaLaunchConfig(total_count, d); - SetZero<<>>( - config.virtual_thread_count, output_grad.data()); + CudaLaunchKernel(SetZero, config.block_count, config.thread_per_block, 0, + d.stream(), config.virtual_thread_count, + output_grad.data()); // Accumulate. total_count = batch * resized_height * resized_width * channels; config = GetCudaLaunchConfig(total_count, d); - ResizeBilinearGradKernel - <<>>( - config.virtual_thread_count, input_grad.data(), height_scale, - width_scale, batch, original_height, original_width, channels, - resized_height, resized_width, output_grad.data()); + CudaLaunchKernel(ResizeBilinearGradKernel, config.block_count, + config.thread_per_block, 0, d.stream(), + config.virtual_thread_count, input_grad.data(), + height_scale, width_scale, batch, original_height, + original_width, channels, resized_height, resized_width, + output_grad.data()); } }; diff --git a/tensorflow/core/kernels/resize_nearest_neighbor_op_gpu.cu.cc b/tensorflow/core/kernels/resize_nearest_neighbor_op_gpu.cu.cc index d65c8fb949..e93948f29b 100644 --- a/tensorflow/core/kernels/resize_nearest_neighbor_op_gpu.cu.cc +++ b/tensorflow/core/kernels/resize_nearest_neighbor_op_gpu.cu.cc @@ -112,10 +112,11 @@ struct ResizeNearestNeighbor { if (output_size == 0) return true; CudaLaunchConfig config = GetCudaLaunchConfig(output_size, d); - ResizeNearestNeighborNHWC - <<>>( - output_size, input.data(), in_height, in_width, channels, - out_height, out_width, height_scale, width_scale, output.data()); + CudaLaunchKernel(ResizeNearestNeighborNHWC, + config.block_count, config.thread_per_block, 0, d.stream(), + output_size, input.data(), in_height, in_width, channels, + out_height, out_width, height_scale, width_scale, + output.data()); return d.ok(); } }; diff --git a/tensorflow/core/kernels/scan_ops_gpu.h b/tensorflow/core/kernels/scan_ops_gpu.h index 976b221540..21fc77ffd1 100644 --- a/tensorflow/core/kernels/scan_ops_gpu.h +++ b/tensorflow/core/kernels/scan_ops_gpu.h @@ -32,6 +32,7 @@ limitations under the License. #include "cuda/include/cuComplex.h" #include "tensorflow/core/framework/numeric_types.h" #include "tensorflow/core/framework/register_types.h" +#include "tensorflow/core/util/cuda_launch_config.h" #include "tensorflow/core/util/permutation_input_iterator.h" #include "tensorflow/core/util/permutation_output_iterator.h" @@ -241,34 +242,34 @@ void LaunchScan(const GPUDevice& d, typename TTypes::ConstTensor in, // Launch on the smallest power of 2 block size that we can. if (ideal_block_size >= 1024 && std::is_same::value) { const int block_size = 1024; - scan_kernel - <<>>( - in.data(), out.data(), dimx, dimy, dimz, exclusive, reverse, op); + CudaLaunchKernel(scan_kernel, + num_blocks, block_size, 0, d.stream(), in.data(), + out.data(), dimx, dimy, dimz, exclusive, reverse, op); } else if (ideal_block_size >= 512) { const int block_size = 512; - scan_kernel - <<>>( - in.data(), out.data(), dimx, dimy, dimz, exclusive, reverse, op); + CudaLaunchKernel(scan_kernel, + num_blocks, block_size, 0, d.stream(), in.data(), + out.data(), dimx, dimy, dimz, exclusive, reverse, op); } else if (ideal_block_size >= 256) { const int block_size = 256; - scan_kernel - <<>>( - in.data(), out.data(), dimx, dimy, dimz, exclusive, reverse, op); + CudaLaunchKernel(scan_kernel, + num_blocks, block_size, 0, d.stream(), in.data(), + out.data(), dimx, dimy, dimz, exclusive, reverse, op); } else if (ideal_block_size >= 128) { const int block_size = 128; - scan_kernel - <<>>( - in.data(), out.data(), dimx, dimy, dimz, exclusive, reverse, op); + CudaLaunchKernel(scan_kernel, + num_blocks, block_size, 0, d.stream(), in.data(), + out.data(), dimx, dimy, dimz, exclusive, reverse, op); } else if (ideal_block_size >= 64) { const int block_size = 64; - scan_kernel - <<>>( - in.data(), out.data(), dimx, dimy, dimz, exclusive, reverse, op); + CudaLaunchKernel(scan_kernel, + num_blocks, block_size, 0, d.stream(), in.data(), + out.data(), dimx, dimy, dimz, exclusive, reverse, op); } else { const int block_size = 32; - scan_kernel - <<>>( - in.data(), out.data(), dimx, dimy, dimz, exclusive, reverse, op); + CudaLaunchKernel(scan_kernel, + num_blocks, block_size, 0, d.stream(), in.data(), + out.data(), dimx, dimy, dimz, exclusive, reverse, op); } } diff --git a/tensorflow/core/kernels/scatter_functor_gpu.cu.h b/tensorflow/core/kernels/scatter_functor_gpu.cu.h index 057755a05c..59d704de9f 100644 --- a/tensorflow/core/kernels/scatter_functor_gpu.cu.h +++ b/tensorflow/core/kernels/scatter_functor_gpu.cu.h @@ -127,10 +127,10 @@ struct ScatterFunctor { const Index indices_size = indices.size(); const Index updates_size = updates.size(); CudaLaunchConfig config = GetCudaLaunchConfig(updates_size, d); - scatter_op_gpu::ScatterOpCustomKernel - <<>>( - params.data(), updates.data(), indices.data(), first_dim_size, - updates_size, indices_size); + CudaLaunchKernel(scatter_op_gpu::ScatterOpCustomKernel, + config.block_count, config.thread_per_block, 0, d.stream(), + params.data(), updates.data(), indices.data(), + first_dim_size, updates_size, indices_size); return -1; } }; @@ -148,10 +148,10 @@ struct ScatterScalarFunctor { 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); + CudaLaunchKernel(scatter_op_gpu::ScatterScalarOpCustomKernel, + config.block_count, config.thread_per_block, 0, d.stream(), + params.data(), update.data(), indices.data(), + first_dim_size, indices_size, synthesized_updates_size); return -1; } }; diff --git a/tensorflow/core/kernels/scatter_nd_op_gpu.cu.cc b/tensorflow/core/kernels/scatter_nd_op_gpu.cu.cc index 08b657f4c3..62954d178e 100644 --- a/tensorflow/core/kernels/scatter_nd_op_gpu.cu.cc +++ b/tensorflow/core/kernels/scatter_nd_op_gpu.cu.cc @@ -136,12 +136,11 @@ struct ScatterNdFunctor { } CudaLaunchConfig config = GetCudaLaunchConfig(Toutput.size(), d); - // clang-format off - ScatterNdOpKernel - <<>>( - Tindices.data(), Tupdates.data(), Toutput.data(), output_shape_prefix, - batch_strides, batch_size, slice_size); - // clang-format on + + CudaLaunchKernel(ScatterNdOpKernel, config.block_count, + config.thread_per_block, 0, d.stream(), Tindices.data(), + Tupdates.data(), Toutput.data(), output_shape_prefix, + batch_strides, batch_size, slice_size); return -1; } diff --git a/tensorflow/core/kernels/searchsorted_op_gpu.cu.cc b/tensorflow/core/kernels/searchsorted_op_gpu.cu.cc index 263b5bf298..22b7179fec 100644 --- a/tensorflow/core/kernels/searchsorted_op_gpu.cu.cc +++ b/tensorflow/core/kernels/searchsorted_op_gpu.cu.cc @@ -68,10 +68,10 @@ struct UpperBoundFunctor { CudaLaunchConfig config = GetCudaLaunchConfig(values.size(), context->eigen_gpu_device()); - UpperBoundKernel - <<>>( - sorted_inputs.data(), batch_size, num_inputs, num_values, - values.data(), output->data()); + CudaLaunchKernel(UpperBoundKernel, config.block_count, + config.thread_per_block, 0, stream, sorted_inputs.data(), + batch_size, num_inputs, num_values, values.data(), + output->data()); return Status::OK(); } @@ -88,10 +88,10 @@ struct LowerBoundFunctor { CudaLaunchConfig config = GetCudaLaunchConfig(values.size(), context->eigen_gpu_device()); - LowerBoundKernel - <<>>( - sorted_inputs.data(), batch_size, num_inputs, num_values, - values.data(), output->data()); + CudaLaunchKernel(LowerBoundKernel, config.block_count, + config.thread_per_block, 0, stream, sorted_inputs.data(), + batch_size, num_inputs, num_values, values.data(), + output->data()); return Status::OK(); } diff --git a/tensorflow/core/kernels/segment_reduction_ops_gpu.cu.cc b/tensorflow/core/kernels/segment_reduction_ops_gpu.cu.cc index 3511c85f71..7af1f6ce2b 100644 --- a/tensorflow/core/kernels/segment_reduction_ops_gpu.cu.cc +++ b/tensorflow/core/kernels/segment_reduction_ops_gpu.cu.cc @@ -138,8 +138,8 @@ void SegmentSumFunctor::operator()( } // Set 'output' to zeros. CudaLaunchConfig config = GetCudaLaunchConfig(output.size(), d); - SetZero<<>>( - output.size(), output.data()); + CudaLaunchKernel(SetZero, config.block_count, config.thread_per_block, 0, + d.stream(), output.size(), output.data()); if (data_size == 0 || segment_ids_shape.num_elements() == 0) { return; } @@ -162,10 +162,10 @@ void SegmentSumFunctor::operator()( input_inner_dim_size * input_outer_dim_num_stripe; config = GetCudaLaunchConfig(total_stripe_count, d); - SortedSegmentSumCustomKernel - <<>>( - input_outer_dim_size, input_inner_dim_size, output_rows, - segment_ids.data(), data, output.data(), total_stripe_count); + CudaLaunchKernel(SortedSegmentSumCustomKernel, + config.block_count, config.thread_per_block, 0, d.stream(), + input_outer_dim_size, input_inner_dim_size, output_rows, + segment_ids.data(), data, output.data(), total_stripe_count); } template { // Set 'output' to initial value. GPUDevice d = ctx->template eigen_device(); CudaLaunchConfig config = GetCudaLaunchConfig(output.size(), d); - SetToValue<<>>( - output.size(), output.data(), InitialValueF()()); + CudaLaunchKernel(SetToValue, config.block_count, config.thread_per_block, + 0, d.stream(), output.size(), output.data(), + InitialValueF()()); if (data_size == 0 || segment_ids_shape.num_elements() == 0) { return; } @@ -196,10 +197,10 @@ struct UnsortedSegmentFunctor { const Index input_inner_dim_size = data_size / input_outer_dim_size; config = GetCudaLaunchConfig(data_size, d); - UnsortedSegmentCustomKernel - <<>>( - input_outer_dim_size, input_inner_dim_size, num_segments, - segment_ids.data(), data, output.data()); + CudaLaunchKernel(UnsortedSegmentCustomKernel, + config.block_count, config.thread_per_block, 0, d.stream(), + input_outer_dim_size, input_inner_dim_size, num_segments, + segment_ids.data(), data, output.data()); } }; diff --git a/tensorflow/core/kernels/softmax_op_gpu.cu.cc b/tensorflow/core/kernels/softmax_op_gpu.cu.cc index d1e677feb0..fb07399203 100644 --- a/tensorflow/core/kernels/softmax_op_gpu.cu.cc +++ b/tensorflow/core/kernels/softmax_op_gpu.cu.cc @@ -180,13 +180,12 @@ class SoftmaxOpGPU : public OpKernel { 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_); + CudaLaunchKernel( + GenerateNormalizedProb, numBlocks, numThreads, 0, + cu_stream, 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/core/kernels/spacetobatch_functor_gpu.cu.cc b/tensorflow/core/kernels/spacetobatch_functor_gpu.cu.cc index 5687141c9e..dc1a7ac656 100644 --- a/tensorflow/core/kernels/spacetobatch_functor_gpu.cu.cc +++ b/tensorflow/core/kernels/spacetobatch_functor_gpu.cu.cc @@ -141,10 +141,11 @@ struct SpaceToBatchFunctor { } CudaLaunchConfig config = GetCudaLaunchConfig(static_cast(total_count), d); - S2B - <<>>( - config.virtual_thread_count, const_cast(space_tensor.data()), - args, const_cast(batch_tensor.data())); + CudaLaunchKernel(S2B, config.block_count, + config.thread_per_block, 0, d.stream(), + config.virtual_thread_count, + const_cast(space_tensor.data()), args, + const_cast(batch_tensor.data())); return Status::OK(); } }; diff --git a/tensorflow/core/kernels/spacetodepth_op_gpu.cu.cc b/tensorflow/core/kernels/spacetodepth_op_gpu.cu.cc index b565927ccb..9fc76da26d 100644 --- a/tensorflow/core/kernels/spacetodepth_op_gpu.cu.cc +++ b/tensorflow/core/kernels/spacetodepth_op_gpu.cu.cc @@ -158,10 +158,11 @@ struct SpaceToDepthOpFunctor { return; } CudaLaunchConfig config = GetCudaLaunchConfig(total_count, d); - S2D_NHWC<<>>( - config.virtual_thread_count, input.data(), block_size, batch_size, - input_height, input_width, input_depth, output_height, output_width, - output_depth, output.data()); + CudaLaunchKernel(S2D_NHWC, config.block_count, config.thread_per_block, + 0, d.stream(), config.virtual_thread_count, input.data(), + block_size, batch_size, input_height, input_width, + input_depth, output_height, output_width, output_depth, + output.data()); } void operator()(const GPUDevice& d, typename TTypes::ConstTensor input, int block_size, typename TTypes::Tensor output) { @@ -193,23 +194,23 @@ struct SpaceToDepthOpFunctor { CudaLaunchConfig config = GetCudaLaunchConfig(total_count, d); switch (block_size) { case 2: - return S2D_NCHW_LOOP - <<>>( - total_count, input.data(), output_width, input_width, - input_depth_by_output_area, output_depth_by_output_area, - output.data()); + return CudaLaunchKernel(S2D_NCHW_LOOP, config.block_count, + config.thread_per_block, 0, d.stream(), + total_count, input.data(), output_width, + input_width, input_depth_by_output_area, + output_depth_by_output_area, output.data()); case 3: - return S2D_NCHW_LOOP - <<>>( - total_count, input.data(), output_width, input_width, - input_depth_by_output_area, output_depth_by_output_area, - output.data()); + return CudaLaunchKernel(S2D_NCHW_LOOP, config.block_count, + config.thread_per_block, 0, d.stream(), + total_count, input.data(), output_width, + input_width, input_depth_by_output_area, + output_depth_by_output_area, output.data()); case 4: - return S2D_NCHW_LOOP - <<>>( - total_count, input.data(), output_width, input_width, - input_depth_by_output_area, output_depth_by_output_area, - output.data()); + return CudaLaunchKernel(S2D_NCHW_LOOP, config.block_count, + config.thread_per_block, 0, d.stream(), + total_count, input.data(), output_width, + input_width, input_depth_by_output_area, + output_depth_by_output_area, output.data()); } } @@ -219,9 +220,10 @@ struct SpaceToDepthOpFunctor { return; } CudaLaunchConfig config = GetCudaLaunchConfig(total_count, d); - S2D_NCHW<<>>( - config.virtual_thread_count, input.data(), block_size, output_width, - input_depth * output_height, output.data()); + CudaLaunchKernel(S2D_NCHW, config.block_count, config.thread_per_block, + 0, d.stream(), config.virtual_thread_count, input.data(), + block_size, output_width, input_depth * output_height, + output.data()); } void operator()(const GPUDevice& d, typename TTypes::ConstTensor input, int block_size, typename TTypes::Tensor output) { diff --git a/tensorflow/core/kernels/sparse_tensor_dense_matmul_op_gpu.cu.cc b/tensorflow/core/kernels/sparse_tensor_dense_matmul_op_gpu.cu.cc index ea95a882b1..ad9cc90605 100644 --- a/tensorflow/core/kernels/sparse_tensor_dense_matmul_op_gpu.cu.cc +++ b/tensorflow/core/kernels/sparse_tensor_dense_matmul_op_gpu.cu.cc @@ -81,10 +81,10 @@ struct SparseTensorDenseMatMulFunctor { // out.size()? Perhaps p * nnz ? CudaLaunchConfig config = GetCudaLaunchConfig(p * nnz, d); - SparseTensorDenseMatMulKernel - <<>>( - nnz, m, b_rows, b_cols, p, a_indices.data(), a_values.data(), - b.data(), out.data()); + CudaLaunchKernel(SparseTensorDenseMatMulKernel, + config.block_count, config.thread_per_block, 0, d.stream(), + nnz, m, b_rows, b_cols, p, a_indices.data(), + a_values.data(), b.data(), out.data()); return Status::OK(); } diff --git a/tensorflow/core/kernels/split_lib_gpu.cu.cc b/tensorflow/core/kernels/split_lib_gpu.cu.cc index a4a59dbcbc..6106e70994 100644 --- a/tensorflow/core/kernels/split_lib_gpu.cu.cc +++ b/tensorflow/core/kernels/split_lib_gpu.cu.cc @@ -199,10 +199,10 @@ struct SplitOpGPULaunch { CudaLaunchConfig config = GetCudaLaunchConfig( prefix_dim_size * split_dim_size * suffix_dim_size, d); - SplitOpKernel - <<>>( - input, prefix_dim_size, split_dim_size, suffix_dim_size, - output_ptr_data); + CudaLaunchKernel(SplitOpKernel, config.block_count, + config.thread_per_block, 0, d.stream(), input, + prefix_dim_size, split_dim_size, suffix_dim_size, + output_ptr_data); } }; diff --git a/tensorflow/core/kernels/stateful_random_ops_gpu.cu.cc b/tensorflow/core/kernels/stateful_random_ops_gpu.cu.cc index 4ce0db7c56..b58d0b6088 100644 --- a/tensorflow/core/kernels/stateful_random_ops_gpu.cu.cc +++ b/tensorflow/core/kernels/stateful_random_ops_gpu.cu.cc @@ -74,9 +74,9 @@ void UpdateVariableAndFill_Philox::operator()( int zero = 0; cudaMemcpyToSymbol(thread_counter, &zero, sizeof(int)); - FillKernel - <<>>( - Distribution(), state_size, output_size, state_data, output_data); + CudaLaunchKernel(FillKernel, cfg.block_count, + cfg.thread_per_block, 0, d.stream(), Distribution(), + state_size, output_size, state_data, output_data); } // Explicit instantiation of the GPU distributions functors. diff --git a/tensorflow/core/kernels/tile_functor_gpu.h b/tensorflow/core/kernels/tile_functor_gpu.h index 0de32e730e..726b3d5688 100644 --- a/tensorflow/core/kernels/tile_functor_gpu.h +++ b/tensorflow/core/kernels/tile_functor_gpu.h @@ -75,9 +75,9 @@ void TileSimple(const Device& d, Tensor* out, const Tensor& in) { const T* p = in.flat().data(); T* q = out->flat().data(); CudaLaunchConfig cfg = GetCudaLaunchConfig(out_nelem, d); - TileKernel<<>>( - cfg.virtual_thread_count, p, reinterpret_cast(dev_buf), - ndims, q); + CudaLaunchKernel(TileKernel, cfg.block_count, cfg.thread_per_block, 0, + d.stream(), cfg.virtual_thread_count, p, + reinterpret_cast(dev_buf), ndims, q); // Safe to deallocate immediately after the kernel launch. d.deallocate(dev_buf); } diff --git a/tensorflow/core/kernels/topk_op_gpu.h b/tensorflow/core/kernels/topk_op_gpu.h index 70d6a60664..27dd984314 100644 --- a/tensorflow/core/kernels/topk_op_gpu.h +++ b/tensorflow/core/kernels/topk_op_gpu.h @@ -401,8 +401,8 @@ cudaError LaunchTopKKernel(const cudaStream_t& stream, int num_shards, // We are limited by the amount of shared memory we have per block. auto shared_memory_size = (num_shards + 1) * k * sizeof(Entry); - TopKKernel<<>>( - input, length, k, sorted, output, indices); + CudaLaunchKernel(TopKKernel, batch_size, num_shards, shared_memory_size, + stream, input, length, k, sorted, output, indices); return cudaGetLastError(); } diff --git a/tensorflow/core/kernels/transpose_functor_gpu.cu.cc b/tensorflow/core/kernels/transpose_functor_gpu.cu.cc index d6a237d6c1..11da9c17ea 100644 --- a/tensorflow/core/kernels/transpose_functor_gpu.cu.cc +++ b/tensorflow/core/kernels/transpose_functor_gpu.cu.cc @@ -80,10 +80,10 @@ void TransposeSimple(const GPUDevice& d, const Tensor& in, const T* p = reinterpret_cast(in.tensor_data().data()); T* q = reinterpret_cast(const_cast((out->tensor_data().data()))); CudaLaunchConfig cfg = GetCudaLaunchConfig(nelem, d); - TransposeKernel - <<>>( - cfg.virtual_thread_count, p, reinterpret_cast(dev_buf), - ndims, q); + CudaLaunchKernel(TransposeKernel, cfg.block_count, + cfg.thread_per_block, 0, d.stream(), + cfg.virtual_thread_count, p, + reinterpret_cast(dev_buf), ndims, q); // Safe to deallocate immediately after the kernel launch. d.deallocate(dev_buf); } diff --git a/tensorflow/core/kernels/where_op_gpu.cu.h b/tensorflow/core/kernels/where_op_gpu.cu.h index 54b22d230a..72d109b9ff 100644 --- a/tensorflow/core/kernels/where_op_gpu.cu.h +++ b/tensorflow/core/kernels/where_op_gpu.cu.h @@ -324,9 +324,9 @@ struct Where { CalculateStrides(input); const TIndex output_rows = output.dimension(0); CudaLaunchConfig config = GetCudaLaunchConfig(output_rows, d); - PropagateWhereIndicesKernel - <<>>( - output_rows, strides, output.data()); + CudaLaunchKernel(PropagateWhereIndicesKernel, + config.block_count, config.thread_per_block, 0, d.stream(), + output_rows, strides, output.data()); return Status::OK(); } diff --git a/tensorflow/core/util/cuda_kernel_helper_test.cu.cc b/tensorflow/core/util/cuda_kernel_helper_test.cu.cc index 2b035ab0e9..f0fc0312ce 100644 --- a/tensorflow/core/util/cuda_kernel_helper_test.cu.cc +++ b/tensorflow/core/util/cuda_kernel_helper_test.cu.cc @@ -19,6 +19,7 @@ limitations under the License. #include #include "tensorflow/core/platform/test.h" #include "tensorflow/core/util/cuda_kernel_helper.h" +#include "tensorflow/core/util/cuda_launch_config.h" #define CUDA_EXPECT_SUCCESS \ { \ @@ -152,22 +153,22 @@ TEST_F(CudaLaunchConfigTest, GetCudaLaunchConfig) { // test valid inputs #define TEST_LAUNCH_PARAMETER(work_element_count) \ cfg = GetCudaLaunchConfig(bufsize, d); \ - SetOutbufZero<<>>( \ - cfg, outbuf); \ + CudaLaunchKernel(SetOutbufZero, cfg.block_count, cfg.thread_per_block, 0, \ + d.stream(), cfg, outbuf); \ CUDA_ASSERT_SUCCESS \ cfg = GetCudaLaunchConfig(work_element_count, d); \ - Count1D<<>>( \ - cfg, bufsize, outbuf); \ + CudaLaunchKernel(Count1D, cfg.block_count, cfg.thread_per_block, 0, \ + d.stream(), cfg, bufsize, outbuf); \ CUDA_EXPECT_SUCCESS \ EXPECT_EQ(work_element_count, std::accumulate(outbuf, outbuf + bufsize, 0)); \ \ cfg = GetCudaLaunchConfig(bufsize, d, SetOutbufZero, 0, 0); \ - SetOutbufZero<<>>( \ - cfg, outbuf); \ + CudaLaunchKernel(SetOutbufZero, cfg.block_count, cfg.thread_per_block, 0, \ + d.stream(), cfg, outbuf); \ CUDA_ASSERT_SUCCESS \ cfg = GetCudaLaunchConfig(work_element_count, d, Count1D, 0, 0); \ - Count1D<<>>( \ - cfg, bufsize, outbuf); \ + CudaLaunchKernel(Count1D, cfg.block_count, cfg.thread_per_block, 0, \ + d.stream(), cfg, bufsize, outbuf); \ CUDA_EXPECT_SUCCESS \ EXPECT_EQ(work_element_count, std::accumulate(outbuf, outbuf + bufsize, 0)) @@ -201,25 +202,25 @@ TEST_F(CudaLaunchConfigTest, GetCuda2DLaunchConfig) { CudaLaunchConfig cfg1d; // test valid inputs -#define TEST_LAUNCH_PARAMETER(dimx, dimy) \ - cfg1d = GetCudaLaunchConfig(bufsize, d); \ - SetOutbufZero<<>>( \ - cfg1d, outbuf); \ - CUDA_ASSERT_SUCCESS \ - cfg = GetCuda2DLaunchConfig(dimx, dimy, d); \ - Count2D<<>>( \ - cfg, bufsize, outbuf); \ - CUDA_EXPECT_SUCCESS \ - EXPECT_EQ(dimx* dimy, std::accumulate(outbuf, outbuf + bufsize, 0)); \ - \ - cfg1d = GetCudaLaunchConfig(bufsize, d, SetOutbufZero, 0, 0); \ - SetOutbufZero<<>>( \ - cfg1d, outbuf); \ - CUDA_ASSERT_SUCCESS \ - cfg = GetCuda2DLaunchConfig(dimx, dimy, d, Count2D, 0, 0); \ - Count2D<<>>( \ - cfg, bufsize, outbuf); \ - CUDA_EXPECT_SUCCESS \ +#define TEST_LAUNCH_PARAMETER(dimx, dimy) \ + cfg1d = GetCudaLaunchConfig(bufsize, d); \ + CudaLaunchKernel(SetOutbufZero, cfg1d.block_count, cfg1d.thread_per_block, \ + 0, d.stream(), cfg1d, outbuf); \ + CUDA_ASSERT_SUCCESS \ + cfg = GetCuda2DLaunchConfig(dimx, dimy, d); \ + CudaLaunchKernel(Count2D, cfg.block_count, cfg.thread_per_block, 0, \ + d.stream(), cfg, bufsize, outbuf); \ + CUDA_EXPECT_SUCCESS \ + EXPECT_EQ(dimx* dimy, std::accumulate(outbuf, outbuf + bufsize, 0)); \ + \ + cfg1d = GetCudaLaunchConfig(bufsize, d, SetOutbufZero, 0, 0); \ + CudaLaunchKernel(SetOutbufZero, cfg1d.block_count, cfg1d.thread_per_block, \ + 0, d.stream(), cfg1d, outbuf); \ + CUDA_ASSERT_SUCCESS \ + cfg = GetCuda2DLaunchConfig(dimx, dimy, d, Count2D, 0, 0); \ + CudaLaunchKernel(Count2D, cfg.block_count, cfg.thread_per_block, 0, \ + d.stream(), cfg, bufsize, outbuf); \ + CUDA_EXPECT_SUCCESS \ EXPECT_EQ(dimx* dimy, std::accumulate(outbuf, outbuf + bufsize, 0)) TEST_LAUNCH_PARAMETER(128, 128); @@ -241,15 +242,15 @@ TEST_F(CudaLaunchConfigTest, GetCuda3DLaunchConfig) { CudaLaunchConfig cfg1d; // test valid inputs -#define TEST_LAUNCH_PARAMETER(dimx, dimy, dimz) \ - cfg1d = GetCudaLaunchConfig(bufsize, d, SetOutbufZero, 0, 0); \ - SetOutbufZero<<>>( \ - cfg1d, outbuf); \ - CUDA_ASSERT_SUCCESS \ - cfg = GetCuda3DLaunchConfig(dimx, dimy, dimz, d, Count3D, 0, 0); \ - Count3D<<>>( \ - cfg, bufsize, outbuf); \ - CUDA_EXPECT_SUCCESS \ +#define TEST_LAUNCH_PARAMETER(dimx, dimy, dimz) \ + cfg1d = GetCudaLaunchConfig(bufsize, d, SetOutbufZero, 0, 0); \ + CudaLaunchKernel(SetOutbufZero, cfg1d.block_count, cfg1d.thread_per_block, \ + 0, d.stream(), cfg1d, outbuf); \ + CUDA_ASSERT_SUCCESS \ + cfg = GetCuda3DLaunchConfig(dimx, dimy, dimz, d, Count3D, 0, 0); \ + CudaLaunchKernel(Count3D, cfg.block_count, cfg.thread_per_block, 0, \ + d.stream(), cfg, bufsize, outbuf); \ + CUDA_EXPECT_SUCCESS \ EXPECT_EQ(dimx* dimy* dimz, std::accumulate(outbuf, outbuf + bufsize, 0)) TEST_LAUNCH_PARAMETER(128, 128, 128); @@ -271,7 +272,7 @@ TEST(CudaDeviceFunctionsTest, ShuffleGetSrcLane) { unsigned* failure_count; ASSERT_EQ(cudaMallocManaged(&failure_count, sizeof(unsigned)), cudaSuccess); *failure_count = 0; - CudaShuffleGetSrcLaneTest<<<1, 32>>>(failure_count); + CudaLaunchKernel(CudaShuffleGetSrcLaneTest, 1, 32, 0, nullptr, failure_count); ASSERT_EQ(cudaDeviceSynchronize(), cudaSuccess); ASSERT_EQ(*failure_count, 0); cudaFree(failure_count); diff --git a/tensorflow/core/util/cuda_launch_config.h b/tensorflow/core/util/cuda_launch_config.h index c0ae6349f7..6a1bcb167b 100644 --- a/tensorflow/core/util/cuda_launch_config.h +++ b/tensorflow/core/util/cuda_launch_config.h @@ -20,6 +20,7 @@ limitations under the License. #include +#include "absl/base/casts.h" #include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor" #include "tensorflow/core/framework/op_kernel.h" #include "tensorflow/core/platform/logging.h" @@ -298,6 +299,49 @@ inline const cudaStream_t& GetCudaStream(OpKernelContext* context) { return *ptr; } +namespace detail { +template +std::array GetArrayOfElementPointersImpl( + std::tuple* tuple, absl::index_sequence) { + return {{&std::get(*tuple)...}}; +} +// Returns an array of void pointers to the elements of the given tuple. +template +std::array GetArrayOfElementPointers( + std::tuple* tuple) { + return GetArrayOfElementPointersImpl(tuple, + absl::index_sequence_for{}); +} + +template +struct BoolPack; +template +using NoneTrue = std::is_same, BoolPack>; +// Returns whether none of the types in Ts is a reference. +template +constexpr bool NoneIsReference() { + return NoneTrue<(std::is_reference::value)...>::value; +} +} // namespace detail + +// Launches a CUDA kernel through cudaLaunchKernel with the given arguments. +// +// The kernel parameters 'Ts' must be constructible from the arguments 'Args'. +template +void CudaLaunchKernel(void (*function)(Ts...), dim3 grid_dim, dim3 block_dim, + size_t shared_memory_size_bytes, cudaStream_t stream, + Args... arguments) { + static_assert(detail::NoneIsReference(), + "Kernels with reference arguments have undefined behaviour."); + // Cast arguments and forward them as an array of pointers. + auto args_tuple = std::tuple(arguments...); + auto arg_ptrs = detail::GetArrayOfElementPointers(&args_tuple); + auto func_ptr = absl::bit_cast(function); + auto result = cudaLaunchKernel(func_ptr, grid_dim, block_dim, arg_ptrs.data(), + shared_memory_size_bytes, stream); + DCHECK_EQ(result, cudaSuccess) << cudaGetErrorString(result); +} + } // namespace tensorflow #endif // GOOGLE_CUDA diff --git a/tensorflow/examples/adding_an_op/cuda_op_kernel.cu.cc b/tensorflow/examples/adding_an_op/cuda_op_kernel.cu.cc index 65b50bd3ae..418fb08298 100644 --- a/tensorflow/examples/adding_an_op/cuda_op_kernel.cu.cc +++ b/tensorflow/examples/adding_an_op/cuda_op_kernel.cu.cc @@ -16,6 +16,7 @@ limitations under the License. #if GOOGLE_CUDA #define EIGEN_USE_GPU #include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor" +#include "tensorflow/core/util/cuda_launch_config.h" __global__ void AddOneKernel(const int* in, const int N, int* out) { for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < N; @@ -25,7 +26,7 @@ __global__ void AddOneKernel(const int* in, const int N, int* out) { } void AddOneKernelLauncher(const int* in, const int N, int* out) { - AddOneKernel<<<32, 256>>>(in, N, out); + ::tensorflow::CudaLaunchKernel(AddOneKernel, 32, 256, 0, nullptr, in, N, out); } #endif diff --git a/tensorflow/tools/ci_build/builds/user_ops/cuda_op_kernel.cu.cc b/tensorflow/tools/ci_build/builds/user_ops/cuda_op_kernel.cu.cc index 65b50bd3ae..418fb08298 100644 --- a/tensorflow/tools/ci_build/builds/user_ops/cuda_op_kernel.cu.cc +++ b/tensorflow/tools/ci_build/builds/user_ops/cuda_op_kernel.cu.cc @@ -16,6 +16,7 @@ limitations under the License. #if GOOGLE_CUDA #define EIGEN_USE_GPU #include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor" +#include "tensorflow/core/util/cuda_launch_config.h" __global__ void AddOneKernel(const int* in, const int N, int* out) { for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < N; @@ -25,7 +26,7 @@ __global__ void AddOneKernel(const int* in, const int N, int* out) { } void AddOneKernelLauncher(const int* in, const int N, int* out) { - AddOneKernel<<<32, 256>>>(in, N, out); + ::tensorflow::CudaLaunchKernel(AddOneKernel, 32, 256, 0, nullptr, in, N, out); } #endif -- GitLab From c80a1ff86b0dccd5c1c40dd76cbf785022664451 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 21 Feb 2019 22:38:17 -0800 Subject: [PATCH 324/766] Add implementations for CUDA library functions which dynamically load the CUDA library DSO and forward the call to the actual function. Most of the code is auto-generated. Libraries implemented: CUDA driver API, cuRAND, cuFFT, cuBLAS, and cuDNN. The CUDA runtime is not implemented because it's not clear whether it should just be linked statically. Older versions of CUDA (starting with 8.0) and cuDNN (starting with 7.0) are handled with two approaches: - if the API didn't change and only new functions were added, declare types that were introduced afterwards. It is acceptable to implement APIs that were only introduced later: if they are called despite not being declared (in older headers), they will not be able to dlsym the corresponding function and return an error. - if the API changed, different contextual headers are included. PiperOrigin-RevId: 235135165 --- .../gpu/gpu_cudamalloc_allocator.cc | 6 +- .../gpu/gpu_managed_allocator.cc | 5 +- tensorflow/stream_executor/build_defs.bzl | 3 +- tensorflow/stream_executor/cuda/BUILD | 91 +- .../stream_executor/cuda/cublas_10_0.inc | 5200 +++++++++++++++++ .../stream_executor/cuda/cublas_9_0.inc | 5124 ++++++++++++++++ .../stream_executor/cuda/cublas_stub.cc | 65 + tensorflow/stream_executor/cuda/cuda_10_0.inc | 1822 ++++++ tensorflow/stream_executor/cuda/cuda_blas.cc | 1355 ++--- tensorflow/stream_executor/cuda/cuda_dnn.cc | 151 - .../stream_executor/cuda/cuda_driver.cc | 216 +- .../cuda/cuda_driver_wrapper.h | 144 - tensorflow/stream_executor/cuda/cuda_fft.cc | 193 +- .../stream_executor/cuda/cuda_gpu_executor.cc | 5 +- tensorflow/stream_executor/cuda/cuda_rng.cc | 91 +- tensorflow/stream_executor/cuda/cuda_stub.cc | 99 + tensorflow/stream_executor/cuda/cudnn_6_0.inc | 1773 ++++++ tensorflow/stream_executor/cuda/cudnn_7_0.inc | 1946 ++++++ tensorflow/stream_executor/cuda/cudnn_7_1.inc | 2281 ++++++++ tensorflow/stream_executor/cuda/cudnn_7_3.inc | 2510 ++++++++ tensorflow/stream_executor/cuda/cudnn_7_4.inc | 2643 +++++++++ tensorflow/stream_executor/cuda/cudnn_stub.cc | 62 + .../stream_executor/cuda/cufft_10_0.inc | 320 + tensorflow/stream_executor/cuda/cufft_stub.cc | 50 + .../stream_executor/cuda/curand_10_0.inc | 246 + .../stream_executor/cuda/curand_stub.cc | 50 + 26 files changed, 24957 insertions(+), 1494 deletions(-) create mode 100644 tensorflow/stream_executor/cuda/cublas_10_0.inc create mode 100644 tensorflow/stream_executor/cuda/cublas_9_0.inc create mode 100644 tensorflow/stream_executor/cuda/cublas_stub.cc create mode 100644 tensorflow/stream_executor/cuda/cuda_10_0.inc delete mode 100644 tensorflow/stream_executor/cuda/cuda_driver_wrapper.h create mode 100644 tensorflow/stream_executor/cuda/cuda_stub.cc create mode 100644 tensorflow/stream_executor/cuda/cudnn_6_0.inc create mode 100644 tensorflow/stream_executor/cuda/cudnn_7_0.inc create mode 100644 tensorflow/stream_executor/cuda/cudnn_7_1.inc create mode 100644 tensorflow/stream_executor/cuda/cudnn_7_3.inc create mode 100644 tensorflow/stream_executor/cuda/cudnn_7_4.inc create mode 100644 tensorflow/stream_executor/cuda/cudnn_stub.cc create mode 100644 tensorflow/stream_executor/cuda/cufft_10_0.inc create mode 100644 tensorflow/stream_executor/cuda/cufft_stub.cc create mode 100644 tensorflow/stream_executor/cuda/curand_10_0.inc create mode 100644 tensorflow/stream_executor/cuda/curand_stub.cc diff --git a/tensorflow/core/common_runtime/gpu/gpu_cudamalloc_allocator.cc b/tensorflow/core/common_runtime/gpu/gpu_cudamalloc_allocator.cc index 4be1bbb7df..d85ca8892f 100644 --- a/tensorflow/core/common_runtime/gpu/gpu_cudamalloc_allocator.cc +++ b/tensorflow/core/common_runtime/gpu/gpu_cudamalloc_allocator.cc @@ -16,7 +16,6 @@ limitations under the License. #ifdef GOOGLE_CUDA #include "cuda/include/cuda.h" #include "tensorflow/stream_executor/cuda/cuda_activation.h" -#include "tensorflow/stream_executor/cuda/cuda_driver_wrapper.h" #endif // GOOGLE_CUDA #include "tensorflow/core/common_runtime/gpu/gpu_cudamalloc_allocator.h" @@ -42,7 +41,7 @@ void* GPUcudaMallocAllocator::AllocateRaw(size_t alignment, size_t num_bytes) { // allocate with cudaMalloc se::cuda::ScopedActivateExecutorContext scoped_activation{stream_exec_}; CUdeviceptr rv = 0; - CUresult res = tensorflow::wrap::cuMemAlloc(&rv, num_bytes); + CUresult res = cuMemAlloc(&rv, num_bytes); if (res != CUDA_SUCCESS) { LOG(ERROR) << "cuMemAlloc failed to allocate " << num_bytes; return nullptr; @@ -55,8 +54,7 @@ void* GPUcudaMallocAllocator::AllocateRaw(size_t alignment, size_t num_bytes) { void GPUcudaMallocAllocator::DeallocateRaw(void* ptr) { #ifdef GOOGLE_CUDA // free with cudaFree - CUresult res = - tensorflow::wrap::cuMemFree(reinterpret_cast(ptr)); + CUresult res = cuMemFree(reinterpret_cast(ptr)); if (res != CUDA_SUCCESS) { LOG(ERROR) << "cuMemFree failed to free " << ptr; } diff --git a/tensorflow/core/common_runtime/gpu/gpu_managed_allocator.cc b/tensorflow/core/common_runtime/gpu/gpu_managed_allocator.cc index 2375896606..aad42df5f1 100644 --- a/tensorflow/core/common_runtime/gpu/gpu_managed_allocator.cc +++ b/tensorflow/core/common_runtime/gpu/gpu_managed_allocator.cc @@ -14,7 +14,7 @@ limitations under the License. ==============================================================================*/ #ifdef GOOGLE_CUDA -#include "tensorflow/stream_executor/cuda/cuda_driver_wrapper.h" +#include "cuda/include/cuda.h" #define EIGEN_USE_GPU #endif @@ -26,8 +26,7 @@ void* GpuManagedAllocator::AllocateRaw(size_t alignment, size_t num_bytes) { void* ptr = nullptr; #ifdef GOOGLE_CUDA CUdeviceptr result = 0; - CHECK_EQ(tensorflow::wrap::cuMemAllocManaged(&result, num_bytes, - CU_MEM_ATTACH_GLOBAL), + CHECK_EQ(cuMemAllocManaged(&result, num_bytes, CU_MEM_ATTACH_GLOBAL), CUDA_SUCCESS); ptr = reinterpret_cast(result); #endif diff --git a/tensorflow/stream_executor/build_defs.bzl b/tensorflow/stream_executor/build_defs.bzl index b2da13e5ae..575ff639e7 100644 --- a/tensorflow/stream_executor/build_defs.bzl +++ b/tensorflow/stream_executor/build_defs.bzl @@ -7,9 +7,8 @@ def stream_executor_friends(): def tf_additional_cuda_platform_deps(): return [] -# Use dynamic loading, therefore should be empty. def tf_additional_cuda_driver_deps(): - return [] + return [":cuda_stub"] def tf_additional_cudnn_plugin_deps(): return [] diff --git a/tensorflow/stream_executor/cuda/BUILD b/tensorflow/stream_executor/cuda/BUILD index 03dd92e19c..5929cb2a7e 100644 --- a/tensorflow/stream_executor/cuda/BUILD +++ b/tensorflow/stream_executor/cuda/BUILD @@ -74,13 +74,21 @@ cc_library( ]), ) +cc_library( + name = "cuda_stub", + srcs = if_cuda_is_configured(["cuda_stub.cc"]), + textual_hdrs = ["cuda_10_0.inc"], + deps = if_cuda_is_configured([ + "@local_config_cuda//cuda:cuda_headers", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform:dso_loader", + ]), +) + cc_library( name = "cuda_driver", srcs = if_cuda_is_configured(["cuda_driver.cc"]), - hdrs = if_cuda_is_configured([ - "cuda_driver.h", - "cuda_driver_wrapper.h", - ]), + hdrs = if_cuda_is_configured(["cuda_driver.h"]), deps = if_cuda_is_configured([ ":cuda_diagnostics", "@com_google_absl//absl/base", @@ -111,11 +119,13 @@ cc_library( visibility = ["//visibility:public"], deps = select({ "//tensorflow:using_cuda_nvcc_with_dynamic_build": [ + ":cuda_stub", "@local_config_cuda//cuda:cuda_headers", "//tensorflow/stream_executor/lib", "//tensorflow/stream_executor/platform:dso_loader", ], "//tensorflow:using_cuda_clang_with_dynamic_build": [ + ":cuda_stub", "@local_config_cuda//cuda:cuda_headers", "//tensorflow/stream_executor/lib", "//tensorflow/stream_executor/platform:dso_loader", @@ -163,6 +173,17 @@ cc_library( ]), ) +cc_library( + name = "cublas_stub", + srcs = if_cuda_is_configured(["cublas_stub.cc"]), + textual_hdrs = glob(["cublas_*.inc"]), + deps = if_cuda_is_configured([ + "@local_config_cuda//cuda:cuda_headers", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform:dso_loader", + ]), +) + cc_library( name = "cublas_plugin", srcs = if_cuda_is_configured(["cuda_blas.cc"]), @@ -188,11 +209,24 @@ cc_library( "//tensorflow/stream_executor/gpu:gpu_helpers_header", "//tensorflow/stream_executor/lib", "//tensorflow/stream_executor/platform", - "//tensorflow/stream_executor/platform:dso_loader", - ] + if_static(["@local_config_cuda//cuda:cublas"])), + ] + if_static( + ["@local_config_cuda//cuda:cublas"], + [":cublas_stub"], + )), alwayslink = True, ) +cc_library( + name = "cufft_stub", + srcs = if_cuda_is_configured(["cufft_stub.cc"]), + textual_hdrs = ["cufft_10_0.inc"], + deps = if_cuda_is_configured([ + "@local_config_cuda//cuda:cuda_headers", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform:dso_loader", + ]), +) + cc_library( name = "cufft_plugin", srcs = if_cuda_is_configured(["cuda_fft.cc"]), @@ -213,19 +247,28 @@ cc_library( "//tensorflow/stream_executor/lib", "//tensorflow/stream_executor/platform", "//tensorflow/stream_executor/platform:dso_loader", - ] + if_static(["@local_config_cuda//cuda:cufft"])), + ] + if_static( + ["@local_config_cuda//cuda:cufft"], + [":cufft_stub"], + )), alwayslink = True, ) +cc_library( + name = "cudnn_stub", + srcs = if_cuda_is_configured(["cudnn_stub.cc"]), + textual_hdrs = glob(["cudnn_*.inc"]), + deps = if_cuda_is_configured([ + "@local_config_cuda//cuda:cudnn_header", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform:dso_loader", + ]), +) + cc_library( name = "cudnn_plugin", srcs = if_cuda_is_configured(["cuda_dnn.cc"]), hdrs = if_cuda_is_configured(["cuda_dnn.h"]), - copts = [ - # STREAM_EXECUTOR_CUDNN_WRAP would fail on Clang with the default - # setting of template depth 256 - "-ftemplate-depth-512", - ], visibility = ["//visibility:public"], deps = if_cuda_is_configured([ ":cuda_activation", @@ -251,13 +294,24 @@ cc_library( "//tensorflow/stream_executor:temporary_device_memory", "//tensorflow/stream_executor/lib", "//tensorflow/stream_executor/platform", - "//tensorflow/stream_executor/platform:dso_loader", - ]) + tf_additional_cudnn_plugin_deps() + if_cuda_is_configured(if_static([ - "@local_config_cuda//cuda:cudnn", - ])), + ]) + tf_additional_cudnn_plugin_deps() + if_cuda_is_configured(if_static( + ["@local_config_cuda//cuda:cudnn"], + [":cudnn_stub"], + )), alwayslink = True, ) +cc_library( + name = "curand_stub", + srcs = if_cuda_is_configured(["curand_stub.cc"]), + textual_hdrs = ["curand_10_0.inc"], + deps = if_cuda_is_configured([ + "@local_config_cuda//cuda:cuda_headers", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform:dso_loader", + ]), +) + cc_library( name = "curand_plugin", srcs = if_cuda_is_configured(["cuda_rng.cc"]), @@ -277,7 +331,10 @@ cc_library( "//tensorflow/stream_executor/lib", "//tensorflow/stream_executor/platform", "//tensorflow/stream_executor/platform:dso_loader", - ] + if_static(["@local_config_cuda//cuda:curand"])), + ] + if_static( + ["@local_config_cuda//cuda:curand"], + [":curand_stub"], + )), alwayslink = True, ) diff --git a/tensorflow/stream_executor/cuda/cublas_10_0.inc b/tensorflow/stream_executor/cuda/cublas_10_0.inc new file mode 100644 index 0000000000..854545f4f7 --- /dev/null +++ b/tensorflow/stream_executor/cuda/cublas_10_0.inc @@ -0,0 +1,5200 @@ +// Auto-generated, do not edit. + +extern "C" { + +cublasStatus_t CUBLASWINAPI cublasCreate_v2 (cublasHandle_t *handle) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t *); + static auto func_ptr = LoadSymbol("cublasCreate_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle); +} + +cublasStatus_t CUBLASWINAPI cublasDestroy_v2 (cublasHandle_t handle) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t); + static auto func_ptr = LoadSymbol("cublasDestroy_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle); +} + +cublasStatus_t CUBLASWINAPI cublasGetVersion_v2(cublasHandle_t handle, int *version) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int *); + static auto func_ptr = LoadSymbol("cublasGetVersion_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, version); +} + +cublasStatus_t CUBLASWINAPI cublasGetProperty(libraryPropertyType type, int *value) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(libraryPropertyType, int *); + static auto func_ptr = LoadSymbol("cublasGetProperty"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(type, value); +} + +cublasStatus_t CUBLASWINAPI cublasSetStream_v2 (cublasHandle_t handle, cudaStream_t streamId) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cudaStream_t); + static auto func_ptr = LoadSymbol("cublasSetStream_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, streamId); +} + +cublasStatus_t CUBLASWINAPI cublasGetStream_v2 (cublasHandle_t handle, cudaStream_t *streamId) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cudaStream_t *); + static auto func_ptr = LoadSymbol("cublasGetStream_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, streamId); +} + +cublasStatus_t CUBLASWINAPI cublasGetPointerMode_v2 (cublasHandle_t handle, cublasPointerMode_t *mode) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasPointerMode_t *); + static auto func_ptr = LoadSymbol("cublasGetPointerMode_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode); +} + +cublasStatus_t CUBLASWINAPI cublasSetPointerMode_v2 (cublasHandle_t handle, cublasPointerMode_t mode) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasPointerMode_t); + static auto func_ptr = LoadSymbol("cublasSetPointerMode_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode); +} + +cublasStatus_t CUBLASWINAPI cublasGetAtomicsMode(cublasHandle_t handle, cublasAtomicsMode_t *mode) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasAtomicsMode_t *); + static auto func_ptr = LoadSymbol("cublasGetAtomicsMode"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode); +} + +cublasStatus_t CUBLASWINAPI cublasSetAtomicsMode(cublasHandle_t handle, cublasAtomicsMode_t mode) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasAtomicsMode_t); + static auto func_ptr = LoadSymbol("cublasSetAtomicsMode"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode); +} + +cublasStatus_t CUBLASWINAPI cublasGetMathMode(cublasHandle_t handle, cublasMath_t *mode) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasMath_t *); + static auto func_ptr = LoadSymbol("cublasGetMathMode"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode); +} + +cublasStatus_t CUBLASWINAPI cublasSetMathMode(cublasHandle_t handle, cublasMath_t mode) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasMath_t); + static auto func_ptr = LoadSymbol("cublasSetMathMode"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode); +} + +cublasStatus_t CUBLASWINAPI cublasLoggerConfigure(int logIsOn, int logToStdOut, int logToStdErr, const char* logFileName) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(int, int, int, const char *); + static auto func_ptr = LoadSymbol("cublasLoggerConfigure"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(logIsOn, logToStdOut, logToStdErr, logFileName); +} + +cublasStatus_t CUBLASWINAPI cublasSetLoggerCallback(cublasLogCallback userCallback) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasLogCallback); + static auto func_ptr = LoadSymbol("cublasSetLoggerCallback"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(userCallback); +} + +cublasStatus_t CUBLASWINAPI cublasGetLoggerCallback(cublasLogCallback* userCallback) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasLogCallback *); + static auto func_ptr = LoadSymbol("cublasGetLoggerCallback"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(userCallback); +} + +cublasStatus_t CUBLASWINAPI cublasSetVector (int n, int elemSize, const void *x, + int incx, void *devicePtr, int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(int, int, const void *, int, void *, int); + static auto func_ptr = LoadSymbol("cublasSetVector"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(n, elemSize, x, incx, devicePtr, incy); +} + +cublasStatus_t CUBLASWINAPI cublasGetVector (int n, int elemSize, const void *x, + int incx, void *y, int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(int, int, const void *, int, void *, int); + static auto func_ptr = LoadSymbol("cublasGetVector"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(n, elemSize, x, incx, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasSetMatrix (int rows, int cols, int elemSize, + const void *A, int lda, void *B, + int ldb) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(int, int, int, const void *, int, void *, int); + static auto func_ptr = LoadSymbol("cublasSetMatrix"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rows, cols, elemSize, A, lda, B, ldb); +} + +cublasStatus_t CUBLASWINAPI cublasGetMatrix (int rows, int cols, int elemSize, + const void *A, int lda, void *B, + int ldb) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(int, int, int, const void *, int, void *, int); + static auto func_ptr = LoadSymbol("cublasGetMatrix"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rows, cols, elemSize, A, lda, B, ldb); +} + +cublasStatus_t CUBLASWINAPI cublasSetVectorAsync (int n, int elemSize, + const void *hostPtr, int incx, + void *devicePtr, int incy, + cudaStream_t stream) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(int, int, const void *, int, void *, int, cudaStream_t); + static auto func_ptr = LoadSymbol("cublasSetVectorAsync"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(n, elemSize, hostPtr, incx, devicePtr, incy, stream); +} + +cublasStatus_t CUBLASWINAPI cublasGetVectorAsync (int n, int elemSize, + const void *devicePtr, int incx, + void *hostPtr, int incy, + cudaStream_t stream) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(int, int, const void *, int, void *, int, cudaStream_t); + static auto func_ptr = LoadSymbol("cublasGetVectorAsync"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(n, elemSize, devicePtr, incx, hostPtr, incy, stream); +} + +cublasStatus_t CUBLASWINAPI cublasSetMatrixAsync (int rows, int cols, int elemSize, + const void *A, int lda, void *B, + int ldb, cudaStream_t stream) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(int, int, int, const void *, int, void *, int, cudaStream_t); + static auto func_ptr = LoadSymbol("cublasSetMatrixAsync"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rows, cols, elemSize, A, lda, B, ldb, stream); +} + +cublasStatus_t CUBLASWINAPI cublasGetMatrixAsync (int rows, int cols, int elemSize, + const void *A, int lda, void *B, + int ldb, cudaStream_t stream) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(int, int, int, const void *, int, void *, int, cudaStream_t); + static auto func_ptr = LoadSymbol("cublasGetMatrixAsync"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rows, cols, elemSize, A, lda, B, ldb, stream); +} + +void CUBLASWINAPI cublasXerbla (const char *srName, int info) { + using FuncPtr = void (CUBLASWINAPI *)(const char *, int); + static auto func_ptr = LoadSymbol("cublasXerbla"); + if (!func_ptr) LogFatalSymbolNotFound("cublasXerbla"); + return func_ptr(srName, info); +} + +cublasStatus_t CUBLASWINAPI cublasNrm2Ex(cublasHandle_t handle, + int n, + const void *x, + cudaDataType xType, + int incx, + void *result, + cudaDataType resultType, + cudaDataType executionType) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const void *, cudaDataType, int, void *, cudaDataType, cudaDataType); + static auto func_ptr = LoadSymbol("cublasNrm2Ex"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, xType, incx, result, resultType, executionType); +} + +cublasStatus_t CUBLASWINAPI cublasSnrm2_v2(cublasHandle_t handle, + int n, + const float *x, + int incx, + float *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const float *, int, float *); + static auto func_ptr = LoadSymbol("cublasSnrm2_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, result); +} + +cublasStatus_t CUBLASWINAPI cublasDnrm2_v2(cublasHandle_t handle, + int n, + const double *x, + int incx, + double *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const double *, int, double *); + static auto func_ptr = LoadSymbol("cublasDnrm2_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, result); +} + +cublasStatus_t CUBLASWINAPI cublasScnrm2_v2(cublasHandle_t handle, + int n, + const cuComplex *x, + int incx, + float *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuComplex *, int, float *); + static auto func_ptr = LoadSymbol("cublasScnrm2_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, result); +} + +cublasStatus_t CUBLASWINAPI cublasDznrm2_v2(cublasHandle_t handle, + int n, + const cuDoubleComplex *x, + int incx, + double *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuDoubleComplex *, int, double *); + static auto func_ptr = LoadSymbol("cublasDznrm2_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, result); +} + +cublasStatus_t CUBLASWINAPI cublasDotEx (cublasHandle_t handle, + int n, + const void *x, + cudaDataType xType, + int incx, + const void *y, + cudaDataType yType, + int incy, + void *result, + cudaDataType resultType, + cudaDataType executionType) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const void *, cudaDataType, int, const void *, cudaDataType, int, void *, cudaDataType, cudaDataType); + static auto func_ptr = LoadSymbol("cublasDotEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, xType, incx, y, yType, incy, result, resultType, executionType); +} + +cublasStatus_t CUBLASWINAPI cublasDotcEx (cublasHandle_t handle, + int n, + const void *x, + cudaDataType xType, + int incx, + const void *y, + cudaDataType yType, + int incy, + void *result, + cudaDataType resultType, + cudaDataType executionType) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const void *, cudaDataType, int, const void *, cudaDataType, int, void *, cudaDataType, cudaDataType); + static auto func_ptr = LoadSymbol("cublasDotcEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, xType, incx, y, yType, incy, result, resultType, executionType); +} + +cublasStatus_t CUBLASWINAPI cublasSdot_v2 (cublasHandle_t handle, + int n, + const float *x, + int incx, + const float *y, + int incy, + float *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const float *, int, const float *, int, float *); + static auto func_ptr = LoadSymbol("cublasSdot_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy, result); +} + +cublasStatus_t CUBLASWINAPI cublasDdot_v2 (cublasHandle_t handle, + int n, + const double *x, + int incx, + const double *y, + int incy, + double *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const double *, int, const double *, int, double *); + static auto func_ptr = LoadSymbol("cublasDdot_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy, result); +} + +cublasStatus_t CUBLASWINAPI cublasCdotu_v2 (cublasHandle_t handle, + int n, + const cuComplex *x, + int incx, + const cuComplex *y, + int incy, + cuComplex *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuComplex *, int, const cuComplex *, int, cuComplex *); + static auto func_ptr = LoadSymbol("cublasCdotu_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy, result); +} + +cublasStatus_t CUBLASWINAPI cublasCdotc_v2 (cublasHandle_t handle, + int n, + const cuComplex *x, + int incx, + const cuComplex *y, + int incy, + cuComplex *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuComplex *, int, const cuComplex *, int, cuComplex *); + static auto func_ptr = LoadSymbol("cublasCdotc_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy, result); +} + +cublasStatus_t CUBLASWINAPI cublasZdotu_v2 (cublasHandle_t handle, + int n, + const cuDoubleComplex *x, + int incx, + const cuDoubleComplex *y, + int incy, + cuDoubleComplex *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex *); + static auto func_ptr = LoadSymbol("cublasZdotu_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy, result); +} + +cublasStatus_t CUBLASWINAPI cublasZdotc_v2 (cublasHandle_t handle, + int n, + const cuDoubleComplex *x, + int incx, + const cuDoubleComplex *y, + int incy, + cuDoubleComplex *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex *); + static auto func_ptr = LoadSymbol("cublasZdotc_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy, result); +} + +cublasStatus_t CUBLASWINAPI cublasScalEx(cublasHandle_t handle, + int n, + const void *alpha, /* host or device pointer */ + cudaDataType alphaType, + void *x, + cudaDataType xType, + int incx, + cudaDataType executionType) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const void *, cudaDataType, void *, cudaDataType, int, cudaDataType); + static auto func_ptr = LoadSymbol("cublasScalEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, alpha, alphaType, x, xType, incx, executionType); +} + +cublasStatus_t CUBLASWINAPI cublasSscal_v2(cublasHandle_t handle, + int n, + const float *alpha, /* host or device pointer */ + float *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const float *, float *, int); + static auto func_ptr = LoadSymbol("cublasSscal_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, alpha, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasDscal_v2(cublasHandle_t handle, + int n, + const double *alpha, /* host or device pointer */ + double *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const double *, double *, int); + static auto func_ptr = LoadSymbol("cublasDscal_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, alpha, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasCscal_v2(cublasHandle_t handle, + int n, + const cuComplex *alpha, /* host or device pointer */ + cuComplex *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCscal_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, alpha, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasCsscal_v2(cublasHandle_t handle, + int n, + const float *alpha, /* host or device pointer */ + cuComplex *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const float *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCsscal_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, alpha, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasZscal_v2(cublasHandle_t handle, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + cuDoubleComplex *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZscal_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, alpha, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasZdscal_v2(cublasHandle_t handle, + int n, + const double *alpha, /* host or device pointer */ + cuDoubleComplex *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const double *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZdscal_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, alpha, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasAxpyEx (cublasHandle_t handle, + int n, + const void *alpha, /* host or device pointer */ + cudaDataType alphaType, + const void *x, + cudaDataType xType, + int incx, + void *y, + cudaDataType yType, + int incy, + cudaDataType executiontype) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const void *, cudaDataType, const void *, cudaDataType, int, void *, cudaDataType, int, cudaDataType); + static auto func_ptr = LoadSymbol("cublasAxpyEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, alpha, alphaType, x, xType, incx, y, yType, incy, executiontype); +} + +cublasStatus_t CUBLASWINAPI cublasSaxpy_v2 (cublasHandle_t handle, + int n, + const float *alpha, /* host or device pointer */ + const float *x, + int incx, + float *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const float *, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasSaxpy_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, alpha, x, incx, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasDaxpy_v2 (cublasHandle_t handle, + int n, + const double *alpha, /* host or device pointer */ + const double *x, + int incx, + double *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const double *, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDaxpy_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, alpha, x, incx, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasCaxpy_v2 (cublasHandle_t handle, + int n, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *x, + int incx, + cuComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuComplex *, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCaxpy_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, alpha, x, incx, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasZaxpy_v2 (cublasHandle_t handle, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *x, + int incx, + cuDoubleComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuDoubleComplex *, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZaxpy_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, alpha, x, incx, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasScopy_v2 (cublasHandle_t handle, + int n, + const float *x, + int incx, + float *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasScopy_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasDcopy_v2 (cublasHandle_t handle, + int n, + const double *x, + int incx, + double *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDcopy_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasCcopy_v2 (cublasHandle_t handle, + int n, + const cuComplex *x, + int incx, + cuComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCcopy_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasZcopy_v2 (cublasHandle_t handle, + int n, + const cuDoubleComplex *x, + int incx, + cuDoubleComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZcopy_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasSswap_v2 (cublasHandle_t handle, + int n, + float *x, + int incx, + float *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasSswap_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasDswap_v2 (cublasHandle_t handle, + int n, + double *x, + int incx, + double *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDswap_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasCswap_v2 (cublasHandle_t handle, + int n, + cuComplex *x, + int incx, + cuComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCswap_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasZswap_v2 (cublasHandle_t handle, + int n, + cuDoubleComplex *x, + int incx, + cuDoubleComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZswap_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasIsamax_v2(cublasHandle_t handle, + int n, + const float *x, + int incx, + int *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const float *, int, int *); + static auto func_ptr = LoadSymbol("cublasIsamax_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, result); +} + +cublasStatus_t CUBLASWINAPI cublasIdamax_v2(cublasHandle_t handle, + int n, + const double *x, + int incx, + int *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const double *, int, int *); + static auto func_ptr = LoadSymbol("cublasIdamax_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, result); +} + +cublasStatus_t CUBLASWINAPI cublasIcamax_v2(cublasHandle_t handle, + int n, + const cuComplex *x, + int incx, + int *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuComplex *, int, int *); + static auto func_ptr = LoadSymbol("cublasIcamax_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, result); +} + +cublasStatus_t CUBLASWINAPI cublasIzamax_v2(cublasHandle_t handle, + int n, + const cuDoubleComplex *x, + int incx, + int *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuDoubleComplex *, int, int *); + static auto func_ptr = LoadSymbol("cublasIzamax_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, result); +} + +cublasStatus_t CUBLASWINAPI cublasIsamin_v2(cublasHandle_t handle, + int n, + const float *x, + int incx, + int *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const float *, int, int *); + static auto func_ptr = LoadSymbol("cublasIsamin_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, result); +} + +cublasStatus_t CUBLASWINAPI cublasIdamin_v2(cublasHandle_t handle, + int n, + const double *x, + int incx, + int *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const double *, int, int *); + static auto func_ptr = LoadSymbol("cublasIdamin_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, result); +} + +cublasStatus_t CUBLASWINAPI cublasIcamin_v2(cublasHandle_t handle, + int n, + const cuComplex *x, + int incx, + int *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuComplex *, int, int *); + static auto func_ptr = LoadSymbol("cublasIcamin_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, result); +} + +cublasStatus_t CUBLASWINAPI cublasIzamin_v2(cublasHandle_t handle, + int n, + const cuDoubleComplex *x, + int incx, + int *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuDoubleComplex *, int, int *); + static auto func_ptr = LoadSymbol("cublasIzamin_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, result); +} + +cublasStatus_t CUBLASWINAPI cublasSasum_v2(cublasHandle_t handle, + int n, + const float *x, + int incx, + float *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const float *, int, float *); + static auto func_ptr = LoadSymbol("cublasSasum_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, result); +} + +cublasStatus_t CUBLASWINAPI cublasDasum_v2(cublasHandle_t handle, + int n, + const double *x, + int incx, + double *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const double *, int, double *); + static auto func_ptr = LoadSymbol("cublasDasum_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, result); +} + +cublasStatus_t CUBLASWINAPI cublasScasum_v2(cublasHandle_t handle, + int n, + const cuComplex *x, + int incx, + float *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuComplex *, int, float *); + static auto func_ptr = LoadSymbol("cublasScasum_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, result); +} + +cublasStatus_t CUBLASWINAPI cublasDzasum_v2(cublasHandle_t handle, + int n, + const cuDoubleComplex *x, + int incx, + double *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuDoubleComplex *, int, double *); + static auto func_ptr = LoadSymbol("cublasDzasum_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, result); +} + +cublasStatus_t CUBLASWINAPI cublasSrot_v2 (cublasHandle_t handle, + int n, + float *x, + int incx, + float *y, + int incy, + const float *c, /* host or device pointer */ + const float *s) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, float *, int, float *, int, const float *, const float *); + static auto func_ptr = LoadSymbol("cublasSrot_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy, c, s); +} + +cublasStatus_t CUBLASWINAPI cublasDrot_v2 (cublasHandle_t handle, + int n, + double *x, + int incx, + double *y, + int incy, + const double *c, /* host or device pointer */ + const double *s) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, double *, int, double *, int, const double *, const double *); + static auto func_ptr = LoadSymbol("cublasDrot_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy, c, s); +} + +cublasStatus_t CUBLASWINAPI cublasCrot_v2 (cublasHandle_t handle, + int n, + cuComplex *x, + int incx, + cuComplex *y, + int incy, + const float *c, /* host or device pointer */ + const cuComplex *s) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, cuComplex *, int, cuComplex *, int, const float *, const cuComplex *); + static auto func_ptr = LoadSymbol("cublasCrot_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy, c, s); +} + +cublasStatus_t CUBLASWINAPI cublasCsrot_v2(cublasHandle_t handle, + int n, + cuComplex *x, + int incx, + cuComplex *y, + int incy, + const float *c, /* host or device pointer */ + const float *s) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, cuComplex *, int, cuComplex *, int, const float *, const float *); + static auto func_ptr = LoadSymbol("cublasCsrot_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy, c, s); +} + +cublasStatus_t CUBLASWINAPI cublasZrot_v2 (cublasHandle_t handle, + int n, + cuDoubleComplex *x, + int incx, + cuDoubleComplex *y, + int incy, + const double *c, /* host or device pointer */ + const cuDoubleComplex *s) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, cuDoubleComplex *, int, cuDoubleComplex *, int, const double *, const cuDoubleComplex *); + static auto func_ptr = LoadSymbol("cublasZrot_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy, c, s); +} + +cublasStatus_t CUBLASWINAPI cublasZdrot_v2(cublasHandle_t handle, + int n, + cuDoubleComplex *x, + int incx, + cuDoubleComplex *y, + int incy, + const double *c, /* host or device pointer */ + const double *s) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, cuDoubleComplex *, int, cuDoubleComplex *, int, const double *, const double *); + static auto func_ptr = LoadSymbol("cublasZdrot_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy, c, s); +} + +cublasStatus_t CUBLASWINAPI cublasSrotg_v2(cublasHandle_t handle, + float *a, /* host or device pointer */ + float *b, /* host or device pointer */ + float *c, /* host or device pointer */ + float *s) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, float *, float *, float *, float *); + static auto func_ptr = LoadSymbol("cublasSrotg_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, a, b, c, s); +} + +cublasStatus_t CUBLASWINAPI cublasDrotg_v2(cublasHandle_t handle, + double *a, /* host or device pointer */ + double *b, /* host or device pointer */ + double *c, /* host or device pointer */ + double *s) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, double *, double *, double *, double *); + static auto func_ptr = LoadSymbol("cublasDrotg_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, a, b, c, s); +} + +cublasStatus_t CUBLASWINAPI cublasCrotg_v2(cublasHandle_t handle, + cuComplex *a, /* host or device pointer */ + cuComplex *b, /* host or device pointer */ + float *c, /* host or device pointer */ + cuComplex *s) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cuComplex *, cuComplex *, float *, cuComplex *); + static auto func_ptr = LoadSymbol("cublasCrotg_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, a, b, c, s); +} + +cublasStatus_t CUBLASWINAPI cublasZrotg_v2(cublasHandle_t handle, + cuDoubleComplex *a, /* host or device pointer */ + cuDoubleComplex *b, /* host or device pointer */ + double *c, /* host or device pointer */ + cuDoubleComplex *s) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cuDoubleComplex *, cuDoubleComplex *, double *, cuDoubleComplex *); + static auto func_ptr = LoadSymbol("cublasZrotg_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, a, b, c, s); +} + +cublasStatus_t CUBLASWINAPI cublasSrotm_v2(cublasHandle_t handle, + int n, + float *x, + int incx, + float *y, + int incy, + const float* param) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, float *, int, float *, int, const float *); + static auto func_ptr = LoadSymbol("cublasSrotm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy, param); +} + +cublasStatus_t CUBLASWINAPI cublasDrotm_v2(cublasHandle_t handle, + int n, + double *x, + int incx, + double *y, + int incy, + const double* param) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, double *, int, double *, int, const double *); + static auto func_ptr = LoadSymbol("cublasDrotm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy, param); +} + +cublasStatus_t CUBLASWINAPI cublasSrotmg_v2(cublasHandle_t handle, + float *d1, /* host or device pointer */ + float *d2, /* host or device pointer */ + float *x1, /* host or device pointer */ + const float *y1, /* host or device pointer */ + float *param) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, float *, float *, float *, const float *, float *); + static auto func_ptr = LoadSymbol("cublasSrotmg_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, d1, d2, x1, y1, param); +} + +cublasStatus_t CUBLASWINAPI cublasDrotmg_v2(cublasHandle_t handle, + double *d1, /* host or device pointer */ + double *d2, /* host or device pointer */ + double *x1, /* host or device pointer */ + const double *y1, /* host or device pointer */ + double *param) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, double *, double *, double *, const double *, double *); + static auto func_ptr = LoadSymbol("cublasDrotmg_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, d1, d2, x1, y1, param); +} + +cublasStatus_t CUBLASWINAPI cublasSgemv_v2 (cublasHandle_t handle, + cublasOperation_t trans, + int m, + int n, + const float *alpha, /* host or device pointer */ + const float *A, + int lda, + const float *x, + int incx, + const float *beta, /* host or device pointer */ + float *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, int, int, const float *, const float *, int, const float *, int, const float *, float *, int); + static auto func_ptr = LoadSymbol("cublasSgemv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, trans, m, n, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasDgemv_v2 (cublasHandle_t handle, + cublasOperation_t trans, + int m, + int n, + const double *alpha, /* host or device pointer */ + const double *A, + int lda, + const double *x, + int incx, + const double *beta, /* host or device pointer */ + double *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, int, int, const double *, const double *, int, const double *, int, const double *, double *, int); + static auto func_ptr = LoadSymbol("cublasDgemv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, trans, m, n, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasCgemv_v2 (cublasHandle_t handle, + cublasOperation_t trans, + int m, + int n, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const cuComplex *x, + int incx, + const cuComplex *beta, /* host or device pointer */ + cuComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, int, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCgemv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, trans, m, n, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasZgemv_v2 (cublasHandle_t handle, + cublasOperation_t trans, + int m, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *x, + int incx, + const cuDoubleComplex *beta, /* host or device pointer */ + cuDoubleComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZgemv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, trans, m, n, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasSgbmv_v2 (cublasHandle_t handle, + cublasOperation_t trans, + int m, + int n, + int kl, + int ku, + const float *alpha, /* host or device pointer */ + const float *A, + int lda, + const float *x, + int incx, + const float *beta, /* host or device pointer */ + float *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, int, int, int, int, const float *, const float *, int, const float *, int, const float *, float *, int); + static auto func_ptr = LoadSymbol("cublasSgbmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, trans, m, n, kl, ku, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasDgbmv_v2 (cublasHandle_t handle, + cublasOperation_t trans, + int m, + int n, + int kl, + int ku, + const double *alpha, /* host or device pointer */ + const double *A, + int lda, + const double *x, + int incx, + const double *beta, /* host or device pointer */ + double *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, int, int, int, int, const double *, const double *, int, const double *, int, const double *, double *, int); + static auto func_ptr = LoadSymbol("cublasDgbmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, trans, m, n, kl, ku, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasCgbmv_v2 (cublasHandle_t handle, + cublasOperation_t trans, + int m, + int n, + int kl, + int ku, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const cuComplex *x, + int incx, + const cuComplex *beta, /* host or device pointer */ + cuComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, int, int, int, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCgbmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, trans, m, n, kl, ku, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasZgbmv_v2 (cublasHandle_t handle, + cublasOperation_t trans, + int m, + int n, + int kl, + int ku, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *x, + int incx, + const cuDoubleComplex *beta, /* host or device pointer */ + cuDoubleComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, int, int, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZgbmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, trans, m, n, kl, ku, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasStrmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + const float *A, + int lda, + float *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasStrmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, A, lda, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasDtrmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + const double *A, + int lda, + double *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDtrmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, A, lda, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasCtrmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + const cuComplex *A, + int lda, + cuComplex *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtrmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, A, lda, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasZtrmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + const cuDoubleComplex *A, + int lda, + cuDoubleComplex *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtrmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, A, lda, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasStbmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + int k, + const float *A, + int lda, + float *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasStbmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, k, A, lda, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasDtbmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + int k, + const double *A, + int lda, + double *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDtbmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, k, A, lda, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasCtbmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + int k, + const cuComplex *A, + int lda, + cuComplex *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtbmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, k, A, lda, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasZtbmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + int k, + const cuDoubleComplex *A, + int lda, + cuDoubleComplex *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtbmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, k, A, lda, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasStpmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + const float *AP, + float *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, const float *, float *, int); + static auto func_ptr = LoadSymbol("cublasStpmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, AP, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasDtpmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + const double *AP, + double *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, const double *, double *, int); + static auto func_ptr = LoadSymbol("cublasDtpmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, AP, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasCtpmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + const cuComplex *AP, + cuComplex *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtpmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, AP, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasZtpmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + const cuDoubleComplex *AP, + cuDoubleComplex *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtpmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, AP, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasStrsv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + const float *A, + int lda, + float *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasStrsv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, A, lda, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasDtrsv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + const double *A, + int lda, + double *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDtrsv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, A, lda, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasCtrsv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + const cuComplex *A, + int lda, + cuComplex *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtrsv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, A, lda, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasZtrsv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + const cuDoubleComplex *A, + int lda, + cuDoubleComplex *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtrsv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, A, lda, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasStpsv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + const float *AP, + float *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, const float *, float *, int); + static auto func_ptr = LoadSymbol("cublasStpsv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, AP, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasDtpsv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + const double *AP, + double *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, const double *, double *, int); + static auto func_ptr = LoadSymbol("cublasDtpsv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, AP, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasCtpsv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + const cuComplex *AP, + cuComplex *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtpsv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, AP, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasZtpsv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + const cuDoubleComplex *AP, + cuDoubleComplex *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtpsv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, AP, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasStbsv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + int k, + const float *A, + int lda, + float *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasStbsv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, k, A, lda, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasDtbsv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + int k, + const double *A, + int lda, + double *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDtbsv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, k, A, lda, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasCtbsv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + int k, + const cuComplex *A, + int lda, + cuComplex *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtbsv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, k, A, lda, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasZtbsv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + int k, + const cuDoubleComplex *A, + int lda, + cuDoubleComplex *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtbsv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, k, A, lda, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasSsymv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const float *alpha, /* host or device pointer */ + const float *A, + int lda, + const float *x, + int incx, + const float *beta, /* host or device pointer */ + float *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const float *, const float *, int, const float *, int, const float *, float *, int); + static auto func_ptr = LoadSymbol("cublasSsymv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasDsymv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const double *alpha, /* host or device pointer */ + const double *A, + int lda, + const double *x, + int incx, + const double *beta, /* host or device pointer */ + double *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const double *, const double *, int, const double *, int, const double *, double *, int); + static auto func_ptr = LoadSymbol("cublasDsymv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasCsymv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const cuComplex *x, + int incx, + const cuComplex *beta, /* host or device pointer */ + cuComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCsymv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasZsymv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *x, + int incx, + const cuDoubleComplex *beta, /* host or device pointer */ + cuDoubleComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZsymv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasChemv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const cuComplex *x, + int incx, + const cuComplex *beta, /* host or device pointer */ + cuComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasChemv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasZhemv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *x, + int incx, + const cuDoubleComplex *beta, /* host or device pointer */ + cuDoubleComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZhemv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasSsbmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + int k, + const float *alpha, /* host or device pointer */ + const float *A, + int lda, + const float *x, + int incx, + const float *beta, /* host or device pointer */ + float *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, int, const float *, const float *, int, const float *, int, const float *, float *, int); + static auto func_ptr = LoadSymbol("cublasSsbmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, k, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasDsbmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + int k, + const double *alpha, /* host or device pointer */ + const double *A, + int lda, + const double *x, + int incx, + const double *beta, /* host or device pointer */ + double *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, int, const double *, const double *, int, const double *, int, const double *, double *, int); + static auto func_ptr = LoadSymbol("cublasDsbmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, k, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasChbmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + int k, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const cuComplex *x, + int incx, + const cuComplex *beta, /* host or device pointer */ + cuComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasChbmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, k, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasZhbmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + int k, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *x, + int incx, + const cuDoubleComplex *beta, /* host or device pointer */ + cuDoubleComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZhbmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, k, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasSspmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const float *alpha, /* host or device pointer */ + const float *AP, + const float *x, + int incx, + const float *beta, /* host or device pointer */ + float *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const float *, const float *, const float *, int, const float *, float *, int); + static auto func_ptr = LoadSymbol("cublasSspmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, AP, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasDspmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const double *alpha, /* host or device pointer */ + const double *AP, + const double *x, + int incx, + const double *beta, /* host or device pointer */ + double *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const double *, const double *, const double *, int, const double *, double *, int); + static auto func_ptr = LoadSymbol("cublasDspmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, AP, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasChpmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *AP, + const cuComplex *x, + int incx, + const cuComplex *beta, /* host or device pointer */ + cuComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuComplex *, const cuComplex *, const cuComplex *, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasChpmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, AP, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasZhpmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *AP, + const cuDoubleComplex *x, + int incx, + const cuDoubleComplex *beta, /* host or device pointer */ + cuDoubleComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuDoubleComplex *, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZhpmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, AP, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasSger_v2 (cublasHandle_t handle, + int m, + int n, + const float *alpha, /* host or device pointer */ + const float *x, + int incx, + const float *y, + int incy, + float *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, int, const float *, const float *, int, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasSger_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, m, n, alpha, x, incx, y, incy, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasDger_v2 (cublasHandle_t handle, + int m, + int n, + const double *alpha, /* host or device pointer */ + const double *x, + int incx, + const double *y, + int incy, + double *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, int, const double *, const double *, int, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDger_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, m, n, alpha, x, incx, y, incy, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasCgeru_v2 (cublasHandle_t handle, + int m, + int n, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *x, + int incx, + const cuComplex *y, + int incy, + cuComplex *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCgeru_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, m, n, alpha, x, incx, y, incy, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasCgerc_v2 (cublasHandle_t handle, + int m, + int n, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *x, + int incx, + const cuComplex *y, + int incy, + cuComplex *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCgerc_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, m, n, alpha, x, incx, y, incy, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasZgeru_v2 (cublasHandle_t handle, + int m, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *x, + int incx, + const cuDoubleComplex *y, + int incy, + cuDoubleComplex *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZgeru_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, m, n, alpha, x, incx, y, incy, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasZgerc_v2 (cublasHandle_t handle, + int m, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *x, + int incx, + const cuDoubleComplex *y, + int incy, + cuDoubleComplex *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZgerc_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, m, n, alpha, x, incx, y, incy, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasSsyr_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const float *alpha, /* host or device pointer */ + const float *x, + int incx, + float *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const float *, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasSsyr_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasDsyr_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const double *alpha, /* host or device pointer */ + const double *x, + int incx, + double *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const double *, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDsyr_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasCsyr_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *x, + int incx, + cuComplex *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuComplex *, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCsyr_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasZsyr_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *x, + int incx, + cuDoubleComplex *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuDoubleComplex *, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZsyr_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasCher_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const float *alpha, /* host or device pointer */ + const cuComplex *x, + int incx, + cuComplex *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const float *, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCher_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasZher_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const double *alpha, /* host or device pointer */ + const cuDoubleComplex *x, + int incx, + cuDoubleComplex *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const double *, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZher_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasSspr_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const float *alpha, /* host or device pointer */ + const float *x, + int incx, + float *AP) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const float *, const float *, int, float *); + static auto func_ptr = LoadSymbol("cublasSspr_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, AP); +} + +cublasStatus_t CUBLASWINAPI cublasDspr_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const double *alpha, /* host or device pointer */ + const double *x, + int incx, + double *AP) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const double *, const double *, int, double *); + static auto func_ptr = LoadSymbol("cublasDspr_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, AP); +} + +cublasStatus_t CUBLASWINAPI cublasChpr_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const float *alpha, /* host or device pointer */ + const cuComplex *x, + int incx, + cuComplex *AP) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const float *, const cuComplex *, int, cuComplex *); + static auto func_ptr = LoadSymbol("cublasChpr_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, AP); +} + +cublasStatus_t CUBLASWINAPI cublasZhpr_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const double *alpha, /* host or device pointer */ + const cuDoubleComplex *x, + int incx, + cuDoubleComplex *AP) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const double *, const cuDoubleComplex *, int, cuDoubleComplex *); + static auto func_ptr = LoadSymbol("cublasZhpr_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, AP); +} + +cublasStatus_t CUBLASWINAPI cublasSsyr2_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const float *alpha, /* host or device pointer */ + const float *x, + int incx, + const float *y, + int incy, + float *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const float *, const float *, int, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasSsyr2_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, y, incy, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasDsyr2_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const double *alpha, /* host or device pointer */ + const double *x, + int incx, + const double *y, + int incy, + double *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const double *, const double *, int, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDsyr2_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, y, incy, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasCsyr2_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, int n, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *x, + int incx, + const cuComplex *y, + int incy, + cuComplex *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCsyr2_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, y, incy, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasZsyr2_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *x, + int incx, + const cuDoubleComplex *y, + int incy, + cuDoubleComplex *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZsyr2_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, y, incy, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasCher2_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, int n, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *x, + int incx, + const cuComplex *y, + int incy, + cuComplex *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCher2_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, y, incy, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasZher2_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *x, + int incx, + const cuDoubleComplex *y, + int incy, + cuDoubleComplex *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZher2_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, y, incy, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasSspr2_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const float *alpha, /* host or device pointer */ + const float *x, + int incx, + const float *y, + int incy, + float *AP) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const float *, const float *, int, const float *, int, float *); + static auto func_ptr = LoadSymbol("cublasSspr2_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, y, incy, AP); +} + +cublasStatus_t CUBLASWINAPI cublasDspr2_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const double *alpha, /* host or device pointer */ + const double *x, + int incx, + const double *y, + int incy, + double *AP) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const double *, const double *, int, const double *, int, double *); + static auto func_ptr = LoadSymbol("cublasDspr2_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, y, incy, AP); +} + +cublasStatus_t CUBLASWINAPI cublasChpr2_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *x, + int incx, + const cuComplex *y, + int incy, + cuComplex *AP) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, cuComplex *); + static auto func_ptr = LoadSymbol("cublasChpr2_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, y, incy, AP); +} + +cublasStatus_t CUBLASWINAPI cublasZhpr2_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *x, + int incx, + const cuDoubleComplex *y, + int incy, + cuDoubleComplex *AP) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex *); + static auto func_ptr = LoadSymbol("cublasZhpr2_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, y, incy, AP); +} + +cublasStatus_t CUBLASWINAPI cublasSgemm_v2 (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const float *alpha, /* host or device pointer */ + const float *A, + int lda, + const float *B, + int ldb, + const float *beta, /* host or device pointer */ + float *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const float *, const float *, int, const float *, int, const float *, float *, int); + static auto func_ptr = LoadSymbol("cublasSgemm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasDgemm_v2 (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const double *alpha, /* host or device pointer */ + const double *A, + int lda, + const double *B, + int ldb, + const double *beta, /* host or device pointer */ + double *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const double *, const double *, int, const double *, int, const double *, double *, int); + static auto func_ptr = LoadSymbol("cublasDgemm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCgemm_v2 (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const cuComplex *B, + int ldb, + const cuComplex *beta, /* host or device pointer */ + cuComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCgemm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCgemm3m (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const cuComplex *B, + int ldb, + const cuComplex *beta, /* host or device pointer */ + cuComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCgemm3m"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCgemm3mEx (cublasHandle_t handle, + cublasOperation_t transa, cublasOperation_t transb, + int m, int n, int k, + const cuComplex *alpha, + const void *A, + cudaDataType Atype, + int lda, + const void *B, + cudaDataType Btype, + int ldb, + const cuComplex *beta, + void *C, + cudaDataType Ctype, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const cuComplex *, const void *, cudaDataType, int, const void *, cudaDataType, int, const cuComplex *, void *, cudaDataType, int); + static auto func_ptr = LoadSymbol("cublasCgemm3mEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, A, Atype, lda, B, Btype, ldb, beta, C, Ctype, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasZgemm_v2 (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *B, + int ldb, + const cuDoubleComplex *beta, /* host or device pointer */ + cuDoubleComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZgemm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasZgemm3m (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *B, + int ldb, + const cuDoubleComplex *beta, /* host or device pointer */ + cuDoubleComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZgemm3m"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasSgemmEx (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const float *alpha, /* host or device pointer */ + const void *A, + cudaDataType Atype, + int lda, + const void *B, + cudaDataType Btype, + int ldb, + const float *beta, /* host or device pointer */ + void *C, + cudaDataType Ctype, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const float *, const void *, cudaDataType, int, const void *, cudaDataType, int, const float *, void *, cudaDataType, int); + static auto func_ptr = LoadSymbol("cublasSgemmEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, A, Atype, lda, B, Btype, ldb, beta, C, Ctype, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasGemmEx (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const void *alpha, /* host or device pointer */ + const void *A, + cudaDataType Atype, + int lda, + const void *B, + cudaDataType Btype, + int ldb, + const void *beta, /* host or device pointer */ + void *C, + cudaDataType Ctype, + int ldc, + cudaDataType computeType, + cublasGemmAlgo_t algo) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const void *, const void *, cudaDataType, int, const void *, cudaDataType, int, const void *, void *, cudaDataType, int, cudaDataType, cublasGemmAlgo_t); + static auto func_ptr = LoadSymbol("cublasGemmEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, A, Atype, lda, B, Btype, ldb, beta, C, Ctype, ldc, computeType, algo); +} + +cublasStatus_t CUBLASWINAPI cublasCgemmEx (cublasHandle_t handle, + cublasOperation_t transa, cublasOperation_t transb, + int m, int n, int k, + const cuComplex *alpha, + const void *A, + cudaDataType Atype, + int lda, + const void *B, + cudaDataType Btype, + int ldb, + const cuComplex *beta, + void *C, + cudaDataType Ctype, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const cuComplex *, const void *, cudaDataType, int, const void *, cudaDataType, int, const cuComplex *, void *, cudaDataType, int); + static auto func_ptr = LoadSymbol("cublasCgemmEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, A, Atype, lda, B, Btype, ldb, beta, C, Ctype, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasUint8gemmBias (cublasHandle_t handle, + cublasOperation_t transa, cublasOperation_t transb, cublasOperation_t transc, + int m, int n, int k, + const unsigned char *A, int A_bias, int lda, + const unsigned char *B, int B_bias, int ldb, + unsigned char *C, int C_bias, int ldc, + int C_mult, int C_shift) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, cublasOperation_t, int, int, int, const unsigned char *, int, int, const unsigned char *, int, int, unsigned char *, int, int, int, int); + static auto func_ptr = LoadSymbol("cublasUint8gemmBias"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, transc, m, n, k, A, A_bias, lda, B, B_bias, ldb, C, C_bias, ldc, C_mult, C_shift); +} + +cublasStatus_t CUBLASWINAPI cublasSsyrk_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const float *alpha, /* host or device pointer */ + const float *A, + int lda, + const float *beta, /* host or device pointer */ + float *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const float *, const float *, int, const float *, float *, int); + static auto func_ptr = LoadSymbol("cublasSsyrk_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasDsyrk_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const double *alpha, /* host or device pointer */ + const double *A, + int lda, + const double *beta, /* host or device pointer */ + double *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const double *, const double *, int, const double *, double *, int); + static auto func_ptr = LoadSymbol("cublasDsyrk_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCsyrk_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const cuComplex *beta, /* host or device pointer */ + cuComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const cuComplex *, const cuComplex *, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCsyrk_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasZsyrk_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *beta, /* host or device pointer */ + cuDoubleComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZsyrk_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCsyrkEx ( cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const cuComplex *alpha, /* host or device pointer */ + const void *A, + cudaDataType Atype, + int lda, + const cuComplex *beta, /* host or device pointer */ + void *C, + cudaDataType Ctype, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const cuComplex *, const void *, cudaDataType, int, const cuComplex *, void *, cudaDataType, int); + static auto func_ptr = LoadSymbol("cublasCsyrkEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, Atype, lda, beta, C, Ctype, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCsyrk3mEx(cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const cuComplex *alpha, + const void *A, + cudaDataType Atype, + int lda, + const cuComplex *beta, + void *C, + cudaDataType Ctype, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const cuComplex *, const void *, cudaDataType, int, const cuComplex *, void *, cudaDataType, int); + static auto func_ptr = LoadSymbol("cublasCsyrk3mEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, Atype, lda, beta, C, Ctype, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCherk_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const float *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const float *beta, /* host or device pointer */ + cuComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const float *, const cuComplex *, int, const float *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCherk_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasZherk_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const double *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const double *beta, /* host or device pointer */ + cuDoubleComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const double *, const cuDoubleComplex *, int, const double *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZherk_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCherkEx (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const float *alpha, /* host or device pointer */ + const void *A, + cudaDataType Atype, + int lda, + const float *beta, /* host or device pointer */ + void *C, + cudaDataType Ctype, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const float *, const void *, cudaDataType, int, const float *, void *, cudaDataType, int); + static auto func_ptr = LoadSymbol("cublasCherkEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, Atype, lda, beta, C, Ctype, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCherk3mEx (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const float *alpha, + const void *A, cudaDataType Atype, + int lda, + const float *beta, + void *C, + cudaDataType Ctype, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const float *, const void *, cudaDataType, int, const float *, void *, cudaDataType, int); + static auto func_ptr = LoadSymbol("cublasCherk3mEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, Atype, lda, beta, C, Ctype, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasSsyr2k_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const float *alpha, /* host or device pointer */ + const float *A, + int lda, + const float *B, + int ldb, + const float *beta, /* host or device pointer */ + float *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const float *, const float *, int, const float *, int, const float *, float *, int); + static auto func_ptr = LoadSymbol("cublasSsyr2k_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasDsyr2k_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const double *alpha, /* host or device pointer */ + const double *A, + int lda, + const double *B, + int ldb, + const double *beta, /* host or device pointer */ + double *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const double *, const double *, int, const double *, int, const double *, double *, int); + static auto func_ptr = LoadSymbol("cublasDsyr2k_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCsyr2k_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const cuComplex *B, + int ldb, + const cuComplex *beta, /* host or device pointer */ + cuComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCsyr2k_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasZsyr2k_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *B, + int ldb, + const cuDoubleComplex *beta, /* host or device pointer */ + cuDoubleComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZsyr2k_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCher2k_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const cuComplex *B, + int ldb, + const float *beta, /* host or device pointer */ + cuComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, const float *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCher2k_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasZher2k_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *B, + int ldb, + const double *beta, /* host or device pointer */ + cuDoubleComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, const double *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZher2k_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasSsyrkx (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const float *alpha, /* host or device pointer */ + const float *A, + int lda, + const float *B, + int ldb, + const float *beta, /* host or device pointer */ + float *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const float *, const float *, int, const float *, int, const float *, float *, int); + static auto func_ptr = LoadSymbol("cublasSsyrkx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasDsyrkx (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const double *alpha, /* host or device pointer */ + const double *A, + int lda, + const double *B, + int ldb, + const double *beta, /* host or device pointer */ + double *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const double *, const double *, int, const double *, int, const double *, double *, int); + static auto func_ptr = LoadSymbol("cublasDsyrkx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCsyrkx (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const cuComplex *B, + int ldb, + const cuComplex *beta, /* host or device pointer */ + cuComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCsyrkx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasZsyrkx (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *B, + int ldb, + const cuDoubleComplex *beta, /* host or device pointer */ + cuDoubleComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZsyrkx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCherkx (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const cuComplex *B, + int ldb, + const float *beta, /* host or device pointer */ + cuComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, const float *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCherkx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasZherkx (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *B, + int ldb, + const double *beta, /* host or device pointer */ + cuDoubleComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, const double *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZherkx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasSsymm_v2 (cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + int m, + int n, + const float *alpha, /* host or device pointer */ + const float *A, + int lda, + const float *B, + int ldb, + const float *beta, /* host or device pointer */ + float *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, int, int, const float *, const float *, int, const float *, int, const float *, float *, int); + static auto func_ptr = LoadSymbol("cublasSsymm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, m, n, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasDsymm_v2 (cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + int m, + int n, + const double *alpha, /* host or device pointer */ + const double *A, + int lda, + const double *B, + int ldb, + const double *beta, /* host or device pointer */ + double *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, int, int, const double *, const double *, int, const double *, int, const double *, double *, int); + static auto func_ptr = LoadSymbol("cublasDsymm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, m, n, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCsymm_v2 (cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + int m, + int n, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const cuComplex *B, + int ldb, + const cuComplex *beta, /* host or device pointer */ + cuComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, int, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCsymm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, m, n, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasZsymm_v2 (cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + int m, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *B, + int ldb, + const cuDoubleComplex *beta, /* host or device pointer */ + cuDoubleComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZsymm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, m, n, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasChemm_v2 (cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + int m, + int n, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const cuComplex *B, + int ldb, + const cuComplex *beta, /* host or device pointer */ + cuComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, int, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasChemm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, m, n, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasZhemm_v2 (cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + int m, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *B, + int ldb, + const cuDoubleComplex *beta, /* host or device pointer */ + cuDoubleComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZhemm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, m, n, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasStrsm_v2 (cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int m, + int n, + const float *alpha, /* host or device pointer */ + const float *A, + int lda, + float *B, + int ldb) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const float *, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasStrsm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, trans, diag, m, n, alpha, A, lda, B, ldb); +} + +cublasStatus_t CUBLASWINAPI cublasDtrsm_v2 (cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int m, + int n, + const double *alpha, /* host or device pointer */ + const double *A, + int lda, + double *B, + int ldb) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const double *, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDtrsm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, trans, diag, m, n, alpha, A, lda, B, ldb); +} + +cublasStatus_t CUBLASWINAPI cublasCtrsm_v2(cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int m, + int n, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + cuComplex *B, + int ldb) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const cuComplex *, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtrsm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, trans, diag, m, n, alpha, A, lda, B, ldb); +} + +cublasStatus_t CUBLASWINAPI cublasZtrsm_v2(cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int m, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + cuDoubleComplex *B, + int ldb) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtrsm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, trans, diag, m, n, alpha, A, lda, B, ldb); +} + +cublasStatus_t CUBLASWINAPI cublasStrmm_v2 (cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int m, + int n, + const float *alpha, /* host or device pointer */ + const float *A, + int lda, + const float *B, + int ldb, + float *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const float *, const float *, int, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasStrmm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, trans, diag, m, n, alpha, A, lda, B, ldb, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasDtrmm_v2 (cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int m, + int n, + const double *alpha, /* host or device pointer */ + const double *A, + int lda, + const double *B, + int ldb, + double *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const double *, const double *, int, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDtrmm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, trans, diag, m, n, alpha, A, lda, B, ldb, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCtrmm_v2(cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int m, + int n, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const cuComplex *B, + int ldb, + cuComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtrmm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, trans, diag, m, n, alpha, A, lda, B, ldb, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasZtrmm_v2(cublasHandle_t handle, cublasSideMode_t side, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int m, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *B, + int ldb, + cuDoubleComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtrmm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, trans, diag, m, n, alpha, A, lda, B, ldb, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasSgemmBatched (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const float *alpha, /* host or device pointer */ + const float *const Aarray[], + int lda, + const float *const Barray[], + int ldb, + const float *beta, /* host or device pointer */ + float *const Carray[], + int ldc, + int batchCount) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const float *, const float *const [], int, const float *const [], int, const float *, float *const [], int, int); + static auto func_ptr = LoadSymbol("cublasSgemmBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, Aarray, lda, Barray, ldb, beta, Carray, ldc, batchCount); +} + +cublasStatus_t CUBLASWINAPI cublasDgemmBatched (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const double *alpha, /* host or device pointer */ + const double *const Aarray[], + int lda, + const double *const Barray[], + int ldb, + const double *beta, /* host or device pointer */ + double *const Carray[], + int ldc, + int batchCount) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const double *, const double *const [], int, const double *const [], int, const double *, double *const [], int, int); + static auto func_ptr = LoadSymbol("cublasDgemmBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, Aarray, lda, Barray, ldb, beta, Carray, ldc, batchCount); +} + +cublasStatus_t CUBLASWINAPI cublasCgemmBatched (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *const Aarray[], + int lda, + const cuComplex *const Barray[], + int ldb, + const cuComplex *beta, /* host or device pointer */ + cuComplex *const Carray[], + int ldc, + int batchCount) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const cuComplex *, const cuComplex *const [], int, const cuComplex *const [], int, const cuComplex *, cuComplex *const [], int, int); + static auto func_ptr = LoadSymbol("cublasCgemmBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, Aarray, lda, Barray, ldb, beta, Carray, ldc, batchCount); +} + +cublasStatus_t CUBLASWINAPI cublasCgemm3mBatched (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *const Aarray[], + int lda, + const cuComplex *const Barray[], + int ldb, + const cuComplex *beta, /* host or device pointer */ + cuComplex *const Carray[], + int ldc, + int batchCount) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const cuComplex *, const cuComplex *const [], int, const cuComplex *const [], int, const cuComplex *, cuComplex *const [], int, int); + static auto func_ptr = LoadSymbol("cublasCgemm3mBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, Aarray, lda, Barray, ldb, beta, Carray, ldc, batchCount); +} + +cublasStatus_t CUBLASWINAPI cublasZgemmBatched (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *const Aarray[], + int lda, + const cuDoubleComplex *const Barray[], + int ldb, + const cuDoubleComplex *beta, /* host or device pointer */ + cuDoubleComplex *const Carray[], + int ldc, + int batchCount) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const cuDoubleComplex *, const cuDoubleComplex *const [], int, const cuDoubleComplex *const [], int, const cuDoubleComplex *, cuDoubleComplex *const [], int, int); + static auto func_ptr = LoadSymbol("cublasZgemmBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, Aarray, lda, Barray, ldb, beta, Carray, ldc, batchCount); +} + +cublasStatus_t CUBLASWINAPI cublasGemmBatchedEx (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const void *alpha, /* host or device pointer */ + const void *const Aarray[], + cudaDataType Atype, + int lda, + const void *const Barray[], + cudaDataType Btype, + int ldb, + const void *beta, /* host or device pointer */ + void *const Carray[], + cudaDataType Ctype, + int ldc, + int batchCount, + cudaDataType computeType, + cublasGemmAlgo_t algo) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const void *, const void *const [], cudaDataType, int, const void *const [], cudaDataType, int, const void *, void *const [], cudaDataType, int, int, cudaDataType, cublasGemmAlgo_t); + static auto func_ptr = LoadSymbol("cublasGemmBatchedEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, Aarray, Atype, lda, Barray, Btype, ldb, beta, Carray, Ctype, ldc, batchCount, computeType, algo); +} + +cublasStatus_t CUBLASWINAPI cublasGemmStridedBatchedEx (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const void *alpha, /* host or device pointer */ + const void *A, + cudaDataType Atype, + int lda, + long long int strideA, /* purposely signed */ + const void *B, + cudaDataType Btype, + int ldb, + long long int strideB, + const void *beta, /* host or device pointer */ + void *C, + cudaDataType Ctype, + int ldc, + long long int strideC, + int batchCount, + cudaDataType computeType, + cublasGemmAlgo_t algo) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const void *, const void *, cudaDataType, int, long long, const void *, cudaDataType, int, long long, const void *, void *, cudaDataType, int, long long, int, cudaDataType, cublasGemmAlgo_t); + static auto func_ptr = LoadSymbol("cublasGemmStridedBatchedEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, A, Atype, lda, strideA, B, Btype, ldb, strideB, beta, C, Ctype, ldc, strideC, batchCount, computeType, algo); +} + +cublasStatus_t CUBLASWINAPI cublasSgemmStridedBatched (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const float *alpha, /* host or device pointer */ + const float *A, + int lda, + long long int strideA, /* purposely signed */ + const float *B, + int ldb, + long long int strideB, + const float *beta, /* host or device pointer */ + float *C, + int ldc, + long long int strideC, + int batchCount) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const float *, const float *, int, long long, const float *, int, long long, const float *, float *, int, long long, int); + static auto func_ptr = LoadSymbol("cublasSgemmStridedBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, A, lda, strideA, B, ldb, strideB, beta, C, ldc, strideC, batchCount); +} + +cublasStatus_t CUBLASWINAPI cublasDgemmStridedBatched (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const double *alpha, /* host or device pointer */ + const double *A, + int lda, + long long int strideA, /* purposely signed */ + const double *B, + int ldb, + long long int strideB, + const double *beta, /* host or device pointer */ + double *C, + int ldc, + long long int strideC, + int batchCount) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const double *, const double *, int, long long, const double *, int, long long, const double *, double *, int, long long, int); + static auto func_ptr = LoadSymbol("cublasDgemmStridedBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, A, lda, strideA, B, ldb, strideB, beta, C, ldc, strideC, batchCount); +} + +cublasStatus_t CUBLASWINAPI cublasCgemmStridedBatched (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + long long int strideA, /* purposely signed */ + const cuComplex *B, + int ldb, + long long int strideB, + const cuComplex *beta, /* host or device pointer */ + cuComplex *C, + int ldc, + long long int strideC, + int batchCount) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const cuComplex *, const cuComplex *, int, long long, const cuComplex *, int, long long, const cuComplex *, cuComplex *, int, long long, int); + static auto func_ptr = LoadSymbol("cublasCgemmStridedBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, A, lda, strideA, B, ldb, strideB, beta, C, ldc, strideC, batchCount); +} + +cublasStatus_t CUBLASWINAPI cublasCgemm3mStridedBatched (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + long long int strideA, /* purposely signed */ + const cuComplex *B, + int ldb, + long long int strideB, + const cuComplex *beta, /* host or device pointer */ + cuComplex *C, + int ldc, + long long int strideC, + int batchCount) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const cuComplex *, const cuComplex *, int, long long, const cuComplex *, int, long long, const cuComplex *, cuComplex *, int, long long, int); + static auto func_ptr = LoadSymbol("cublasCgemm3mStridedBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, A, lda, strideA, B, ldb, strideB, beta, C, ldc, strideC, batchCount); +} + +cublasStatus_t CUBLASWINAPI cublasZgemmStridedBatched (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + long long int strideA, /* purposely signed */ + const cuDoubleComplex *B, + int ldb, + long long int strideB, + const cuDoubleComplex *beta, /* host or device poi */ + cuDoubleComplex *C, + int ldc, + long long int strideC, + int batchCount) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, long long, const cuDoubleComplex *, int, long long, const cuDoubleComplex *, cuDoubleComplex *, int, long long, int); + static auto func_ptr = LoadSymbol("cublasZgemmStridedBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, A, lda, strideA, B, ldb, strideB, beta, C, ldc, strideC, batchCount); +} + +cublasStatus_t CUBLASWINAPI cublasSgeam(cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + const float *alpha, /* host or device pointer */ + const float *A, + int lda, + const float *beta , /* host or device pointer */ + const float *B, + int ldb, + float *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, const float *, const float *, int, const float *, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasSgeam"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, alpha, A, lda, beta, B, ldb, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasDgeam(cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + const double *alpha, /* host or device pointer */ + const double *A, + int lda, + const double *beta, /* host or device pointer */ + const double *B, + int ldb, + double *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, const double *, const double *, int, const double *, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDgeam"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, alpha, A, lda, beta, B, ldb, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCgeam(cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const cuComplex *beta, /* host or device pointer */ + const cuComplex *B, + int ldb, + cuComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, const cuComplex *, const cuComplex *, int, const cuComplex *, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCgeam"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, alpha, A, lda, beta, B, ldb, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasZgeam(cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *beta, /* host or device pointer */ + const cuDoubleComplex *B, + int ldb, + cuDoubleComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZgeam"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, alpha, A, lda, beta, B, ldb, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasSgetrfBatched(cublasHandle_t handle, + int n, + float *const A[], /*Device pointer*/ + int lda, + int *P, /*Device Pointer*/ + int *info, /*Device Pointer*/ + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, float *const [], int, int *, int *, int); + static auto func_ptr = LoadSymbol("cublasSgetrfBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, A, lda, P, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasDgetrfBatched(cublasHandle_t handle, + int n, + double *const A[], /*Device pointer*/ + int lda, + int *P, /*Device Pointer*/ + int *info, /*Device Pointer*/ + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, double *const [], int, int *, int *, int); + static auto func_ptr = LoadSymbol("cublasDgetrfBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, A, lda, P, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasCgetrfBatched(cublasHandle_t handle, + int n, + cuComplex *const A[], /*Device pointer*/ + int lda, + int *P, /*Device Pointer*/ + int *info, /*Device Pointer*/ + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, cuComplex *const [], int, int *, int *, int); + static auto func_ptr = LoadSymbol("cublasCgetrfBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, A, lda, P, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasZgetrfBatched(cublasHandle_t handle, + int n, + cuDoubleComplex *const A[], /*Device pointer*/ + int lda, + int *P, /*Device Pointer*/ + int *info, /*Device Pointer*/ + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, cuDoubleComplex *const [], int, int *, int *, int); + static auto func_ptr = LoadSymbol("cublasZgetrfBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, A, lda, P, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasSgetriBatched(cublasHandle_t handle, + int n, + const float *const A[], /*Device pointer*/ + int lda, + const int *P, /*Device pointer*/ + float *const C[], /*Device pointer*/ + int ldc, + int *info, + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const float *const [], int, const int *, float *const [], int, int *, int); + static auto func_ptr = LoadSymbol("cublasSgetriBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, A, lda, P, C, ldc, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasDgetriBatched(cublasHandle_t handle, + int n, + const double *const A[], /*Device pointer*/ + int lda, + const int *P, /*Device pointer*/ + double *const C[], /*Device pointer*/ + int ldc, + int *info, + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const double *const [], int, const int *, double *const [], int, int *, int); + static auto func_ptr = LoadSymbol("cublasDgetriBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, A, lda, P, C, ldc, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasCgetriBatched(cublasHandle_t handle, + int n, + const cuComplex *const A[], /*Device pointer*/ + int lda, + const int *P, /*Device pointer*/ + cuComplex *const C[], /*Device pointer*/ + int ldc, + int *info, + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuComplex *const [], int, const int *, cuComplex *const [], int, int *, int); + static auto func_ptr = LoadSymbol("cublasCgetriBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, A, lda, P, C, ldc, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasZgetriBatched(cublasHandle_t handle, + int n, + const cuDoubleComplex *const A[], /*Device pointer*/ + int lda, + const int *P, /*Device pointer*/ + cuDoubleComplex *const C[], /*Device pointer*/ + int ldc, + int *info, + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuDoubleComplex *const [], int, const int *, cuDoubleComplex *const [], int, int *, int); + static auto func_ptr = LoadSymbol("cublasZgetriBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, A, lda, P, C, ldc, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasSgetrsBatched( cublasHandle_t handle, + cublasOperation_t trans, + int n, + int nrhs, + const float *const Aarray[], + int lda, + const int *devIpiv, + float *const Barray[], + int ldb, + int *info, + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, int, int, const float *const [], int, const int *, float *const [], int, int *, int); + static auto func_ptr = LoadSymbol("cublasSgetrsBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, trans, n, nrhs, Aarray, lda, devIpiv, Barray, ldb, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasDgetrsBatched( cublasHandle_t handle, + cublasOperation_t trans, + int n, + int nrhs, + const double *const Aarray[], + int lda, + const int *devIpiv, + double *const Barray[], + int ldb, + int *info, + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, int, int, const double *const [], int, const int *, double *const [], int, int *, int); + static auto func_ptr = LoadSymbol("cublasDgetrsBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, trans, n, nrhs, Aarray, lda, devIpiv, Barray, ldb, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasCgetrsBatched( cublasHandle_t handle, + cublasOperation_t trans, + int n, + int nrhs, + const cuComplex *const Aarray[], + int lda, + const int *devIpiv, + cuComplex *const Barray[], + int ldb, + int *info, + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, int, int, const cuComplex *const [], int, const int *, cuComplex *const [], int, int *, int); + static auto func_ptr = LoadSymbol("cublasCgetrsBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, trans, n, nrhs, Aarray, lda, devIpiv, Barray, ldb, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasZgetrsBatched( cublasHandle_t handle, + cublasOperation_t trans, + int n, + int nrhs, + const cuDoubleComplex *const Aarray[], + int lda, + const int *devIpiv, + cuDoubleComplex *const Barray[], + int ldb, + int *info, + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, int, int, const cuDoubleComplex *const [], int, const int *, cuDoubleComplex *const [], int, int *, int); + static auto func_ptr = LoadSymbol("cublasZgetrsBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, trans, n, nrhs, Aarray, lda, devIpiv, Barray, ldb, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasStrsmBatched( cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int m, + int n, + const float *alpha, /*Host or Device Pointer*/ + const float *const A[], + int lda, + float *const B[], + int ldb, + int batchCount) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const float *, const float *const [], int, float *const [], int, int); + static auto func_ptr = LoadSymbol("cublasStrsmBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, trans, diag, m, n, alpha, A, lda, B, ldb, batchCount); +} + +cublasStatus_t CUBLASWINAPI cublasDtrsmBatched( cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int m, + int n, + const double *alpha, /*Host or Device Pointer*/ + const double *const A[], + int lda, + double *const B[], + int ldb, + int batchCount) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const double *, const double *const [], int, double *const [], int, int); + static auto func_ptr = LoadSymbol("cublasDtrsmBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, trans, diag, m, n, alpha, A, lda, B, ldb, batchCount); +} + +cublasStatus_t CUBLASWINAPI cublasCtrsmBatched( cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int m, + int n, + const cuComplex *alpha, /*Host or Device Pointer*/ + const cuComplex *const A[], + int lda, + cuComplex *const B[], + int ldb, + int batchCount) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const cuComplex *, const cuComplex *const [], int, cuComplex *const [], int, int); + static auto func_ptr = LoadSymbol("cublasCtrsmBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, trans, diag, m, n, alpha, A, lda, B, ldb, batchCount); +} + +cublasStatus_t CUBLASWINAPI cublasZtrsmBatched( cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int m, + int n, + const cuDoubleComplex *alpha, /*Host or Device Pointer*/ + const cuDoubleComplex *const A[], + int lda, + cuDoubleComplex *const B[], + int ldb, + int batchCount) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const cuDoubleComplex *, const cuDoubleComplex *const [], int, cuDoubleComplex *const [], int, int); + static auto func_ptr = LoadSymbol("cublasZtrsmBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, trans, diag, m, n, alpha, A, lda, B, ldb, batchCount); +} + +cublasStatus_t CUBLASWINAPI cublasSmatinvBatched(cublasHandle_t handle, + int n, + const float *const A[], /*Device pointer*/ + int lda, + float *const Ainv[], /*Device pointer*/ + int lda_inv, + int *info, /*Device Pointer*/ + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const float *const [], int, float *const [], int, int *, int); + static auto func_ptr = LoadSymbol("cublasSmatinvBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, A, lda, Ainv, lda_inv, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasDmatinvBatched(cublasHandle_t handle, + int n, + const double *const A[], /*Device pointer*/ + int lda, + double *const Ainv[], /*Device pointer*/ + int lda_inv, + int *info, /*Device Pointer*/ + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const double *const [], int, double *const [], int, int *, int); + static auto func_ptr = LoadSymbol("cublasDmatinvBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, A, lda, Ainv, lda_inv, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasCmatinvBatched(cublasHandle_t handle, + int n, + const cuComplex *const A[], /*Device pointer*/ + int lda, + cuComplex *const Ainv[], /*Device pointer*/ + int lda_inv, + int *info, /*Device Pointer*/ + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuComplex *const [], int, cuComplex *const [], int, int *, int); + static auto func_ptr = LoadSymbol("cublasCmatinvBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, A, lda, Ainv, lda_inv, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasZmatinvBatched(cublasHandle_t handle, + int n, + const cuDoubleComplex *const A[], /*Device pointer*/ + int lda, + cuDoubleComplex *const Ainv[], /*Device pointer*/ + int lda_inv, + int *info, /*Device Pointer*/ + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuDoubleComplex *const [], int, cuDoubleComplex *const [], int, int *, int); + static auto func_ptr = LoadSymbol("cublasZmatinvBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, A, lda, Ainv, lda_inv, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasSgeqrfBatched( cublasHandle_t handle, + int m, + int n, + float *const Aarray[], /*Device pointer*/ + int lda, + float *const TauArray[], /*Device pointer*/ + int *info, + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, int, float *const [], int, float *const [], int *, int); + static auto func_ptr = LoadSymbol("cublasSgeqrfBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, m, n, Aarray, lda, TauArray, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasDgeqrfBatched( cublasHandle_t handle, + int m, + int n, + double *const Aarray[], /*Device pointer*/ + int lda, + double *const TauArray[], /*Device pointer*/ + int *info, + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, int, double *const [], int, double *const [], int *, int); + static auto func_ptr = LoadSymbol("cublasDgeqrfBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, m, n, Aarray, lda, TauArray, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasCgeqrfBatched( cublasHandle_t handle, + int m, + int n, + cuComplex *const Aarray[], /*Device pointer*/ + int lda, + cuComplex *const TauArray[], /*Device pointer*/ + int *info, + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, int, cuComplex *const [], int, cuComplex *const [], int *, int); + static auto func_ptr = LoadSymbol("cublasCgeqrfBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, m, n, Aarray, lda, TauArray, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasZgeqrfBatched( cublasHandle_t handle, + int m, + int n, + cuDoubleComplex *const Aarray[], /*Device pointer*/ + int lda, + cuDoubleComplex *const TauArray[], /*Device pointer*/ + int *info, + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, int, cuDoubleComplex *const [], int, cuDoubleComplex *const [], int *, int); + static auto func_ptr = LoadSymbol("cublasZgeqrfBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, m, n, Aarray, lda, TauArray, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasSgelsBatched( cublasHandle_t handle, + cublasOperation_t trans, + int m, + int n, + int nrhs, + float *const Aarray[], /*Device pointer*/ + int lda, + float *const Carray[], /*Device pointer*/ + int ldc, + int *info, + int *devInfoArray, /*Device pointer*/ + int batchSize ) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, int, int, int, float *const [], int, float *const [], int, int *, int *, int); + static auto func_ptr = LoadSymbol("cublasSgelsBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, trans, m, n, nrhs, Aarray, lda, Carray, ldc, info, devInfoArray, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasDgelsBatched( cublasHandle_t handle, + cublasOperation_t trans, + int m, + int n, + int nrhs, + double *const Aarray[], /*Device pointer*/ + int lda, + double *const Carray[], /*Device pointer*/ + int ldc, + int *info, + int *devInfoArray, /*Device pointer*/ + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, int, int, int, double *const [], int, double *const [], int, int *, int *, int); + static auto func_ptr = LoadSymbol("cublasDgelsBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, trans, m, n, nrhs, Aarray, lda, Carray, ldc, info, devInfoArray, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasCgelsBatched( cublasHandle_t handle, + cublasOperation_t trans, + int m, + int n, + int nrhs, + cuComplex *const Aarray[], /*Device pointer*/ + int lda, + cuComplex *const Carray[], /*Device pointer*/ + int ldc, + int *info, + int *devInfoArray, + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, int, int, int, cuComplex *const [], int, cuComplex *const [], int, int *, int *, int); + static auto func_ptr = LoadSymbol("cublasCgelsBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, trans, m, n, nrhs, Aarray, lda, Carray, ldc, info, devInfoArray, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasZgelsBatched( cublasHandle_t handle, + cublasOperation_t trans, + int m, + int n, + int nrhs, + cuDoubleComplex *const Aarray[], /*Device pointer*/ + int lda, + cuDoubleComplex *const Carray[], /*Device pointer*/ + int ldc, + int *info, + int *devInfoArray, + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, int, int, int, cuDoubleComplex *const [], int, cuDoubleComplex *const [], int, int *, int *, int); + static auto func_ptr = LoadSymbol("cublasZgelsBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, trans, m, n, nrhs, Aarray, lda, Carray, ldc, info, devInfoArray, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasSdgmm(cublasHandle_t handle, + cublasSideMode_t mode, + int m, + int n, + const float *A, + int lda, + const float *x, + int incx, + float *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, int, int, const float *, int, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasSdgmm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode, m, n, A, lda, x, incx, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasDdgmm(cublasHandle_t handle, + cublasSideMode_t mode, + int m, + int n, + const double *A, + int lda, + const double *x, + int incx, + double *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, int, int, const double *, int, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDdgmm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode, m, n, A, lda, x, incx, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCdgmm(cublasHandle_t handle, + cublasSideMode_t mode, + int m, + int n, + const cuComplex *A, + int lda, + const cuComplex *x, + int incx, + cuComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, int, int, const cuComplex *, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCdgmm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode, m, n, A, lda, x, incx, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasZdgmm(cublasHandle_t handle, + cublasSideMode_t mode, + int m, + int n, + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *x, + int incx, + cuDoubleComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, int, int, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZdgmm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode, m, n, A, lda, x, incx, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasStpttr ( cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const float *AP, + float *A, + int lda ) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const float *, float *, int); + static auto func_ptr = LoadSymbol("cublasStpttr"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, AP, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasDtpttr ( cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const double *AP, + double *A, + int lda ) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const double *, double *, int); + static auto func_ptr = LoadSymbol("cublasDtpttr"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, AP, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasCtpttr ( cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const cuComplex *AP, + cuComplex *A, + int lda ) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtpttr"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, AP, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasZtpttr ( cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const cuDoubleComplex *AP, + cuDoubleComplex *A, + int lda ) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtpttr"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, AP, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasStrttp ( cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const float *A, + int lda, + float *AP ) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const float *, int, float *); + static auto func_ptr = LoadSymbol("cublasStrttp"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, A, lda, AP); +} + +cublasStatus_t CUBLASWINAPI cublasDtrttp ( cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const double *A, + int lda, + double *AP ) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const double *, int, double *); + static auto func_ptr = LoadSymbol("cublasDtrttp"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, A, lda, AP); +} + +cublasStatus_t CUBLASWINAPI cublasCtrttp ( cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const cuComplex *A, + int lda, + cuComplex *AP ) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuComplex *, int, cuComplex *); + static auto func_ptr = LoadSymbol("cublasCtrttp"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, A, lda, AP); +} + +cublasStatus_t CUBLASWINAPI cublasZtrttp ( cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const cuDoubleComplex *A, + int lda, + cuDoubleComplex *AP ) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuDoubleComplex *, int, cuDoubleComplex *); + static auto func_ptr = LoadSymbol("cublasZtrttp"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, A, lda, AP); +} + +cublasStatus CUBLASWINAPI cublasInit (void) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(); + static auto func_ptr = LoadSymbol("cublasInit"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(); +} + +cublasStatus CUBLASWINAPI cublasShutdown (void) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(); + static auto func_ptr = LoadSymbol("cublasShutdown"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(); +} + +cublasStatus CUBLASWINAPI cublasGetError (void) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(); + static auto func_ptr = LoadSymbol("cublasGetError"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(); +} + +cublasStatus CUBLASWINAPI cublasGetVersion(int *version) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(int *); + static auto func_ptr = LoadSymbol("cublasGetVersion"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(version); +} + +cublasStatus CUBLASWINAPI cublasAlloc (int n, int elemSize, void **devicePtr) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(int, int, void **); + static auto func_ptr = LoadSymbol("cublasAlloc"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(n, elemSize, devicePtr); +} + +cublasStatus CUBLASWINAPI cublasFree (void *devicePtr) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(void *); + static auto func_ptr = LoadSymbol("cublasFree"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(devicePtr); +} + +cublasStatus CUBLASWINAPI cublasSetKernelStream (cudaStream_t stream) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cudaStream_t); + static auto func_ptr = LoadSymbol("cublasSetKernelStream"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(stream); +} + +float CUBLASWINAPI cublasSnrm2 (int n, const float *x, int incx) { + using FuncPtr = float (CUBLASWINAPI *)(int, const float *, int); + static auto func_ptr = LoadSymbol("cublasSnrm2"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSnrm2"); + return func_ptr(n, x, incx); +} + +double CUBLASWINAPI cublasDnrm2 (int n, const double *x, int incx) { + using FuncPtr = double (CUBLASWINAPI *)(int, const double *, int); + static auto func_ptr = LoadSymbol("cublasDnrm2"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDnrm2"); + return func_ptr(n, x, incx); +} + +float CUBLASWINAPI cublasScnrm2 (int n, const cuComplex *x, int incx) { + using FuncPtr = float (CUBLASWINAPI *)(int, const cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasScnrm2"); + if (!func_ptr) LogFatalSymbolNotFound("cublasScnrm2"); + return func_ptr(n, x, incx); +} + +double CUBLASWINAPI cublasDznrm2 (int n, const cuDoubleComplex *x, int incx) { + using FuncPtr = double (CUBLASWINAPI *)(int, const cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasDznrm2"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDznrm2"); + return func_ptr(n, x, incx); +} + +float CUBLASWINAPI cublasSdot (int n, const float *x, int incx, const float *y, + int incy) { + using FuncPtr = float (CUBLASWINAPI *)(int, const float *, int, const float *, int); + static auto func_ptr = LoadSymbol("cublasSdot"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSdot"); + return func_ptr(n, x, incx, y, incy); +} + +double CUBLASWINAPI cublasDdot (int n, const double *x, int incx, const double *y, + int incy) { + using FuncPtr = double (CUBLASWINAPI *)(int, const double *, int, const double *, int); + static auto func_ptr = LoadSymbol("cublasDdot"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDdot"); + return func_ptr(n, x, incx, y, incy); +} + +cuComplex CUBLASWINAPI cublasCdotu (int n, const cuComplex *x, int incx, const cuComplex *y, + int incy) { + using FuncPtr = cuComplex (CUBLASWINAPI *)(int, const cuComplex *, int, const cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCdotu"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCdotu"); + return func_ptr(n, x, incx, y, incy); +} + +cuComplex CUBLASWINAPI cublasCdotc (int n, const cuComplex *x, int incx, const cuComplex *y, + int incy) { + using FuncPtr = cuComplex (CUBLASWINAPI *)(int, const cuComplex *, int, const cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCdotc"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCdotc"); + return func_ptr(n, x, incx, y, incy); +} + +cuDoubleComplex CUBLASWINAPI cublasZdotu (int n, const cuDoubleComplex *x, int incx, const cuDoubleComplex *y, + int incy) { + using FuncPtr = cuDoubleComplex (CUBLASWINAPI *)(int, const cuDoubleComplex *, int, const cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZdotu"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZdotu"); + return func_ptr(n, x, incx, y, incy); +} + +cuDoubleComplex CUBLASWINAPI cublasZdotc (int n, const cuDoubleComplex *x, int incx, const cuDoubleComplex *y, + int incy) { + using FuncPtr = cuDoubleComplex (CUBLASWINAPI *)(int, const cuDoubleComplex *, int, const cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZdotc"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZdotc"); + return func_ptr(n, x, incx, y, incy); +} + +void CUBLASWINAPI cublasSscal (int n, float alpha, float *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(int, float, float *, int); + static auto func_ptr = LoadSymbol("cublasSscal"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSscal"); + return func_ptr(n, alpha, x, incx); +} + +void CUBLASWINAPI cublasDscal (int n, double alpha, double *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(int, double, double *, int); + static auto func_ptr = LoadSymbol("cublasDscal"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDscal"); + return func_ptr(n, alpha, x, incx); +} + +void CUBLASWINAPI cublasCscal (int n, cuComplex alpha, cuComplex *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(int, cuComplex, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCscal"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCscal"); + return func_ptr(n, alpha, x, incx); +} + +void CUBLASWINAPI cublasZscal (int n, cuDoubleComplex alpha, cuDoubleComplex *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(int, cuDoubleComplex, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZscal"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZscal"); + return func_ptr(n, alpha, x, incx); +} + +void CUBLASWINAPI cublasCsscal (int n, float alpha, cuComplex *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(int, float, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCsscal"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCsscal"); + return func_ptr(n, alpha, x, incx); +} + +void CUBLASWINAPI cublasZdscal (int n, double alpha, cuDoubleComplex *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(int, double, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZdscal"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZdscal"); + return func_ptr(n, alpha, x, incx); +} + +void CUBLASWINAPI cublasSaxpy (int n, float alpha, const float *x, int incx, + float *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(int, float, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasSaxpy"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSaxpy"); + return func_ptr(n, alpha, x, incx, y, incy); +} + +void CUBLASWINAPI cublasDaxpy (int n, double alpha, const double *x, + int incx, double *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(int, double, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDaxpy"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDaxpy"); + return func_ptr(n, alpha, x, incx, y, incy); +} + +void CUBLASWINAPI cublasCaxpy (int n, cuComplex alpha, const cuComplex *x, + int incx, cuComplex *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(int, cuComplex, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCaxpy"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCaxpy"); + return func_ptr(n, alpha, x, incx, y, incy); +} + +void CUBLASWINAPI cublasZaxpy (int n, cuDoubleComplex alpha, const cuDoubleComplex *x, + int incx, cuDoubleComplex *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(int, cuDoubleComplex, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZaxpy"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZaxpy"); + return func_ptr(n, alpha, x, incx, y, incy); +} + +void CUBLASWINAPI cublasScopy (int n, const float *x, int incx, float *y, + int incy) { + using FuncPtr = void (CUBLASWINAPI *)(int, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasScopy"); + if (!func_ptr) LogFatalSymbolNotFound("cublasScopy"); + return func_ptr(n, x, incx, y, incy); +} + +void CUBLASWINAPI cublasDcopy (int n, const double *x, int incx, double *y, + int incy) { + using FuncPtr = void (CUBLASWINAPI *)(int, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDcopy"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDcopy"); + return func_ptr(n, x, incx, y, incy); +} + +void CUBLASWINAPI cublasCcopy (int n, const cuComplex *x, int incx, cuComplex *y, + int incy) { + using FuncPtr = void (CUBLASWINAPI *)(int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCcopy"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCcopy"); + return func_ptr(n, x, incx, y, incy); +} + +void CUBLASWINAPI cublasZcopy (int n, const cuDoubleComplex *x, int incx, cuDoubleComplex *y, + int incy) { + using FuncPtr = void (CUBLASWINAPI *)(int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZcopy"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZcopy"); + return func_ptr(n, x, incx, y, incy); +} + +void CUBLASWINAPI cublasSswap (int n, float *x, int incx, float *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(int, float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasSswap"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSswap"); + return func_ptr(n, x, incx, y, incy); +} + +void CUBLASWINAPI cublasDswap (int n, double *x, int incx, double *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(int, double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDswap"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDswap"); + return func_ptr(n, x, incx, y, incy); +} + +void CUBLASWINAPI cublasCswap (int n, cuComplex *x, int incx, cuComplex *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(int, cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCswap"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCswap"); + return func_ptr(n, x, incx, y, incy); +} + +void CUBLASWINAPI cublasZswap (int n, cuDoubleComplex *x, int incx, cuDoubleComplex *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(int, cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZswap"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZswap"); + return func_ptr(n, x, incx, y, incy); +} + +int CUBLASWINAPI cublasIsamax (int n, const float *x, int incx) { + using FuncPtr = int (CUBLASWINAPI *)(int, const float *, int); + static auto func_ptr = LoadSymbol("cublasIsamax"); + if (!func_ptr) LogFatalSymbolNotFound("cublasIsamax"); + return func_ptr(n, x, incx); +} + +int CUBLASWINAPI cublasIdamax (int n, const double *x, int incx) { + using FuncPtr = int (CUBLASWINAPI *)(int, const double *, int); + static auto func_ptr = LoadSymbol("cublasIdamax"); + if (!func_ptr) LogFatalSymbolNotFound("cublasIdamax"); + return func_ptr(n, x, incx); +} + +int CUBLASWINAPI cublasIcamax (int n, const cuComplex *x, int incx) { + using FuncPtr = int (CUBLASWINAPI *)(int, const cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasIcamax"); + if (!func_ptr) LogFatalSymbolNotFound("cublasIcamax"); + return func_ptr(n, x, incx); +} + +int CUBLASWINAPI cublasIzamax (int n, const cuDoubleComplex *x, int incx) { + using FuncPtr = int (CUBLASWINAPI *)(int, const cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasIzamax"); + if (!func_ptr) LogFatalSymbolNotFound("cublasIzamax"); + return func_ptr(n, x, incx); +} + +int CUBLASWINAPI cublasIsamin (int n, const float *x, int incx) { + using FuncPtr = int (CUBLASWINAPI *)(int, const float *, int); + static auto func_ptr = LoadSymbol("cublasIsamin"); + if (!func_ptr) LogFatalSymbolNotFound("cublasIsamin"); + return func_ptr(n, x, incx); +} + +int CUBLASWINAPI cublasIdamin (int n, const double *x, int incx) { + using FuncPtr = int (CUBLASWINAPI *)(int, const double *, int); + static auto func_ptr = LoadSymbol("cublasIdamin"); + if (!func_ptr) LogFatalSymbolNotFound("cublasIdamin"); + return func_ptr(n, x, incx); +} + +int CUBLASWINAPI cublasIcamin (int n, const cuComplex *x, int incx) { + using FuncPtr = int (CUBLASWINAPI *)(int, const cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasIcamin"); + if (!func_ptr) LogFatalSymbolNotFound("cublasIcamin"); + return func_ptr(n, x, incx); +} + +int CUBLASWINAPI cublasIzamin (int n, const cuDoubleComplex *x, int incx) { + using FuncPtr = int (CUBLASWINAPI *)(int, const cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasIzamin"); + if (!func_ptr) LogFatalSymbolNotFound("cublasIzamin"); + return func_ptr(n, x, incx); +} + +float CUBLASWINAPI cublasSasum (int n, const float *x, int incx) { + using FuncPtr = float (CUBLASWINAPI *)(int, const float *, int); + static auto func_ptr = LoadSymbol("cublasSasum"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSasum"); + return func_ptr(n, x, incx); +} + +double CUBLASWINAPI cublasDasum (int n, const double *x, int incx) { + using FuncPtr = double (CUBLASWINAPI *)(int, const double *, int); + static auto func_ptr = LoadSymbol("cublasDasum"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDasum"); + return func_ptr(n, x, incx); +} + +float CUBLASWINAPI cublasScasum (int n, const cuComplex *x, int incx) { + using FuncPtr = float (CUBLASWINAPI *)(int, const cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasScasum"); + if (!func_ptr) LogFatalSymbolNotFound("cublasScasum"); + return func_ptr(n, x, incx); +} + +double CUBLASWINAPI cublasDzasum (int n, const cuDoubleComplex *x, int incx) { + using FuncPtr = double (CUBLASWINAPI *)(int, const cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasDzasum"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDzasum"); + return func_ptr(n, x, incx); +} + +void CUBLASWINAPI cublasSrot (int n, float *x, int incx, float *y, int incy, + float sc, float ss) { + using FuncPtr = void (CUBLASWINAPI *)(int, float *, int, float *, int, float, float); + static auto func_ptr = LoadSymbol("cublasSrot"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSrot"); + return func_ptr(n, x, incx, y, incy, sc, ss); +} + +void CUBLASWINAPI cublasDrot (int n, double *x, int incx, double *y, int incy, + double sc, double ss) { + using FuncPtr = void (CUBLASWINAPI *)(int, double *, int, double *, int, double, double); + static auto func_ptr = LoadSymbol("cublasDrot"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDrot"); + return func_ptr(n, x, incx, y, incy, sc, ss); +} + +void CUBLASWINAPI cublasCrot (int n, cuComplex *x, int incx, cuComplex *y, + int incy, float c, cuComplex s) { + using FuncPtr = void (CUBLASWINAPI *)(int, cuComplex *, int, cuComplex *, int, float, cuComplex); + static auto func_ptr = LoadSymbol("cublasCrot"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCrot"); + return func_ptr(n, x, incx, y, incy, c, s); +} + +void CUBLASWINAPI cublasZrot (int n, cuDoubleComplex *x, int incx, + cuDoubleComplex *y, int incy, double sc, + cuDoubleComplex cs) { + using FuncPtr = void (CUBLASWINAPI *)(int, cuDoubleComplex *, int, cuDoubleComplex *, int, double, cuDoubleComplex); + static auto func_ptr = LoadSymbol("cublasZrot"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZrot"); + return func_ptr(n, x, incx, y, incy, sc, cs); +} + +void CUBLASWINAPI cublasCsrot (int n, cuComplex *x, int incx, cuComplex *y, + int incy, float c, float s) { + using FuncPtr = void (CUBLASWINAPI *)(int, cuComplex *, int, cuComplex *, int, float, float); + static auto func_ptr = LoadSymbol("cublasCsrot"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCsrot"); + return func_ptr(n, x, incx, y, incy, c, s); +} + +void CUBLASWINAPI cublasZdrot (int n, cuDoubleComplex *x, int incx, + cuDoubleComplex *y, int incy, double c, double s) { + using FuncPtr = void (CUBLASWINAPI *)(int, cuDoubleComplex *, int, cuDoubleComplex *, int, double, double); + static auto func_ptr = LoadSymbol("cublasZdrot"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZdrot"); + return func_ptr(n, x, incx, y, incy, c, s); +} + +void CUBLASWINAPI cublasSrotg (float *sa, float *sb, float *sc, float *ss) { + using FuncPtr = void (CUBLASWINAPI *)(float *, float *, float *, float *); + static auto func_ptr = LoadSymbol("cublasSrotg"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSrotg"); + return func_ptr(sa, sb, sc, ss); +} + +void CUBLASWINAPI cublasDrotg (double *sa, double *sb, double *sc, double *ss) { + using FuncPtr = void (CUBLASWINAPI *)(double *, double *, double *, double *); + static auto func_ptr = LoadSymbol("cublasDrotg"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDrotg"); + return func_ptr(sa, sb, sc, ss); +} + +void CUBLASWINAPI cublasCrotg (cuComplex *ca, cuComplex cb, float *sc, + cuComplex *cs) { + using FuncPtr = void (CUBLASWINAPI *)(cuComplex *, cuComplex, float *, cuComplex *); + static auto func_ptr = LoadSymbol("cublasCrotg"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCrotg"); + return func_ptr(ca, cb, sc, cs); +} + +void CUBLASWINAPI cublasZrotg (cuDoubleComplex *ca, cuDoubleComplex cb, double *sc, + cuDoubleComplex *cs) { + using FuncPtr = void (CUBLASWINAPI *)(cuDoubleComplex *, cuDoubleComplex, double *, cuDoubleComplex *); + static auto func_ptr = LoadSymbol("cublasZrotg"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZrotg"); + return func_ptr(ca, cb, sc, cs); +} + +void CUBLASWINAPI cublasSrotm(int n, float *x, int incx, float *y, int incy, + const float* sparam) { + using FuncPtr = void (CUBLASWINAPI *)(int, float *, int, float *, int, const float *); + static auto func_ptr = LoadSymbol("cublasSrotm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSrotm"); + return func_ptr(n, x, incx, y, incy, sparam); +} + +void CUBLASWINAPI cublasDrotm(int n, double *x, int incx, double *y, int incy, + const double* sparam) { + using FuncPtr = void (CUBLASWINAPI *)(int, double *, int, double *, int, const double *); + static auto func_ptr = LoadSymbol("cublasDrotm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDrotm"); + return func_ptr(n, x, incx, y, incy, sparam); +} + +void CUBLASWINAPI cublasSrotmg (float *sd1, float *sd2, float *sx1, + const float *sy1, float* sparam) { + using FuncPtr = void (CUBLASWINAPI *)(float *, float *, float *, const float *, float *); + static auto func_ptr = LoadSymbol("cublasSrotmg"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSrotmg"); + return func_ptr(sd1, sd2, sx1, sy1, sparam); +} + +void CUBLASWINAPI cublasDrotmg (double *sd1, double *sd2, double *sx1, + const double *sy1, double* sparam) { + using FuncPtr = void (CUBLASWINAPI *)(double *, double *, double *, const double *, double *); + static auto func_ptr = LoadSymbol("cublasDrotmg"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDrotmg"); + return func_ptr(sd1, sd2, sx1, sy1, sparam); +} + +void CUBLASWINAPI cublasSgemv (char trans, int m, int n, float alpha, + const float *A, int lda, const float *x, int incx, + float beta, float *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, int, float, const float *, int, const float *, int, float, float *, int); + static auto func_ptr = LoadSymbol("cublasSgemv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSgemv"); + return func_ptr(trans, m, n, alpha, A, lda, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasDgemv (char trans, int m, int n, double alpha, + const double *A, int lda, const double *x, int incx, + double beta, double *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, int, double, const double *, int, const double *, int, double, double *, int); + static auto func_ptr = LoadSymbol("cublasDgemv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDgemv"); + return func_ptr(trans, m, n, alpha, A, lda, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasCgemv (char trans, int m, int n, cuComplex alpha, + const cuComplex *A, int lda, const cuComplex *x, int incx, + cuComplex beta, cuComplex *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, int, cuComplex, const cuComplex *, int, const cuComplex *, int, cuComplex, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCgemv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCgemv"); + return func_ptr(trans, m, n, alpha, A, lda, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasZgemv (char trans, int m, int n, cuDoubleComplex alpha, + const cuDoubleComplex *A, int lda, const cuDoubleComplex *x, int incx, + cuDoubleComplex beta, cuDoubleComplex *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, int, cuDoubleComplex, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZgemv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZgemv"); + return func_ptr(trans, m, n, alpha, A, lda, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasSgbmv (char trans, int m, int n, int kl, int ku, + float alpha, const float *A, int lda, + const float *x, int incx, float beta, float *y, + int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, int, int, int, float, const float *, int, const float *, int, float, float *, int); + static auto func_ptr = LoadSymbol("cublasSgbmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSgbmv"); + return func_ptr(trans, m, n, kl, ku, alpha, A, lda, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasDgbmv (char trans, int m, int n, int kl, int ku, + double alpha, const double *A, int lda, + const double *x, int incx, double beta, double *y, + int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, int, int, int, double, const double *, int, const double *, int, double, double *, int); + static auto func_ptr = LoadSymbol("cublasDgbmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDgbmv"); + return func_ptr(trans, m, n, kl, ku, alpha, A, lda, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasCgbmv (char trans, int m, int n, int kl, int ku, + cuComplex alpha, const cuComplex *A, int lda, + const cuComplex *x, int incx, cuComplex beta, cuComplex *y, + int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, int, int, int, cuComplex, const cuComplex *, int, const cuComplex *, int, cuComplex, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCgbmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCgbmv"); + return func_ptr(trans, m, n, kl, ku, alpha, A, lda, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasZgbmv (char trans, int m, int n, int kl, int ku, + cuDoubleComplex alpha, const cuDoubleComplex *A, int lda, + const cuDoubleComplex *x, int incx, cuDoubleComplex beta, cuDoubleComplex *y, + int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, int, int, int, cuDoubleComplex, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZgbmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZgbmv"); + return func_ptr(trans, m, n, kl, ku, alpha, A, lda, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasStrmv (char uplo, char trans, char diag, int n, + const float *A, int lda, float *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasStrmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasStrmv"); + return func_ptr(uplo, trans, diag, n, A, lda, x, incx); +} + +void CUBLASWINAPI cublasDtrmv (char uplo, char trans, char diag, int n, + const double *A, int lda, double *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDtrmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDtrmv"); + return func_ptr(uplo, trans, diag, n, A, lda, x, incx); +} + +void CUBLASWINAPI cublasCtrmv (char uplo, char trans, char diag, int n, + const cuComplex *A, int lda, cuComplex *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtrmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCtrmv"); + return func_ptr(uplo, trans, diag, n, A, lda, x, incx); +} + +void CUBLASWINAPI cublasZtrmv (char uplo, char trans, char diag, int n, + const cuDoubleComplex *A, int lda, cuDoubleComplex *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtrmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZtrmv"); + return func_ptr(uplo, trans, diag, n, A, lda, x, incx); +} + +void CUBLASWINAPI cublasStbmv (char uplo, char trans, char diag, int n, int k, + const float *A, int lda, float *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, int, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasStbmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasStbmv"); + return func_ptr(uplo, trans, diag, n, k, A, lda, x, incx); +} + +void CUBLASWINAPI cublasDtbmv (char uplo, char trans, char diag, int n, int k, + const double *A, int lda, double *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, int, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDtbmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDtbmv"); + return func_ptr(uplo, trans, diag, n, k, A, lda, x, incx); +} + +void CUBLASWINAPI cublasCtbmv (char uplo, char trans, char diag, int n, int k, + const cuComplex *A, int lda, cuComplex *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtbmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCtbmv"); + return func_ptr(uplo, trans, diag, n, k, A, lda, x, incx); +} + +void CUBLASWINAPI cublasZtbmv (char uplo, char trans, char diag, int n, int k, + const cuDoubleComplex *A, int lda, cuDoubleComplex *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtbmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZtbmv"); + return func_ptr(uplo, trans, diag, n, k, A, lda, x, incx); +} + +void CUBLASWINAPI cublasStpmv(char uplo, char trans, char diag, int n, const float *AP, float *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, const float *, float *, int); + static auto func_ptr = LoadSymbol("cublasStpmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasStpmv"); + return func_ptr(uplo, trans, diag, n, AP, x, incx); +} + +void CUBLASWINAPI cublasDtpmv(char uplo, char trans, char diag, int n, const double *AP, double *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, const double *, double *, int); + static auto func_ptr = LoadSymbol("cublasDtpmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDtpmv"); + return func_ptr(uplo, trans, diag, n, AP, x, incx); +} + +void CUBLASWINAPI cublasCtpmv(char uplo, char trans, char diag, int n, const cuComplex *AP, cuComplex *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtpmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCtpmv"); + return func_ptr(uplo, trans, diag, n, AP, x, incx); +} + +void CUBLASWINAPI cublasZtpmv(char uplo, char trans, char diag, int n, const cuDoubleComplex *AP, cuDoubleComplex *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtpmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZtpmv"); + return func_ptr(uplo, trans, diag, n, AP, x, incx); +} + +void CUBLASWINAPI cublasStrsv(char uplo, char trans, char diag, int n, const float *A, int lda, float *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasStrsv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasStrsv"); + return func_ptr(uplo, trans, diag, n, A, lda, x, incx); +} + +void CUBLASWINAPI cublasDtrsv(char uplo, char trans, char diag, int n, const double *A, int lda, double *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDtrsv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDtrsv"); + return func_ptr(uplo, trans, diag, n, A, lda, x, incx); +} + +void CUBLASWINAPI cublasCtrsv(char uplo, char trans, char diag, int n, const cuComplex *A, int lda, cuComplex *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtrsv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCtrsv"); + return func_ptr(uplo, trans, diag, n, A, lda, x, incx); +} + +void CUBLASWINAPI cublasZtrsv(char uplo, char trans, char diag, int n, const cuDoubleComplex *A, int lda, + cuDoubleComplex *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtrsv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZtrsv"); + return func_ptr(uplo, trans, diag, n, A, lda, x, incx); +} + +void CUBLASWINAPI cublasStpsv(char uplo, char trans, char diag, int n, const float *AP, + float *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, const float *, float *, int); + static auto func_ptr = LoadSymbol("cublasStpsv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasStpsv"); + return func_ptr(uplo, trans, diag, n, AP, x, incx); +} + +void CUBLASWINAPI cublasDtpsv(char uplo, char trans, char diag, int n, const double *AP, double *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, const double *, double *, int); + static auto func_ptr = LoadSymbol("cublasDtpsv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDtpsv"); + return func_ptr(uplo, trans, diag, n, AP, x, incx); +} + +void CUBLASWINAPI cublasCtpsv(char uplo, char trans, char diag, int n, const cuComplex *AP, cuComplex *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtpsv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCtpsv"); + return func_ptr(uplo, trans, diag, n, AP, x, incx); +} + +void CUBLASWINAPI cublasZtpsv(char uplo, char trans, char diag, int n, const cuDoubleComplex *AP, + cuDoubleComplex *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtpsv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZtpsv"); + return func_ptr(uplo, trans, diag, n, AP, x, incx); +} + +void CUBLASWINAPI cublasStbsv(char uplo, char trans, + char diag, int n, int k, const float *A, + int lda, float *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, int, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasStbsv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasStbsv"); + return func_ptr(uplo, trans, diag, n, k, A, lda, x, incx); +} + +void CUBLASWINAPI cublasDtbsv(char uplo, char trans, + char diag, int n, int k, const double *A, + int lda, double *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, int, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDtbsv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDtbsv"); + return func_ptr(uplo, trans, diag, n, k, A, lda, x, incx); +} + +void CUBLASWINAPI cublasCtbsv(char uplo, char trans, + char diag, int n, int k, const cuComplex *A, + int lda, cuComplex *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtbsv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCtbsv"); + return func_ptr(uplo, trans, diag, n, k, A, lda, x, incx); +} + +void CUBLASWINAPI cublasZtbsv(char uplo, char trans, + char diag, int n, int k, const cuDoubleComplex *A, + int lda, cuDoubleComplex *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtbsv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZtbsv"); + return func_ptr(uplo, trans, diag, n, k, A, lda, x, incx); +} + +void CUBLASWINAPI cublasSsymv (char uplo, int n, float alpha, const float *A, + int lda, const float *x, int incx, float beta, + float *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, float, const float *, int, const float *, int, float, float *, int); + static auto func_ptr = LoadSymbol("cublasSsymv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSsymv"); + return func_ptr(uplo, n, alpha, A, lda, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasDsymv (char uplo, int n, double alpha, const double *A, + int lda, const double *x, int incx, double beta, + double *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, double, const double *, int, const double *, int, double, double *, int); + static auto func_ptr = LoadSymbol("cublasDsymv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDsymv"); + return func_ptr(uplo, n, alpha, A, lda, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasChemv (char uplo, int n, cuComplex alpha, const cuComplex *A, + int lda, const cuComplex *x, int incx, cuComplex beta, + cuComplex *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, cuComplex, const cuComplex *, int, const cuComplex *, int, cuComplex, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasChemv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasChemv"); + return func_ptr(uplo, n, alpha, A, lda, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasZhemv (char uplo, int n, cuDoubleComplex alpha, const cuDoubleComplex *A, + int lda, const cuDoubleComplex *x, int incx, cuDoubleComplex beta, + cuDoubleComplex *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, cuDoubleComplex, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZhemv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZhemv"); + return func_ptr(uplo, n, alpha, A, lda, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasSsbmv (char uplo, int n, int k, float alpha, + const float *A, int lda, const float *x, int incx, + float beta, float *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, int, float, const float *, int, const float *, int, float, float *, int); + static auto func_ptr = LoadSymbol("cublasSsbmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSsbmv"); + return func_ptr(uplo, n, k, alpha, A, lda, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasDsbmv (char uplo, int n, int k, double alpha, + const double *A, int lda, const double *x, int incx, + double beta, double *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, int, double, const double *, int, const double *, int, double, double *, int); + static auto func_ptr = LoadSymbol("cublasDsbmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDsbmv"); + return func_ptr(uplo, n, k, alpha, A, lda, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasChbmv (char uplo, int n, int k, cuComplex alpha, + const cuComplex *A, int lda, const cuComplex *x, int incx, + cuComplex beta, cuComplex *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, int, cuComplex, const cuComplex *, int, const cuComplex *, int, cuComplex, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasChbmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasChbmv"); + return func_ptr(uplo, n, k, alpha, A, lda, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasZhbmv (char uplo, int n, int k, cuDoubleComplex alpha, + const cuDoubleComplex *A, int lda, const cuDoubleComplex *x, int incx, + cuDoubleComplex beta, cuDoubleComplex *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, int, cuDoubleComplex, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZhbmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZhbmv"); + return func_ptr(uplo, n, k, alpha, A, lda, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasSspmv(char uplo, int n, float alpha, + const float *AP, const float *x, + int incx, float beta, float *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, float, const float *, const float *, int, float, float *, int); + static auto func_ptr = LoadSymbol("cublasSspmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSspmv"); + return func_ptr(uplo, n, alpha, AP, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasDspmv(char uplo, int n, double alpha, + const double *AP, const double *x, + int incx, double beta, double *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, double, const double *, const double *, int, double, double *, int); + static auto func_ptr = LoadSymbol("cublasDspmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDspmv"); + return func_ptr(uplo, n, alpha, AP, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasChpmv(char uplo, int n, cuComplex alpha, + const cuComplex *AP, const cuComplex *x, + int incx, cuComplex beta, cuComplex *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, cuComplex, const cuComplex *, const cuComplex *, int, cuComplex, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasChpmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasChpmv"); + return func_ptr(uplo, n, alpha, AP, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasZhpmv(char uplo, int n, cuDoubleComplex alpha, + const cuDoubleComplex *AP, const cuDoubleComplex *x, + int incx, cuDoubleComplex beta, cuDoubleComplex *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, cuDoubleComplex, const cuDoubleComplex *, const cuDoubleComplex *, int, cuDoubleComplex, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZhpmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZhpmv"); + return func_ptr(uplo, n, alpha, AP, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasSger (int m, int n, float alpha, const float *x, int incx, + const float *y, int incy, float *A, int lda) { + using FuncPtr = void (CUBLASWINAPI *)(int, int, float, const float *, int, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasSger"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSger"); + return func_ptr(m, n, alpha, x, incx, y, incy, A, lda); +} + +void CUBLASWINAPI cublasDger (int m, int n, double alpha, const double *x, int incx, + const double *y, int incy, double *A, int lda) { + using FuncPtr = void (CUBLASWINAPI *)(int, int, double, const double *, int, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDger"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDger"); + return func_ptr(m, n, alpha, x, incx, y, incy, A, lda); +} + +void CUBLASWINAPI cublasCgeru (int m, int n, cuComplex alpha, const cuComplex *x, + int incx, const cuComplex *y, int incy, + cuComplex *A, int lda) { + using FuncPtr = void (CUBLASWINAPI *)(int, int, cuComplex, const cuComplex *, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCgeru"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCgeru"); + return func_ptr(m, n, alpha, x, incx, y, incy, A, lda); +} + +void CUBLASWINAPI cublasCgerc (int m, int n, cuComplex alpha, const cuComplex *x, + int incx, const cuComplex *y, int incy, + cuComplex *A, int lda) { + using FuncPtr = void (CUBLASWINAPI *)(int, int, cuComplex, const cuComplex *, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCgerc"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCgerc"); + return func_ptr(m, n, alpha, x, incx, y, incy, A, lda); +} + +void CUBLASWINAPI cublasZgeru (int m, int n, cuDoubleComplex alpha, const cuDoubleComplex *x, + int incx, const cuDoubleComplex *y, int incy, + cuDoubleComplex *A, int lda) { + using FuncPtr = void (CUBLASWINAPI *)(int, int, cuDoubleComplex, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZgeru"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZgeru"); + return func_ptr(m, n, alpha, x, incx, y, incy, A, lda); +} + +void CUBLASWINAPI cublasZgerc (int m, int n, cuDoubleComplex alpha, const cuDoubleComplex *x, + int incx, const cuDoubleComplex *y, int incy, + cuDoubleComplex *A, int lda) { + using FuncPtr = void (CUBLASWINAPI *)(int, int, cuDoubleComplex, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZgerc"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZgerc"); + return func_ptr(m, n, alpha, x, incx, y, incy, A, lda); +} + +void CUBLASWINAPI cublasSsyr (char uplo, int n, float alpha, const float *x, + int incx, float *A, int lda) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, float, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasSsyr"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSsyr"); + return func_ptr(uplo, n, alpha, x, incx, A, lda); +} + +void CUBLASWINAPI cublasDsyr (char uplo, int n, double alpha, const double *x, + int incx, double *A, int lda) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, double, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDsyr"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDsyr"); + return func_ptr(uplo, n, alpha, x, incx, A, lda); +} + +void CUBLASWINAPI cublasCher (char uplo, int n, float alpha, + const cuComplex *x, int incx, cuComplex *A, int lda) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, float, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCher"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCher"); + return func_ptr(uplo, n, alpha, x, incx, A, lda); +} + +void CUBLASWINAPI cublasZher (char uplo, int n, double alpha, + const cuDoubleComplex *x, int incx, cuDoubleComplex *A, int lda) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, double, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZher"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZher"); + return func_ptr(uplo, n, alpha, x, incx, A, lda); +} + +void CUBLASWINAPI cublasSspr (char uplo, int n, float alpha, const float *x, + int incx, float *AP) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, float, const float *, int, float *); + static auto func_ptr = LoadSymbol("cublasSspr"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSspr"); + return func_ptr(uplo, n, alpha, x, incx, AP); +} + +void CUBLASWINAPI cublasDspr (char uplo, int n, double alpha, const double *x, + int incx, double *AP) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, double, const double *, int, double *); + static auto func_ptr = LoadSymbol("cublasDspr"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDspr"); + return func_ptr(uplo, n, alpha, x, incx, AP); +} + +void CUBLASWINAPI cublasChpr (char uplo, int n, float alpha, const cuComplex *x, + int incx, cuComplex *AP) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, float, const cuComplex *, int, cuComplex *); + static auto func_ptr = LoadSymbol("cublasChpr"); + if (!func_ptr) LogFatalSymbolNotFound("cublasChpr"); + return func_ptr(uplo, n, alpha, x, incx, AP); +} + +void CUBLASWINAPI cublasZhpr (char uplo, int n, double alpha, const cuDoubleComplex *x, + int incx, cuDoubleComplex *AP) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, double, const cuDoubleComplex *, int, cuDoubleComplex *); + static auto func_ptr = LoadSymbol("cublasZhpr"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZhpr"); + return func_ptr(uplo, n, alpha, x, incx, AP); +} + +void CUBLASWINAPI cublasSsyr2 (char uplo, int n, float alpha, const float *x, + int incx, const float *y, int incy, float *A, + int lda) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, float, const float *, int, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasSsyr2"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSsyr2"); + return func_ptr(uplo, n, alpha, x, incx, y, incy, A, lda); +} + +void CUBLASWINAPI cublasDsyr2 (char uplo, int n, double alpha, const double *x, + int incx, const double *y, int incy, double *A, + int lda) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, double, const double *, int, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDsyr2"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDsyr2"); + return func_ptr(uplo, n, alpha, x, incx, y, incy, A, lda); +} + +void CUBLASWINAPI cublasCher2 (char uplo, int n, cuComplex alpha, const cuComplex *x, + int incx, const cuComplex *y, int incy, cuComplex *A, + int lda) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, cuComplex, const cuComplex *, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCher2"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCher2"); + return func_ptr(uplo, n, alpha, x, incx, y, incy, A, lda); +} + +void CUBLASWINAPI cublasZher2 (char uplo, int n, cuDoubleComplex alpha, const cuDoubleComplex *x, + int incx, const cuDoubleComplex *y, int incy, cuDoubleComplex *A, + int lda) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, cuDoubleComplex, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZher2"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZher2"); + return func_ptr(uplo, n, alpha, x, incx, y, incy, A, lda); +} + +void CUBLASWINAPI cublasSspr2 (char uplo, int n, float alpha, const float *x, + int incx, const float *y, int incy, float *AP) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, float, const float *, int, const float *, int, float *); + static auto func_ptr = LoadSymbol("cublasSspr2"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSspr2"); + return func_ptr(uplo, n, alpha, x, incx, y, incy, AP); +} + +void CUBLASWINAPI cublasDspr2 (char uplo, int n, double alpha, + const double *x, int incx, const double *y, + int incy, double *AP) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, double, const double *, int, const double *, int, double *); + static auto func_ptr = LoadSymbol("cublasDspr2"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDspr2"); + return func_ptr(uplo, n, alpha, x, incx, y, incy, AP); +} + +void CUBLASWINAPI cublasChpr2 (char uplo, int n, cuComplex alpha, + const cuComplex *x, int incx, const cuComplex *y, + int incy, cuComplex *AP) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, cuComplex, const cuComplex *, int, const cuComplex *, int, cuComplex *); + static auto func_ptr = LoadSymbol("cublasChpr2"); + if (!func_ptr) LogFatalSymbolNotFound("cublasChpr2"); + return func_ptr(uplo, n, alpha, x, incx, y, incy, AP); +} + +void CUBLASWINAPI cublasZhpr2 (char uplo, int n, cuDoubleComplex alpha, + const cuDoubleComplex *x, int incx, const cuDoubleComplex *y, + int incy, cuDoubleComplex *AP) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, cuDoubleComplex, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex *); + static auto func_ptr = LoadSymbol("cublasZhpr2"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZhpr2"); + return func_ptr(uplo, n, alpha, x, incx, y, incy, AP); +} + +void CUBLASWINAPI cublasSgemm (char transa, char transb, int m, int n, int k, + float alpha, const float *A, int lda, + const float *B, int ldb, float beta, float *C, + int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, int, float, const float *, int, const float *, int, float, float *, int); + static auto func_ptr = LoadSymbol("cublasSgemm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSgemm"); + return func_ptr(transa, transb, m, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +void CUBLASWINAPI cublasDgemm (char transa, char transb, int m, int n, int k, + double alpha, const double *A, int lda, + const double *B, int ldb, double beta, double *C, + int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, int, double, const double *, int, const double *, int, double, double *, int); + static auto func_ptr = LoadSymbol("cublasDgemm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDgemm"); + return func_ptr(transa, transb, m, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +void CUBLASWINAPI cublasCgemm (char transa, char transb, int m, int n, int k, + cuComplex alpha, const cuComplex *A, int lda, + const cuComplex *B, int ldb, cuComplex beta, + cuComplex *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, int, cuComplex, const cuComplex *, int, const cuComplex *, int, cuComplex, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCgemm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCgemm"); + return func_ptr(transa, transb, m, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +void CUBLASWINAPI cublasZgemm (char transa, char transb, int m, int n, + int k, cuDoubleComplex alpha, + const cuDoubleComplex *A, int lda, + const cuDoubleComplex *B, int ldb, + cuDoubleComplex beta, cuDoubleComplex *C, + int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, int, cuDoubleComplex, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZgemm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZgemm"); + return func_ptr(transa, transb, m, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +void CUBLASWINAPI cublasSsyrk (char uplo, char trans, int n, int k, float alpha, + const float *A, int lda, float beta, float *C, + int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, float, const float *, int, float, float *, int); + static auto func_ptr = LoadSymbol("cublasSsyrk"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSsyrk"); + return func_ptr(uplo, trans, n, k, alpha, A, lda, beta, C, ldc); +} + +void CUBLASWINAPI cublasDsyrk (char uplo, char trans, int n, int k, + double alpha, const double *A, int lda, + double beta, double *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, double, const double *, int, double, double *, int); + static auto func_ptr = LoadSymbol("cublasDsyrk"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDsyrk"); + return func_ptr(uplo, trans, n, k, alpha, A, lda, beta, C, ldc); +} + +void CUBLASWINAPI cublasCsyrk (char uplo, char trans, int n, int k, + cuComplex alpha, const cuComplex *A, int lda, + cuComplex beta, cuComplex *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, cuComplex, const cuComplex *, int, cuComplex, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCsyrk"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCsyrk"); + return func_ptr(uplo, trans, n, k, alpha, A, lda, beta, C, ldc); +} + +void CUBLASWINAPI cublasZsyrk (char uplo, char trans, int n, int k, + cuDoubleComplex alpha, + const cuDoubleComplex *A, int lda, + cuDoubleComplex beta, + cuDoubleComplex *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, cuDoubleComplex, const cuDoubleComplex *, int, cuDoubleComplex, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZsyrk"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZsyrk"); + return func_ptr(uplo, trans, n, k, alpha, A, lda, beta, C, ldc); +} + +void CUBLASWINAPI cublasCherk (char uplo, char trans, int n, int k, + float alpha, const cuComplex *A, int lda, + float beta, cuComplex *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, float, const cuComplex *, int, float, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCherk"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCherk"); + return func_ptr(uplo, trans, n, k, alpha, A, lda, beta, C, ldc); +} + +void CUBLASWINAPI cublasZherk (char uplo, char trans, int n, int k, + double alpha, + const cuDoubleComplex *A, int lda, + double beta, + cuDoubleComplex *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, double, const cuDoubleComplex *, int, double, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZherk"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZherk"); + return func_ptr(uplo, trans, n, k, alpha, A, lda, beta, C, ldc); +} + +void CUBLASWINAPI cublasSsyr2k (char uplo, char trans, int n, int k, float alpha, + const float *A, int lda, const float *B, int ldb, + float beta, float *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, float, const float *, int, const float *, int, float, float *, int); + static auto func_ptr = LoadSymbol("cublasSsyr2k"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSsyr2k"); + return func_ptr(uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +void CUBLASWINAPI cublasDsyr2k (char uplo, char trans, int n, int k, + double alpha, const double *A, int lda, + const double *B, int ldb, double beta, + double *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, double, const double *, int, const double *, int, double, double *, int); + static auto func_ptr = LoadSymbol("cublasDsyr2k"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDsyr2k"); + return func_ptr(uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +void CUBLASWINAPI cublasCsyr2k (char uplo, char trans, int n, int k, + cuComplex alpha, const cuComplex *A, int lda, + const cuComplex *B, int ldb, cuComplex beta, + cuComplex *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, cuComplex, const cuComplex *, int, const cuComplex *, int, cuComplex, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCsyr2k"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCsyr2k"); + return func_ptr(uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +void CUBLASWINAPI cublasZsyr2k (char uplo, char trans, int n, int k, + cuDoubleComplex alpha, const cuDoubleComplex *A, int lda, + const cuDoubleComplex *B, int ldb, cuDoubleComplex beta, + cuDoubleComplex *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, cuDoubleComplex, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZsyr2k"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZsyr2k"); + return func_ptr(uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +void CUBLASWINAPI cublasCher2k (char uplo, char trans, int n, int k, + cuComplex alpha, const cuComplex *A, int lda, + const cuComplex *B, int ldb, float beta, + cuComplex *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, cuComplex, const cuComplex *, int, const cuComplex *, int, float, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCher2k"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCher2k"); + return func_ptr(uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +void CUBLASWINAPI cublasZher2k (char uplo, char trans, int n, int k, + cuDoubleComplex alpha, const cuDoubleComplex *A, int lda, + const cuDoubleComplex *B, int ldb, double beta, + cuDoubleComplex *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, cuDoubleComplex, const cuDoubleComplex *, int, const cuDoubleComplex *, int, double, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZher2k"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZher2k"); + return func_ptr(uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +void CUBLASWINAPI cublasSsymm (char side, char uplo, int m, int n, float alpha, + const float *A, int lda, const float *B, int ldb, + float beta, float *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, float, const float *, int, const float *, int, float, float *, int); + static auto func_ptr = LoadSymbol("cublasSsymm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSsymm"); + return func_ptr(side, uplo, m, n, alpha, A, lda, B, ldb, beta, C, ldc); +} + +void CUBLASWINAPI cublasDsymm (char side, char uplo, int m, int n, double alpha, + const double *A, int lda, const double *B, int ldb, + double beta, double *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, double, const double *, int, const double *, int, double, double *, int); + static auto func_ptr = LoadSymbol("cublasDsymm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDsymm"); + return func_ptr(side, uplo, m, n, alpha, A, lda, B, ldb, beta, C, ldc); +} + +void CUBLASWINAPI cublasCsymm (char side, char uplo, int m, int n, cuComplex alpha, + const cuComplex *A, int lda, const cuComplex *B, int ldb, + cuComplex beta, cuComplex *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, cuComplex, const cuComplex *, int, const cuComplex *, int, cuComplex, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCsymm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCsymm"); + return func_ptr(side, uplo, m, n, alpha, A, lda, B, ldb, beta, C, ldc); +} + +void CUBLASWINAPI cublasZsymm (char side, char uplo, int m, int n, cuDoubleComplex alpha, + const cuDoubleComplex *A, int lda, const cuDoubleComplex *B, int ldb, + cuDoubleComplex beta, cuDoubleComplex *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, cuDoubleComplex, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZsymm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZsymm"); + return func_ptr(side, uplo, m, n, alpha, A, lda, B, ldb, beta, C, ldc); +} + +void CUBLASWINAPI cublasChemm (char side, char uplo, int m, int n, + cuComplex alpha, const cuComplex *A, int lda, + const cuComplex *B, int ldb, cuComplex beta, + cuComplex *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, cuComplex, const cuComplex *, int, const cuComplex *, int, cuComplex, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasChemm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasChemm"); + return func_ptr(side, uplo, m, n, alpha, A, lda, B, ldb, beta, C, ldc); +} + +void CUBLASWINAPI cublasZhemm (char side, char uplo, int m, int n, + cuDoubleComplex alpha, const cuDoubleComplex *A, int lda, + const cuDoubleComplex *B, int ldb, cuDoubleComplex beta, + cuDoubleComplex *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, cuDoubleComplex, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZhemm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZhemm"); + return func_ptr(side, uplo, m, n, alpha, A, lda, B, ldb, beta, C, ldc); +} + +void CUBLASWINAPI cublasStrsm (char side, char uplo, char transa, char diag, + int m, int n, float alpha, const float *A, int lda, + float *B, int ldb) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, char, int, int, float, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasStrsm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasStrsm"); + return func_ptr(side, uplo, transa, diag, m, n, alpha, A, lda, B, ldb); +} + +void CUBLASWINAPI cublasDtrsm (char side, char uplo, char transa, + char diag, int m, int n, double alpha, + const double *A, int lda, double *B, + int ldb) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, char, int, int, double, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDtrsm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDtrsm"); + return func_ptr(side, uplo, transa, diag, m, n, alpha, A, lda, B, ldb); +} + +void CUBLASWINAPI cublasCtrsm (char side, char uplo, char transa, char diag, + int m, int n, cuComplex alpha, const cuComplex *A, + int lda, cuComplex *B, int ldb) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, char, int, int, cuComplex, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtrsm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCtrsm"); + return func_ptr(side, uplo, transa, diag, m, n, alpha, A, lda, B, ldb); +} + +void CUBLASWINAPI cublasZtrsm (char side, char uplo, char transa, + char diag, int m, int n, cuDoubleComplex alpha, + const cuDoubleComplex *A, int lda, + cuDoubleComplex *B, int ldb) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, char, int, int, cuDoubleComplex, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtrsm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZtrsm"); + return func_ptr(side, uplo, transa, diag, m, n, alpha, A, lda, B, ldb); +} + +void CUBLASWINAPI cublasStrmm (char side, char uplo, char transa, char diag, + int m, int n, float alpha, const float *A, int lda, + float *B, int ldb) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, char, int, int, float, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasStrmm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasStrmm"); + return func_ptr(side, uplo, transa, diag, m, n, alpha, A, lda, B, ldb); +} + +void CUBLASWINAPI cublasDtrmm (char side, char uplo, char transa, + char diag, int m, int n, double alpha, + const double *A, int lda, double *B, + int ldb) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, char, int, int, double, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDtrmm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDtrmm"); + return func_ptr(side, uplo, transa, diag, m, n, alpha, A, lda, B, ldb); +} + +void CUBLASWINAPI cublasCtrmm (char side, char uplo, char transa, char diag, + int m, int n, cuComplex alpha, const cuComplex *A, + int lda, cuComplex *B, int ldb) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, char, int, int, cuComplex, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtrmm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCtrmm"); + return func_ptr(side, uplo, transa, diag, m, n, alpha, A, lda, B, ldb); +} + +void CUBLASWINAPI cublasZtrmm (char side, char uplo, char transa, + char diag, int m, int n, cuDoubleComplex alpha, + const cuDoubleComplex *A, int lda, cuDoubleComplex *B, + int ldb) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, char, int, int, cuDoubleComplex, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtrmm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZtrmm"); + return func_ptr(side, uplo, transa, diag, m, n, alpha, A, lda, B, ldb); +} + +} // extern "C" diff --git a/tensorflow/stream_executor/cuda/cublas_9_0.inc b/tensorflow/stream_executor/cuda/cublas_9_0.inc new file mode 100644 index 0000000000..ba46426878 --- /dev/null +++ b/tensorflow/stream_executor/cuda/cublas_9_0.inc @@ -0,0 +1,5124 @@ +// Auto-generated, do not edit. + +extern "C" { + +cublasStatus_t CUBLASWINAPI cublasCreate_v2 (cublasHandle_t *handle) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t *); + static auto func_ptr = LoadSymbol("cublasCreate_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle); +} + +cublasStatus_t CUBLASWINAPI cublasDestroy_v2 (cublasHandle_t handle) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t); + static auto func_ptr = LoadSymbol("cublasDestroy_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle); +} + +cublasStatus_t CUBLASWINAPI cublasGetVersion_v2(cublasHandle_t handle, int *version) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int *); + static auto func_ptr = LoadSymbol("cublasGetVersion_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, version); +} + +cublasStatus_t CUBLASWINAPI cublasGetProperty(libraryPropertyType type, int *value) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(libraryPropertyType, int *); + static auto func_ptr = LoadSymbol("cublasGetProperty"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(type, value); +} + +cublasStatus_t CUBLASWINAPI cublasSetStream_v2 (cublasHandle_t handle, cudaStream_t streamId) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cudaStream_t); + static auto func_ptr = LoadSymbol("cublasSetStream_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, streamId); +} + +cublasStatus_t CUBLASWINAPI cublasGetStream_v2 (cublasHandle_t handle, cudaStream_t *streamId) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cudaStream_t *); + static auto func_ptr = LoadSymbol("cublasGetStream_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, streamId); +} + +cublasStatus_t CUBLASWINAPI cublasGetPointerMode_v2 (cublasHandle_t handle, cublasPointerMode_t *mode) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasPointerMode_t *); + static auto func_ptr = LoadSymbol("cublasGetPointerMode_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode); +} + +cublasStatus_t CUBLASWINAPI cublasSetPointerMode_v2 (cublasHandle_t handle, cublasPointerMode_t mode) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasPointerMode_t); + static auto func_ptr = LoadSymbol("cublasSetPointerMode_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode); +} + +cublasStatus_t CUBLASWINAPI cublasGetAtomicsMode(cublasHandle_t handle, cublasAtomicsMode_t *mode) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasAtomicsMode_t *); + static auto func_ptr = LoadSymbol("cublasGetAtomicsMode"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode); +} + +cublasStatus_t CUBLASWINAPI cublasSetAtomicsMode(cublasHandle_t handle, cublasAtomicsMode_t mode) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasAtomicsMode_t); + static auto func_ptr = LoadSymbol("cublasSetAtomicsMode"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode); +} + +cublasStatus_t CUBLASWINAPI cublasGetMathMode(cublasHandle_t handle, cublasMath_t *mode) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasMath_t *); + static auto func_ptr = LoadSymbol("cublasGetMathMode"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode); +} + +cublasStatus_t CUBLASWINAPI cublasSetMathMode(cublasHandle_t handle, cublasMath_t mode) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasMath_t); + static auto func_ptr = LoadSymbol("cublasSetMathMode"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode); +} + +cublasStatus_t CUBLASWINAPI cublasSetVector (int n, int elemSize, const void *x, + int incx, void *devicePtr, int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(int, int, const void *, int, void *, int); + static auto func_ptr = LoadSymbol("cublasSetVector"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(n, elemSize, x, incx, devicePtr, incy); +} + +cublasStatus_t CUBLASWINAPI cublasGetVector (int n, int elemSize, const void *x, + int incx, void *y, int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(int, int, const void *, int, void *, int); + static auto func_ptr = LoadSymbol("cublasGetVector"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(n, elemSize, x, incx, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasSetMatrix (int rows, int cols, int elemSize, + const void *A, int lda, void *B, + int ldb) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(int, int, int, const void *, int, void *, int); + static auto func_ptr = LoadSymbol("cublasSetMatrix"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rows, cols, elemSize, A, lda, B, ldb); +} + +cublasStatus_t CUBLASWINAPI cublasGetMatrix (int rows, int cols, int elemSize, + const void *A, int lda, void *B, + int ldb) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(int, int, int, const void *, int, void *, int); + static auto func_ptr = LoadSymbol("cublasGetMatrix"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rows, cols, elemSize, A, lda, B, ldb); +} + +cublasStatus_t CUBLASWINAPI cublasSetVectorAsync (int n, int elemSize, + const void *hostPtr, int incx, + void *devicePtr, int incy, + cudaStream_t stream) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(int, int, const void *, int, void *, int, cudaStream_t); + static auto func_ptr = LoadSymbol("cublasSetVectorAsync"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(n, elemSize, hostPtr, incx, devicePtr, incy, stream); +} + +cublasStatus_t CUBLASWINAPI cublasGetVectorAsync (int n, int elemSize, + const void *devicePtr, int incx, + void *hostPtr, int incy, + cudaStream_t stream) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(int, int, const void *, int, void *, int, cudaStream_t); + static auto func_ptr = LoadSymbol("cublasGetVectorAsync"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(n, elemSize, devicePtr, incx, hostPtr, incy, stream); +} + +cublasStatus_t CUBLASWINAPI cublasSetMatrixAsync (int rows, int cols, int elemSize, + const void *A, int lda, void *B, + int ldb, cudaStream_t stream) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(int, int, int, const void *, int, void *, int, cudaStream_t); + static auto func_ptr = LoadSymbol("cublasSetMatrixAsync"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rows, cols, elemSize, A, lda, B, ldb, stream); +} + +cublasStatus_t CUBLASWINAPI cublasGetMatrixAsync (int rows, int cols, int elemSize, + const void *A, int lda, void *B, + int ldb, cudaStream_t stream) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(int, int, int, const void *, int, void *, int, cudaStream_t); + static auto func_ptr = LoadSymbol("cublasGetMatrixAsync"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rows, cols, elemSize, A, lda, B, ldb, stream); +} + +void CUBLASWINAPI cublasXerbla (const char *srName, int info) { + using FuncPtr = void (CUBLASWINAPI *)(const char *, int); + static auto func_ptr = LoadSymbol("cublasXerbla"); + if (!func_ptr) LogFatalSymbolNotFound("cublasXerbla"); + return func_ptr(srName, info); +} + +cublasStatus_t CUBLASWINAPI cublasNrm2Ex(cublasHandle_t handle, + int n, + const void *x, + cudaDataType xType, + int incx, + void *result, + cudaDataType resultType, + cudaDataType executionType) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const void *, cudaDataType, int, void *, cudaDataType, cudaDataType); + static auto func_ptr = LoadSymbol("cublasNrm2Ex"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, xType, incx, result, resultType, executionType); +} + +cublasStatus_t CUBLASWINAPI cublasSnrm2_v2(cublasHandle_t handle, + int n, + const float *x, + int incx, + float *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const float *, int, float *); + static auto func_ptr = LoadSymbol("cublasSnrm2_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, result); +} + +cublasStatus_t CUBLASWINAPI cublasDnrm2_v2(cublasHandle_t handle, + int n, + const double *x, + int incx, + double *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const double *, int, double *); + static auto func_ptr = LoadSymbol("cublasDnrm2_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, result); +} + +cublasStatus_t CUBLASWINAPI cublasScnrm2_v2(cublasHandle_t handle, + int n, + const cuComplex *x, + int incx, + float *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuComplex *, int, float *); + static auto func_ptr = LoadSymbol("cublasScnrm2_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, result); +} + +cublasStatus_t CUBLASWINAPI cublasDznrm2_v2(cublasHandle_t handle, + int n, + const cuDoubleComplex *x, + int incx, + double *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuDoubleComplex *, int, double *); + static auto func_ptr = LoadSymbol("cublasDznrm2_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, result); +} + +cublasStatus_t CUBLASWINAPI cublasDotEx (cublasHandle_t handle, + int n, + const void *x, + cudaDataType xType, + int incx, + const void *y, + cudaDataType yType, + int incy, + void *result, + cudaDataType resultType, + cudaDataType executionType) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const void *, cudaDataType, int, const void *, cudaDataType, int, void *, cudaDataType, cudaDataType); + static auto func_ptr = LoadSymbol("cublasDotEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, xType, incx, y, yType, incy, result, resultType, executionType); +} + +cublasStatus_t CUBLASWINAPI cublasDotcEx (cublasHandle_t handle, + int n, + const void *x, + cudaDataType xType, + int incx, + const void *y, + cudaDataType yType, + int incy, + void *result, + cudaDataType resultType, + cudaDataType executionType) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const void *, cudaDataType, int, const void *, cudaDataType, int, void *, cudaDataType, cudaDataType); + static auto func_ptr = LoadSymbol("cublasDotcEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, xType, incx, y, yType, incy, result, resultType, executionType); +} + +cublasStatus_t CUBLASWINAPI cublasSdot_v2 (cublasHandle_t handle, + int n, + const float *x, + int incx, + const float *y, + int incy, + float *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const float *, int, const float *, int, float *); + static auto func_ptr = LoadSymbol("cublasSdot_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy, result); +} + +cublasStatus_t CUBLASWINAPI cublasDdot_v2 (cublasHandle_t handle, + int n, + const double *x, + int incx, + const double *y, + int incy, + double *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const double *, int, const double *, int, double *); + static auto func_ptr = LoadSymbol("cublasDdot_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy, result); +} + +cublasStatus_t CUBLASWINAPI cublasCdotu_v2 (cublasHandle_t handle, + int n, + const cuComplex *x, + int incx, + const cuComplex *y, + int incy, + cuComplex *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuComplex *, int, const cuComplex *, int, cuComplex *); + static auto func_ptr = LoadSymbol("cublasCdotu_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy, result); +} + +cublasStatus_t CUBLASWINAPI cublasCdotc_v2 (cublasHandle_t handle, + int n, + const cuComplex *x, + int incx, + const cuComplex *y, + int incy, + cuComplex *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuComplex *, int, const cuComplex *, int, cuComplex *); + static auto func_ptr = LoadSymbol("cublasCdotc_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy, result); +} + +cublasStatus_t CUBLASWINAPI cublasZdotu_v2 (cublasHandle_t handle, + int n, + const cuDoubleComplex *x, + int incx, + const cuDoubleComplex *y, + int incy, + cuDoubleComplex *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex *); + static auto func_ptr = LoadSymbol("cublasZdotu_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy, result); +} + +cublasStatus_t CUBLASWINAPI cublasZdotc_v2 (cublasHandle_t handle, + int n, + const cuDoubleComplex *x, + int incx, + const cuDoubleComplex *y, + int incy, + cuDoubleComplex *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex *); + static auto func_ptr = LoadSymbol("cublasZdotc_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy, result); +} + +cublasStatus_t CUBLASWINAPI cublasScalEx(cublasHandle_t handle, + int n, + const void *alpha, /* host or device pointer */ + cudaDataType alphaType, + void *x, + cudaDataType xType, + int incx, + cudaDataType executionType) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const void *, cudaDataType, void *, cudaDataType, int, cudaDataType); + static auto func_ptr = LoadSymbol("cublasScalEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, alpha, alphaType, x, xType, incx, executionType); +} + +cublasStatus_t CUBLASWINAPI cublasSscal_v2(cublasHandle_t handle, + int n, + const float *alpha, /* host or device pointer */ + float *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const float *, float *, int); + static auto func_ptr = LoadSymbol("cublasSscal_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, alpha, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasDscal_v2(cublasHandle_t handle, + int n, + const double *alpha, /* host or device pointer */ + double *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const double *, double *, int); + static auto func_ptr = LoadSymbol("cublasDscal_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, alpha, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasCscal_v2(cublasHandle_t handle, + int n, + const cuComplex *alpha, /* host or device pointer */ + cuComplex *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCscal_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, alpha, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasCsscal_v2(cublasHandle_t handle, + int n, + const float *alpha, /* host or device pointer */ + cuComplex *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const float *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCsscal_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, alpha, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasZscal_v2(cublasHandle_t handle, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + cuDoubleComplex *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZscal_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, alpha, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasZdscal_v2(cublasHandle_t handle, + int n, + const double *alpha, /* host or device pointer */ + cuDoubleComplex *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const double *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZdscal_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, alpha, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasAxpyEx (cublasHandle_t handle, + int n, + const void *alpha, /* host or device pointer */ + cudaDataType alphaType, + const void *x, + cudaDataType xType, + int incx, + void *y, + cudaDataType yType, + int incy, + cudaDataType executiontype) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const void *, cudaDataType, const void *, cudaDataType, int, void *, cudaDataType, int, cudaDataType); + static auto func_ptr = LoadSymbol("cublasAxpyEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, alpha, alphaType, x, xType, incx, y, yType, incy, executiontype); +} + +cublasStatus_t CUBLASWINAPI cublasSaxpy_v2 (cublasHandle_t handle, + int n, + const float *alpha, /* host or device pointer */ + const float *x, + int incx, + float *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const float *, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasSaxpy_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, alpha, x, incx, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasDaxpy_v2 (cublasHandle_t handle, + int n, + const double *alpha, /* host or device pointer */ + const double *x, + int incx, + double *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const double *, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDaxpy_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, alpha, x, incx, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasCaxpy_v2 (cublasHandle_t handle, + int n, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *x, + int incx, + cuComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuComplex *, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCaxpy_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, alpha, x, incx, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasZaxpy_v2 (cublasHandle_t handle, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *x, + int incx, + cuDoubleComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuDoubleComplex *, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZaxpy_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, alpha, x, incx, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasScopy_v2 (cublasHandle_t handle, + int n, + const float *x, + int incx, + float *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasScopy_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasDcopy_v2 (cublasHandle_t handle, + int n, + const double *x, + int incx, + double *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDcopy_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasCcopy_v2 (cublasHandle_t handle, + int n, + const cuComplex *x, + int incx, + cuComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCcopy_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasZcopy_v2 (cublasHandle_t handle, + int n, + const cuDoubleComplex *x, + int incx, + cuDoubleComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZcopy_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasSswap_v2 (cublasHandle_t handle, + int n, + float *x, + int incx, + float *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasSswap_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasDswap_v2 (cublasHandle_t handle, + int n, + double *x, + int incx, + double *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDswap_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasCswap_v2 (cublasHandle_t handle, + int n, + cuComplex *x, + int incx, + cuComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCswap_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasZswap_v2 (cublasHandle_t handle, + int n, + cuDoubleComplex *x, + int incx, + cuDoubleComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZswap_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasIsamax_v2(cublasHandle_t handle, + int n, + const float *x, + int incx, + int *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const float *, int, int *); + static auto func_ptr = LoadSymbol("cublasIsamax_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, result); +} + +cublasStatus_t CUBLASWINAPI cublasIdamax_v2(cublasHandle_t handle, + int n, + const double *x, + int incx, + int *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const double *, int, int *); + static auto func_ptr = LoadSymbol("cublasIdamax_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, result); +} + +cublasStatus_t CUBLASWINAPI cublasIcamax_v2(cublasHandle_t handle, + int n, + const cuComplex *x, + int incx, + int *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuComplex *, int, int *); + static auto func_ptr = LoadSymbol("cublasIcamax_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, result); +} + +cublasStatus_t CUBLASWINAPI cublasIzamax_v2(cublasHandle_t handle, + int n, + const cuDoubleComplex *x, + int incx, + int *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuDoubleComplex *, int, int *); + static auto func_ptr = LoadSymbol("cublasIzamax_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, result); +} + +cublasStatus_t CUBLASWINAPI cublasIsamin_v2(cublasHandle_t handle, + int n, + const float *x, + int incx, + int *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const float *, int, int *); + static auto func_ptr = LoadSymbol("cublasIsamin_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, result); +} + +cublasStatus_t CUBLASWINAPI cublasIdamin_v2(cublasHandle_t handle, + int n, + const double *x, + int incx, + int *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const double *, int, int *); + static auto func_ptr = LoadSymbol("cublasIdamin_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, result); +} + +cublasStatus_t CUBLASWINAPI cublasIcamin_v2(cublasHandle_t handle, + int n, + const cuComplex *x, + int incx, + int *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuComplex *, int, int *); + static auto func_ptr = LoadSymbol("cublasIcamin_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, result); +} + +cublasStatus_t CUBLASWINAPI cublasIzamin_v2(cublasHandle_t handle, + int n, + const cuDoubleComplex *x, + int incx, + int *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuDoubleComplex *, int, int *); + static auto func_ptr = LoadSymbol("cublasIzamin_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, result); +} + +cublasStatus_t CUBLASWINAPI cublasSasum_v2(cublasHandle_t handle, + int n, + const float *x, + int incx, + float *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const float *, int, float *); + static auto func_ptr = LoadSymbol("cublasSasum_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, result); +} + +cublasStatus_t CUBLASWINAPI cublasDasum_v2(cublasHandle_t handle, + int n, + const double *x, + int incx, + double *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const double *, int, double *); + static auto func_ptr = LoadSymbol("cublasDasum_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, result); +} + +cublasStatus_t CUBLASWINAPI cublasScasum_v2(cublasHandle_t handle, + int n, + const cuComplex *x, + int incx, + float *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuComplex *, int, float *); + static auto func_ptr = LoadSymbol("cublasScasum_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, result); +} + +cublasStatus_t CUBLASWINAPI cublasDzasum_v2(cublasHandle_t handle, + int n, + const cuDoubleComplex *x, + int incx, + double *result) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuDoubleComplex *, int, double *); + static auto func_ptr = LoadSymbol("cublasDzasum_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, result); +} + +cublasStatus_t CUBLASWINAPI cublasSrot_v2 (cublasHandle_t handle, + int n, + float *x, + int incx, + float *y, + int incy, + const float *c, /* host or device pointer */ + const float *s) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, float *, int, float *, int, const float *, const float *); + static auto func_ptr = LoadSymbol("cublasSrot_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy, c, s); +} + +cublasStatus_t CUBLASWINAPI cublasDrot_v2 (cublasHandle_t handle, + int n, + double *x, + int incx, + double *y, + int incy, + const double *c, /* host or device pointer */ + const double *s) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, double *, int, double *, int, const double *, const double *); + static auto func_ptr = LoadSymbol("cublasDrot_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy, c, s); +} + +cublasStatus_t CUBLASWINAPI cublasCrot_v2 (cublasHandle_t handle, + int n, + cuComplex *x, + int incx, + cuComplex *y, + int incy, + const float *c, /* host or device pointer */ + const cuComplex *s) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, cuComplex *, int, cuComplex *, int, const float *, const cuComplex *); + static auto func_ptr = LoadSymbol("cublasCrot_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy, c, s); +} + +cublasStatus_t CUBLASWINAPI cublasCsrot_v2(cublasHandle_t handle, + int n, + cuComplex *x, + int incx, + cuComplex *y, + int incy, + const float *c, /* host or device pointer */ + const float *s) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, cuComplex *, int, cuComplex *, int, const float *, const float *); + static auto func_ptr = LoadSymbol("cublasCsrot_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy, c, s); +} + +cublasStatus_t CUBLASWINAPI cublasZrot_v2 (cublasHandle_t handle, + int n, + cuDoubleComplex *x, + int incx, + cuDoubleComplex *y, + int incy, + const double *c, /* host or device pointer */ + const cuDoubleComplex *s) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, cuDoubleComplex *, int, cuDoubleComplex *, int, const double *, const cuDoubleComplex *); + static auto func_ptr = LoadSymbol("cublasZrot_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy, c, s); +} + +cublasStatus_t CUBLASWINAPI cublasZdrot_v2(cublasHandle_t handle, + int n, + cuDoubleComplex *x, + int incx, + cuDoubleComplex *y, + int incy, + const double *c, /* host or device pointer */ + const double *s) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, cuDoubleComplex *, int, cuDoubleComplex *, int, const double *, const double *); + static auto func_ptr = LoadSymbol("cublasZdrot_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy, c, s); +} + +cublasStatus_t CUBLASWINAPI cublasSrotg_v2(cublasHandle_t handle, + float *a, /* host or device pointer */ + float *b, /* host or device pointer */ + float *c, /* host or device pointer */ + float *s) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, float *, float *, float *, float *); + static auto func_ptr = LoadSymbol("cublasSrotg_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, a, b, c, s); +} + +cublasStatus_t CUBLASWINAPI cublasDrotg_v2(cublasHandle_t handle, + double *a, /* host or device pointer */ + double *b, /* host or device pointer */ + double *c, /* host or device pointer */ + double *s) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, double *, double *, double *, double *); + static auto func_ptr = LoadSymbol("cublasDrotg_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, a, b, c, s); +} + +cublasStatus_t CUBLASWINAPI cublasCrotg_v2(cublasHandle_t handle, + cuComplex *a, /* host or device pointer */ + cuComplex *b, /* host or device pointer */ + float *c, /* host or device pointer */ + cuComplex *s) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cuComplex *, cuComplex *, float *, cuComplex *); + static auto func_ptr = LoadSymbol("cublasCrotg_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, a, b, c, s); +} + +cublasStatus_t CUBLASWINAPI cublasZrotg_v2(cublasHandle_t handle, + cuDoubleComplex *a, /* host or device pointer */ + cuDoubleComplex *b, /* host or device pointer */ + double *c, /* host or device pointer */ + cuDoubleComplex *s) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cuDoubleComplex *, cuDoubleComplex *, double *, cuDoubleComplex *); + static auto func_ptr = LoadSymbol("cublasZrotg_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, a, b, c, s); +} + +cublasStatus_t CUBLASWINAPI cublasSrotm_v2(cublasHandle_t handle, + int n, + float *x, + int incx, + float *y, + int incy, + const float* param) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, float *, int, float *, int, const float *); + static auto func_ptr = LoadSymbol("cublasSrotm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy, param); +} + +cublasStatus_t CUBLASWINAPI cublasDrotm_v2(cublasHandle_t handle, + int n, + double *x, + int incx, + double *y, + int incy, + const double* param) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, double *, int, double *, int, const double *); + static auto func_ptr = LoadSymbol("cublasDrotm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, x, incx, y, incy, param); +} + +cublasStatus_t CUBLASWINAPI cublasSrotmg_v2(cublasHandle_t handle, + float *d1, /* host or device pointer */ + float *d2, /* host or device pointer */ + float *x1, /* host or device pointer */ + const float *y1, /* host or device pointer */ + float *param) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, float *, float *, float *, const float *, float *); + static auto func_ptr = LoadSymbol("cublasSrotmg_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, d1, d2, x1, y1, param); +} + +cublasStatus_t CUBLASWINAPI cublasDrotmg_v2(cublasHandle_t handle, + double *d1, /* host or device pointer */ + double *d2, /* host or device pointer */ + double *x1, /* host or device pointer */ + const double *y1, /* host or device pointer */ + double *param) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, double *, double *, double *, const double *, double *); + static auto func_ptr = LoadSymbol("cublasDrotmg_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, d1, d2, x1, y1, param); +} + +cublasStatus_t CUBLASWINAPI cublasSgemv_v2 (cublasHandle_t handle, + cublasOperation_t trans, + int m, + int n, + const float *alpha, /* host or device pointer */ + const float *A, + int lda, + const float *x, + int incx, + const float *beta, /* host or device pointer */ + float *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, int, int, const float *, const float *, int, const float *, int, const float *, float *, int); + static auto func_ptr = LoadSymbol("cublasSgemv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, trans, m, n, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasDgemv_v2 (cublasHandle_t handle, + cublasOperation_t trans, + int m, + int n, + const double *alpha, /* host or device pointer */ + const double *A, + int lda, + const double *x, + int incx, + const double *beta, /* host or device pointer */ + double *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, int, int, const double *, const double *, int, const double *, int, const double *, double *, int); + static auto func_ptr = LoadSymbol("cublasDgemv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, trans, m, n, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasCgemv_v2 (cublasHandle_t handle, + cublasOperation_t trans, + int m, + int n, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const cuComplex *x, + int incx, + const cuComplex *beta, /* host or device pointer */ + cuComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, int, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCgemv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, trans, m, n, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasZgemv_v2 (cublasHandle_t handle, + cublasOperation_t trans, + int m, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *x, + int incx, + const cuDoubleComplex *beta, /* host or device pointer */ + cuDoubleComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZgemv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, trans, m, n, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasSgbmv_v2 (cublasHandle_t handle, + cublasOperation_t trans, + int m, + int n, + int kl, + int ku, + const float *alpha, /* host or device pointer */ + const float *A, + int lda, + const float *x, + int incx, + const float *beta, /* host or device pointer */ + float *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, int, int, int, int, const float *, const float *, int, const float *, int, const float *, float *, int); + static auto func_ptr = LoadSymbol("cublasSgbmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, trans, m, n, kl, ku, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasDgbmv_v2 (cublasHandle_t handle, + cublasOperation_t trans, + int m, + int n, + int kl, + int ku, + const double *alpha, /* host or device pointer */ + const double *A, + int lda, + const double *x, + int incx, + const double *beta, /* host or device pointer */ + double *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, int, int, int, int, const double *, const double *, int, const double *, int, const double *, double *, int); + static auto func_ptr = LoadSymbol("cublasDgbmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, trans, m, n, kl, ku, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasCgbmv_v2 (cublasHandle_t handle, + cublasOperation_t trans, + int m, + int n, + int kl, + int ku, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const cuComplex *x, + int incx, + const cuComplex *beta, /* host or device pointer */ + cuComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, int, int, int, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCgbmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, trans, m, n, kl, ku, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasZgbmv_v2 (cublasHandle_t handle, + cublasOperation_t trans, + int m, + int n, + int kl, + int ku, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *x, + int incx, + const cuDoubleComplex *beta, /* host or device pointer */ + cuDoubleComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, int, int, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZgbmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, trans, m, n, kl, ku, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasStrmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + const float *A, + int lda, + float *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasStrmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, A, lda, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasDtrmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + const double *A, + int lda, + double *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDtrmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, A, lda, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasCtrmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + const cuComplex *A, + int lda, + cuComplex *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtrmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, A, lda, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasZtrmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + const cuDoubleComplex *A, + int lda, + cuDoubleComplex *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtrmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, A, lda, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasStbmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + int k, + const float *A, + int lda, + float *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasStbmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, k, A, lda, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasDtbmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + int k, + const double *A, + int lda, + double *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDtbmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, k, A, lda, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasCtbmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + int k, + const cuComplex *A, + int lda, + cuComplex *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtbmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, k, A, lda, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasZtbmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + int k, + const cuDoubleComplex *A, + int lda, + cuDoubleComplex *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtbmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, k, A, lda, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasStpmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + const float *AP, + float *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, const float *, float *, int); + static auto func_ptr = LoadSymbol("cublasStpmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, AP, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasDtpmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + const double *AP, + double *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, const double *, double *, int); + static auto func_ptr = LoadSymbol("cublasDtpmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, AP, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasCtpmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + const cuComplex *AP, + cuComplex *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtpmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, AP, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasZtpmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + const cuDoubleComplex *AP, + cuDoubleComplex *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtpmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, AP, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasStrsv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + const float *A, + int lda, + float *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasStrsv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, A, lda, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasDtrsv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + const double *A, + int lda, + double *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDtrsv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, A, lda, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasCtrsv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + const cuComplex *A, + int lda, + cuComplex *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtrsv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, A, lda, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasZtrsv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + const cuDoubleComplex *A, + int lda, + cuDoubleComplex *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtrsv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, A, lda, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasStpsv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + const float *AP, + float *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, const float *, float *, int); + static auto func_ptr = LoadSymbol("cublasStpsv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, AP, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasDtpsv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + const double *AP, + double *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, const double *, double *, int); + static auto func_ptr = LoadSymbol("cublasDtpsv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, AP, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasCtpsv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + const cuComplex *AP, + cuComplex *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtpsv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, AP, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasZtpsv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + const cuDoubleComplex *AP, + cuDoubleComplex *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtpsv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, AP, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasStbsv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + int k, + const float *A, + int lda, + float *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasStbsv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, k, A, lda, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasDtbsv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + int k, + const double *A, + int lda, + double *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDtbsv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, k, A, lda, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasCtbsv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + int k, + const cuComplex *A, + int lda, + cuComplex *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtbsv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, k, A, lda, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasZtbsv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int n, + int k, + const cuDoubleComplex *A, + int lda, + cuDoubleComplex *x, + int incx) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtbsv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, diag, n, k, A, lda, x, incx); +} + +cublasStatus_t CUBLASWINAPI cublasSsymv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const float *alpha, /* host or device pointer */ + const float *A, + int lda, + const float *x, + int incx, + const float *beta, /* host or device pointer */ + float *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const float *, const float *, int, const float *, int, const float *, float *, int); + static auto func_ptr = LoadSymbol("cublasSsymv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasDsymv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const double *alpha, /* host or device pointer */ + const double *A, + int lda, + const double *x, + int incx, + const double *beta, /* host or device pointer */ + double *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const double *, const double *, int, const double *, int, const double *, double *, int); + static auto func_ptr = LoadSymbol("cublasDsymv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasCsymv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const cuComplex *x, + int incx, + const cuComplex *beta, /* host or device pointer */ + cuComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCsymv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasZsymv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *x, + int incx, + const cuDoubleComplex *beta, /* host or device pointer */ + cuDoubleComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZsymv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasChemv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const cuComplex *x, + int incx, + const cuComplex *beta, /* host or device pointer */ + cuComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasChemv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasZhemv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *x, + int incx, + const cuDoubleComplex *beta, /* host or device pointer */ + cuDoubleComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZhemv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasSsbmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + int k, + const float *alpha, /* host or device pointer */ + const float *A, + int lda, + const float *x, + int incx, + const float *beta, /* host or device pointer */ + float *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, int, const float *, const float *, int, const float *, int, const float *, float *, int); + static auto func_ptr = LoadSymbol("cublasSsbmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, k, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasDsbmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + int k, + const double *alpha, /* host or device pointer */ + const double *A, + int lda, + const double *x, + int incx, + const double *beta, /* host or device pointer */ + double *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, int, const double *, const double *, int, const double *, int, const double *, double *, int); + static auto func_ptr = LoadSymbol("cublasDsbmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, k, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasChbmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + int k, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const cuComplex *x, + int incx, + const cuComplex *beta, /* host or device pointer */ + cuComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasChbmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, k, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasZhbmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + int k, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *x, + int incx, + const cuDoubleComplex *beta, /* host or device pointer */ + cuDoubleComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZhbmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, k, alpha, A, lda, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasSspmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const float *alpha, /* host or device pointer */ + const float *AP, + const float *x, + int incx, + const float *beta, /* host or device pointer */ + float *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const float *, const float *, const float *, int, const float *, float *, int); + static auto func_ptr = LoadSymbol("cublasSspmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, AP, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasDspmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const double *alpha, /* host or device pointer */ + const double *AP, + const double *x, + int incx, + const double *beta, /* host or device pointer */ + double *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const double *, const double *, const double *, int, const double *, double *, int); + static auto func_ptr = LoadSymbol("cublasDspmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, AP, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasChpmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *AP, + const cuComplex *x, + int incx, + const cuComplex *beta, /* host or device pointer */ + cuComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuComplex *, const cuComplex *, const cuComplex *, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasChpmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, AP, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasZhpmv_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *AP, + const cuDoubleComplex *x, + int incx, + const cuDoubleComplex *beta, /* host or device pointer */ + cuDoubleComplex *y, + int incy) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuDoubleComplex *, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZhpmv_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, AP, x, incx, beta, y, incy); +} + +cublasStatus_t CUBLASWINAPI cublasSger_v2 (cublasHandle_t handle, + int m, + int n, + const float *alpha, /* host or device pointer */ + const float *x, + int incx, + const float *y, + int incy, + float *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, int, const float *, const float *, int, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasSger_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, m, n, alpha, x, incx, y, incy, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasDger_v2 (cublasHandle_t handle, + int m, + int n, + const double *alpha, /* host or device pointer */ + const double *x, + int incx, + const double *y, + int incy, + double *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, int, const double *, const double *, int, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDger_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, m, n, alpha, x, incx, y, incy, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasCgeru_v2 (cublasHandle_t handle, + int m, + int n, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *x, + int incx, + const cuComplex *y, + int incy, + cuComplex *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCgeru_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, m, n, alpha, x, incx, y, incy, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasCgerc_v2 (cublasHandle_t handle, + int m, + int n, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *x, + int incx, + const cuComplex *y, + int incy, + cuComplex *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCgerc_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, m, n, alpha, x, incx, y, incy, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasZgeru_v2 (cublasHandle_t handle, + int m, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *x, + int incx, + const cuDoubleComplex *y, + int incy, + cuDoubleComplex *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZgeru_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, m, n, alpha, x, incx, y, incy, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasZgerc_v2 (cublasHandle_t handle, + int m, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *x, + int incx, + const cuDoubleComplex *y, + int incy, + cuDoubleComplex *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZgerc_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, m, n, alpha, x, incx, y, incy, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasSsyr_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const float *alpha, /* host or device pointer */ + const float *x, + int incx, + float *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const float *, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasSsyr_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasDsyr_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const double *alpha, /* host or device pointer */ + const double *x, + int incx, + double *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const double *, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDsyr_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasCsyr_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *x, + int incx, + cuComplex *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuComplex *, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCsyr_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasZsyr_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *x, + int incx, + cuDoubleComplex *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuDoubleComplex *, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZsyr_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasCher_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const float *alpha, /* host or device pointer */ + const cuComplex *x, + int incx, + cuComplex *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const float *, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCher_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasZher_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const double *alpha, /* host or device pointer */ + const cuDoubleComplex *x, + int incx, + cuDoubleComplex *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const double *, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZher_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasSspr_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const float *alpha, /* host or device pointer */ + const float *x, + int incx, + float *AP) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const float *, const float *, int, float *); + static auto func_ptr = LoadSymbol("cublasSspr_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, AP); +} + +cublasStatus_t CUBLASWINAPI cublasDspr_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const double *alpha, /* host or device pointer */ + const double *x, + int incx, + double *AP) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const double *, const double *, int, double *); + static auto func_ptr = LoadSymbol("cublasDspr_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, AP); +} + +cublasStatus_t CUBLASWINAPI cublasChpr_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const float *alpha, /* host or device pointer */ + const cuComplex *x, + int incx, + cuComplex *AP) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const float *, const cuComplex *, int, cuComplex *); + static auto func_ptr = LoadSymbol("cublasChpr_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, AP); +} + +cublasStatus_t CUBLASWINAPI cublasZhpr_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const double *alpha, /* host or device pointer */ + const cuDoubleComplex *x, + int incx, + cuDoubleComplex *AP) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const double *, const cuDoubleComplex *, int, cuDoubleComplex *); + static auto func_ptr = LoadSymbol("cublasZhpr_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, AP); +} + +cublasStatus_t CUBLASWINAPI cublasSsyr2_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const float *alpha, /* host or device pointer */ + const float *x, + int incx, + const float *y, + int incy, + float *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const float *, const float *, int, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasSsyr2_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, y, incy, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasDsyr2_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const double *alpha, /* host or device pointer */ + const double *x, + int incx, + const double *y, + int incy, + double *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const double *, const double *, int, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDsyr2_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, y, incy, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasCsyr2_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, int n, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *x, + int incx, + const cuComplex *y, + int incy, + cuComplex *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCsyr2_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, y, incy, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasZsyr2_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *x, + int incx, + const cuDoubleComplex *y, + int incy, + cuDoubleComplex *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZsyr2_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, y, incy, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasCher2_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, int n, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *x, + int incx, + const cuComplex *y, + int incy, + cuComplex *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCher2_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, y, incy, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasZher2_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *x, + int incx, + const cuDoubleComplex *y, + int incy, + cuDoubleComplex *A, + int lda) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZher2_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, y, incy, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasSspr2_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const float *alpha, /* host or device pointer */ + const float *x, + int incx, + const float *y, + int incy, + float *AP) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const float *, const float *, int, const float *, int, float *); + static auto func_ptr = LoadSymbol("cublasSspr2_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, y, incy, AP); +} + +cublasStatus_t CUBLASWINAPI cublasDspr2_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const double *alpha, /* host or device pointer */ + const double *x, + int incx, + const double *y, + int incy, + double *AP) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const double *, const double *, int, const double *, int, double *); + static auto func_ptr = LoadSymbol("cublasDspr2_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, y, incy, AP); +} + +cublasStatus_t CUBLASWINAPI cublasChpr2_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *x, + int incx, + const cuComplex *y, + int incy, + cuComplex *AP) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, cuComplex *); + static auto func_ptr = LoadSymbol("cublasChpr2_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, y, incy, AP); +} + +cublasStatus_t CUBLASWINAPI cublasZhpr2_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *x, + int incx, + const cuDoubleComplex *y, + int incy, + cuDoubleComplex *AP) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex *); + static auto func_ptr = LoadSymbol("cublasZhpr2_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, alpha, x, incx, y, incy, AP); +} + +cublasStatus_t CUBLASWINAPI cublasSgemm_v2 (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const float *alpha, /* host or device pointer */ + const float *A, + int lda, + const float *B, + int ldb, + const float *beta, /* host or device pointer */ + float *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const float *, const float *, int, const float *, int, const float *, float *, int); + static auto func_ptr = LoadSymbol("cublasSgemm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasDgemm_v2 (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const double *alpha, /* host or device pointer */ + const double *A, + int lda, + const double *B, + int ldb, + const double *beta, /* host or device pointer */ + double *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const double *, const double *, int, const double *, int, const double *, double *, int); + static auto func_ptr = LoadSymbol("cublasDgemm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCgemm_v2 (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const cuComplex *B, + int ldb, + const cuComplex *beta, /* host or device pointer */ + cuComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCgemm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCgemm3m (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const cuComplex *B, + int ldb, + const cuComplex *beta, /* host or device pointer */ + cuComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCgemm3m"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCgemm3mEx (cublasHandle_t handle, + cublasOperation_t transa, cublasOperation_t transb, + int m, int n, int k, + const cuComplex *alpha, + const void *A, + cudaDataType Atype, + int lda, + const void *B, + cudaDataType Btype, + int ldb, + const cuComplex *beta, + void *C, + cudaDataType Ctype, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const cuComplex *, const void *, cudaDataType, int, const void *, cudaDataType, int, const cuComplex *, void *, cudaDataType, int); + static auto func_ptr = LoadSymbol("cublasCgemm3mEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, A, Atype, lda, B, Btype, ldb, beta, C, Ctype, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasZgemm_v2 (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *B, + int ldb, + const cuDoubleComplex *beta, /* host or device pointer */ + cuDoubleComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZgemm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasZgemm3m (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *B, + int ldb, + const cuDoubleComplex *beta, /* host or device pointer */ + cuDoubleComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZgemm3m"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasSgemmEx (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const float *alpha, /* host or device pointer */ + const void *A, + cudaDataType Atype, + int lda, + const void *B, + cudaDataType Btype, + int ldb, + const float *beta, /* host or device pointer */ + void *C, + cudaDataType Ctype, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const float *, const void *, cudaDataType, int, const void *, cudaDataType, int, const float *, void *, cudaDataType, int); + static auto func_ptr = LoadSymbol("cublasSgemmEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, A, Atype, lda, B, Btype, ldb, beta, C, Ctype, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasGemmEx (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const void *alpha, /* host or device pointer */ + const void *A, + cudaDataType Atype, + int lda, + const void *B, + cudaDataType Btype, + int ldb, + const void *beta, /* host or device pointer */ + void *C, + cudaDataType Ctype, + int ldc, + cudaDataType computeType, + cublasGemmAlgo_t algo) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const void *, const void *, cudaDataType, int, const void *, cudaDataType, int, const void *, void *, cudaDataType, int, cudaDataType, cublasGemmAlgo_t); + static auto func_ptr = LoadSymbol("cublasGemmEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, A, Atype, lda, B, Btype, ldb, beta, C, Ctype, ldc, computeType, algo); +} + +cublasStatus_t CUBLASWINAPI cublasCgemmEx (cublasHandle_t handle, + cublasOperation_t transa, cublasOperation_t transb, + int m, int n, int k, + const cuComplex *alpha, + const void *A, + cudaDataType Atype, + int lda, + const void *B, + cudaDataType Btype, + int ldb, + const cuComplex *beta, + void *C, + cudaDataType Ctype, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const cuComplex *, const void *, cudaDataType, int, const void *, cudaDataType, int, const cuComplex *, void *, cudaDataType, int); + static auto func_ptr = LoadSymbol("cublasCgemmEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, A, Atype, lda, B, Btype, ldb, beta, C, Ctype, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasUint8gemmBias (cublasHandle_t handle, + cublasOperation_t transa, cublasOperation_t transb, cublasOperation_t transc, + int m, int n, int k, + const unsigned char *A, int A_bias, int lda, + const unsigned char *B, int B_bias, int ldb, + unsigned char *C, int C_bias, int ldc, + int C_mult, int C_shift) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, cublasOperation_t, int, int, int, const unsigned char *, int, int, const unsigned char *, int, int, unsigned char *, int, int, int, int); + static auto func_ptr = LoadSymbol("cublasUint8gemmBias"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, transc, m, n, k, A, A_bias, lda, B, B_bias, ldb, C, C_bias, ldc, C_mult, C_shift); +} + +cublasStatus_t CUBLASWINAPI cublasSsyrk_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const float *alpha, /* host or device pointer */ + const float *A, + int lda, + const float *beta, /* host or device pointer */ + float *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const float *, const float *, int, const float *, float *, int); + static auto func_ptr = LoadSymbol("cublasSsyrk_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasDsyrk_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const double *alpha, /* host or device pointer */ + const double *A, + int lda, + const double *beta, /* host or device pointer */ + double *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const double *, const double *, int, const double *, double *, int); + static auto func_ptr = LoadSymbol("cublasDsyrk_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCsyrk_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const cuComplex *beta, /* host or device pointer */ + cuComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const cuComplex *, const cuComplex *, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCsyrk_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasZsyrk_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *beta, /* host or device pointer */ + cuDoubleComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZsyrk_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCsyrkEx ( cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const cuComplex *alpha, /* host or device pointer */ + const void *A, + cudaDataType Atype, + int lda, + const cuComplex *beta, /* host or device pointer */ + void *C, + cudaDataType Ctype, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const cuComplex *, const void *, cudaDataType, int, const cuComplex *, void *, cudaDataType, int); + static auto func_ptr = LoadSymbol("cublasCsyrkEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, Atype, lda, beta, C, Ctype, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCsyrk3mEx(cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const cuComplex *alpha, + const void *A, + cudaDataType Atype, + int lda, + const cuComplex *beta, + void *C, + cudaDataType Ctype, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const cuComplex *, const void *, cudaDataType, int, const cuComplex *, void *, cudaDataType, int); + static auto func_ptr = LoadSymbol("cublasCsyrk3mEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, Atype, lda, beta, C, Ctype, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCherk_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const float *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const float *beta, /* host or device pointer */ + cuComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const float *, const cuComplex *, int, const float *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCherk_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasZherk_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const double *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const double *beta, /* host or device pointer */ + cuDoubleComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const double *, const cuDoubleComplex *, int, const double *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZherk_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCherkEx (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const float *alpha, /* host or device pointer */ + const void *A, + cudaDataType Atype, + int lda, + const float *beta, /* host or device pointer */ + void *C, + cudaDataType Ctype, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const float *, const void *, cudaDataType, int, const float *, void *, cudaDataType, int); + static auto func_ptr = LoadSymbol("cublasCherkEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, Atype, lda, beta, C, Ctype, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCherk3mEx (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const float *alpha, + const void *A, cudaDataType Atype, + int lda, + const float *beta, + void *C, + cudaDataType Ctype, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const float *, const void *, cudaDataType, int, const float *, void *, cudaDataType, int); + static auto func_ptr = LoadSymbol("cublasCherk3mEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, Atype, lda, beta, C, Ctype, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasSsyr2k_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const float *alpha, /* host or device pointer */ + const float *A, + int lda, + const float *B, + int ldb, + const float *beta, /* host or device pointer */ + float *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const float *, const float *, int, const float *, int, const float *, float *, int); + static auto func_ptr = LoadSymbol("cublasSsyr2k_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasDsyr2k_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const double *alpha, /* host or device pointer */ + const double *A, + int lda, + const double *B, + int ldb, + const double *beta, /* host or device pointer */ + double *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const double *, const double *, int, const double *, int, const double *, double *, int); + static auto func_ptr = LoadSymbol("cublasDsyr2k_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCsyr2k_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const cuComplex *B, + int ldb, + const cuComplex *beta, /* host or device pointer */ + cuComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCsyr2k_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasZsyr2k_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *B, + int ldb, + const cuDoubleComplex *beta, /* host or device pointer */ + cuDoubleComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZsyr2k_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCher2k_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const cuComplex *B, + int ldb, + const float *beta, /* host or device pointer */ + cuComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, const float *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCher2k_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasZher2k_v2 (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *B, + int ldb, + const double *beta, /* host or device pointer */ + cuDoubleComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, const double *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZher2k_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasSsyrkx (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const float *alpha, /* host or device pointer */ + const float *A, + int lda, + const float *B, + int ldb, + const float *beta, /* host or device pointer */ + float *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const float *, const float *, int, const float *, int, const float *, float *, int); + static auto func_ptr = LoadSymbol("cublasSsyrkx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasDsyrkx (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const double *alpha, /* host or device pointer */ + const double *A, + int lda, + const double *B, + int ldb, + const double *beta, /* host or device pointer */ + double *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const double *, const double *, int, const double *, int, const double *, double *, int); + static auto func_ptr = LoadSymbol("cublasDsyrkx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCsyrkx (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const cuComplex *B, + int ldb, + const cuComplex *beta, /* host or device pointer */ + cuComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCsyrkx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasZsyrkx (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *B, + int ldb, + const cuDoubleComplex *beta, /* host or device pointer */ + cuDoubleComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZsyrkx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCherkx (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const cuComplex *B, + int ldb, + const float *beta, /* host or device pointer */ + cuComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, const float *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCherkx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasZherkx (cublasHandle_t handle, + cublasFillMode_t uplo, + cublasOperation_t trans, + int n, + int k, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *B, + int ldb, + const double *beta, /* host or device pointer */ + cuDoubleComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, cublasOperation_t, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, const double *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZherkx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasSsymm_v2 (cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + int m, + int n, + const float *alpha, /* host or device pointer */ + const float *A, + int lda, + const float *B, + int ldb, + const float *beta, /* host or device pointer */ + float *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, int, int, const float *, const float *, int, const float *, int, const float *, float *, int); + static auto func_ptr = LoadSymbol("cublasSsymm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, m, n, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasDsymm_v2 (cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + int m, + int n, + const double *alpha, /* host or device pointer */ + const double *A, + int lda, + const double *B, + int ldb, + const double *beta, /* host or device pointer */ + double *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, int, int, const double *, const double *, int, const double *, int, const double *, double *, int); + static auto func_ptr = LoadSymbol("cublasDsymm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, m, n, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCsymm_v2 (cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + int m, + int n, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const cuComplex *B, + int ldb, + const cuComplex *beta, /* host or device pointer */ + cuComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, int, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCsymm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, m, n, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasZsymm_v2 (cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + int m, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *B, + int ldb, + const cuDoubleComplex *beta, /* host or device pointer */ + cuDoubleComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZsymm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, m, n, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasChemm_v2 (cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + int m, + int n, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const cuComplex *B, + int ldb, + const cuComplex *beta, /* host or device pointer */ + cuComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, int, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasChemm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, m, n, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasZhemm_v2 (cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + int m, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *B, + int ldb, + const cuDoubleComplex *beta, /* host or device pointer */ + cuDoubleComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZhemm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, m, n, alpha, A, lda, B, ldb, beta, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasStrsm_v2 (cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int m, + int n, + const float *alpha, /* host or device pointer */ + const float *A, + int lda, + float *B, + int ldb) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const float *, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasStrsm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, trans, diag, m, n, alpha, A, lda, B, ldb); +} + +cublasStatus_t CUBLASWINAPI cublasDtrsm_v2 (cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int m, + int n, + const double *alpha, /* host or device pointer */ + const double *A, + int lda, + double *B, + int ldb) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const double *, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDtrsm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, trans, diag, m, n, alpha, A, lda, B, ldb); +} + +cublasStatus_t CUBLASWINAPI cublasCtrsm_v2(cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int m, + int n, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + cuComplex *B, + int ldb) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const cuComplex *, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtrsm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, trans, diag, m, n, alpha, A, lda, B, ldb); +} + +cublasStatus_t CUBLASWINAPI cublasZtrsm_v2(cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int m, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + cuDoubleComplex *B, + int ldb) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtrsm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, trans, diag, m, n, alpha, A, lda, B, ldb); +} + +cublasStatus_t CUBLASWINAPI cublasStrmm_v2 (cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int m, + int n, + const float *alpha, /* host or device pointer */ + const float *A, + int lda, + const float *B, + int ldb, + float *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const float *, const float *, int, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasStrmm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, trans, diag, m, n, alpha, A, lda, B, ldb, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasDtrmm_v2 (cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int m, + int n, + const double *alpha, /* host or device pointer */ + const double *A, + int lda, + const double *B, + int ldb, + double *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const double *, const double *, int, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDtrmm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, trans, diag, m, n, alpha, A, lda, B, ldb, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCtrmm_v2(cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int m, + int n, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const cuComplex *B, + int ldb, + cuComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const cuComplex *, const cuComplex *, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtrmm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, trans, diag, m, n, alpha, A, lda, B, ldb, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasZtrmm_v2(cublasHandle_t handle, cublasSideMode_t side, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int m, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *B, + int ldb, + cuDoubleComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtrmm_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, trans, diag, m, n, alpha, A, lda, B, ldb, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasSgemmBatched (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const float *alpha, /* host or device pointer */ + const float *Aarray[], + int lda, + const float *Barray[], + int ldb, + const float *beta, /* host or device pointer */ + float *Carray[], + int ldc, + int batchCount) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const float *, const float *[], int, const float *[], int, const float *, float *[], int, int); + static auto func_ptr = LoadSymbol("cublasSgemmBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, Aarray, lda, Barray, ldb, beta, Carray, ldc, batchCount); +} + +cublasStatus_t CUBLASWINAPI cublasDgemmBatched (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const double *alpha, /* host or device pointer */ + const double *Aarray[], + int lda, + const double *Barray[], + int ldb, + const double *beta, /* host or device pointer */ + double *Carray[], + int ldc, + int batchCount) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const double *, const double *[], int, const double *[], int, const double *, double *[], int, int); + static auto func_ptr = LoadSymbol("cublasDgemmBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, Aarray, lda, Barray, ldb, beta, Carray, ldc, batchCount); +} + +cublasStatus_t CUBLASWINAPI cublasCgemmBatched (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *Aarray[], + int lda, + const cuComplex *Barray[], + int ldb, + const cuComplex *beta, /* host or device pointer */ + cuComplex *Carray[], + int ldc, + int batchCount) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const cuComplex *, const cuComplex *[], int, const cuComplex *[], int, const cuComplex *, cuComplex *[], int, int); + static auto func_ptr = LoadSymbol("cublasCgemmBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, Aarray, lda, Barray, ldb, beta, Carray, ldc, batchCount); +} + +cublasStatus_t CUBLASWINAPI cublasCgemm3mBatched (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *Aarray[], + int lda, + const cuComplex *Barray[], + int ldb, + const cuComplex *beta, /* host or device pointer */ + cuComplex *Carray[], + int ldc, + int batchCount) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const cuComplex *, const cuComplex *[], int, const cuComplex *[], int, const cuComplex *, cuComplex *[], int, int); + static auto func_ptr = LoadSymbol("cublasCgemm3mBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, Aarray, lda, Barray, ldb, beta, Carray, ldc, batchCount); +} + +cublasStatus_t CUBLASWINAPI cublasZgemmBatched (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *Aarray[], + int lda, + const cuDoubleComplex *Barray[], + int ldb, + const cuDoubleComplex *beta, /* host or device pointer */ + cuDoubleComplex *Carray[], + int ldc, + int batchCount) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const cuDoubleComplex *, const cuDoubleComplex *[], int, const cuDoubleComplex *[], int, const cuDoubleComplex *, cuDoubleComplex *[], int, int); + static auto func_ptr = LoadSymbol("cublasZgemmBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, Aarray, lda, Barray, ldb, beta, Carray, ldc, batchCount); +} + +cublasStatus_t CUBLASWINAPI cublasSgemmStridedBatched (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const float *alpha, /* host or device pointer */ + const float *A, + int lda, + long long int strideA, /* purposely signed */ + const float *B, + int ldb, + long long int strideB, + const float *beta, /* host or device pointer */ + float *C, + int ldc, + long long int strideC, + int batchCount) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const float *, const float *, int, long long, const float *, int, long long, const float *, float *, int, long long, int); + static auto func_ptr = LoadSymbol("cublasSgemmStridedBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, A, lda, strideA, B, ldb, strideB, beta, C, ldc, strideC, batchCount); +} + +cublasStatus_t CUBLASWINAPI cublasDgemmStridedBatched (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const double *alpha, /* host or device pointer */ + const double *A, + int lda, + long long int strideA, /* purposely signed */ + const double *B, + int ldb, + long long int strideB, + const double *beta, /* host or device pointer */ + double *C, + int ldc, + long long int strideC, + int batchCount) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const double *, const double *, int, long long, const double *, int, long long, const double *, double *, int, long long, int); + static auto func_ptr = LoadSymbol("cublasDgemmStridedBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, A, lda, strideA, B, ldb, strideB, beta, C, ldc, strideC, batchCount); +} + +cublasStatus_t CUBLASWINAPI cublasCgemmStridedBatched (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + long long int strideA, /* purposely signed */ + const cuComplex *B, + int ldb, + long long int strideB, + const cuComplex *beta, /* host or device pointer */ + cuComplex *C, + int ldc, + long long int strideC, + int batchCount) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const cuComplex *, const cuComplex *, int, long long, const cuComplex *, int, long long, const cuComplex *, cuComplex *, int, long long, int); + static auto func_ptr = LoadSymbol("cublasCgemmStridedBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, A, lda, strideA, B, ldb, strideB, beta, C, ldc, strideC, batchCount); +} + +cublasStatus_t CUBLASWINAPI cublasCgemm3mStridedBatched (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + long long int strideA, /* purposely signed */ + const cuComplex *B, + int ldb, + long long int strideB, + const cuComplex *beta, /* host or device pointer */ + cuComplex *C, + int ldc, + long long int strideC, + int batchCount) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const cuComplex *, const cuComplex *, int, long long, const cuComplex *, int, long long, const cuComplex *, cuComplex *, int, long long, int); + static auto func_ptr = LoadSymbol("cublasCgemm3mStridedBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, A, lda, strideA, B, ldb, strideB, beta, C, ldc, strideC, batchCount); +} + +cublasStatus_t CUBLASWINAPI cublasZgemmStridedBatched (cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + int k, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + long long int strideA, /* purposely signed */ + const cuDoubleComplex *B, + int ldb, + long long int strideB, + const cuDoubleComplex *beta, /* host or device poi */ + cuDoubleComplex *C, + int ldc, + long long int strideC, + int batchCount) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, long long, const cuDoubleComplex *, int, long long, const cuDoubleComplex *, cuDoubleComplex *, int, long long, int); + static auto func_ptr = LoadSymbol("cublasZgemmStridedBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, k, alpha, A, lda, strideA, B, ldb, strideB, beta, C, ldc, strideC, batchCount); +} + +cublasStatus_t CUBLASWINAPI cublasSgeam(cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + const float *alpha, /* host or device pointer */ + const float *A, + int lda, + const float *beta , /* host or device pointer */ + const float *B, + int ldb, + float *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, const float *, const float *, int, const float *, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasSgeam"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, alpha, A, lda, beta, B, ldb, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasDgeam(cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + const double *alpha, /* host or device pointer */ + const double *A, + int lda, + const double *beta, /* host or device pointer */ + const double *B, + int ldb, + double *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, const double *, const double *, int, const double *, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDgeam"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, alpha, A, lda, beta, B, ldb, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCgeam(cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + const cuComplex *alpha, /* host or device pointer */ + const cuComplex *A, + int lda, + const cuComplex *beta, /* host or device pointer */ + const cuComplex *B, + int ldb, + cuComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, const cuComplex *, const cuComplex *, int, const cuComplex *, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCgeam"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, alpha, A, lda, beta, B, ldb, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasZgeam(cublasHandle_t handle, + cublasOperation_t transa, + cublasOperation_t transb, + int m, + int n, + const cuDoubleComplex *alpha, /* host or device pointer */ + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *beta, /* host or device pointer */ + const cuDoubleComplex *B, + int ldb, + cuDoubleComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, cublasOperation_t, int, int, const cuDoubleComplex *, const cuDoubleComplex *, int, const cuDoubleComplex *, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZgeam"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, transa, transb, m, n, alpha, A, lda, beta, B, ldb, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasSgetrfBatched(cublasHandle_t handle, + int n, + float *A[], /*Device pointer*/ + int lda, + int *P, /*Device Pointer*/ + int *info, /*Device Pointer*/ + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, float *[], int, int *, int *, int); + static auto func_ptr = LoadSymbol("cublasSgetrfBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, A, lda, P, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasDgetrfBatched(cublasHandle_t handle, + int n, + double *A[], /*Device pointer*/ + int lda, + int *P, /*Device Pointer*/ + int *info, /*Device Pointer*/ + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, double *[], int, int *, int *, int); + static auto func_ptr = LoadSymbol("cublasDgetrfBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, A, lda, P, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasCgetrfBatched(cublasHandle_t handle, + int n, + cuComplex *A[], /*Device pointer*/ + int lda, + int *P, /*Device Pointer*/ + int *info, /*Device Pointer*/ + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, cuComplex *[], int, int *, int *, int); + static auto func_ptr = LoadSymbol("cublasCgetrfBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, A, lda, P, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasZgetrfBatched(cublasHandle_t handle, + int n, + cuDoubleComplex *A[], /*Device pointer*/ + int lda, + int *P, /*Device Pointer*/ + int *info, /*Device Pointer*/ + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, cuDoubleComplex *[], int, int *, int *, int); + static auto func_ptr = LoadSymbol("cublasZgetrfBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, A, lda, P, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasSgetriBatched(cublasHandle_t handle, + int n, + const float *A[], /*Device pointer*/ + int lda, + const int *P, /*Device pointer*/ + float *C[], /*Device pointer*/ + int ldc, + int *info, + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const float *[], int, const int *, float *[], int, int *, int); + static auto func_ptr = LoadSymbol("cublasSgetriBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, A, lda, P, C, ldc, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasDgetriBatched(cublasHandle_t handle, + int n, + const double *A[], /*Device pointer*/ + int lda, + const int *P, /*Device pointer*/ + double *C[], /*Device pointer*/ + int ldc, + int *info, + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const double *[], int, const int *, double *[], int, int *, int); + static auto func_ptr = LoadSymbol("cublasDgetriBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, A, lda, P, C, ldc, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasCgetriBatched(cublasHandle_t handle, + int n, + const cuComplex *A[], /*Device pointer*/ + int lda, + const int *P, /*Device pointer*/ + cuComplex *C[], /*Device pointer*/ + int ldc, + int *info, + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuComplex *[], int, const int *, cuComplex *[], int, int *, int); + static auto func_ptr = LoadSymbol("cublasCgetriBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, A, lda, P, C, ldc, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasZgetriBatched(cublasHandle_t handle, + int n, + const cuDoubleComplex *A[], /*Device pointer*/ + int lda, + const int *P, /*Device pointer*/ + cuDoubleComplex *C[], /*Device pointer*/ + int ldc, + int *info, + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuDoubleComplex *[], int, const int *, cuDoubleComplex *[], int, int *, int); + static auto func_ptr = LoadSymbol("cublasZgetriBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, A, lda, P, C, ldc, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasSgetrsBatched( cublasHandle_t handle, + cublasOperation_t trans, + int n, + int nrhs, + const float *Aarray[], + int lda, + const int *devIpiv, + float *Barray[], + int ldb, + int *info, + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, int, int, const float *[], int, const int *, float *[], int, int *, int); + static auto func_ptr = LoadSymbol("cublasSgetrsBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, trans, n, nrhs, Aarray, lda, devIpiv, Barray, ldb, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasDgetrsBatched( cublasHandle_t handle, + cublasOperation_t trans, + int n, + int nrhs, + const double *Aarray[], + int lda, + const int *devIpiv, + double *Barray[], + int ldb, + int *info, + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, int, int, const double *[], int, const int *, double *[], int, int *, int); + static auto func_ptr = LoadSymbol("cublasDgetrsBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, trans, n, nrhs, Aarray, lda, devIpiv, Barray, ldb, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasCgetrsBatched( cublasHandle_t handle, + cublasOperation_t trans, + int n, + int nrhs, + const cuComplex *Aarray[], + int lda, + const int *devIpiv, + cuComplex *Barray[], + int ldb, + int *info, + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, int, int, const cuComplex *[], int, const int *, cuComplex *[], int, int *, int); + static auto func_ptr = LoadSymbol("cublasCgetrsBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, trans, n, nrhs, Aarray, lda, devIpiv, Barray, ldb, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasZgetrsBatched( cublasHandle_t handle, + cublasOperation_t trans, + int n, + int nrhs, + const cuDoubleComplex *Aarray[], + int lda, + const int *devIpiv, + cuDoubleComplex *Barray[], + int ldb, + int *info, + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, int, int, const cuDoubleComplex *[], int, const int *, cuDoubleComplex *[], int, int *, int); + static auto func_ptr = LoadSymbol("cublasZgetrsBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, trans, n, nrhs, Aarray, lda, devIpiv, Barray, ldb, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasStrsmBatched( cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int m, + int n, + const float *alpha, /*Host or Device Pointer*/ + const float *A[], + int lda, + float *B[], + int ldb, + int batchCount) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const float *, const float *[], int, float *[], int, int); + static auto func_ptr = LoadSymbol("cublasStrsmBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, trans, diag, m, n, alpha, A, lda, B, ldb, batchCount); +} + +cublasStatus_t CUBLASWINAPI cublasDtrsmBatched( cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int m, + int n, + const double *alpha, /*Host or Device Pointer*/ + const double *A[], + int lda, + double *B[], + int ldb, + int batchCount) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const double *, const double *[], int, double *[], int, int); + static auto func_ptr = LoadSymbol("cublasDtrsmBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, trans, diag, m, n, alpha, A, lda, B, ldb, batchCount); +} + +cublasStatus_t CUBLASWINAPI cublasCtrsmBatched( cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int m, + int n, + const cuComplex *alpha, /*Host or Device Pointer*/ + const cuComplex *A[], + int lda, + cuComplex *B[], + int ldb, + int batchCount) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const cuComplex *, const cuComplex *[], int, cuComplex *[], int, int); + static auto func_ptr = LoadSymbol("cublasCtrsmBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, trans, diag, m, n, alpha, A, lda, B, ldb, batchCount); +} + +cublasStatus_t CUBLASWINAPI cublasZtrsmBatched( cublasHandle_t handle, + cublasSideMode_t side, + cublasFillMode_t uplo, + cublasOperation_t trans, + cublasDiagType_t diag, + int m, + int n, + const cuDoubleComplex *alpha, /*Host or Device Pointer*/ + const cuDoubleComplex *A[], + int lda, + cuDoubleComplex *B[], + int ldb, + int batchCount) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, cublasFillMode_t, cublasOperation_t, cublasDiagType_t, int, int, const cuDoubleComplex *, const cuDoubleComplex *[], int, cuDoubleComplex *[], int, int); + static auto func_ptr = LoadSymbol("cublasZtrsmBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, side, uplo, trans, diag, m, n, alpha, A, lda, B, ldb, batchCount); +} + +cublasStatus_t CUBLASWINAPI cublasSmatinvBatched(cublasHandle_t handle, + int n, + const float *A[], /*Device pointer*/ + int lda, + float *Ainv[], /*Device pointer*/ + int lda_inv, + int *info, /*Device Pointer*/ + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const float *[], int, float *[], int, int *, int); + static auto func_ptr = LoadSymbol("cublasSmatinvBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, A, lda, Ainv, lda_inv, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasDmatinvBatched(cublasHandle_t handle, + int n, + const double *A[], /*Device pointer*/ + int lda, + double *Ainv[], /*Device pointer*/ + int lda_inv, + int *info, /*Device Pointer*/ + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const double *[], int, double *[], int, int *, int); + static auto func_ptr = LoadSymbol("cublasDmatinvBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, A, lda, Ainv, lda_inv, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasCmatinvBatched(cublasHandle_t handle, + int n, + const cuComplex *A[], /*Device pointer*/ + int lda, + cuComplex *Ainv[], /*Device pointer*/ + int lda_inv, + int *info, /*Device Pointer*/ + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuComplex *[], int, cuComplex *[], int, int *, int); + static auto func_ptr = LoadSymbol("cublasCmatinvBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, A, lda, Ainv, lda_inv, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasZmatinvBatched(cublasHandle_t handle, + int n, + const cuDoubleComplex *A[], /*Device pointer*/ + int lda, + cuDoubleComplex *Ainv[], /*Device pointer*/ + int lda_inv, + int *info, /*Device Pointer*/ + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, const cuDoubleComplex *[], int, cuDoubleComplex *[], int, int *, int); + static auto func_ptr = LoadSymbol("cublasZmatinvBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, n, A, lda, Ainv, lda_inv, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasSgeqrfBatched( cublasHandle_t handle, + int m, + int n, + float *Aarray[], /*Device pointer*/ + int lda, + float *TauArray[], /* Device pointer*/ + int *info, + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, int, float *[], int, float *[], int *, int); + static auto func_ptr = LoadSymbol("cublasSgeqrfBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, m, n, Aarray, lda, TauArray, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasDgeqrfBatched( cublasHandle_t handle, + int m, + int n, + double *Aarray[], /*Device pointer*/ + int lda, + double *TauArray[], /* Device pointer*/ + int *info, + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, int, double *[], int, double *[], int *, int); + static auto func_ptr = LoadSymbol("cublasDgeqrfBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, m, n, Aarray, lda, TauArray, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasCgeqrfBatched( cublasHandle_t handle, + int m, + int n, + cuComplex *Aarray[], /*Device pointer*/ + int lda, + cuComplex *TauArray[], /* Device pointer*/ + int *info, + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, int, cuComplex *[], int, cuComplex *[], int *, int); + static auto func_ptr = LoadSymbol("cublasCgeqrfBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, m, n, Aarray, lda, TauArray, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasZgeqrfBatched( cublasHandle_t handle, + int m, + int n, + cuDoubleComplex *Aarray[], /*Device pointer*/ + int lda, + cuDoubleComplex *TauArray[], /* Device pointer*/ + int *info, + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, int, int, cuDoubleComplex *[], int, cuDoubleComplex *[], int *, int); + static auto func_ptr = LoadSymbol("cublasZgeqrfBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, m, n, Aarray, lda, TauArray, info, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasSgelsBatched( cublasHandle_t handle, + cublasOperation_t trans, + int m, + int n, + int nrhs, + float *Aarray[], /*Device pointer*/ + int lda, + float *Carray[], /* Device pointer*/ + int ldc, + int *info, + int *devInfoArray, /* Device pointer*/ + int batchSize ) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, int, int, int, float *[], int, float *[], int, int *, int *, int); + static auto func_ptr = LoadSymbol("cublasSgelsBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, trans, m, n, nrhs, Aarray, lda, Carray, ldc, info, devInfoArray, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasDgelsBatched( cublasHandle_t handle, + cublasOperation_t trans, + int m, + int n, + int nrhs, + double *Aarray[], /*Device pointer*/ + int lda, + double *Carray[], /* Device pointer*/ + int ldc, + int *info, + int *devInfoArray, /* Device pointer*/ + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, int, int, int, double *[], int, double *[], int, int *, int *, int); + static auto func_ptr = LoadSymbol("cublasDgelsBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, trans, m, n, nrhs, Aarray, lda, Carray, ldc, info, devInfoArray, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasCgelsBatched( cublasHandle_t handle, + cublasOperation_t trans, + int m, + int n, + int nrhs, + cuComplex *Aarray[], /*Device pointer*/ + int lda, + cuComplex *Carray[], /* Device pointer*/ + int ldc, + int *info, + int *devInfoArray, + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, int, int, int, cuComplex *[], int, cuComplex *[], int, int *, int *, int); + static auto func_ptr = LoadSymbol("cublasCgelsBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, trans, m, n, nrhs, Aarray, lda, Carray, ldc, info, devInfoArray, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasZgelsBatched( cublasHandle_t handle, + cublasOperation_t trans, + int m, + int n, + int nrhs, + cuDoubleComplex *Aarray[], /*Device pointer*/ + int lda, + cuDoubleComplex *Carray[], /* Device pointer*/ + int ldc, + int *info, + int *devInfoArray, + int batchSize) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasOperation_t, int, int, int, cuDoubleComplex *[], int, cuDoubleComplex *[], int, int *, int *, int); + static auto func_ptr = LoadSymbol("cublasZgelsBatched"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, trans, m, n, nrhs, Aarray, lda, Carray, ldc, info, devInfoArray, batchSize); +} + +cublasStatus_t CUBLASWINAPI cublasSdgmm(cublasHandle_t handle, + cublasSideMode_t mode, + int m, + int n, + const float *A, + int lda, + const float *x, + int incx, + float *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, int, int, const float *, int, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasSdgmm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode, m, n, A, lda, x, incx, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasDdgmm(cublasHandle_t handle, + cublasSideMode_t mode, + int m, + int n, + const double *A, + int lda, + const double *x, + int incx, + double *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, int, int, const double *, int, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDdgmm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode, m, n, A, lda, x, incx, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasCdgmm(cublasHandle_t handle, + cublasSideMode_t mode, + int m, + int n, + const cuComplex *A, + int lda, + const cuComplex *x, + int incx, + cuComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, int, int, const cuComplex *, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCdgmm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode, m, n, A, lda, x, incx, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasZdgmm(cublasHandle_t handle, + cublasSideMode_t mode, + int m, + int n, + const cuDoubleComplex *A, + int lda, + const cuDoubleComplex *x, + int incx, + cuDoubleComplex *C, + int ldc) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasSideMode_t, int, int, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZdgmm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode, m, n, A, lda, x, incx, C, ldc); +} + +cublasStatus_t CUBLASWINAPI cublasStpttr ( cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const float *AP, + float *A, + int lda ) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const float *, float *, int); + static auto func_ptr = LoadSymbol("cublasStpttr"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, AP, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasDtpttr ( cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const double *AP, + double *A, + int lda ) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const double *, double *, int); + static auto func_ptr = LoadSymbol("cublasDtpttr"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, AP, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasCtpttr ( cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const cuComplex *AP, + cuComplex *A, + int lda ) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtpttr"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, AP, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasZtpttr ( cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const cuDoubleComplex *AP, + cuDoubleComplex *A, + int lda ) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtpttr"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, AP, A, lda); +} + +cublasStatus_t CUBLASWINAPI cublasStrttp ( cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const float *A, + int lda, + float *AP ) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const float *, int, float *); + static auto func_ptr = LoadSymbol("cublasStrttp"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, A, lda, AP); +} + +cublasStatus_t CUBLASWINAPI cublasDtrttp ( cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const double *A, + int lda, + double *AP ) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const double *, int, double *); + static auto func_ptr = LoadSymbol("cublasDtrttp"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, A, lda, AP); +} + +cublasStatus_t CUBLASWINAPI cublasCtrttp ( cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const cuComplex *A, + int lda, + cuComplex *AP ) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuComplex *, int, cuComplex *); + static auto func_ptr = LoadSymbol("cublasCtrttp"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, A, lda, AP); +} + +cublasStatus_t CUBLASWINAPI cublasZtrttp ( cublasHandle_t handle, + cublasFillMode_t uplo, + int n, + const cuDoubleComplex *A, + int lda, + cuDoubleComplex *AP ) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cublasHandle_t, cublasFillMode_t, int, const cuDoubleComplex *, int, cuDoubleComplex *); + static auto func_ptr = LoadSymbol("cublasZtrttp"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, uplo, n, A, lda, AP); +} + +cublasStatus CUBLASWINAPI cublasInit (void) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(); + static auto func_ptr = LoadSymbol("cublasInit"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(); +} + +cublasStatus CUBLASWINAPI cublasShutdown (void) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(); + static auto func_ptr = LoadSymbol("cublasShutdown"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(); +} + +cublasStatus CUBLASWINAPI cublasGetError (void) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(); + static auto func_ptr = LoadSymbol("cublasGetError"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(); +} + +cublasStatus CUBLASWINAPI cublasGetVersion(int *version) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(int *); + static auto func_ptr = LoadSymbol("cublasGetVersion"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(version); +} + +cublasStatus CUBLASWINAPI cublasAlloc (int n, int elemSize, void **devicePtr) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(int, int, void **); + static auto func_ptr = LoadSymbol("cublasAlloc"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(n, elemSize, devicePtr); +} + +cublasStatus CUBLASWINAPI cublasFree (void *devicePtr) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(void *); + static auto func_ptr = LoadSymbol("cublasFree"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(devicePtr); +} + +cublasStatus CUBLASWINAPI cublasSetKernelStream (cudaStream_t stream) { + using FuncPtr = cublasStatus_t (CUBLASWINAPI *)(cudaStream_t); + static auto func_ptr = LoadSymbol("cublasSetKernelStream"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(stream); +} + +float CUBLASWINAPI cublasSnrm2 (int n, const float *x, int incx) { + using FuncPtr = float (CUBLASWINAPI *)(int, const float *, int); + static auto func_ptr = LoadSymbol("cublasSnrm2"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSnrm2"); + return func_ptr(n, x, incx); +} + +double CUBLASWINAPI cublasDnrm2 (int n, const double *x, int incx) { + using FuncPtr = double (CUBLASWINAPI *)(int, const double *, int); + static auto func_ptr = LoadSymbol("cublasDnrm2"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDnrm2"); + return func_ptr(n, x, incx); +} + +float CUBLASWINAPI cublasScnrm2 (int n, const cuComplex *x, int incx) { + using FuncPtr = float (CUBLASWINAPI *)(int, const cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasScnrm2"); + if (!func_ptr) LogFatalSymbolNotFound("cublasScnrm2"); + return func_ptr(n, x, incx); +} + +double CUBLASWINAPI cublasDznrm2 (int n, const cuDoubleComplex *x, int incx) { + using FuncPtr = double (CUBLASWINAPI *)(int, const cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasDznrm2"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDznrm2"); + return func_ptr(n, x, incx); +} + +float CUBLASWINAPI cublasSdot (int n, const float *x, int incx, const float *y, + int incy) { + using FuncPtr = float (CUBLASWINAPI *)(int, const float *, int, const float *, int); + static auto func_ptr = LoadSymbol("cublasSdot"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSdot"); + return func_ptr(n, x, incx, y, incy); +} + +double CUBLASWINAPI cublasDdot (int n, const double *x, int incx, const double *y, + int incy) { + using FuncPtr = double (CUBLASWINAPI *)(int, const double *, int, const double *, int); + static auto func_ptr = LoadSymbol("cublasDdot"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDdot"); + return func_ptr(n, x, incx, y, incy); +} + +cuComplex CUBLASWINAPI cublasCdotu (int n, const cuComplex *x, int incx, const cuComplex *y, + int incy) { + using FuncPtr = cuComplex (CUBLASWINAPI *)(int, const cuComplex *, int, const cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCdotu"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCdotu"); + return func_ptr(n, x, incx, y, incy); +} + +cuComplex CUBLASWINAPI cublasCdotc (int n, const cuComplex *x, int incx, const cuComplex *y, + int incy) { + using FuncPtr = cuComplex (CUBLASWINAPI *)(int, const cuComplex *, int, const cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCdotc"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCdotc"); + return func_ptr(n, x, incx, y, incy); +} + +cuDoubleComplex CUBLASWINAPI cublasZdotu (int n, const cuDoubleComplex *x, int incx, const cuDoubleComplex *y, + int incy) { + using FuncPtr = cuDoubleComplex (CUBLASWINAPI *)(int, const cuDoubleComplex *, int, const cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZdotu"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZdotu"); + return func_ptr(n, x, incx, y, incy); +} + +cuDoubleComplex CUBLASWINAPI cublasZdotc (int n, const cuDoubleComplex *x, int incx, const cuDoubleComplex *y, + int incy) { + using FuncPtr = cuDoubleComplex (CUBLASWINAPI *)(int, const cuDoubleComplex *, int, const cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZdotc"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZdotc"); + return func_ptr(n, x, incx, y, incy); +} + +void CUBLASWINAPI cublasSscal (int n, float alpha, float *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(int, float, float *, int); + static auto func_ptr = LoadSymbol("cublasSscal"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSscal"); + return func_ptr(n, alpha, x, incx); +} + +void CUBLASWINAPI cublasDscal (int n, double alpha, double *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(int, double, double *, int); + static auto func_ptr = LoadSymbol("cublasDscal"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDscal"); + return func_ptr(n, alpha, x, incx); +} + +void CUBLASWINAPI cublasCscal (int n, cuComplex alpha, cuComplex *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(int, cuComplex, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCscal"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCscal"); + return func_ptr(n, alpha, x, incx); +} + +void CUBLASWINAPI cublasZscal (int n, cuDoubleComplex alpha, cuDoubleComplex *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(int, cuDoubleComplex, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZscal"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZscal"); + return func_ptr(n, alpha, x, incx); +} + +void CUBLASWINAPI cublasCsscal (int n, float alpha, cuComplex *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(int, float, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCsscal"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCsscal"); + return func_ptr(n, alpha, x, incx); +} + +void CUBLASWINAPI cublasZdscal (int n, double alpha, cuDoubleComplex *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(int, double, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZdscal"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZdscal"); + return func_ptr(n, alpha, x, incx); +} + +void CUBLASWINAPI cublasSaxpy (int n, float alpha, const float *x, int incx, + float *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(int, float, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasSaxpy"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSaxpy"); + return func_ptr(n, alpha, x, incx, y, incy); +} + +void CUBLASWINAPI cublasDaxpy (int n, double alpha, const double *x, + int incx, double *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(int, double, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDaxpy"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDaxpy"); + return func_ptr(n, alpha, x, incx, y, incy); +} + +void CUBLASWINAPI cublasCaxpy (int n, cuComplex alpha, const cuComplex *x, + int incx, cuComplex *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(int, cuComplex, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCaxpy"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCaxpy"); + return func_ptr(n, alpha, x, incx, y, incy); +} + +void CUBLASWINAPI cublasZaxpy (int n, cuDoubleComplex alpha, const cuDoubleComplex *x, + int incx, cuDoubleComplex *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(int, cuDoubleComplex, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZaxpy"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZaxpy"); + return func_ptr(n, alpha, x, incx, y, incy); +} + +void CUBLASWINAPI cublasScopy (int n, const float *x, int incx, float *y, + int incy) { + using FuncPtr = void (CUBLASWINAPI *)(int, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasScopy"); + if (!func_ptr) LogFatalSymbolNotFound("cublasScopy"); + return func_ptr(n, x, incx, y, incy); +} + +void CUBLASWINAPI cublasDcopy (int n, const double *x, int incx, double *y, + int incy) { + using FuncPtr = void (CUBLASWINAPI *)(int, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDcopy"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDcopy"); + return func_ptr(n, x, incx, y, incy); +} + +void CUBLASWINAPI cublasCcopy (int n, const cuComplex *x, int incx, cuComplex *y, + int incy) { + using FuncPtr = void (CUBLASWINAPI *)(int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCcopy"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCcopy"); + return func_ptr(n, x, incx, y, incy); +} + +void CUBLASWINAPI cublasZcopy (int n, const cuDoubleComplex *x, int incx, cuDoubleComplex *y, + int incy) { + using FuncPtr = void (CUBLASWINAPI *)(int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZcopy"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZcopy"); + return func_ptr(n, x, incx, y, incy); +} + +void CUBLASWINAPI cublasSswap (int n, float *x, int incx, float *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(int, float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasSswap"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSswap"); + return func_ptr(n, x, incx, y, incy); +} + +void CUBLASWINAPI cublasDswap (int n, double *x, int incx, double *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(int, double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDswap"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDswap"); + return func_ptr(n, x, incx, y, incy); +} + +void CUBLASWINAPI cublasCswap (int n, cuComplex *x, int incx, cuComplex *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(int, cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCswap"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCswap"); + return func_ptr(n, x, incx, y, incy); +} + +void CUBLASWINAPI cublasZswap (int n, cuDoubleComplex *x, int incx, cuDoubleComplex *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(int, cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZswap"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZswap"); + return func_ptr(n, x, incx, y, incy); +} + +int CUBLASWINAPI cublasIsamax (int n, const float *x, int incx) { + using FuncPtr = int (CUBLASWINAPI *)(int, const float *, int); + static auto func_ptr = LoadSymbol("cublasIsamax"); + if (!func_ptr) LogFatalSymbolNotFound("cublasIsamax"); + return func_ptr(n, x, incx); +} + +int CUBLASWINAPI cublasIdamax (int n, const double *x, int incx) { + using FuncPtr = int (CUBLASWINAPI *)(int, const double *, int); + static auto func_ptr = LoadSymbol("cublasIdamax"); + if (!func_ptr) LogFatalSymbolNotFound("cublasIdamax"); + return func_ptr(n, x, incx); +} + +int CUBLASWINAPI cublasIcamax (int n, const cuComplex *x, int incx) { + using FuncPtr = int (CUBLASWINAPI *)(int, const cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasIcamax"); + if (!func_ptr) LogFatalSymbolNotFound("cublasIcamax"); + return func_ptr(n, x, incx); +} + +int CUBLASWINAPI cublasIzamax (int n, const cuDoubleComplex *x, int incx) { + using FuncPtr = int (CUBLASWINAPI *)(int, const cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasIzamax"); + if (!func_ptr) LogFatalSymbolNotFound("cublasIzamax"); + return func_ptr(n, x, incx); +} + +int CUBLASWINAPI cublasIsamin (int n, const float *x, int incx) { + using FuncPtr = int (CUBLASWINAPI *)(int, const float *, int); + static auto func_ptr = LoadSymbol("cublasIsamin"); + if (!func_ptr) LogFatalSymbolNotFound("cublasIsamin"); + return func_ptr(n, x, incx); +} + +int CUBLASWINAPI cublasIdamin (int n, const double *x, int incx) { + using FuncPtr = int (CUBLASWINAPI *)(int, const double *, int); + static auto func_ptr = LoadSymbol("cublasIdamin"); + if (!func_ptr) LogFatalSymbolNotFound("cublasIdamin"); + return func_ptr(n, x, incx); +} + +int CUBLASWINAPI cublasIcamin (int n, const cuComplex *x, int incx) { + using FuncPtr = int (CUBLASWINAPI *)(int, const cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasIcamin"); + if (!func_ptr) LogFatalSymbolNotFound("cublasIcamin"); + return func_ptr(n, x, incx); +} + +int CUBLASWINAPI cublasIzamin (int n, const cuDoubleComplex *x, int incx) { + using FuncPtr = int (CUBLASWINAPI *)(int, const cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasIzamin"); + if (!func_ptr) LogFatalSymbolNotFound("cublasIzamin"); + return func_ptr(n, x, incx); +} + +float CUBLASWINAPI cublasSasum (int n, const float *x, int incx) { + using FuncPtr = float (CUBLASWINAPI *)(int, const float *, int); + static auto func_ptr = LoadSymbol("cublasSasum"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSasum"); + return func_ptr(n, x, incx); +} + +double CUBLASWINAPI cublasDasum (int n, const double *x, int incx) { + using FuncPtr = double (CUBLASWINAPI *)(int, const double *, int); + static auto func_ptr = LoadSymbol("cublasDasum"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDasum"); + return func_ptr(n, x, incx); +} + +float CUBLASWINAPI cublasScasum (int n, const cuComplex *x, int incx) { + using FuncPtr = float (CUBLASWINAPI *)(int, const cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasScasum"); + if (!func_ptr) LogFatalSymbolNotFound("cublasScasum"); + return func_ptr(n, x, incx); +} + +double CUBLASWINAPI cublasDzasum (int n, const cuDoubleComplex *x, int incx) { + using FuncPtr = double (CUBLASWINAPI *)(int, const cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasDzasum"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDzasum"); + return func_ptr(n, x, incx); +} + +void CUBLASWINAPI cublasSrot (int n, float *x, int incx, float *y, int incy, + float sc, float ss) { + using FuncPtr = void (CUBLASWINAPI *)(int, float *, int, float *, int, float, float); + static auto func_ptr = LoadSymbol("cublasSrot"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSrot"); + return func_ptr(n, x, incx, y, incy, sc, ss); +} + +void CUBLASWINAPI cublasDrot (int n, double *x, int incx, double *y, int incy, + double sc, double ss) { + using FuncPtr = void (CUBLASWINAPI *)(int, double *, int, double *, int, double, double); + static auto func_ptr = LoadSymbol("cublasDrot"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDrot"); + return func_ptr(n, x, incx, y, incy, sc, ss); +} + +void CUBLASWINAPI cublasCrot (int n, cuComplex *x, int incx, cuComplex *y, + int incy, float c, cuComplex s) { + using FuncPtr = void (CUBLASWINAPI *)(int, cuComplex *, int, cuComplex *, int, float, cuComplex); + static auto func_ptr = LoadSymbol("cublasCrot"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCrot"); + return func_ptr(n, x, incx, y, incy, c, s); +} + +void CUBLASWINAPI cublasZrot (int n, cuDoubleComplex *x, int incx, + cuDoubleComplex *y, int incy, double sc, + cuDoubleComplex cs) { + using FuncPtr = void (CUBLASWINAPI *)(int, cuDoubleComplex *, int, cuDoubleComplex *, int, double, cuDoubleComplex); + static auto func_ptr = LoadSymbol("cublasZrot"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZrot"); + return func_ptr(n, x, incx, y, incy, sc, cs); +} + +void CUBLASWINAPI cublasCsrot (int n, cuComplex *x, int incx, cuComplex *y, + int incy, float c, float s) { + using FuncPtr = void (CUBLASWINAPI *)(int, cuComplex *, int, cuComplex *, int, float, float); + static auto func_ptr = LoadSymbol("cublasCsrot"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCsrot"); + return func_ptr(n, x, incx, y, incy, c, s); +} + +void CUBLASWINAPI cublasZdrot (int n, cuDoubleComplex *x, int incx, + cuDoubleComplex *y, int incy, double c, double s) { + using FuncPtr = void (CUBLASWINAPI *)(int, cuDoubleComplex *, int, cuDoubleComplex *, int, double, double); + static auto func_ptr = LoadSymbol("cublasZdrot"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZdrot"); + return func_ptr(n, x, incx, y, incy, c, s); +} + +void CUBLASWINAPI cublasSrotg (float *sa, float *sb, float *sc, float *ss) { + using FuncPtr = void (CUBLASWINAPI *)(float *, float *, float *, float *); + static auto func_ptr = LoadSymbol("cublasSrotg"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSrotg"); + return func_ptr(sa, sb, sc, ss); +} + +void CUBLASWINAPI cublasDrotg (double *sa, double *sb, double *sc, double *ss) { + using FuncPtr = void (CUBLASWINAPI *)(double *, double *, double *, double *); + static auto func_ptr = LoadSymbol("cublasDrotg"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDrotg"); + return func_ptr(sa, sb, sc, ss); +} + +void CUBLASWINAPI cublasCrotg (cuComplex *ca, cuComplex cb, float *sc, + cuComplex *cs) { + using FuncPtr = void (CUBLASWINAPI *)(cuComplex *, cuComplex, float *, cuComplex *); + static auto func_ptr = LoadSymbol("cublasCrotg"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCrotg"); + return func_ptr(ca, cb, sc, cs); +} + +void CUBLASWINAPI cublasZrotg (cuDoubleComplex *ca, cuDoubleComplex cb, double *sc, + cuDoubleComplex *cs) { + using FuncPtr = void (CUBLASWINAPI *)(cuDoubleComplex *, cuDoubleComplex, double *, cuDoubleComplex *); + static auto func_ptr = LoadSymbol("cublasZrotg"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZrotg"); + return func_ptr(ca, cb, sc, cs); +} + +void CUBLASWINAPI cublasSrotm(int n, float *x, int incx, float *y, int incy, + const float* sparam) { + using FuncPtr = void (CUBLASWINAPI *)(int, float *, int, float *, int, const float *); + static auto func_ptr = LoadSymbol("cublasSrotm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSrotm"); + return func_ptr(n, x, incx, y, incy, sparam); +} + +void CUBLASWINAPI cublasDrotm(int n, double *x, int incx, double *y, int incy, + const double* sparam) { + using FuncPtr = void (CUBLASWINAPI *)(int, double *, int, double *, int, const double *); + static auto func_ptr = LoadSymbol("cublasDrotm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDrotm"); + return func_ptr(n, x, incx, y, incy, sparam); +} + +void CUBLASWINAPI cublasSrotmg (float *sd1, float *sd2, float *sx1, + const float *sy1, float* sparam) { + using FuncPtr = void (CUBLASWINAPI *)(float *, float *, float *, const float *, float *); + static auto func_ptr = LoadSymbol("cublasSrotmg"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSrotmg"); + return func_ptr(sd1, sd2, sx1, sy1, sparam); +} + +void CUBLASWINAPI cublasDrotmg (double *sd1, double *sd2, double *sx1, + const double *sy1, double* sparam) { + using FuncPtr = void (CUBLASWINAPI *)(double *, double *, double *, const double *, double *); + static auto func_ptr = LoadSymbol("cublasDrotmg"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDrotmg"); + return func_ptr(sd1, sd2, sx1, sy1, sparam); +} + +void CUBLASWINAPI cublasSgemv (char trans, int m, int n, float alpha, + const float *A, int lda, const float *x, int incx, + float beta, float *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, int, float, const float *, int, const float *, int, float, float *, int); + static auto func_ptr = LoadSymbol("cublasSgemv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSgemv"); + return func_ptr(trans, m, n, alpha, A, lda, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasDgemv (char trans, int m, int n, double alpha, + const double *A, int lda, const double *x, int incx, + double beta, double *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, int, double, const double *, int, const double *, int, double, double *, int); + static auto func_ptr = LoadSymbol("cublasDgemv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDgemv"); + return func_ptr(trans, m, n, alpha, A, lda, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasCgemv (char trans, int m, int n, cuComplex alpha, + const cuComplex *A, int lda, const cuComplex *x, int incx, + cuComplex beta, cuComplex *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, int, cuComplex, const cuComplex *, int, const cuComplex *, int, cuComplex, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCgemv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCgemv"); + return func_ptr(trans, m, n, alpha, A, lda, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasZgemv (char trans, int m, int n, cuDoubleComplex alpha, + const cuDoubleComplex *A, int lda, const cuDoubleComplex *x, int incx, + cuDoubleComplex beta, cuDoubleComplex *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, int, cuDoubleComplex, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZgemv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZgemv"); + return func_ptr(trans, m, n, alpha, A, lda, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasSgbmv (char trans, int m, int n, int kl, int ku, + float alpha, const float *A, int lda, + const float *x, int incx, float beta, float *y, + int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, int, int, int, float, const float *, int, const float *, int, float, float *, int); + static auto func_ptr = LoadSymbol("cublasSgbmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSgbmv"); + return func_ptr(trans, m, n, kl, ku, alpha, A, lda, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasDgbmv (char trans, int m, int n, int kl, int ku, + double alpha, const double *A, int lda, + const double *x, int incx, double beta, double *y, + int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, int, int, int, double, const double *, int, const double *, int, double, double *, int); + static auto func_ptr = LoadSymbol("cublasDgbmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDgbmv"); + return func_ptr(trans, m, n, kl, ku, alpha, A, lda, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasCgbmv (char trans, int m, int n, int kl, int ku, + cuComplex alpha, const cuComplex *A, int lda, + const cuComplex *x, int incx, cuComplex beta, cuComplex *y, + int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, int, int, int, cuComplex, const cuComplex *, int, const cuComplex *, int, cuComplex, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCgbmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCgbmv"); + return func_ptr(trans, m, n, kl, ku, alpha, A, lda, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasZgbmv (char trans, int m, int n, int kl, int ku, + cuDoubleComplex alpha, const cuDoubleComplex *A, int lda, + const cuDoubleComplex *x, int incx, cuDoubleComplex beta, cuDoubleComplex *y, + int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, int, int, int, cuDoubleComplex, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZgbmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZgbmv"); + return func_ptr(trans, m, n, kl, ku, alpha, A, lda, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasStrmv (char uplo, char trans, char diag, int n, + const float *A, int lda, float *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasStrmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasStrmv"); + return func_ptr(uplo, trans, diag, n, A, lda, x, incx); +} + +void CUBLASWINAPI cublasDtrmv (char uplo, char trans, char diag, int n, + const double *A, int lda, double *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDtrmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDtrmv"); + return func_ptr(uplo, trans, diag, n, A, lda, x, incx); +} + +void CUBLASWINAPI cublasCtrmv (char uplo, char trans, char diag, int n, + const cuComplex *A, int lda, cuComplex *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtrmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCtrmv"); + return func_ptr(uplo, trans, diag, n, A, lda, x, incx); +} + +void CUBLASWINAPI cublasZtrmv (char uplo, char trans, char diag, int n, + const cuDoubleComplex *A, int lda, cuDoubleComplex *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtrmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZtrmv"); + return func_ptr(uplo, trans, diag, n, A, lda, x, incx); +} + +void CUBLASWINAPI cublasStbmv (char uplo, char trans, char diag, int n, int k, + const float *A, int lda, float *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, int, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasStbmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasStbmv"); + return func_ptr(uplo, trans, diag, n, k, A, lda, x, incx); +} + +void CUBLASWINAPI cublasDtbmv (char uplo, char trans, char diag, int n, int k, + const double *A, int lda, double *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, int, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDtbmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDtbmv"); + return func_ptr(uplo, trans, diag, n, k, A, lda, x, incx); +} + +void CUBLASWINAPI cublasCtbmv (char uplo, char trans, char diag, int n, int k, + const cuComplex *A, int lda, cuComplex *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtbmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCtbmv"); + return func_ptr(uplo, trans, diag, n, k, A, lda, x, incx); +} + +void CUBLASWINAPI cublasZtbmv (char uplo, char trans, char diag, int n, int k, + const cuDoubleComplex *A, int lda, cuDoubleComplex *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtbmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZtbmv"); + return func_ptr(uplo, trans, diag, n, k, A, lda, x, incx); +} + +void CUBLASWINAPI cublasStpmv(char uplo, char trans, char diag, int n, const float *AP, float *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, const float *, float *, int); + static auto func_ptr = LoadSymbol("cublasStpmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasStpmv"); + return func_ptr(uplo, trans, diag, n, AP, x, incx); +} + +void CUBLASWINAPI cublasDtpmv(char uplo, char trans, char diag, int n, const double *AP, double *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, const double *, double *, int); + static auto func_ptr = LoadSymbol("cublasDtpmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDtpmv"); + return func_ptr(uplo, trans, diag, n, AP, x, incx); +} + +void CUBLASWINAPI cublasCtpmv(char uplo, char trans, char diag, int n, const cuComplex *AP, cuComplex *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtpmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCtpmv"); + return func_ptr(uplo, trans, diag, n, AP, x, incx); +} + +void CUBLASWINAPI cublasZtpmv(char uplo, char trans, char diag, int n, const cuDoubleComplex *AP, cuDoubleComplex *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtpmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZtpmv"); + return func_ptr(uplo, trans, diag, n, AP, x, incx); +} + +void CUBLASWINAPI cublasStrsv(char uplo, char trans, char diag, int n, const float *A, int lda, float *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasStrsv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasStrsv"); + return func_ptr(uplo, trans, diag, n, A, lda, x, incx); +} + +void CUBLASWINAPI cublasDtrsv(char uplo, char trans, char diag, int n, const double *A, int lda, double *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDtrsv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDtrsv"); + return func_ptr(uplo, trans, diag, n, A, lda, x, incx); +} + +void CUBLASWINAPI cublasCtrsv(char uplo, char trans, char diag, int n, const cuComplex *A, int lda, cuComplex *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtrsv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCtrsv"); + return func_ptr(uplo, trans, diag, n, A, lda, x, incx); +} + +void CUBLASWINAPI cublasZtrsv(char uplo, char trans, char diag, int n, const cuDoubleComplex *A, int lda, + cuDoubleComplex *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtrsv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZtrsv"); + return func_ptr(uplo, trans, diag, n, A, lda, x, incx); +} + +void CUBLASWINAPI cublasStpsv(char uplo, char trans, char diag, int n, const float *AP, + float *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, const float *, float *, int); + static auto func_ptr = LoadSymbol("cublasStpsv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasStpsv"); + return func_ptr(uplo, trans, diag, n, AP, x, incx); +} + +void CUBLASWINAPI cublasDtpsv(char uplo, char trans, char diag, int n, const double *AP, double *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, const double *, double *, int); + static auto func_ptr = LoadSymbol("cublasDtpsv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDtpsv"); + return func_ptr(uplo, trans, diag, n, AP, x, incx); +} + +void CUBLASWINAPI cublasCtpsv(char uplo, char trans, char diag, int n, const cuComplex *AP, cuComplex *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, const cuComplex *, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtpsv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCtpsv"); + return func_ptr(uplo, trans, diag, n, AP, x, incx); +} + +void CUBLASWINAPI cublasZtpsv(char uplo, char trans, char diag, int n, const cuDoubleComplex *AP, + cuDoubleComplex *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, const cuDoubleComplex *, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtpsv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZtpsv"); + return func_ptr(uplo, trans, diag, n, AP, x, incx); +} + +void CUBLASWINAPI cublasStbsv(char uplo, char trans, + char diag, int n, int k, const float *A, + int lda, float *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, int, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasStbsv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasStbsv"); + return func_ptr(uplo, trans, diag, n, k, A, lda, x, incx); +} + +void CUBLASWINAPI cublasDtbsv(char uplo, char trans, + char diag, int n, int k, const double *A, + int lda, double *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, int, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDtbsv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDtbsv"); + return func_ptr(uplo, trans, diag, n, k, A, lda, x, incx); +} + +void CUBLASWINAPI cublasCtbsv(char uplo, char trans, + char diag, int n, int k, const cuComplex *A, + int lda, cuComplex *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtbsv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCtbsv"); + return func_ptr(uplo, trans, diag, n, k, A, lda, x, incx); +} + +void CUBLASWINAPI cublasZtbsv(char uplo, char trans, + char diag, int n, int k, const cuDoubleComplex *A, + int lda, cuDoubleComplex *x, int incx) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, int, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtbsv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZtbsv"); + return func_ptr(uplo, trans, diag, n, k, A, lda, x, incx); +} + +void CUBLASWINAPI cublasSsymv (char uplo, int n, float alpha, const float *A, + int lda, const float *x, int incx, float beta, + float *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, float, const float *, int, const float *, int, float, float *, int); + static auto func_ptr = LoadSymbol("cublasSsymv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSsymv"); + return func_ptr(uplo, n, alpha, A, lda, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasDsymv (char uplo, int n, double alpha, const double *A, + int lda, const double *x, int incx, double beta, + double *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, double, const double *, int, const double *, int, double, double *, int); + static auto func_ptr = LoadSymbol("cublasDsymv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDsymv"); + return func_ptr(uplo, n, alpha, A, lda, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasChemv (char uplo, int n, cuComplex alpha, const cuComplex *A, + int lda, const cuComplex *x, int incx, cuComplex beta, + cuComplex *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, cuComplex, const cuComplex *, int, const cuComplex *, int, cuComplex, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasChemv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasChemv"); + return func_ptr(uplo, n, alpha, A, lda, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasZhemv (char uplo, int n, cuDoubleComplex alpha, const cuDoubleComplex *A, + int lda, const cuDoubleComplex *x, int incx, cuDoubleComplex beta, + cuDoubleComplex *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, cuDoubleComplex, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZhemv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZhemv"); + return func_ptr(uplo, n, alpha, A, lda, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasSsbmv (char uplo, int n, int k, float alpha, + const float *A, int lda, const float *x, int incx, + float beta, float *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, int, float, const float *, int, const float *, int, float, float *, int); + static auto func_ptr = LoadSymbol("cublasSsbmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSsbmv"); + return func_ptr(uplo, n, k, alpha, A, lda, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasDsbmv (char uplo, int n, int k, double alpha, + const double *A, int lda, const double *x, int incx, + double beta, double *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, int, double, const double *, int, const double *, int, double, double *, int); + static auto func_ptr = LoadSymbol("cublasDsbmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDsbmv"); + return func_ptr(uplo, n, k, alpha, A, lda, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasChbmv (char uplo, int n, int k, cuComplex alpha, + const cuComplex *A, int lda, const cuComplex *x, int incx, + cuComplex beta, cuComplex *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, int, cuComplex, const cuComplex *, int, const cuComplex *, int, cuComplex, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasChbmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasChbmv"); + return func_ptr(uplo, n, k, alpha, A, lda, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasZhbmv (char uplo, int n, int k, cuDoubleComplex alpha, + const cuDoubleComplex *A, int lda, const cuDoubleComplex *x, int incx, + cuDoubleComplex beta, cuDoubleComplex *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, int, cuDoubleComplex, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZhbmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZhbmv"); + return func_ptr(uplo, n, k, alpha, A, lda, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasSspmv(char uplo, int n, float alpha, + const float *AP, const float *x, + int incx, float beta, float *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, float, const float *, const float *, int, float, float *, int); + static auto func_ptr = LoadSymbol("cublasSspmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSspmv"); + return func_ptr(uplo, n, alpha, AP, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasDspmv(char uplo, int n, double alpha, + const double *AP, const double *x, + int incx, double beta, double *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, double, const double *, const double *, int, double, double *, int); + static auto func_ptr = LoadSymbol("cublasDspmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDspmv"); + return func_ptr(uplo, n, alpha, AP, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasChpmv(char uplo, int n, cuComplex alpha, + const cuComplex *AP, const cuComplex *x, + int incx, cuComplex beta, cuComplex *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, cuComplex, const cuComplex *, const cuComplex *, int, cuComplex, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasChpmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasChpmv"); + return func_ptr(uplo, n, alpha, AP, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasZhpmv(char uplo, int n, cuDoubleComplex alpha, + const cuDoubleComplex *AP, const cuDoubleComplex *x, + int incx, cuDoubleComplex beta, cuDoubleComplex *y, int incy) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, cuDoubleComplex, const cuDoubleComplex *, const cuDoubleComplex *, int, cuDoubleComplex, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZhpmv"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZhpmv"); + return func_ptr(uplo, n, alpha, AP, x, incx, beta, y, incy); +} + +void CUBLASWINAPI cublasSger (int m, int n, float alpha, const float *x, int incx, + const float *y, int incy, float *A, int lda) { + using FuncPtr = void (CUBLASWINAPI *)(int, int, float, const float *, int, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasSger"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSger"); + return func_ptr(m, n, alpha, x, incx, y, incy, A, lda); +} + +void CUBLASWINAPI cublasDger (int m, int n, double alpha, const double *x, int incx, + const double *y, int incy, double *A, int lda) { + using FuncPtr = void (CUBLASWINAPI *)(int, int, double, const double *, int, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDger"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDger"); + return func_ptr(m, n, alpha, x, incx, y, incy, A, lda); +} + +void CUBLASWINAPI cublasCgeru (int m, int n, cuComplex alpha, const cuComplex *x, + int incx, const cuComplex *y, int incy, + cuComplex *A, int lda) { + using FuncPtr = void (CUBLASWINAPI *)(int, int, cuComplex, const cuComplex *, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCgeru"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCgeru"); + return func_ptr(m, n, alpha, x, incx, y, incy, A, lda); +} + +void CUBLASWINAPI cublasCgerc (int m, int n, cuComplex alpha, const cuComplex *x, + int incx, const cuComplex *y, int incy, + cuComplex *A, int lda) { + using FuncPtr = void (CUBLASWINAPI *)(int, int, cuComplex, const cuComplex *, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCgerc"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCgerc"); + return func_ptr(m, n, alpha, x, incx, y, incy, A, lda); +} + +void CUBLASWINAPI cublasZgeru (int m, int n, cuDoubleComplex alpha, const cuDoubleComplex *x, + int incx, const cuDoubleComplex *y, int incy, + cuDoubleComplex *A, int lda) { + using FuncPtr = void (CUBLASWINAPI *)(int, int, cuDoubleComplex, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZgeru"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZgeru"); + return func_ptr(m, n, alpha, x, incx, y, incy, A, lda); +} + +void CUBLASWINAPI cublasZgerc (int m, int n, cuDoubleComplex alpha, const cuDoubleComplex *x, + int incx, const cuDoubleComplex *y, int incy, + cuDoubleComplex *A, int lda) { + using FuncPtr = void (CUBLASWINAPI *)(int, int, cuDoubleComplex, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZgerc"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZgerc"); + return func_ptr(m, n, alpha, x, incx, y, incy, A, lda); +} + +void CUBLASWINAPI cublasSsyr (char uplo, int n, float alpha, const float *x, + int incx, float *A, int lda) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, float, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasSsyr"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSsyr"); + return func_ptr(uplo, n, alpha, x, incx, A, lda); +} + +void CUBLASWINAPI cublasDsyr (char uplo, int n, double alpha, const double *x, + int incx, double *A, int lda) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, double, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDsyr"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDsyr"); + return func_ptr(uplo, n, alpha, x, incx, A, lda); +} + +void CUBLASWINAPI cublasCher (char uplo, int n, float alpha, + const cuComplex *x, int incx, cuComplex *A, int lda) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, float, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCher"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCher"); + return func_ptr(uplo, n, alpha, x, incx, A, lda); +} + +void CUBLASWINAPI cublasZher (char uplo, int n, double alpha, + const cuDoubleComplex *x, int incx, cuDoubleComplex *A, int lda) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, double, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZher"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZher"); + return func_ptr(uplo, n, alpha, x, incx, A, lda); +} + +void CUBLASWINAPI cublasSspr (char uplo, int n, float alpha, const float *x, + int incx, float *AP) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, float, const float *, int, float *); + static auto func_ptr = LoadSymbol("cublasSspr"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSspr"); + return func_ptr(uplo, n, alpha, x, incx, AP); +} + +void CUBLASWINAPI cublasDspr (char uplo, int n, double alpha, const double *x, + int incx, double *AP) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, double, const double *, int, double *); + static auto func_ptr = LoadSymbol("cublasDspr"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDspr"); + return func_ptr(uplo, n, alpha, x, incx, AP); +} + +void CUBLASWINAPI cublasChpr (char uplo, int n, float alpha, const cuComplex *x, + int incx, cuComplex *AP) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, float, const cuComplex *, int, cuComplex *); + static auto func_ptr = LoadSymbol("cublasChpr"); + if (!func_ptr) LogFatalSymbolNotFound("cublasChpr"); + return func_ptr(uplo, n, alpha, x, incx, AP); +} + +void CUBLASWINAPI cublasZhpr (char uplo, int n, double alpha, const cuDoubleComplex *x, + int incx, cuDoubleComplex *AP) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, double, const cuDoubleComplex *, int, cuDoubleComplex *); + static auto func_ptr = LoadSymbol("cublasZhpr"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZhpr"); + return func_ptr(uplo, n, alpha, x, incx, AP); +} + +void CUBLASWINAPI cublasSsyr2 (char uplo, int n, float alpha, const float *x, + int incx, const float *y, int incy, float *A, + int lda) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, float, const float *, int, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasSsyr2"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSsyr2"); + return func_ptr(uplo, n, alpha, x, incx, y, incy, A, lda); +} + +void CUBLASWINAPI cublasDsyr2 (char uplo, int n, double alpha, const double *x, + int incx, const double *y, int incy, double *A, + int lda) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, double, const double *, int, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDsyr2"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDsyr2"); + return func_ptr(uplo, n, alpha, x, incx, y, incy, A, lda); +} + +void CUBLASWINAPI cublasCher2 (char uplo, int n, cuComplex alpha, const cuComplex *x, + int incx, const cuComplex *y, int incy, cuComplex *A, + int lda) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, cuComplex, const cuComplex *, int, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCher2"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCher2"); + return func_ptr(uplo, n, alpha, x, incx, y, incy, A, lda); +} + +void CUBLASWINAPI cublasZher2 (char uplo, int n, cuDoubleComplex alpha, const cuDoubleComplex *x, + int incx, const cuDoubleComplex *y, int incy, cuDoubleComplex *A, + int lda) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, cuDoubleComplex, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZher2"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZher2"); + return func_ptr(uplo, n, alpha, x, incx, y, incy, A, lda); +} + +void CUBLASWINAPI cublasSspr2 (char uplo, int n, float alpha, const float *x, + int incx, const float *y, int incy, float *AP) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, float, const float *, int, const float *, int, float *); + static auto func_ptr = LoadSymbol("cublasSspr2"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSspr2"); + return func_ptr(uplo, n, alpha, x, incx, y, incy, AP); +} + +void CUBLASWINAPI cublasDspr2 (char uplo, int n, double alpha, + const double *x, int incx, const double *y, + int incy, double *AP) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, double, const double *, int, const double *, int, double *); + static auto func_ptr = LoadSymbol("cublasDspr2"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDspr2"); + return func_ptr(uplo, n, alpha, x, incx, y, incy, AP); +} + +void CUBLASWINAPI cublasChpr2 (char uplo, int n, cuComplex alpha, + const cuComplex *x, int incx, const cuComplex *y, + int incy, cuComplex *AP) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, cuComplex, const cuComplex *, int, const cuComplex *, int, cuComplex *); + static auto func_ptr = LoadSymbol("cublasChpr2"); + if (!func_ptr) LogFatalSymbolNotFound("cublasChpr2"); + return func_ptr(uplo, n, alpha, x, incx, y, incy, AP); +} + +void CUBLASWINAPI cublasZhpr2 (char uplo, int n, cuDoubleComplex alpha, + const cuDoubleComplex *x, int incx, const cuDoubleComplex *y, + int incy, cuDoubleComplex *AP) { + using FuncPtr = void (CUBLASWINAPI *)(char, int, cuDoubleComplex, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex *); + static auto func_ptr = LoadSymbol("cublasZhpr2"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZhpr2"); + return func_ptr(uplo, n, alpha, x, incx, y, incy, AP); +} + +void CUBLASWINAPI cublasSgemm (char transa, char transb, int m, int n, int k, + float alpha, const float *A, int lda, + const float *B, int ldb, float beta, float *C, + int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, int, float, const float *, int, const float *, int, float, float *, int); + static auto func_ptr = LoadSymbol("cublasSgemm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSgemm"); + return func_ptr(transa, transb, m, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +void CUBLASWINAPI cublasDgemm (char transa, char transb, int m, int n, int k, + double alpha, const double *A, int lda, + const double *B, int ldb, double beta, double *C, + int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, int, double, const double *, int, const double *, int, double, double *, int); + static auto func_ptr = LoadSymbol("cublasDgemm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDgemm"); + return func_ptr(transa, transb, m, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +void CUBLASWINAPI cublasCgemm (char transa, char transb, int m, int n, int k, + cuComplex alpha, const cuComplex *A, int lda, + const cuComplex *B, int ldb, cuComplex beta, + cuComplex *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, int, cuComplex, const cuComplex *, int, const cuComplex *, int, cuComplex, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCgemm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCgemm"); + return func_ptr(transa, transb, m, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +void CUBLASWINAPI cublasZgemm (char transa, char transb, int m, int n, + int k, cuDoubleComplex alpha, + const cuDoubleComplex *A, int lda, + const cuDoubleComplex *B, int ldb, + cuDoubleComplex beta, cuDoubleComplex *C, + int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, int, cuDoubleComplex, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZgemm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZgemm"); + return func_ptr(transa, transb, m, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +void CUBLASWINAPI cublasSsyrk (char uplo, char trans, int n, int k, float alpha, + const float *A, int lda, float beta, float *C, + int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, float, const float *, int, float, float *, int); + static auto func_ptr = LoadSymbol("cublasSsyrk"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSsyrk"); + return func_ptr(uplo, trans, n, k, alpha, A, lda, beta, C, ldc); +} + +void CUBLASWINAPI cublasDsyrk (char uplo, char trans, int n, int k, + double alpha, const double *A, int lda, + double beta, double *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, double, const double *, int, double, double *, int); + static auto func_ptr = LoadSymbol("cublasDsyrk"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDsyrk"); + return func_ptr(uplo, trans, n, k, alpha, A, lda, beta, C, ldc); +} + +void CUBLASWINAPI cublasCsyrk (char uplo, char trans, int n, int k, + cuComplex alpha, const cuComplex *A, int lda, + cuComplex beta, cuComplex *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, cuComplex, const cuComplex *, int, cuComplex, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCsyrk"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCsyrk"); + return func_ptr(uplo, trans, n, k, alpha, A, lda, beta, C, ldc); +} + +void CUBLASWINAPI cublasZsyrk (char uplo, char trans, int n, int k, + cuDoubleComplex alpha, + const cuDoubleComplex *A, int lda, + cuDoubleComplex beta, + cuDoubleComplex *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, cuDoubleComplex, const cuDoubleComplex *, int, cuDoubleComplex, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZsyrk"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZsyrk"); + return func_ptr(uplo, trans, n, k, alpha, A, lda, beta, C, ldc); +} + +void CUBLASWINAPI cublasCherk (char uplo, char trans, int n, int k, + float alpha, const cuComplex *A, int lda, + float beta, cuComplex *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, float, const cuComplex *, int, float, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCherk"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCherk"); + return func_ptr(uplo, trans, n, k, alpha, A, lda, beta, C, ldc); +} + +void CUBLASWINAPI cublasZherk (char uplo, char trans, int n, int k, + double alpha, + const cuDoubleComplex *A, int lda, + double beta, + cuDoubleComplex *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, double, const cuDoubleComplex *, int, double, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZherk"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZherk"); + return func_ptr(uplo, trans, n, k, alpha, A, lda, beta, C, ldc); +} + +void CUBLASWINAPI cublasSsyr2k (char uplo, char trans, int n, int k, float alpha, + const float *A, int lda, const float *B, int ldb, + float beta, float *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, float, const float *, int, const float *, int, float, float *, int); + static auto func_ptr = LoadSymbol("cublasSsyr2k"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSsyr2k"); + return func_ptr(uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +void CUBLASWINAPI cublasDsyr2k (char uplo, char trans, int n, int k, + double alpha, const double *A, int lda, + const double *B, int ldb, double beta, + double *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, double, const double *, int, const double *, int, double, double *, int); + static auto func_ptr = LoadSymbol("cublasDsyr2k"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDsyr2k"); + return func_ptr(uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +void CUBLASWINAPI cublasCsyr2k (char uplo, char trans, int n, int k, + cuComplex alpha, const cuComplex *A, int lda, + const cuComplex *B, int ldb, cuComplex beta, + cuComplex *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, cuComplex, const cuComplex *, int, const cuComplex *, int, cuComplex, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCsyr2k"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCsyr2k"); + return func_ptr(uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +void CUBLASWINAPI cublasZsyr2k (char uplo, char trans, int n, int k, + cuDoubleComplex alpha, const cuDoubleComplex *A, int lda, + const cuDoubleComplex *B, int ldb, cuDoubleComplex beta, + cuDoubleComplex *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, cuDoubleComplex, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZsyr2k"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZsyr2k"); + return func_ptr(uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +void CUBLASWINAPI cublasCher2k (char uplo, char trans, int n, int k, + cuComplex alpha, const cuComplex *A, int lda, + const cuComplex *B, int ldb, float beta, + cuComplex *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, cuComplex, const cuComplex *, int, const cuComplex *, int, float, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCher2k"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCher2k"); + return func_ptr(uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +void CUBLASWINAPI cublasZher2k (char uplo, char trans, int n, int k, + cuDoubleComplex alpha, const cuDoubleComplex *A, int lda, + const cuDoubleComplex *B, int ldb, double beta, + cuDoubleComplex *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, cuDoubleComplex, const cuDoubleComplex *, int, const cuDoubleComplex *, int, double, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZher2k"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZher2k"); + return func_ptr(uplo, trans, n, k, alpha, A, lda, B, ldb, beta, C, ldc); +} + +void CUBLASWINAPI cublasSsymm (char side, char uplo, int m, int n, float alpha, + const float *A, int lda, const float *B, int ldb, + float beta, float *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, float, const float *, int, const float *, int, float, float *, int); + static auto func_ptr = LoadSymbol("cublasSsymm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasSsymm"); + return func_ptr(side, uplo, m, n, alpha, A, lda, B, ldb, beta, C, ldc); +} + +void CUBLASWINAPI cublasDsymm (char side, char uplo, int m, int n, double alpha, + const double *A, int lda, const double *B, int ldb, + double beta, double *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, double, const double *, int, const double *, int, double, double *, int); + static auto func_ptr = LoadSymbol("cublasDsymm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDsymm"); + return func_ptr(side, uplo, m, n, alpha, A, lda, B, ldb, beta, C, ldc); +} + +void CUBLASWINAPI cublasCsymm (char side, char uplo, int m, int n, cuComplex alpha, + const cuComplex *A, int lda, const cuComplex *B, int ldb, + cuComplex beta, cuComplex *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, cuComplex, const cuComplex *, int, const cuComplex *, int, cuComplex, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCsymm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCsymm"); + return func_ptr(side, uplo, m, n, alpha, A, lda, B, ldb, beta, C, ldc); +} + +void CUBLASWINAPI cublasZsymm (char side, char uplo, int m, int n, cuDoubleComplex alpha, + const cuDoubleComplex *A, int lda, const cuDoubleComplex *B, int ldb, + cuDoubleComplex beta, cuDoubleComplex *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, cuDoubleComplex, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZsymm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZsymm"); + return func_ptr(side, uplo, m, n, alpha, A, lda, B, ldb, beta, C, ldc); +} + +void CUBLASWINAPI cublasChemm (char side, char uplo, int m, int n, + cuComplex alpha, const cuComplex *A, int lda, + const cuComplex *B, int ldb, cuComplex beta, + cuComplex *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, cuComplex, const cuComplex *, int, const cuComplex *, int, cuComplex, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasChemm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasChemm"); + return func_ptr(side, uplo, m, n, alpha, A, lda, B, ldb, beta, C, ldc); +} + +void CUBLASWINAPI cublasZhemm (char side, char uplo, int m, int n, + cuDoubleComplex alpha, const cuDoubleComplex *A, int lda, + const cuDoubleComplex *B, int ldb, cuDoubleComplex beta, + cuDoubleComplex *C, int ldc) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, int, int, cuDoubleComplex, const cuDoubleComplex *, int, const cuDoubleComplex *, int, cuDoubleComplex, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZhemm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZhemm"); + return func_ptr(side, uplo, m, n, alpha, A, lda, B, ldb, beta, C, ldc); +} + +void CUBLASWINAPI cublasStrsm (char side, char uplo, char transa, char diag, + int m, int n, float alpha, const float *A, int lda, + float *B, int ldb) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, char, int, int, float, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasStrsm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasStrsm"); + return func_ptr(side, uplo, transa, diag, m, n, alpha, A, lda, B, ldb); +} + +void CUBLASWINAPI cublasDtrsm (char side, char uplo, char transa, + char diag, int m, int n, double alpha, + const double *A, int lda, double *B, + int ldb) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, char, int, int, double, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDtrsm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDtrsm"); + return func_ptr(side, uplo, transa, diag, m, n, alpha, A, lda, B, ldb); +} + +void CUBLASWINAPI cublasCtrsm (char side, char uplo, char transa, char diag, + int m, int n, cuComplex alpha, const cuComplex *A, + int lda, cuComplex *B, int ldb) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, char, int, int, cuComplex, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtrsm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCtrsm"); + return func_ptr(side, uplo, transa, diag, m, n, alpha, A, lda, B, ldb); +} + +void CUBLASWINAPI cublasZtrsm (char side, char uplo, char transa, + char diag, int m, int n, cuDoubleComplex alpha, + const cuDoubleComplex *A, int lda, + cuDoubleComplex *B, int ldb) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, char, int, int, cuDoubleComplex, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtrsm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZtrsm"); + return func_ptr(side, uplo, transa, diag, m, n, alpha, A, lda, B, ldb); +} + +void CUBLASWINAPI cublasStrmm (char side, char uplo, char transa, char diag, + int m, int n, float alpha, const float *A, int lda, + float *B, int ldb) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, char, int, int, float, const float *, int, float *, int); + static auto func_ptr = LoadSymbol("cublasStrmm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasStrmm"); + return func_ptr(side, uplo, transa, diag, m, n, alpha, A, lda, B, ldb); +} + +void CUBLASWINAPI cublasDtrmm (char side, char uplo, char transa, + char diag, int m, int n, double alpha, + const double *A, int lda, double *B, + int ldb) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, char, int, int, double, const double *, int, double *, int); + static auto func_ptr = LoadSymbol("cublasDtrmm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasDtrmm"); + return func_ptr(side, uplo, transa, diag, m, n, alpha, A, lda, B, ldb); +} + +void CUBLASWINAPI cublasCtrmm (char side, char uplo, char transa, char diag, + int m, int n, cuComplex alpha, const cuComplex *A, + int lda, cuComplex *B, int ldb) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, char, int, int, cuComplex, const cuComplex *, int, cuComplex *, int); + static auto func_ptr = LoadSymbol("cublasCtrmm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasCtrmm"); + return func_ptr(side, uplo, transa, diag, m, n, alpha, A, lda, B, ldb); +} + +void CUBLASWINAPI cublasZtrmm (char side, char uplo, char transa, + char diag, int m, int n, cuDoubleComplex alpha, + const cuDoubleComplex *A, int lda, cuDoubleComplex *B, + int ldb) { + using FuncPtr = void (CUBLASWINAPI *)(char, char, char, char, int, int, cuDoubleComplex, const cuDoubleComplex *, int, cuDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cublasZtrmm"); + if (!func_ptr) LogFatalSymbolNotFound("cublasZtrmm"); + return func_ptr(side, uplo, transa, diag, m, n, alpha, A, lda, B, ldb); +} + +} // extern "C" diff --git a/tensorflow/stream_executor/cuda/cublas_stub.cc b/tensorflow/stream_executor/cuda/cublas_stub.cc new file mode 100644 index 0000000000..beca8c1a46 --- /dev/null +++ b/tensorflow/stream_executor/cuda/cublas_stub.cc @@ -0,0 +1,65 @@ +/* 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 "cuda/include/cublas.h" +#include "cuda/include/cuda.h" +#include "tensorflow/stream_executor/lib/env.h" +#include "tensorflow/stream_executor/platform/dso_loader.h" + +// Implements the cuBLAS API by forwarding to cuBLAS loaded from the DSO. +// Note that it does not implement the v1 interface. + +namespace { +// Returns DSO handle or null if loading the DSO fails. +void* GetDsoHandle() { +#ifdef PLATFORM_GOOGLE + return nullptr; +#else + static auto handle = []() -> void* { + auto handle_or = stream_executor::internal::DsoLoader::GetCublasDsoHandle(); + if (!handle_or.ok()) return nullptr; + return handle_or.ValueOrDie(); + }(); + return handle; +#endif +} + +template +T LoadSymbol(const char* symbol_name) { + void* symbol = nullptr; + if (auto handle = GetDsoHandle()) { + stream_executor::port::Env::Default() + ->GetSymbolFromLibrary(handle, symbol_name, &symbol) + .IgnoreError(); + } + return reinterpret_cast(symbol); +} + +void LogFatalSymbolNotFound(const char* symbol_name) { + LOG(FATAL) << symbol_name << " symbol not found."; +} + +cublasStatus_t GetSymbolNotFoundError() { return CUBLAS_STATUS_INTERNAL_ERROR; } +} // namespace + +#if CUDA_VERSION < 9000 +typedef enum {} cublasMath_t; +#endif + +// Parameter constness changed in cuBLAS 9.2 +#if CUDA_VERSION < 9020 +#include "tensorflow/stream_executor/cuda/cublas_9_0.inc" +#else +#include "tensorflow/stream_executor/cuda/cublas_10_0.inc" +#endif diff --git a/tensorflow/stream_executor/cuda/cuda_10_0.inc b/tensorflow/stream_executor/cuda/cuda_10_0.inc new file mode 100644 index 0000000000..f096b99aa1 --- /dev/null +++ b/tensorflow/stream_executor/cuda/cuda_10_0.inc @@ -0,0 +1,1822 @@ +// Auto-generated, do not edit. + +extern "C" { +CUresult CUDAAPI cuGetErrorString(CUresult error, const char **pStr) { + using FuncPtr = CUresult (CUDAAPI *)(CUresult, const char **); + static auto func_ptr = LoadSymbol("cuGetErrorString"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(error, pStr); +} + +CUresult CUDAAPI cuGetErrorName(CUresult error, const char **pStr) { + using FuncPtr = CUresult (CUDAAPI *)(CUresult, const char **); + static auto func_ptr = LoadSymbol("cuGetErrorName"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(error, pStr); +} + +CUresult CUDAAPI cuInit(unsigned int Flags) { + using FuncPtr = CUresult (CUDAAPI *)(unsigned int); + static auto func_ptr = LoadSymbol("cuInit"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(Flags); +} + +CUresult CUDAAPI cuDriverGetVersion(int *driverVersion) { + using FuncPtr = CUresult (CUDAAPI *)(int *); + static auto func_ptr = LoadSymbol("cuDriverGetVersion"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(driverVersion); +} + +CUresult CUDAAPI cuDeviceGet(CUdevice *device, int ordinal) { + using FuncPtr = CUresult (CUDAAPI *)(CUdevice *, int); + static auto func_ptr = LoadSymbol("cuDeviceGet"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(device, ordinal); +} + +CUresult CUDAAPI cuDeviceGetCount(int *count) { + using FuncPtr = CUresult (CUDAAPI *)(int *); + static auto func_ptr = LoadSymbol("cuDeviceGetCount"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(count); +} + +CUresult CUDAAPI cuDeviceGetName(char *name, int len, CUdevice dev) { + using FuncPtr = CUresult (CUDAAPI *)(char *, int, CUdevice); + static auto func_ptr = LoadSymbol("cuDeviceGetName"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(name, len, dev); +} + +CUresult CUDAAPI cuDeviceGetUuid(CUuuid *uuid, CUdevice dev) { + using FuncPtr = CUresult (CUDAAPI *)(CUuuid *, CUdevice); + static auto func_ptr = LoadSymbol("cuDeviceGetUuid"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(uuid, dev); +} + +CUresult CUDAAPI cuDeviceTotalMem(size_t *bytes, CUdevice dev) { + using FuncPtr = CUresult (CUDAAPI *)(size_t *, CUdevice); + static auto func_ptr = LoadSymbol("cuDeviceTotalMem_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(bytes, dev); +} + +CUresult CUDAAPI cuDeviceGetAttribute(int *pi, CUdevice_attribute attrib, CUdevice dev) { + using FuncPtr = CUresult (CUDAAPI *)(int *, CUdevice_attribute, CUdevice); + static auto func_ptr = LoadSymbol("cuDeviceGetAttribute"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pi, attrib, dev); +} + +__CUDA_DEPRECATED CUresult CUDAAPI cuDeviceGetProperties(CUdevprop *prop, CUdevice dev) { + using FuncPtr = CUresult (CUDAAPI *)(CUdevprop *, CUdevice); + static auto func_ptr = LoadSymbol("cuDeviceGetProperties"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(prop, dev); +} + +__CUDA_DEPRECATED CUresult CUDAAPI cuDeviceComputeCapability(int *major, int *minor, CUdevice dev) { + using FuncPtr = CUresult (CUDAAPI *)(int *, int *, CUdevice); + static auto func_ptr = LoadSymbol("cuDeviceComputeCapability"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(major, minor, dev); +} + +CUresult CUDAAPI cuDevicePrimaryCtxRetain(CUcontext *pctx, CUdevice dev) { + using FuncPtr = CUresult (CUDAAPI *)(CUcontext *, CUdevice); + static auto func_ptr = LoadSymbol("cuDevicePrimaryCtxRetain"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pctx, dev); +} + +CUresult CUDAAPI cuDevicePrimaryCtxRelease(CUdevice dev) { + using FuncPtr = CUresult (CUDAAPI *)(CUdevice); + static auto func_ptr = LoadSymbol("cuDevicePrimaryCtxRelease"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dev); +} + +CUresult CUDAAPI cuDevicePrimaryCtxSetFlags(CUdevice dev, unsigned int flags) { + using FuncPtr = CUresult (CUDAAPI *)(CUdevice, unsigned int); + static auto func_ptr = LoadSymbol("cuDevicePrimaryCtxSetFlags"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dev, flags); +} + +CUresult CUDAAPI cuDevicePrimaryCtxGetState(CUdevice dev, unsigned int *flags, int *active) { + using FuncPtr = CUresult (CUDAAPI *)(CUdevice, unsigned int *, int *); + static auto func_ptr = LoadSymbol("cuDevicePrimaryCtxGetState"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dev, flags, active); +} + +CUresult CUDAAPI cuDevicePrimaryCtxReset(CUdevice dev) { + using FuncPtr = CUresult (CUDAAPI *)(CUdevice); + static auto func_ptr = LoadSymbol("cuDevicePrimaryCtxReset"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dev); +} + +CUresult CUDAAPI cuCtxCreate(CUcontext *pctx, unsigned int flags, CUdevice dev) { + using FuncPtr = CUresult (CUDAAPI *)(CUcontext *, unsigned int, CUdevice); + static auto func_ptr = LoadSymbol("cuCtxCreate_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pctx, flags, dev); +} + +CUresult CUDAAPI cuCtxDestroy(CUcontext ctx) { + using FuncPtr = CUresult (CUDAAPI *)(CUcontext); + static auto func_ptr = LoadSymbol("cuCtxDestroy_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(ctx); +} + +CUresult CUDAAPI cuCtxPushCurrent(CUcontext ctx) { + using FuncPtr = CUresult (CUDAAPI *)(CUcontext); + static auto func_ptr = LoadSymbol("cuCtxPushCurrent_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(ctx); +} + +CUresult CUDAAPI cuCtxPopCurrent(CUcontext *pctx) { + using FuncPtr = CUresult (CUDAAPI *)(CUcontext *); + static auto func_ptr = LoadSymbol("cuCtxPopCurrent_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pctx); +} + +CUresult CUDAAPI cuCtxSetCurrent(CUcontext ctx) { + using FuncPtr = CUresult (CUDAAPI *)(CUcontext); + static auto func_ptr = LoadSymbol("cuCtxSetCurrent"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(ctx); +} + +CUresult CUDAAPI cuCtxGetCurrent(CUcontext *pctx) { + using FuncPtr = CUresult (CUDAAPI *)(CUcontext *); + static auto func_ptr = LoadSymbol("cuCtxGetCurrent"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pctx); +} + +CUresult CUDAAPI cuCtxGetDevice(CUdevice *device) { + using FuncPtr = CUresult (CUDAAPI *)(CUdevice *); + static auto func_ptr = LoadSymbol("cuCtxGetDevice"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(device); +} + +CUresult CUDAAPI cuCtxGetFlags(unsigned int *flags) { + using FuncPtr = CUresult (CUDAAPI *)(unsigned int *); + static auto func_ptr = LoadSymbol("cuCtxGetFlags"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(flags); +} + +CUresult CUDAAPI cuCtxSynchronize(void) { + using FuncPtr = CUresult (CUDAAPI *)(); + static auto func_ptr = LoadSymbol("cuCtxSynchronize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(); +} + +CUresult CUDAAPI cuCtxSetLimit(CUlimit limit, size_t value) { + using FuncPtr = CUresult (CUDAAPI *)(CUlimit, size_t); + static auto func_ptr = LoadSymbol("cuCtxSetLimit"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(limit, value); +} + +CUresult CUDAAPI cuCtxGetLimit(size_t *pvalue, CUlimit limit) { + using FuncPtr = CUresult (CUDAAPI *)(size_t *, CUlimit); + static auto func_ptr = LoadSymbol("cuCtxGetLimit"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pvalue, limit); +} + +CUresult CUDAAPI cuCtxGetCacheConfig(CUfunc_cache *pconfig) { + using FuncPtr = CUresult (CUDAAPI *)(CUfunc_cache *); + static auto func_ptr = LoadSymbol("cuCtxGetCacheConfig"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pconfig); +} + +CUresult CUDAAPI cuCtxSetCacheConfig(CUfunc_cache config) { + using FuncPtr = CUresult (CUDAAPI *)(CUfunc_cache); + static auto func_ptr = LoadSymbol("cuCtxSetCacheConfig"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(config); +} + +CUresult CUDAAPI cuCtxGetSharedMemConfig(CUsharedconfig *pConfig) { + using FuncPtr = CUresult (CUDAAPI *)(CUsharedconfig *); + static auto func_ptr = LoadSymbol("cuCtxGetSharedMemConfig"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pConfig); +} + +CUresult CUDAAPI cuCtxSetSharedMemConfig(CUsharedconfig config) { + using FuncPtr = CUresult (CUDAAPI *)(CUsharedconfig); + static auto func_ptr = LoadSymbol("cuCtxSetSharedMemConfig"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(config); +} + +CUresult CUDAAPI cuCtxGetApiVersion(CUcontext ctx, unsigned int *version) { + using FuncPtr = CUresult (CUDAAPI *)(CUcontext, unsigned int *); + static auto func_ptr = LoadSymbol("cuCtxGetApiVersion"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(ctx, version); +} + +CUresult CUDAAPI cuCtxGetStreamPriorityRange(int *leastPriority, int *greatestPriority) { + using FuncPtr = CUresult (CUDAAPI *)(int *, int *); + static auto func_ptr = LoadSymbol("cuCtxGetStreamPriorityRange"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(leastPriority, greatestPriority); +} + +__CUDA_DEPRECATED CUresult CUDAAPI cuCtxAttach(CUcontext *pctx, unsigned int flags) { + using FuncPtr = CUresult (CUDAAPI *)(CUcontext *, unsigned int); + static auto func_ptr = LoadSymbol("cuCtxAttach"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pctx, flags); +} + +__CUDA_DEPRECATED CUresult CUDAAPI cuCtxDetach(CUcontext ctx) { + using FuncPtr = CUresult (CUDAAPI *)(CUcontext); + static auto func_ptr = LoadSymbol("cuCtxDetach"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(ctx); +} + +CUresult CUDAAPI cuModuleLoad(CUmodule *module, const char *fname) { + using FuncPtr = CUresult (CUDAAPI *)(CUmodule *, const char *); + static auto func_ptr = LoadSymbol("cuModuleLoad"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(module, fname); +} + +CUresult CUDAAPI cuModuleLoadData(CUmodule *module, const void *image) { + using FuncPtr = CUresult (CUDAAPI *)(CUmodule *, const void *); + static auto func_ptr = LoadSymbol("cuModuleLoadData"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(module, image); +} + +CUresult CUDAAPI cuModuleLoadDataEx(CUmodule *module, const void *image, unsigned int numOptions, CUjit_option *options, void **optionValues) { + using FuncPtr = CUresult (CUDAAPI *)(CUmodule *, const void *, unsigned int, CUjit_option *, void **); + static auto func_ptr = LoadSymbol("cuModuleLoadDataEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(module, image, numOptions, options, optionValues); +} + +CUresult CUDAAPI cuModuleLoadFatBinary(CUmodule *module, const void *fatCubin) { + using FuncPtr = CUresult (CUDAAPI *)(CUmodule *, const void *); + static auto func_ptr = LoadSymbol("cuModuleLoadFatBinary"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(module, fatCubin); +} + +CUresult CUDAAPI cuModuleUnload(CUmodule hmod) { + using FuncPtr = CUresult (CUDAAPI *)(CUmodule); + static auto func_ptr = LoadSymbol("cuModuleUnload"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hmod); +} + +CUresult CUDAAPI cuModuleGetFunction(CUfunction *hfunc, CUmodule hmod, const char *name) { + using FuncPtr = CUresult (CUDAAPI *)(CUfunction *, CUmodule, const char *); + static auto func_ptr = LoadSymbol("cuModuleGetFunction"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hfunc, hmod, name); +} + +CUresult CUDAAPI cuModuleGetGlobal(CUdeviceptr *dptr, size_t *bytes, CUmodule hmod, const char *name) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr *, size_t *, CUmodule, const char *); + static auto func_ptr = LoadSymbol("cuModuleGetGlobal_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dptr, bytes, hmod, name); +} + +CUresult CUDAAPI cuModuleGetTexRef(CUtexref *pTexRef, CUmodule hmod, const char *name) { + using FuncPtr = CUresult (CUDAAPI *)(CUtexref *, CUmodule, const char *); + static auto func_ptr = LoadSymbol("cuModuleGetTexRef"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pTexRef, hmod, name); +} + +CUresult CUDAAPI cuModuleGetSurfRef(CUsurfref *pSurfRef, CUmodule hmod, const char *name) { + using FuncPtr = CUresult (CUDAAPI *)(CUsurfref *, CUmodule, const char *); + static auto func_ptr = LoadSymbol("cuModuleGetSurfRef"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pSurfRef, hmod, name); +} + +CUresult CUDAAPI +cuLinkCreate(unsigned int numOptions, CUjit_option *options, void **optionValues, CUlinkState *stateOut) { + using FuncPtr = CUresult (CUDAAPI *)(unsigned int, CUjit_option *, void **, CUlinkState *); + static auto func_ptr = LoadSymbol("cuLinkCreate_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(numOptions, options, optionValues, stateOut); +} + +CUresult CUDAAPI +cuLinkAddData(CUlinkState state, CUjitInputType type, void *data, size_t size, const char *name, + unsigned int numOptions, CUjit_option *options, void **optionValues) { + using FuncPtr = CUresult (CUDAAPI *)(CUlinkState, CUjitInputType, void *, size_t, const char *, unsigned int, CUjit_option *, void **); + static auto func_ptr = LoadSymbol("cuLinkAddData_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(state, type, data, size, name, numOptions, options, optionValues); +} + +CUresult CUDAAPI +cuLinkAddFile(CUlinkState state, CUjitInputType type, const char *path, + unsigned int numOptions, CUjit_option *options, void **optionValues) { + using FuncPtr = CUresult (CUDAAPI *)(CUlinkState, CUjitInputType, const char *, unsigned int, CUjit_option *, void **); + static auto func_ptr = LoadSymbol("cuLinkAddFile_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(state, type, path, numOptions, options, optionValues); +} + +CUresult CUDAAPI +cuLinkComplete(CUlinkState state, void **cubinOut, size_t *sizeOut) { + using FuncPtr = CUresult (CUDAAPI *)(CUlinkState, void **, size_t *); + static auto func_ptr = LoadSymbol("cuLinkComplete"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(state, cubinOut, sizeOut); +} + +CUresult CUDAAPI +cuLinkDestroy(CUlinkState state) { + using FuncPtr = CUresult (CUDAAPI *)(CUlinkState); + static auto func_ptr = LoadSymbol("cuLinkDestroy"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(state); +} + +CUresult CUDAAPI cuMemGetInfo(size_t *free, size_t *total) { + using FuncPtr = CUresult (CUDAAPI *)(size_t *, size_t *); + static auto func_ptr = LoadSymbol("cuMemGetInfo_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(free, total); +} + +CUresult CUDAAPI cuMemAlloc(CUdeviceptr *dptr, size_t bytesize) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr *, size_t); + static auto func_ptr = LoadSymbol("cuMemAlloc_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dptr, bytesize); +} + +CUresult CUDAAPI cuMemAllocPitch(CUdeviceptr *dptr, size_t *pPitch, size_t WidthInBytes, size_t Height, unsigned int ElementSizeBytes) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr *, size_t *, size_t, size_t, unsigned int); + static auto func_ptr = LoadSymbol("cuMemAllocPitch_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dptr, pPitch, WidthInBytes, Height, ElementSizeBytes); +} + +CUresult CUDAAPI cuMemFree(CUdeviceptr dptr) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr); + static auto func_ptr = LoadSymbol("cuMemFree_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dptr); +} + +CUresult CUDAAPI cuMemGetAddressRange(CUdeviceptr *pbase, size_t *psize, CUdeviceptr dptr) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr *, size_t *, CUdeviceptr); + static auto func_ptr = LoadSymbol("cuMemGetAddressRange_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pbase, psize, dptr); +} + +CUresult CUDAAPI cuMemAllocHost(void **pp, size_t bytesize) { + using FuncPtr = CUresult (CUDAAPI *)(void **, size_t); + static auto func_ptr = LoadSymbol("cuMemAllocHost_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pp, bytesize); +} + +CUresult CUDAAPI cuMemFreeHost(void *p) { + using FuncPtr = CUresult (CUDAAPI *)(void *); + static auto func_ptr = LoadSymbol("cuMemFreeHost"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(p); +} + +CUresult CUDAAPI cuMemHostAlloc(void **pp, size_t bytesize, unsigned int Flags) { + using FuncPtr = CUresult (CUDAAPI *)(void **, size_t, unsigned int); + static auto func_ptr = LoadSymbol("cuMemHostAlloc"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pp, bytesize, Flags); +} + +CUresult CUDAAPI cuMemHostGetDevicePointer(CUdeviceptr *pdptr, void *p, unsigned int Flags) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr *, void *, unsigned int); + static auto func_ptr = LoadSymbol("cuMemHostGetDevicePointer_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pdptr, p, Flags); +} + +CUresult CUDAAPI cuMemHostGetFlags(unsigned int *pFlags, void *p) { + using FuncPtr = CUresult (CUDAAPI *)(unsigned int *, void *); + static auto func_ptr = LoadSymbol("cuMemHostGetFlags"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pFlags, p); +} + +CUresult CUDAAPI cuMemAllocManaged(CUdeviceptr *dptr, size_t bytesize, unsigned int flags) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr *, size_t, unsigned int); + static auto func_ptr = LoadSymbol("cuMemAllocManaged"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dptr, bytesize, flags); +} + +CUresult CUDAAPI cuDeviceGetByPCIBusId(CUdevice *dev, const char *pciBusId) { + using FuncPtr = CUresult (CUDAAPI *)(CUdevice *, const char *); + static auto func_ptr = LoadSymbol("cuDeviceGetByPCIBusId"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dev, pciBusId); +} + +CUresult CUDAAPI cuDeviceGetPCIBusId(char *pciBusId, int len, CUdevice dev) { + using FuncPtr = CUresult (CUDAAPI *)(char *, int, CUdevice); + static auto func_ptr = LoadSymbol("cuDeviceGetPCIBusId"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pciBusId, len, dev); +} + +CUresult CUDAAPI cuIpcGetEventHandle(CUipcEventHandle *pHandle, CUevent event) { + using FuncPtr = CUresult (CUDAAPI *)(CUipcEventHandle *, CUevent); + static auto func_ptr = LoadSymbol("cuIpcGetEventHandle"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pHandle, event); +} + +CUresult CUDAAPI cuIpcOpenEventHandle(CUevent *phEvent, CUipcEventHandle handle) { + using FuncPtr = CUresult (CUDAAPI *)(CUevent *, CUipcEventHandle); + static auto func_ptr = LoadSymbol("cuIpcOpenEventHandle"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(phEvent, handle); +} + +CUresult CUDAAPI cuIpcGetMemHandle(CUipcMemHandle *pHandle, CUdeviceptr dptr) { + using FuncPtr = CUresult (CUDAAPI *)(CUipcMemHandle *, CUdeviceptr); + static auto func_ptr = LoadSymbol("cuIpcGetMemHandle"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pHandle, dptr); +} + +CUresult CUDAAPI cuIpcOpenMemHandle(CUdeviceptr *pdptr, CUipcMemHandle handle, unsigned int Flags) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr *, CUipcMemHandle, unsigned int); + static auto func_ptr = LoadSymbol("cuIpcOpenMemHandle"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pdptr, handle, Flags); +} + +CUresult CUDAAPI cuIpcCloseMemHandle(CUdeviceptr dptr) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr); + static auto func_ptr = LoadSymbol("cuIpcCloseMemHandle"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dptr); +} + +CUresult CUDAAPI cuMemHostRegister(void *p, size_t bytesize, unsigned int Flags) { + using FuncPtr = CUresult (CUDAAPI *)(void *, size_t, unsigned int); + static auto func_ptr = LoadSymbol("cuMemHostRegister_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(p, bytesize, Flags); +} + +CUresult CUDAAPI cuMemHostUnregister(void *p) { + using FuncPtr = CUresult (CUDAAPI *)(void *); + static auto func_ptr = LoadSymbol("cuMemHostUnregister"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(p); +} + +CUresult CUDAAPI cuMemcpy(CUdeviceptr dst, CUdeviceptr src, size_t ByteCount) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr, CUdeviceptr, size_t); + static auto func_ptr = LoadSymbol("cuMemcpy"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dst, src, ByteCount); +} + +CUresult CUDAAPI cuMemcpyPeer(CUdeviceptr dstDevice, CUcontext dstContext, CUdeviceptr srcDevice, CUcontext srcContext, size_t ByteCount) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr, CUcontext, CUdeviceptr, CUcontext, size_t); + static auto func_ptr = LoadSymbol("cuMemcpyPeer"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dstDevice, dstContext, srcDevice, srcContext, ByteCount); +} + +CUresult CUDAAPI cuMemcpyHtoD(CUdeviceptr dstDevice, const void *srcHost, size_t ByteCount) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr, const void *, size_t); + static auto func_ptr = LoadSymbol("cuMemcpyHtoD_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dstDevice, srcHost, ByteCount); +} + +CUresult CUDAAPI cuMemcpyDtoH(void *dstHost, CUdeviceptr srcDevice, size_t ByteCount) { + using FuncPtr = CUresult (CUDAAPI *)(void *, CUdeviceptr, size_t); + static auto func_ptr = LoadSymbol("cuMemcpyDtoH_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dstHost, srcDevice, ByteCount); +} + +CUresult CUDAAPI cuMemcpyDtoD(CUdeviceptr dstDevice, CUdeviceptr srcDevice, size_t ByteCount) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr, CUdeviceptr, size_t); + static auto func_ptr = LoadSymbol("cuMemcpyDtoD_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dstDevice, srcDevice, ByteCount); +} + +CUresult CUDAAPI cuMemcpyDtoA(CUarray dstArray, size_t dstOffset, CUdeviceptr srcDevice, size_t ByteCount) { + using FuncPtr = CUresult (CUDAAPI *)(CUarray, size_t, CUdeviceptr, size_t); + static auto func_ptr = LoadSymbol("cuMemcpyDtoA_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dstArray, dstOffset, srcDevice, ByteCount); +} + +CUresult CUDAAPI cuMemcpyAtoD(CUdeviceptr dstDevice, CUarray srcArray, size_t srcOffset, size_t ByteCount) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr, CUarray, size_t, size_t); + static auto func_ptr = LoadSymbol("cuMemcpyAtoD_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dstDevice, srcArray, srcOffset, ByteCount); +} + +CUresult CUDAAPI cuMemcpyHtoA(CUarray dstArray, size_t dstOffset, const void *srcHost, size_t ByteCount) { + using FuncPtr = CUresult (CUDAAPI *)(CUarray, size_t, const void *, size_t); + static auto func_ptr = LoadSymbol("cuMemcpyHtoA_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dstArray, dstOffset, srcHost, ByteCount); +} + +CUresult CUDAAPI cuMemcpyAtoH(void *dstHost, CUarray srcArray, size_t srcOffset, size_t ByteCount) { + using FuncPtr = CUresult (CUDAAPI *)(void *, CUarray, size_t, size_t); + static auto func_ptr = LoadSymbol("cuMemcpyAtoH_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dstHost, srcArray, srcOffset, ByteCount); +} + +CUresult CUDAAPI cuMemcpyAtoA(CUarray dstArray, size_t dstOffset, CUarray srcArray, size_t srcOffset, size_t ByteCount) { + using FuncPtr = CUresult (CUDAAPI *)(CUarray, size_t, CUarray, size_t, size_t); + static auto func_ptr = LoadSymbol("cuMemcpyAtoA_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dstArray, dstOffset, srcArray, srcOffset, ByteCount); +} + +CUresult CUDAAPI cuMemcpy2D(const CUDA_MEMCPY2D *pCopy) { + using FuncPtr = CUresult (CUDAAPI *)(const CUDA_MEMCPY2D *); + static auto func_ptr = LoadSymbol("cuMemcpy2D_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pCopy); +} + +CUresult CUDAAPI cuMemcpy2DUnaligned(const CUDA_MEMCPY2D *pCopy) { + using FuncPtr = CUresult (CUDAAPI *)(const CUDA_MEMCPY2D *); + static auto func_ptr = LoadSymbol("cuMemcpy2DUnaligned_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pCopy); +} + +CUresult CUDAAPI cuMemcpy3D(const CUDA_MEMCPY3D *pCopy) { + using FuncPtr = CUresult (CUDAAPI *)(const CUDA_MEMCPY3D *); + static auto func_ptr = LoadSymbol("cuMemcpy3D_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pCopy); +} + +CUresult CUDAAPI cuMemcpy3DPeer(const CUDA_MEMCPY3D_PEER *pCopy) { + using FuncPtr = CUresult (CUDAAPI *)(const CUDA_MEMCPY3D_PEER *); + static auto func_ptr = LoadSymbol("cuMemcpy3DPeer"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pCopy); +} + +CUresult CUDAAPI cuMemcpyAsync(CUdeviceptr dst, CUdeviceptr src, size_t ByteCount, CUstream hStream) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr, CUdeviceptr, size_t, CUstream); + static auto func_ptr = LoadSymbol("cuMemcpyAsync"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dst, src, ByteCount, hStream); +} + +CUresult CUDAAPI cuMemcpyPeerAsync(CUdeviceptr dstDevice, CUcontext dstContext, CUdeviceptr srcDevice, CUcontext srcContext, size_t ByteCount, CUstream hStream) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr, CUcontext, CUdeviceptr, CUcontext, size_t, CUstream); + static auto func_ptr = LoadSymbol("cuMemcpyPeerAsync"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dstDevice, dstContext, srcDevice, srcContext, ByteCount, hStream); +} + +CUresult CUDAAPI cuMemcpyHtoDAsync(CUdeviceptr dstDevice, const void *srcHost, size_t ByteCount, CUstream hStream) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr, const void *, size_t, CUstream); + static auto func_ptr = LoadSymbol("cuMemcpyHtoDAsync_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dstDevice, srcHost, ByteCount, hStream); +} + +CUresult CUDAAPI cuMemcpyDtoHAsync(void *dstHost, CUdeviceptr srcDevice, size_t ByteCount, CUstream hStream) { + using FuncPtr = CUresult (CUDAAPI *)(void *, CUdeviceptr, size_t, CUstream); + static auto func_ptr = LoadSymbol("cuMemcpyDtoHAsync_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dstHost, srcDevice, ByteCount, hStream); +} + +CUresult CUDAAPI cuMemcpyDtoDAsync(CUdeviceptr dstDevice, CUdeviceptr srcDevice, size_t ByteCount, CUstream hStream) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr, CUdeviceptr, size_t, CUstream); + static auto func_ptr = LoadSymbol("cuMemcpyDtoDAsync_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dstDevice, srcDevice, ByteCount, hStream); +} + +CUresult CUDAAPI cuMemcpyHtoAAsync(CUarray dstArray, size_t dstOffset, const void *srcHost, size_t ByteCount, CUstream hStream) { + using FuncPtr = CUresult (CUDAAPI *)(CUarray, size_t, const void *, size_t, CUstream); + static auto func_ptr = LoadSymbol("cuMemcpyHtoAAsync_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dstArray, dstOffset, srcHost, ByteCount, hStream); +} + +CUresult CUDAAPI cuMemcpyAtoHAsync(void *dstHost, CUarray srcArray, size_t srcOffset, size_t ByteCount, CUstream hStream) { + using FuncPtr = CUresult (CUDAAPI *)(void *, CUarray, size_t, size_t, CUstream); + static auto func_ptr = LoadSymbol("cuMemcpyAtoHAsync_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dstHost, srcArray, srcOffset, ByteCount, hStream); +} + +CUresult CUDAAPI cuMemcpy2DAsync(const CUDA_MEMCPY2D *pCopy, CUstream hStream) { + using FuncPtr = CUresult (CUDAAPI *)(const CUDA_MEMCPY2D *, CUstream); + static auto func_ptr = LoadSymbol("cuMemcpy2DAsync_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pCopy, hStream); +} + +CUresult CUDAAPI cuMemcpy3DAsync(const CUDA_MEMCPY3D *pCopy, CUstream hStream) { + using FuncPtr = CUresult (CUDAAPI *)(const CUDA_MEMCPY3D *, CUstream); + static auto func_ptr = LoadSymbol("cuMemcpy3DAsync_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pCopy, hStream); +} + +CUresult CUDAAPI cuMemcpy3DPeerAsync(const CUDA_MEMCPY3D_PEER *pCopy, CUstream hStream) { + using FuncPtr = CUresult (CUDAAPI *)(const CUDA_MEMCPY3D_PEER *, CUstream); + static auto func_ptr = LoadSymbol("cuMemcpy3DPeerAsync"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pCopy, hStream); +} + +CUresult CUDAAPI cuMemsetD8(CUdeviceptr dstDevice, unsigned char uc, size_t N) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr, unsigned char, size_t); + static auto func_ptr = LoadSymbol("cuMemsetD8_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dstDevice, uc, N); +} + +CUresult CUDAAPI cuMemsetD16(CUdeviceptr dstDevice, unsigned short us, size_t N) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr, unsigned short, size_t); + static auto func_ptr = LoadSymbol("cuMemsetD16_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dstDevice, us, N); +} + +CUresult CUDAAPI cuMemsetD32(CUdeviceptr dstDevice, unsigned int ui, size_t N) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr, unsigned int, size_t); + static auto func_ptr = LoadSymbol("cuMemsetD32_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dstDevice, ui, N); +} + +CUresult CUDAAPI cuMemsetD2D8(CUdeviceptr dstDevice, size_t dstPitch, unsigned char uc, size_t Width, size_t Height) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr, size_t, unsigned char, size_t, size_t); + static auto func_ptr = LoadSymbol("cuMemsetD2D8_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dstDevice, dstPitch, uc, Width, Height); +} + +CUresult CUDAAPI cuMemsetD2D16(CUdeviceptr dstDevice, size_t dstPitch, unsigned short us, size_t Width, size_t Height) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr, size_t, unsigned short, size_t, size_t); + static auto func_ptr = LoadSymbol("cuMemsetD2D16_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dstDevice, dstPitch, us, Width, Height); +} + +CUresult CUDAAPI cuMemsetD2D32(CUdeviceptr dstDevice, size_t dstPitch, unsigned int ui, size_t Width, size_t Height) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr, size_t, unsigned int, size_t, size_t); + static auto func_ptr = LoadSymbol("cuMemsetD2D32_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dstDevice, dstPitch, ui, Width, Height); +} + +CUresult CUDAAPI cuMemsetD8Async(CUdeviceptr dstDevice, unsigned char uc, size_t N, CUstream hStream) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr, unsigned char, size_t, CUstream); + static auto func_ptr = LoadSymbol("cuMemsetD8Async"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dstDevice, uc, N, hStream); +} + +CUresult CUDAAPI cuMemsetD16Async(CUdeviceptr dstDevice, unsigned short us, size_t N, CUstream hStream) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr, unsigned short, size_t, CUstream); + static auto func_ptr = LoadSymbol("cuMemsetD16Async"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dstDevice, us, N, hStream); +} + +CUresult CUDAAPI cuMemsetD32Async(CUdeviceptr dstDevice, unsigned int ui, size_t N, CUstream hStream) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr, unsigned int, size_t, CUstream); + static auto func_ptr = LoadSymbol("cuMemsetD32Async"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dstDevice, ui, N, hStream); +} + +CUresult CUDAAPI cuMemsetD2D8Async(CUdeviceptr dstDevice, size_t dstPitch, unsigned char uc, size_t Width, size_t Height, CUstream hStream) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr, size_t, unsigned char, size_t, size_t, CUstream); + static auto func_ptr = LoadSymbol("cuMemsetD2D8Async"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dstDevice, dstPitch, uc, Width, Height, hStream); +} + +CUresult CUDAAPI cuMemsetD2D16Async(CUdeviceptr dstDevice, size_t dstPitch, unsigned short us, size_t Width, size_t Height, CUstream hStream) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr, size_t, unsigned short, size_t, size_t, CUstream); + static auto func_ptr = LoadSymbol("cuMemsetD2D16Async"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dstDevice, dstPitch, us, Width, Height, hStream); +} + +CUresult CUDAAPI cuMemsetD2D32Async(CUdeviceptr dstDevice, size_t dstPitch, unsigned int ui, size_t Width, size_t Height, CUstream hStream) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr, size_t, unsigned int, size_t, size_t, CUstream); + static auto func_ptr = LoadSymbol("cuMemsetD2D32Async"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dstDevice, dstPitch, ui, Width, Height, hStream); +} + +CUresult CUDAAPI cuArrayCreate(CUarray *pHandle, const CUDA_ARRAY_DESCRIPTOR *pAllocateArray) { + using FuncPtr = CUresult (CUDAAPI *)(CUarray *, const CUDA_ARRAY_DESCRIPTOR *); + static auto func_ptr = LoadSymbol("cuArrayCreate_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pHandle, pAllocateArray); +} + +CUresult CUDAAPI cuArrayGetDescriptor(CUDA_ARRAY_DESCRIPTOR *pArrayDescriptor, CUarray hArray) { + using FuncPtr = CUresult (CUDAAPI *)(CUDA_ARRAY_DESCRIPTOR *, CUarray); + static auto func_ptr = LoadSymbol("cuArrayGetDescriptor_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pArrayDescriptor, hArray); +} + +CUresult CUDAAPI cuArrayDestroy(CUarray hArray) { + using FuncPtr = CUresult (CUDAAPI *)(CUarray); + static auto func_ptr = LoadSymbol("cuArrayDestroy"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hArray); +} + +CUresult CUDAAPI cuArray3DCreate(CUarray *pHandle, const CUDA_ARRAY3D_DESCRIPTOR *pAllocateArray) { + using FuncPtr = CUresult (CUDAAPI *)(CUarray *, const CUDA_ARRAY3D_DESCRIPTOR *); + static auto func_ptr = LoadSymbol("cuArray3DCreate_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pHandle, pAllocateArray); +} + +CUresult CUDAAPI cuArray3DGetDescriptor(CUDA_ARRAY3D_DESCRIPTOR *pArrayDescriptor, CUarray hArray) { + using FuncPtr = CUresult (CUDAAPI *)(CUDA_ARRAY3D_DESCRIPTOR *, CUarray); + static auto func_ptr = LoadSymbol("cuArray3DGetDescriptor_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pArrayDescriptor, hArray); +} + +CUresult CUDAAPI cuMipmappedArrayCreate(CUmipmappedArray *pHandle, const CUDA_ARRAY3D_DESCRIPTOR *pMipmappedArrayDesc, unsigned int numMipmapLevels) { + using FuncPtr = CUresult (CUDAAPI *)(CUmipmappedArray *, const CUDA_ARRAY3D_DESCRIPTOR *, unsigned int); + static auto func_ptr = LoadSymbol("cuMipmappedArrayCreate"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pHandle, pMipmappedArrayDesc, numMipmapLevels); +} + +CUresult CUDAAPI cuMipmappedArrayGetLevel(CUarray *pLevelArray, CUmipmappedArray hMipmappedArray, unsigned int level) { + using FuncPtr = CUresult (CUDAAPI *)(CUarray *, CUmipmappedArray, unsigned int); + static auto func_ptr = LoadSymbol("cuMipmappedArrayGetLevel"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pLevelArray, hMipmappedArray, level); +} + +CUresult CUDAAPI cuMipmappedArrayDestroy(CUmipmappedArray hMipmappedArray) { + using FuncPtr = CUresult (CUDAAPI *)(CUmipmappedArray); + static auto func_ptr = LoadSymbol("cuMipmappedArrayDestroy"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hMipmappedArray); +} + +CUresult CUDAAPI cuPointerGetAttribute(void *data, CUpointer_attribute attribute, CUdeviceptr ptr) { + using FuncPtr = CUresult (CUDAAPI *)(void *, CUpointer_attribute, CUdeviceptr); + static auto func_ptr = LoadSymbol("cuPointerGetAttribute"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(data, attribute, ptr); +} + +CUresult CUDAAPI cuMemPrefetchAsync(CUdeviceptr devPtr, size_t count, CUdevice dstDevice, CUstream hStream) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr, size_t, CUdevice, CUstream); + static auto func_ptr = LoadSymbol("cuMemPrefetchAsync"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(devPtr, count, dstDevice, hStream); +} + +CUresult CUDAAPI cuMemAdvise(CUdeviceptr devPtr, size_t count, CUmem_advise advice, CUdevice device) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr, size_t, CUmem_advise, CUdevice); + static auto func_ptr = LoadSymbol("cuMemAdvise"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(devPtr, count, advice, device); +} + +CUresult CUDAAPI cuMemRangeGetAttribute(void *data, size_t dataSize, CUmem_range_attribute attribute, CUdeviceptr devPtr, size_t count) { + using FuncPtr = CUresult (CUDAAPI *)(void *, size_t, CUmem_range_attribute, CUdeviceptr, size_t); + static auto func_ptr = LoadSymbol("cuMemRangeGetAttribute"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(data, dataSize, attribute, devPtr, count); +} + +CUresult CUDAAPI cuMemRangeGetAttributes(void **data, size_t *dataSizes, CUmem_range_attribute *attributes, size_t numAttributes, CUdeviceptr devPtr, size_t count) { + using FuncPtr = CUresult (CUDAAPI *)(void **, size_t *, CUmem_range_attribute *, size_t, CUdeviceptr, size_t); + static auto func_ptr = LoadSymbol("cuMemRangeGetAttributes"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(data, dataSizes, attributes, numAttributes, devPtr, count); +} + +CUresult CUDAAPI cuPointerSetAttribute(const void *value, CUpointer_attribute attribute, CUdeviceptr ptr) { + using FuncPtr = CUresult (CUDAAPI *)(const void *, CUpointer_attribute, CUdeviceptr); + static auto func_ptr = LoadSymbol("cuPointerSetAttribute"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(value, attribute, ptr); +} + +CUresult CUDAAPI cuPointerGetAttributes(unsigned int numAttributes, CUpointer_attribute *attributes, void **data, CUdeviceptr ptr) { + using FuncPtr = CUresult (CUDAAPI *)(unsigned int, CUpointer_attribute *, void **, CUdeviceptr); + static auto func_ptr = LoadSymbol("cuPointerGetAttributes"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(numAttributes, attributes, data, ptr); +} + +CUresult CUDAAPI cuStreamCreate(CUstream *phStream, unsigned int Flags) { + using FuncPtr = CUresult (CUDAAPI *)(CUstream *, unsigned int); + static auto func_ptr = LoadSymbol("cuStreamCreate"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(phStream, Flags); +} + +CUresult CUDAAPI cuStreamCreateWithPriority(CUstream *phStream, unsigned int flags, int priority) { + using FuncPtr = CUresult (CUDAAPI *)(CUstream *, unsigned int, int); + static auto func_ptr = LoadSymbol("cuStreamCreateWithPriority"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(phStream, flags, priority); +} + +CUresult CUDAAPI cuStreamGetPriority(CUstream hStream, int *priority) { + using FuncPtr = CUresult (CUDAAPI *)(CUstream, int *); + static auto func_ptr = LoadSymbol("cuStreamGetPriority"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hStream, priority); +} + +CUresult CUDAAPI cuStreamGetFlags(CUstream hStream, unsigned int *flags) { + using FuncPtr = CUresult (CUDAAPI *)(CUstream, unsigned int *); + static auto func_ptr = LoadSymbol("cuStreamGetFlags"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hStream, flags); +} + +CUresult CUDAAPI cuStreamGetCtx(CUstream hStream, CUcontext *pctx) { + using FuncPtr = CUresult (CUDAAPI *)(CUstream, CUcontext *); + static auto func_ptr = LoadSymbol("cuStreamGetCtx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hStream, pctx); +} + +CUresult CUDAAPI cuStreamWaitEvent(CUstream hStream, CUevent hEvent, unsigned int Flags) { + using FuncPtr = CUresult (CUDAAPI *)(CUstream, CUevent, unsigned int); + static auto func_ptr = LoadSymbol("cuStreamWaitEvent"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hStream, hEvent, Flags); +} + +CUresult CUDAAPI cuStreamAddCallback(CUstream hStream, CUstreamCallback callback, void *userData, unsigned int flags) { + using FuncPtr = CUresult (CUDAAPI *)(CUstream, CUstreamCallback, void *, unsigned int); + static auto func_ptr = LoadSymbol("cuStreamAddCallback"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hStream, callback, userData, flags); +} + +CUresult CUDAAPI cuStreamBeginCapture(CUstream hStream) { + using FuncPtr = CUresult (CUDAAPI *)(CUstream); + static auto func_ptr = LoadSymbol("cuStreamBeginCapture"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hStream); +} + +CUresult CUDAAPI cuStreamEndCapture(CUstream hStream, CUgraph *phGraph) { + using FuncPtr = CUresult (CUDAAPI *)(CUstream, CUgraph *); + static auto func_ptr = LoadSymbol("cuStreamEndCapture"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hStream, phGraph); +} + +CUresult CUDAAPI cuStreamIsCapturing(CUstream hStream, CUstreamCaptureStatus *captureStatus) { + using FuncPtr = CUresult (CUDAAPI *)(CUstream, CUstreamCaptureStatus *); + static auto func_ptr = LoadSymbol("cuStreamIsCapturing"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hStream, captureStatus); +} + +CUresult CUDAAPI cuStreamAttachMemAsync(CUstream hStream, CUdeviceptr dptr, size_t length, unsigned int flags) { + using FuncPtr = CUresult (CUDAAPI *)(CUstream, CUdeviceptr, size_t, unsigned int); + static auto func_ptr = LoadSymbol("cuStreamAttachMemAsync"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hStream, dptr, length, flags); +} + +CUresult CUDAAPI cuStreamQuery(CUstream hStream) { + using FuncPtr = CUresult (CUDAAPI *)(CUstream); + static auto func_ptr = LoadSymbol("cuStreamQuery"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hStream); +} + +CUresult CUDAAPI cuStreamSynchronize(CUstream hStream) { + using FuncPtr = CUresult (CUDAAPI *)(CUstream); + static auto func_ptr = LoadSymbol("cuStreamSynchronize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hStream); +} + +CUresult CUDAAPI cuStreamDestroy(CUstream hStream) { + using FuncPtr = CUresult (CUDAAPI *)(CUstream); + static auto func_ptr = LoadSymbol("cuStreamDestroy_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hStream); +} + +CUresult CUDAAPI cuEventCreate(CUevent *phEvent, unsigned int Flags) { + using FuncPtr = CUresult (CUDAAPI *)(CUevent *, unsigned int); + static auto func_ptr = LoadSymbol("cuEventCreate"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(phEvent, Flags); +} + +CUresult CUDAAPI cuEventRecord(CUevent hEvent, CUstream hStream) { + using FuncPtr = CUresult (CUDAAPI *)(CUevent, CUstream); + static auto func_ptr = LoadSymbol("cuEventRecord"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hEvent, hStream); +} + +CUresult CUDAAPI cuEventQuery(CUevent hEvent) { + using FuncPtr = CUresult (CUDAAPI *)(CUevent); + static auto func_ptr = LoadSymbol("cuEventQuery"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hEvent); +} + +CUresult CUDAAPI cuEventSynchronize(CUevent hEvent) { + using FuncPtr = CUresult (CUDAAPI *)(CUevent); + static auto func_ptr = LoadSymbol("cuEventSynchronize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hEvent); +} + +CUresult CUDAAPI cuEventDestroy(CUevent hEvent) { + using FuncPtr = CUresult (CUDAAPI *)(CUevent); + static auto func_ptr = LoadSymbol("cuEventDestroy_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hEvent); +} + +CUresult CUDAAPI cuEventElapsedTime(float *pMilliseconds, CUevent hStart, CUevent hEnd) { + using FuncPtr = CUresult (CUDAAPI *)(float *, CUevent, CUevent); + static auto func_ptr = LoadSymbol("cuEventElapsedTime"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pMilliseconds, hStart, hEnd); +} + +CUresult CUDAAPI cuImportExternalMemory(CUexternalMemory *extMem_out, const CUDA_EXTERNAL_MEMORY_HANDLE_DESC *memHandleDesc) { + using FuncPtr = CUresult (CUDAAPI *)(CUexternalMemory *, const CUDA_EXTERNAL_MEMORY_HANDLE_DESC *); + static auto func_ptr = LoadSymbol("cuImportExternalMemory"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(extMem_out, memHandleDesc); +} + +CUresult CUDAAPI cuExternalMemoryGetMappedBuffer(CUdeviceptr *devPtr, CUexternalMemory extMem, const CUDA_EXTERNAL_MEMORY_BUFFER_DESC *bufferDesc) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr *, CUexternalMemory, const CUDA_EXTERNAL_MEMORY_BUFFER_DESC *); + static auto func_ptr = LoadSymbol("cuExternalMemoryGetMappedBuffer"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(devPtr, extMem, bufferDesc); +} + +CUresult CUDAAPI cuExternalMemoryGetMappedMipmappedArray(CUmipmappedArray *mipmap, CUexternalMemory extMem, const CUDA_EXTERNAL_MEMORY_MIPMAPPED_ARRAY_DESC *mipmapDesc) { + using FuncPtr = CUresult (CUDAAPI *)(CUmipmappedArray *, CUexternalMemory, const CUDA_EXTERNAL_MEMORY_MIPMAPPED_ARRAY_DESC *); + static auto func_ptr = LoadSymbol("cuExternalMemoryGetMappedMipmappedArray"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(mipmap, extMem, mipmapDesc); +} + +CUresult CUDAAPI cuDestroyExternalMemory(CUexternalMemory extMem) { + using FuncPtr = CUresult (CUDAAPI *)(CUexternalMemory); + static auto func_ptr = LoadSymbol("cuDestroyExternalMemory"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(extMem); +} + +CUresult CUDAAPI cuImportExternalSemaphore(CUexternalSemaphore *extSem_out, const CUDA_EXTERNAL_SEMAPHORE_HANDLE_DESC *semHandleDesc) { + using FuncPtr = CUresult (CUDAAPI *)(CUexternalSemaphore *, const CUDA_EXTERNAL_SEMAPHORE_HANDLE_DESC *); + static auto func_ptr = LoadSymbol("cuImportExternalSemaphore"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(extSem_out, semHandleDesc); +} + +CUresult CUDAAPI cuSignalExternalSemaphoresAsync(const CUexternalSemaphore *extSemArray, const CUDA_EXTERNAL_SEMAPHORE_SIGNAL_PARAMS *paramsArray, unsigned int numExtSems, CUstream stream) { + using FuncPtr = CUresult (CUDAAPI *)(const CUexternalSemaphore *, const CUDA_EXTERNAL_SEMAPHORE_SIGNAL_PARAMS *, unsigned int, CUstream); + static auto func_ptr = LoadSymbol("cuSignalExternalSemaphoresAsync"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(extSemArray, paramsArray, numExtSems, stream); +} + +CUresult CUDAAPI cuWaitExternalSemaphoresAsync(const CUexternalSemaphore *extSemArray, const CUDA_EXTERNAL_SEMAPHORE_WAIT_PARAMS *paramsArray, unsigned int numExtSems, CUstream stream) { + using FuncPtr = CUresult (CUDAAPI *)(const CUexternalSemaphore *, const CUDA_EXTERNAL_SEMAPHORE_WAIT_PARAMS *, unsigned int, CUstream); + static auto func_ptr = LoadSymbol("cuWaitExternalSemaphoresAsync"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(extSemArray, paramsArray, numExtSems, stream); +} + +CUresult CUDAAPI cuDestroyExternalSemaphore(CUexternalSemaphore extSem) { + using FuncPtr = CUresult (CUDAAPI *)(CUexternalSemaphore); + static auto func_ptr = LoadSymbol("cuDestroyExternalSemaphore"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(extSem); +} + +CUresult CUDAAPI cuStreamWaitValue32(CUstream stream, CUdeviceptr addr, cuuint32_t value, unsigned int flags) { + using FuncPtr = CUresult (CUDAAPI *)(CUstream, CUdeviceptr, cuuint32_t, unsigned int); + static auto func_ptr = LoadSymbol("cuStreamWaitValue32"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(stream, addr, value, flags); +} + +CUresult CUDAAPI cuStreamWaitValue64(CUstream stream, CUdeviceptr addr, cuuint64_t value, unsigned int flags) { + using FuncPtr = CUresult (CUDAAPI *)(CUstream, CUdeviceptr, cuuint64_t, unsigned int); + static auto func_ptr = LoadSymbol("cuStreamWaitValue64"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(stream, addr, value, flags); +} + +CUresult CUDAAPI cuStreamWriteValue32(CUstream stream, CUdeviceptr addr, cuuint32_t value, unsigned int flags) { + using FuncPtr = CUresult (CUDAAPI *)(CUstream, CUdeviceptr, cuuint32_t, unsigned int); + static auto func_ptr = LoadSymbol("cuStreamWriteValue32"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(stream, addr, value, flags); +} + +CUresult CUDAAPI cuStreamWriteValue64(CUstream stream, CUdeviceptr addr, cuuint64_t value, unsigned int flags) { + using FuncPtr = CUresult (CUDAAPI *)(CUstream, CUdeviceptr, cuuint64_t, unsigned int); + static auto func_ptr = LoadSymbol("cuStreamWriteValue64"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(stream, addr, value, flags); +} + +CUresult CUDAAPI cuStreamBatchMemOp(CUstream stream, unsigned int count, CUstreamBatchMemOpParams *paramArray, unsigned int flags) { + using FuncPtr = CUresult (CUDAAPI *)(CUstream, unsigned int, CUstreamBatchMemOpParams *, unsigned int); + static auto func_ptr = LoadSymbol("cuStreamBatchMemOp"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(stream, count, paramArray, flags); +} + +CUresult CUDAAPI cuFuncGetAttribute(int *pi, CUfunction_attribute attrib, CUfunction hfunc) { + using FuncPtr = CUresult (CUDAAPI *)(int *, CUfunction_attribute, CUfunction); + static auto func_ptr = LoadSymbol("cuFuncGetAttribute"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pi, attrib, hfunc); +} + +CUresult CUDAAPI cuFuncSetAttribute(CUfunction hfunc, CUfunction_attribute attrib, int value) { + using FuncPtr = CUresult (CUDAAPI *)(CUfunction, CUfunction_attribute, int); + static auto func_ptr = LoadSymbol("cuFuncSetAttribute"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hfunc, attrib, value); +} + +CUresult CUDAAPI cuFuncSetCacheConfig(CUfunction hfunc, CUfunc_cache config) { + using FuncPtr = CUresult (CUDAAPI *)(CUfunction, CUfunc_cache); + static auto func_ptr = LoadSymbol("cuFuncSetCacheConfig"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hfunc, config); +} + +CUresult CUDAAPI cuFuncSetSharedMemConfig(CUfunction hfunc, CUsharedconfig config) { + using FuncPtr = CUresult (CUDAAPI *)(CUfunction, CUsharedconfig); + static auto func_ptr = LoadSymbol("cuFuncSetSharedMemConfig"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hfunc, config); +} + +CUresult CUDAAPI cuLaunchKernel(CUfunction f, + unsigned int gridDimX, + unsigned int gridDimY, + unsigned int gridDimZ, + unsigned int blockDimX, + unsigned int blockDimY, + unsigned int blockDimZ, + unsigned int sharedMemBytes, + CUstream hStream, + void **kernelParams, + void **extra) { + using FuncPtr = CUresult (CUDAAPI *)(CUfunction, unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, CUstream, void **, void **); + static auto func_ptr = LoadSymbol("cuLaunchKernel"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(f, gridDimX, gridDimY, gridDimZ, blockDimX, blockDimY, blockDimZ, sharedMemBytes, hStream, kernelParams, extra); +} + +CUresult CUDAAPI cuLaunchCooperativeKernel(CUfunction f, + unsigned int gridDimX, + unsigned int gridDimY, + unsigned int gridDimZ, + unsigned int blockDimX, + unsigned int blockDimY, + unsigned int blockDimZ, + unsigned int sharedMemBytes, + CUstream hStream, + void **kernelParams) { + using FuncPtr = CUresult (CUDAAPI *)(CUfunction, unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, CUstream, void **); + static auto func_ptr = LoadSymbol("cuLaunchCooperativeKernel"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(f, gridDimX, gridDimY, gridDimZ, blockDimX, blockDimY, blockDimZ, sharedMemBytes, hStream, kernelParams); +} + +CUresult CUDAAPI cuLaunchCooperativeKernelMultiDevice(CUDA_LAUNCH_PARAMS *launchParamsList, unsigned int numDevices, unsigned int flags) { + using FuncPtr = CUresult (CUDAAPI *)(CUDA_LAUNCH_PARAMS *, unsigned int, unsigned int); + static auto func_ptr = LoadSymbol("cuLaunchCooperativeKernelMultiDevice"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(launchParamsList, numDevices, flags); +} + +CUresult CUDAAPI cuLaunchHostFunc(CUstream hStream, CUhostFn fn, void *userData) { + using FuncPtr = CUresult (CUDAAPI *)(CUstream, CUhostFn, void *); + static auto func_ptr = LoadSymbol("cuLaunchHostFunc"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hStream, fn, userData); +} + +__CUDA_DEPRECATED CUresult CUDAAPI cuFuncSetBlockShape(CUfunction hfunc, int x, int y, int z) { + using FuncPtr = CUresult (CUDAAPI *)(CUfunction, int, int, int); + static auto func_ptr = LoadSymbol("cuFuncSetBlockShape"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hfunc, x, y, z); +} + +__CUDA_DEPRECATED CUresult CUDAAPI cuFuncSetSharedSize(CUfunction hfunc, unsigned int bytes) { + using FuncPtr = CUresult (CUDAAPI *)(CUfunction, unsigned int); + static auto func_ptr = LoadSymbol("cuFuncSetSharedSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hfunc, bytes); +} + +__CUDA_DEPRECATED CUresult CUDAAPI cuParamSetSize(CUfunction hfunc, unsigned int numbytes) { + using FuncPtr = CUresult (CUDAAPI *)(CUfunction, unsigned int); + static auto func_ptr = LoadSymbol("cuParamSetSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hfunc, numbytes); +} + +__CUDA_DEPRECATED CUresult CUDAAPI cuParamSeti(CUfunction hfunc, int offset, unsigned int value) { + using FuncPtr = CUresult (CUDAAPI *)(CUfunction, int, unsigned int); + static auto func_ptr = LoadSymbol("cuParamSeti"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hfunc, offset, value); +} + +__CUDA_DEPRECATED CUresult CUDAAPI cuParamSetf(CUfunction hfunc, int offset, float value) { + using FuncPtr = CUresult (CUDAAPI *)(CUfunction, int, float); + static auto func_ptr = LoadSymbol("cuParamSetf"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hfunc, offset, value); +} + +__CUDA_DEPRECATED CUresult CUDAAPI cuParamSetv(CUfunction hfunc, int offset, void *ptr, unsigned int numbytes) { + using FuncPtr = CUresult (CUDAAPI *)(CUfunction, int, void *, unsigned int); + static auto func_ptr = LoadSymbol("cuParamSetv"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hfunc, offset, ptr, numbytes); +} + +__CUDA_DEPRECATED CUresult CUDAAPI cuLaunch(CUfunction f) { + using FuncPtr = CUresult (CUDAAPI *)(CUfunction); + static auto func_ptr = LoadSymbol("cuLaunch"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(f); +} + +__CUDA_DEPRECATED CUresult CUDAAPI cuLaunchGrid(CUfunction f, int grid_width, int grid_height) { + using FuncPtr = CUresult (CUDAAPI *)(CUfunction, int, int); + static auto func_ptr = LoadSymbol("cuLaunchGrid"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(f, grid_width, grid_height); +} + +__CUDA_DEPRECATED CUresult CUDAAPI cuLaunchGridAsync(CUfunction f, int grid_width, int grid_height, CUstream hStream) { + using FuncPtr = CUresult (CUDAAPI *)(CUfunction, int, int, CUstream); + static auto func_ptr = LoadSymbol("cuLaunchGridAsync"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(f, grid_width, grid_height, hStream); +} + +__CUDA_DEPRECATED CUresult CUDAAPI cuParamSetTexRef(CUfunction hfunc, int texunit, CUtexref hTexRef) { + using FuncPtr = CUresult (CUDAAPI *)(CUfunction, int, CUtexref); + static auto func_ptr = LoadSymbol("cuParamSetTexRef"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hfunc, texunit, hTexRef); +} + +CUresult CUDAAPI cuGraphCreate(CUgraph *phGraph, unsigned int flags) { + using FuncPtr = CUresult (CUDAAPI *)(CUgraph *, unsigned int); + static auto func_ptr = LoadSymbol("cuGraphCreate"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(phGraph, flags); +} + +CUresult CUDAAPI cuGraphAddKernelNode(CUgraphNode *phGraphNode, CUgraph hGraph, CUgraphNode *dependencies, size_t numDependencies, const CUDA_KERNEL_NODE_PARAMS *nodeParams) { + using FuncPtr = CUresult (CUDAAPI *)(CUgraphNode *, CUgraph, CUgraphNode *, size_t, const CUDA_KERNEL_NODE_PARAMS *); + static auto func_ptr = LoadSymbol("cuGraphAddKernelNode"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(phGraphNode, hGraph, dependencies, numDependencies, nodeParams); +} + +CUresult CUDAAPI cuGraphKernelNodeGetParams(CUgraphNode hNode, CUDA_KERNEL_NODE_PARAMS *nodeParams) { + using FuncPtr = CUresult (CUDAAPI *)(CUgraphNode, CUDA_KERNEL_NODE_PARAMS *); + static auto func_ptr = LoadSymbol("cuGraphKernelNodeGetParams"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hNode, nodeParams); +} + +CUresult CUDAAPI cuGraphKernelNodeSetParams(CUgraphNode hNode, const CUDA_KERNEL_NODE_PARAMS *nodeParams) { + using FuncPtr = CUresult (CUDAAPI *)(CUgraphNode, const CUDA_KERNEL_NODE_PARAMS *); + static auto func_ptr = LoadSymbol("cuGraphKernelNodeSetParams"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hNode, nodeParams); +} + +CUresult CUDAAPI cuGraphAddMemcpyNode(CUgraphNode *phGraphNode, CUgraph hGraph, CUgraphNode *dependencies, size_t numDependencies, const CUDA_MEMCPY3D *copyParams, CUcontext ctx) { + using FuncPtr = CUresult (CUDAAPI *)(CUgraphNode *, CUgraph, CUgraphNode *, size_t, const CUDA_MEMCPY3D *, CUcontext); + static auto func_ptr = LoadSymbol("cuGraphAddMemcpyNode"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(phGraphNode, hGraph, dependencies, numDependencies, copyParams, ctx); +} + +CUresult CUDAAPI cuGraphMemcpyNodeGetParams(CUgraphNode hNode, CUDA_MEMCPY3D *nodeParams) { + using FuncPtr = CUresult (CUDAAPI *)(CUgraphNode, CUDA_MEMCPY3D *); + static auto func_ptr = LoadSymbol("cuGraphMemcpyNodeGetParams"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hNode, nodeParams); +} + +CUresult CUDAAPI cuGraphMemcpyNodeSetParams(CUgraphNode hNode, const CUDA_MEMCPY3D *nodeParams) { + using FuncPtr = CUresult (CUDAAPI *)(CUgraphNode, const CUDA_MEMCPY3D *); + static auto func_ptr = LoadSymbol("cuGraphMemcpyNodeSetParams"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hNode, nodeParams); +} + +CUresult CUDAAPI cuGraphAddMemsetNode(CUgraphNode *phGraphNode, CUgraph hGraph, CUgraphNode *dependencies, size_t numDependencies, const CUDA_MEMSET_NODE_PARAMS *memsetParams, CUcontext ctx) { + using FuncPtr = CUresult (CUDAAPI *)(CUgraphNode *, CUgraph, CUgraphNode *, size_t, const CUDA_MEMSET_NODE_PARAMS *, CUcontext); + static auto func_ptr = LoadSymbol("cuGraphAddMemsetNode"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(phGraphNode, hGraph, dependencies, numDependencies, memsetParams, ctx); +} + +CUresult CUDAAPI cuGraphMemsetNodeGetParams(CUgraphNode hNode, CUDA_MEMSET_NODE_PARAMS *nodeParams) { + using FuncPtr = CUresult (CUDAAPI *)(CUgraphNode, CUDA_MEMSET_NODE_PARAMS *); + static auto func_ptr = LoadSymbol("cuGraphMemsetNodeGetParams"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hNode, nodeParams); +} + +CUresult CUDAAPI cuGraphMemsetNodeSetParams(CUgraphNode hNode, const CUDA_MEMSET_NODE_PARAMS *nodeParams) { + using FuncPtr = CUresult (CUDAAPI *)(CUgraphNode, const CUDA_MEMSET_NODE_PARAMS *); + static auto func_ptr = LoadSymbol("cuGraphMemsetNodeSetParams"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hNode, nodeParams); +} + +CUresult CUDAAPI cuGraphAddHostNode(CUgraphNode *phGraphNode, CUgraph hGraph, CUgraphNode *dependencies, size_t numDependencies, const CUDA_HOST_NODE_PARAMS *nodeParams) { + using FuncPtr = CUresult (CUDAAPI *)(CUgraphNode *, CUgraph, CUgraphNode *, size_t, const CUDA_HOST_NODE_PARAMS *); + static auto func_ptr = LoadSymbol("cuGraphAddHostNode"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(phGraphNode, hGraph, dependencies, numDependencies, nodeParams); +} + +CUresult CUDAAPI cuGraphHostNodeGetParams(CUgraphNode hNode, CUDA_HOST_NODE_PARAMS *nodeParams) { + using FuncPtr = CUresult (CUDAAPI *)(CUgraphNode, CUDA_HOST_NODE_PARAMS *); + static auto func_ptr = LoadSymbol("cuGraphHostNodeGetParams"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hNode, nodeParams); +} + +CUresult CUDAAPI cuGraphHostNodeSetParams(CUgraphNode hNode, const CUDA_HOST_NODE_PARAMS *nodeParams) { + using FuncPtr = CUresult (CUDAAPI *)(CUgraphNode, const CUDA_HOST_NODE_PARAMS *); + static auto func_ptr = LoadSymbol("cuGraphHostNodeSetParams"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hNode, nodeParams); +} + +CUresult CUDAAPI cuGraphAddChildGraphNode(CUgraphNode *phGraphNode, CUgraph hGraph, CUgraphNode *dependencies, size_t numDependencies, CUgraph childGraph) { + using FuncPtr = CUresult (CUDAAPI *)(CUgraphNode *, CUgraph, CUgraphNode *, size_t, CUgraph); + static auto func_ptr = LoadSymbol("cuGraphAddChildGraphNode"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(phGraphNode, hGraph, dependencies, numDependencies, childGraph); +} + +CUresult CUDAAPI cuGraphChildGraphNodeGetGraph(CUgraphNode hNode, CUgraph *phGraph) { + using FuncPtr = CUresult (CUDAAPI *)(CUgraphNode, CUgraph *); + static auto func_ptr = LoadSymbol("cuGraphChildGraphNodeGetGraph"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hNode, phGraph); +} + +CUresult CUDAAPI cuGraphAddEmptyNode(CUgraphNode *phGraphNode, CUgraph hGraph, CUgraphNode *dependencies, size_t numDependencies) { + using FuncPtr = CUresult (CUDAAPI *)(CUgraphNode *, CUgraph, CUgraphNode *, size_t); + static auto func_ptr = LoadSymbol("cuGraphAddEmptyNode"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(phGraphNode, hGraph, dependencies, numDependencies); +} + +CUresult CUDAAPI cuGraphClone(CUgraph *phGraphClone, CUgraph originalGraph) { + using FuncPtr = CUresult (CUDAAPI *)(CUgraph *, CUgraph); + static auto func_ptr = LoadSymbol("cuGraphClone"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(phGraphClone, originalGraph); +} + +CUresult CUDAAPI cuGraphNodeFindInClone(CUgraphNode *phNode, CUgraphNode hOriginalNode, CUgraph hClonedGraph) { + using FuncPtr = CUresult (CUDAAPI *)(CUgraphNode *, CUgraphNode, CUgraph); + static auto func_ptr = LoadSymbol("cuGraphNodeFindInClone"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(phNode, hOriginalNode, hClonedGraph); +} + +CUresult CUDAAPI cuGraphNodeGetType(CUgraphNode hNode, CUgraphNodeType *type) { + using FuncPtr = CUresult (CUDAAPI *)(CUgraphNode, CUgraphNodeType *); + static auto func_ptr = LoadSymbol("cuGraphNodeGetType"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hNode, type); +} + +CUresult CUDAAPI cuGraphGetNodes(CUgraph hGraph, CUgraphNode *nodes, size_t *numNodes) { + using FuncPtr = CUresult (CUDAAPI *)(CUgraph, CUgraphNode *, size_t *); + static auto func_ptr = LoadSymbol("cuGraphGetNodes"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hGraph, nodes, numNodes); +} + +CUresult CUDAAPI cuGraphGetRootNodes(CUgraph hGraph, CUgraphNode *rootNodes, size_t *numRootNodes) { + using FuncPtr = CUresult (CUDAAPI *)(CUgraph, CUgraphNode *, size_t *); + static auto func_ptr = LoadSymbol("cuGraphGetRootNodes"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hGraph, rootNodes, numRootNodes); +} + +CUresult CUDAAPI cuGraphGetEdges(CUgraph hGraph, CUgraphNode *from, CUgraphNode *to, size_t *numEdges) { + using FuncPtr = CUresult (CUDAAPI *)(CUgraph, CUgraphNode *, CUgraphNode *, size_t *); + static auto func_ptr = LoadSymbol("cuGraphGetEdges"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hGraph, from, to, numEdges); +} + +CUresult CUDAAPI cuGraphNodeGetDependencies(CUgraphNode hNode, CUgraphNode *dependencies, size_t *numDependencies) { + using FuncPtr = CUresult (CUDAAPI *)(CUgraphNode, CUgraphNode *, size_t *); + static auto func_ptr = LoadSymbol("cuGraphNodeGetDependencies"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hNode, dependencies, numDependencies); +} + +CUresult CUDAAPI cuGraphNodeGetDependentNodes(CUgraphNode hNode, CUgraphNode *dependentNodes, size_t *numDependentNodes) { + using FuncPtr = CUresult (CUDAAPI *)(CUgraphNode, CUgraphNode *, size_t *); + static auto func_ptr = LoadSymbol("cuGraphNodeGetDependentNodes"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hNode, dependentNodes, numDependentNodes); +} + +CUresult CUDAAPI cuGraphAddDependencies(CUgraph hGraph, CUgraphNode *from, CUgraphNode *to, size_t numDependencies) { + using FuncPtr = CUresult (CUDAAPI *)(CUgraph, CUgraphNode *, CUgraphNode *, size_t); + static auto func_ptr = LoadSymbol("cuGraphAddDependencies"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hGraph, from, to, numDependencies); +} + +CUresult CUDAAPI cuGraphRemoveDependencies(CUgraph hGraph, CUgraphNode *from, CUgraphNode *to, size_t numDependencies) { + using FuncPtr = CUresult (CUDAAPI *)(CUgraph, CUgraphNode *, CUgraphNode *, size_t); + static auto func_ptr = LoadSymbol("cuGraphRemoveDependencies"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hGraph, from, to, numDependencies); +} + +CUresult CUDAAPI cuGraphDestroyNode(CUgraphNode hNode) { + using FuncPtr = CUresult (CUDAAPI *)(CUgraphNode); + static auto func_ptr = LoadSymbol("cuGraphDestroyNode"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hNode); +} + +CUresult CUDAAPI cuGraphInstantiate(CUgraphExec *phGraphExec, CUgraph hGraph, CUgraphNode *phErrorNode, char *logBuffer, size_t bufferSize) { + using FuncPtr = CUresult (CUDAAPI *)(CUgraphExec *, CUgraph, CUgraphNode *, char *, size_t); + static auto func_ptr = LoadSymbol("cuGraphInstantiate"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(phGraphExec, hGraph, phErrorNode, logBuffer, bufferSize); +} + +CUresult CUDAAPI cuGraphLaunch(CUgraphExec hGraphExec, CUstream hStream) { + using FuncPtr = CUresult (CUDAAPI *)(CUgraphExec, CUstream); + static auto func_ptr = LoadSymbol("cuGraphLaunch"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hGraphExec, hStream); +} + +CUresult CUDAAPI cuGraphExecDestroy(CUgraphExec hGraphExec) { + using FuncPtr = CUresult (CUDAAPI *)(CUgraphExec); + static auto func_ptr = LoadSymbol("cuGraphExecDestroy"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hGraphExec); +} + +CUresult CUDAAPI cuGraphDestroy(CUgraph hGraph) { + using FuncPtr = CUresult (CUDAAPI *)(CUgraph); + static auto func_ptr = LoadSymbol("cuGraphDestroy"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hGraph); +} + +CUresult CUDAAPI cuOccupancyMaxActiveBlocksPerMultiprocessor(int *numBlocks, CUfunction func, int blockSize, size_t dynamicSMemSize) { + using FuncPtr = CUresult (CUDAAPI *)(int *, CUfunction, int, size_t); + static auto func_ptr = LoadSymbol("cuOccupancyMaxActiveBlocksPerMultiprocessor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(numBlocks, func, blockSize, dynamicSMemSize); +} + +CUresult CUDAAPI cuOccupancyMaxActiveBlocksPerMultiprocessorWithFlags(int *numBlocks, CUfunction func, int blockSize, size_t dynamicSMemSize, unsigned int flags) { + using FuncPtr = CUresult (CUDAAPI *)(int *, CUfunction, int, size_t, unsigned int); + static auto func_ptr = LoadSymbol("cuOccupancyMaxActiveBlocksPerMultiprocessorWithFlags"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(numBlocks, func, blockSize, dynamicSMemSize, flags); +} + +CUresult CUDAAPI cuOccupancyMaxPotentialBlockSize(int *minGridSize, int *blockSize, CUfunction func, CUoccupancyB2DSize blockSizeToDynamicSMemSize, size_t dynamicSMemSize, int blockSizeLimit) { + using FuncPtr = CUresult (CUDAAPI *)(int *, int *, CUfunction, CUoccupancyB2DSize, size_t, int); + static auto func_ptr = LoadSymbol("cuOccupancyMaxPotentialBlockSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(minGridSize, blockSize, func, blockSizeToDynamicSMemSize, dynamicSMemSize, blockSizeLimit); +} + +CUresult CUDAAPI cuOccupancyMaxPotentialBlockSizeWithFlags(int *minGridSize, int *blockSize, CUfunction func, CUoccupancyB2DSize blockSizeToDynamicSMemSize, size_t dynamicSMemSize, int blockSizeLimit, unsigned int flags) { + using FuncPtr = CUresult (CUDAAPI *)(int *, int *, CUfunction, CUoccupancyB2DSize, size_t, int, unsigned int); + static auto func_ptr = LoadSymbol("cuOccupancyMaxPotentialBlockSizeWithFlags"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(minGridSize, blockSize, func, blockSizeToDynamicSMemSize, dynamicSMemSize, blockSizeLimit, flags); +} + +CUresult CUDAAPI cuTexRefSetArray(CUtexref hTexRef, CUarray hArray, unsigned int Flags) { + using FuncPtr = CUresult (CUDAAPI *)(CUtexref, CUarray, unsigned int); + static auto func_ptr = LoadSymbol("cuTexRefSetArray"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hTexRef, hArray, Flags); +} + +CUresult CUDAAPI cuTexRefSetMipmappedArray(CUtexref hTexRef, CUmipmappedArray hMipmappedArray, unsigned int Flags) { + using FuncPtr = CUresult (CUDAAPI *)(CUtexref, CUmipmappedArray, unsigned int); + static auto func_ptr = LoadSymbol("cuTexRefSetMipmappedArray"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hTexRef, hMipmappedArray, Flags); +} + +CUresult CUDAAPI cuTexRefSetAddress(size_t *ByteOffset, CUtexref hTexRef, CUdeviceptr dptr, size_t bytes) { + using FuncPtr = CUresult (CUDAAPI *)(size_t *, CUtexref, CUdeviceptr, size_t); + static auto func_ptr = LoadSymbol("cuTexRefSetAddress_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(ByteOffset, hTexRef, dptr, bytes); +} + +CUresult CUDAAPI cuTexRefSetAddress2D(CUtexref hTexRef, const CUDA_ARRAY_DESCRIPTOR *desc, CUdeviceptr dptr, size_t Pitch) { + using FuncPtr = CUresult (CUDAAPI *)(CUtexref, const CUDA_ARRAY_DESCRIPTOR *, CUdeviceptr, size_t); + static auto func_ptr = LoadSymbol("cuTexRefSetAddress2D_v3"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hTexRef, desc, dptr, Pitch); +} + +CUresult CUDAAPI cuTexRefSetFormat(CUtexref hTexRef, CUarray_format fmt, int NumPackedComponents) { + using FuncPtr = CUresult (CUDAAPI *)(CUtexref, CUarray_format, int); + static auto func_ptr = LoadSymbol("cuTexRefSetFormat"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hTexRef, fmt, NumPackedComponents); +} + +CUresult CUDAAPI cuTexRefSetAddressMode(CUtexref hTexRef, int dim, CUaddress_mode am) { + using FuncPtr = CUresult (CUDAAPI *)(CUtexref, int, CUaddress_mode); + static auto func_ptr = LoadSymbol("cuTexRefSetAddressMode"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hTexRef, dim, am); +} + +CUresult CUDAAPI cuTexRefSetFilterMode(CUtexref hTexRef, CUfilter_mode fm) { + using FuncPtr = CUresult (CUDAAPI *)(CUtexref, CUfilter_mode); + static auto func_ptr = LoadSymbol("cuTexRefSetFilterMode"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hTexRef, fm); +} + +CUresult CUDAAPI cuTexRefSetMipmapFilterMode(CUtexref hTexRef, CUfilter_mode fm) { + using FuncPtr = CUresult (CUDAAPI *)(CUtexref, CUfilter_mode); + static auto func_ptr = LoadSymbol("cuTexRefSetMipmapFilterMode"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hTexRef, fm); +} + +CUresult CUDAAPI cuTexRefSetMipmapLevelBias(CUtexref hTexRef, float bias) { + using FuncPtr = CUresult (CUDAAPI *)(CUtexref, float); + static auto func_ptr = LoadSymbol("cuTexRefSetMipmapLevelBias"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hTexRef, bias); +} + +CUresult CUDAAPI cuTexRefSetMipmapLevelClamp(CUtexref hTexRef, float minMipmapLevelClamp, float maxMipmapLevelClamp) { + using FuncPtr = CUresult (CUDAAPI *)(CUtexref, float, float); + static auto func_ptr = LoadSymbol("cuTexRefSetMipmapLevelClamp"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hTexRef, minMipmapLevelClamp, maxMipmapLevelClamp); +} + +CUresult CUDAAPI cuTexRefSetMaxAnisotropy(CUtexref hTexRef, unsigned int maxAniso) { + using FuncPtr = CUresult (CUDAAPI *)(CUtexref, unsigned int); + static auto func_ptr = LoadSymbol("cuTexRefSetMaxAnisotropy"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hTexRef, maxAniso); +} + +CUresult CUDAAPI cuTexRefSetBorderColor(CUtexref hTexRef, float *pBorderColor) { + using FuncPtr = CUresult (CUDAAPI *)(CUtexref, float *); + static auto func_ptr = LoadSymbol("cuTexRefSetBorderColor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hTexRef, pBorderColor); +} + +CUresult CUDAAPI cuTexRefSetFlags(CUtexref hTexRef, unsigned int Flags) { + using FuncPtr = CUresult (CUDAAPI *)(CUtexref, unsigned int); + static auto func_ptr = LoadSymbol("cuTexRefSetFlags"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hTexRef, Flags); +} + +CUresult CUDAAPI cuTexRefGetAddress(CUdeviceptr *pdptr, CUtexref hTexRef) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr *, CUtexref); + static auto func_ptr = LoadSymbol("cuTexRefGetAddress_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pdptr, hTexRef); +} + +CUresult CUDAAPI cuTexRefGetArray(CUarray *phArray, CUtexref hTexRef) { + using FuncPtr = CUresult (CUDAAPI *)(CUarray *, CUtexref); + static auto func_ptr = LoadSymbol("cuTexRefGetArray"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(phArray, hTexRef); +} + +CUresult CUDAAPI cuTexRefGetMipmappedArray(CUmipmappedArray *phMipmappedArray, CUtexref hTexRef) { + using FuncPtr = CUresult (CUDAAPI *)(CUmipmappedArray *, CUtexref); + static auto func_ptr = LoadSymbol("cuTexRefGetMipmappedArray"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(phMipmappedArray, hTexRef); +} + +CUresult CUDAAPI cuTexRefGetAddressMode(CUaddress_mode *pam, CUtexref hTexRef, int dim) { + using FuncPtr = CUresult (CUDAAPI *)(CUaddress_mode *, CUtexref, int); + static auto func_ptr = LoadSymbol("cuTexRefGetAddressMode"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pam, hTexRef, dim); +} + +CUresult CUDAAPI cuTexRefGetFilterMode(CUfilter_mode *pfm, CUtexref hTexRef) { + using FuncPtr = CUresult (CUDAAPI *)(CUfilter_mode *, CUtexref); + static auto func_ptr = LoadSymbol("cuTexRefGetFilterMode"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pfm, hTexRef); +} + +CUresult CUDAAPI cuTexRefGetFormat(CUarray_format *pFormat, int *pNumChannels, CUtexref hTexRef) { + using FuncPtr = CUresult (CUDAAPI *)(CUarray_format *, int *, CUtexref); + static auto func_ptr = LoadSymbol("cuTexRefGetFormat"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pFormat, pNumChannels, hTexRef); +} + +CUresult CUDAAPI cuTexRefGetMipmapFilterMode(CUfilter_mode *pfm, CUtexref hTexRef) { + using FuncPtr = CUresult (CUDAAPI *)(CUfilter_mode *, CUtexref); + static auto func_ptr = LoadSymbol("cuTexRefGetMipmapFilterMode"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pfm, hTexRef); +} + +CUresult CUDAAPI cuTexRefGetMipmapLevelBias(float *pbias, CUtexref hTexRef) { + using FuncPtr = CUresult (CUDAAPI *)(float *, CUtexref); + static auto func_ptr = LoadSymbol("cuTexRefGetMipmapLevelBias"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pbias, hTexRef); +} + +CUresult CUDAAPI cuTexRefGetMipmapLevelClamp(float *pminMipmapLevelClamp, float *pmaxMipmapLevelClamp, CUtexref hTexRef) { + using FuncPtr = CUresult (CUDAAPI *)(float *, float *, CUtexref); + static auto func_ptr = LoadSymbol("cuTexRefGetMipmapLevelClamp"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pminMipmapLevelClamp, pmaxMipmapLevelClamp, hTexRef); +} + +CUresult CUDAAPI cuTexRefGetMaxAnisotropy(int *pmaxAniso, CUtexref hTexRef) { + using FuncPtr = CUresult (CUDAAPI *)(int *, CUtexref); + static auto func_ptr = LoadSymbol("cuTexRefGetMaxAnisotropy"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pmaxAniso, hTexRef); +} + +CUresult CUDAAPI cuTexRefGetBorderColor(float *pBorderColor, CUtexref hTexRef) { + using FuncPtr = CUresult (CUDAAPI *)(float *, CUtexref); + static auto func_ptr = LoadSymbol("cuTexRefGetBorderColor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pBorderColor, hTexRef); +} + +CUresult CUDAAPI cuTexRefGetFlags(unsigned int *pFlags, CUtexref hTexRef) { + using FuncPtr = CUresult (CUDAAPI *)(unsigned int *, CUtexref); + static auto func_ptr = LoadSymbol("cuTexRefGetFlags"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pFlags, hTexRef); +} + +__CUDA_DEPRECATED CUresult CUDAAPI cuTexRefCreate(CUtexref *pTexRef) { + using FuncPtr = CUresult (CUDAAPI *)(CUtexref *); + static auto func_ptr = LoadSymbol("cuTexRefCreate"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pTexRef); +} + +__CUDA_DEPRECATED CUresult CUDAAPI cuTexRefDestroy(CUtexref hTexRef) { + using FuncPtr = CUresult (CUDAAPI *)(CUtexref); + static auto func_ptr = LoadSymbol("cuTexRefDestroy"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hTexRef); +} + +CUresult CUDAAPI cuSurfRefSetArray(CUsurfref hSurfRef, CUarray hArray, unsigned int Flags) { + using FuncPtr = CUresult (CUDAAPI *)(CUsurfref, CUarray, unsigned int); + static auto func_ptr = LoadSymbol("cuSurfRefSetArray"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(hSurfRef, hArray, Flags); +} + +CUresult CUDAAPI cuSurfRefGetArray(CUarray *phArray, CUsurfref hSurfRef) { + using FuncPtr = CUresult (CUDAAPI *)(CUarray *, CUsurfref); + static auto func_ptr = LoadSymbol("cuSurfRefGetArray"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(phArray, hSurfRef); +} + +CUresult CUDAAPI cuTexObjectCreate(CUtexObject *pTexObject, const CUDA_RESOURCE_DESC *pResDesc, const CUDA_TEXTURE_DESC *pTexDesc, const CUDA_RESOURCE_VIEW_DESC *pResViewDesc) { + using FuncPtr = CUresult (CUDAAPI *)(CUtexObject *, const CUDA_RESOURCE_DESC *, const CUDA_TEXTURE_DESC *, const CUDA_RESOURCE_VIEW_DESC *); + static auto func_ptr = LoadSymbol("cuTexObjectCreate"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pTexObject, pResDesc, pTexDesc, pResViewDesc); +} + +CUresult CUDAAPI cuTexObjectDestroy(CUtexObject texObject) { + using FuncPtr = CUresult (CUDAAPI *)(CUtexObject); + static auto func_ptr = LoadSymbol("cuTexObjectDestroy"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(texObject); +} + +CUresult CUDAAPI cuTexObjectGetResourceDesc(CUDA_RESOURCE_DESC *pResDesc, CUtexObject texObject) { + using FuncPtr = CUresult (CUDAAPI *)(CUDA_RESOURCE_DESC *, CUtexObject); + static auto func_ptr = LoadSymbol("cuTexObjectGetResourceDesc"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pResDesc, texObject); +} + +CUresult CUDAAPI cuTexObjectGetTextureDesc(CUDA_TEXTURE_DESC *pTexDesc, CUtexObject texObject) { + using FuncPtr = CUresult (CUDAAPI *)(CUDA_TEXTURE_DESC *, CUtexObject); + static auto func_ptr = LoadSymbol("cuTexObjectGetTextureDesc"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pTexDesc, texObject); +} + +CUresult CUDAAPI cuTexObjectGetResourceViewDesc(CUDA_RESOURCE_VIEW_DESC *pResViewDesc, CUtexObject texObject) { + using FuncPtr = CUresult (CUDAAPI *)(CUDA_RESOURCE_VIEW_DESC *, CUtexObject); + static auto func_ptr = LoadSymbol("cuTexObjectGetResourceViewDesc"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pResViewDesc, texObject); +} + +CUresult CUDAAPI cuSurfObjectCreate(CUsurfObject *pSurfObject, const CUDA_RESOURCE_DESC *pResDesc) { + using FuncPtr = CUresult (CUDAAPI *)(CUsurfObject *, const CUDA_RESOURCE_DESC *); + static auto func_ptr = LoadSymbol("cuSurfObjectCreate"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pSurfObject, pResDesc); +} + +CUresult CUDAAPI cuSurfObjectDestroy(CUsurfObject surfObject) { + using FuncPtr = CUresult (CUDAAPI *)(CUsurfObject); + static auto func_ptr = LoadSymbol("cuSurfObjectDestroy"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(surfObject); +} + +CUresult CUDAAPI cuSurfObjectGetResourceDesc(CUDA_RESOURCE_DESC *pResDesc, CUsurfObject surfObject) { + using FuncPtr = CUresult (CUDAAPI *)(CUDA_RESOURCE_DESC *, CUsurfObject); + static auto func_ptr = LoadSymbol("cuSurfObjectGetResourceDesc"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pResDesc, surfObject); +} + +CUresult CUDAAPI cuDeviceCanAccessPeer(int *canAccessPeer, CUdevice dev, CUdevice peerDev) { + using FuncPtr = CUresult (CUDAAPI *)(int *, CUdevice, CUdevice); + static auto func_ptr = LoadSymbol("cuDeviceCanAccessPeer"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(canAccessPeer, dev, peerDev); +} + +CUresult CUDAAPI cuCtxEnablePeerAccess(CUcontext peerContext, unsigned int Flags) { + using FuncPtr = CUresult (CUDAAPI *)(CUcontext, unsigned int); + static auto func_ptr = LoadSymbol("cuCtxEnablePeerAccess"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(peerContext, Flags); +} + +CUresult CUDAAPI cuCtxDisablePeerAccess(CUcontext peerContext) { + using FuncPtr = CUresult (CUDAAPI *)(CUcontext); + static auto func_ptr = LoadSymbol("cuCtxDisablePeerAccess"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(peerContext); +} + +CUresult CUDAAPI cuDeviceGetP2PAttribute(int* value, CUdevice_P2PAttribute attrib, CUdevice srcDevice, CUdevice dstDevice) { + using FuncPtr = CUresult (CUDAAPI *)(int *, CUdevice_P2PAttribute, CUdevice, CUdevice); + static auto func_ptr = LoadSymbol("cuDeviceGetP2PAttribute"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(value, attrib, srcDevice, dstDevice); +} + +CUresult CUDAAPI cuGraphicsUnregisterResource(CUgraphicsResource resource) { + using FuncPtr = CUresult (CUDAAPI *)(CUgraphicsResource); + static auto func_ptr = LoadSymbol("cuGraphicsUnregisterResource"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(resource); +} + +CUresult CUDAAPI cuGraphicsSubResourceGetMappedArray(CUarray *pArray, CUgraphicsResource resource, unsigned int arrayIndex, unsigned int mipLevel) { + using FuncPtr = CUresult (CUDAAPI *)(CUarray *, CUgraphicsResource, unsigned int, unsigned int); + static auto func_ptr = LoadSymbol("cuGraphicsSubResourceGetMappedArray"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pArray, resource, arrayIndex, mipLevel); +} + +CUresult CUDAAPI cuGraphicsResourceGetMappedMipmappedArray(CUmipmappedArray *pMipmappedArray, CUgraphicsResource resource) { + using FuncPtr = CUresult (CUDAAPI *)(CUmipmappedArray *, CUgraphicsResource); + static auto func_ptr = LoadSymbol("cuGraphicsResourceGetMappedMipmappedArray"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pMipmappedArray, resource); +} + +CUresult CUDAAPI cuGraphicsResourceGetMappedPointer(CUdeviceptr *pDevPtr, size_t *pSize, CUgraphicsResource resource) { + using FuncPtr = CUresult (CUDAAPI *)(CUdeviceptr *, size_t *, CUgraphicsResource); + static auto func_ptr = LoadSymbol("cuGraphicsResourceGetMappedPointer_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(pDevPtr, pSize, resource); +} + +CUresult CUDAAPI cuGraphicsResourceSetMapFlags(CUgraphicsResource resource, unsigned int flags) { + using FuncPtr = CUresult (CUDAAPI *)(CUgraphicsResource, unsigned int); + static auto func_ptr = LoadSymbol("cuGraphicsResourceSetMapFlags_v2"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(resource, flags); +} + +CUresult CUDAAPI cuGraphicsMapResources(unsigned int count, CUgraphicsResource *resources, CUstream hStream) { + using FuncPtr = CUresult (CUDAAPI *)(unsigned int, CUgraphicsResource *, CUstream); + static auto func_ptr = LoadSymbol("cuGraphicsMapResources"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(count, resources, hStream); +} + +CUresult CUDAAPI cuGraphicsUnmapResources(unsigned int count, CUgraphicsResource *resources, CUstream hStream) { + using FuncPtr = CUresult (CUDAAPI *)(unsigned int, CUgraphicsResource *, CUstream); + static auto func_ptr = LoadSymbol("cuGraphicsUnmapResources"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(count, resources, hStream); +} + +CUresult CUDAAPI cuGetExportTable(const void **ppExportTable, const CUuuid *pExportTableId) { + using FuncPtr = CUresult (CUDAAPI *)(const void **, const CUuuid *); + static auto func_ptr = LoadSymbol("cuGetExportTable"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(ppExportTable, pExportTableId); +} + +} // extern "C" diff --git a/tensorflow/stream_executor/cuda/cuda_blas.cc b/tensorflow/stream_executor/cuda/cuda_blas.cc index 5bbb98664e..b4e8beedcc 100644 --- a/tensorflow/stream_executor/cuda/cuda_blas.cc +++ b/tensorflow/stream_executor/cuda/cuda_blas.cc @@ -63,7 +63,6 @@ limitations under the License. #include "tensorflow/stream_executor/lib/status.h" #include "tensorflow/stream_executor/lib/status_macros.h" #include "tensorflow/stream_executor/lib/stringprintf.h" -#include "tensorflow/stream_executor/platform/dso_loader.h" #include "tensorflow/stream_executor/platform/logging.h" #include "tensorflow/stream_executor/platform/port.h" #include "tensorflow/stream_executor/plugin_registry.h" @@ -75,268 +74,6 @@ namespace gpu { PLUGIN_REGISTRY_DEFINE_PLUGIN_ID(kCuBlasPlugin); -namespace wrap { - -// clang-format off -#define CUBLAS_ROUTINE_EACH(__macro) \ - __macro(cublasSnrm2) \ - __macro(cublasDnrm2) \ - __macro(cublasScnrm2) \ - __macro(cublasDznrm2) \ - __macro(cublasSdot) \ - __macro(cublasDdot) \ - __macro(cublasCdotu) \ - __macro(cublasCdotc) \ - __macro(cublasZdotu) \ - __macro(cublasZdotc) \ - __macro(cublasSscal) \ - __macro(cublasDscal) \ - __macro(cublasCscal) \ - __macro(cublasCsscal) \ - __macro(cublasZscal) \ - __macro(cublasZdscal) \ - __macro(cublasSaxpy) \ - __macro(cublasDaxpy) \ - __macro(cublasCaxpy) \ - __macro(cublasZaxpy) \ - __macro(cublasScopy) \ - __macro(cublasDcopy) \ - __macro(cublasCcopy) \ - __macro(cublasZcopy) \ - __macro(cublasSswap) \ - __macro(cublasDswap) \ - __macro(cublasCswap) \ - __macro(cublasZswap) \ - __macro(cublasIsamax) \ - __macro(cublasIdamax) \ - __macro(cublasIcamax) \ - __macro(cublasIzamax) \ - __macro(cublasIsamin) \ - __macro(cublasIdamin) \ - __macro(cublasIcamin) \ - __macro(cublasIzamin) \ - __macro(cublasSasum) \ - __macro(cublasDasum) \ - __macro(cublasScasum) \ - __macro(cublasDzasum) \ - __macro(cublasSrot) \ - __macro(cublasDrot) \ - __macro(cublasCrot) \ - __macro(cublasCsrot) \ - __macro(cublasZrot) \ - __macro(cublasZdrot) \ - __macro(cublasSrotg) \ - __macro(cublasDrotg) \ - __macro(cublasCrotg) \ - __macro(cublasZrotg) \ - __macro(cublasSrotm) \ - __macro(cublasDrotm) \ - __macro(cublasSrotmg) \ - __macro(cublasDrotmg) \ - __macro(cublasSgemv) \ - __macro(cublasDgemv) \ - __macro(cublasCgemv) \ - __macro(cublasZgemv) \ - __macro(cublasSgbmv) \ - __macro(cublasDgbmv) \ - __macro(cublasCgbmv) \ - __macro(cublasZgbmv) \ - __macro(cublasStrmv) \ - __macro(cublasDtrmv) \ - __macro(cublasCtrmv) \ - __macro(cublasZtrmv) \ - __macro(cublasStbmv) \ - __macro(cublasDtbmv) \ - __macro(cublasCtbmv) \ - __macro(cublasZtbmv) \ - __macro(cublasStpmv) \ - __macro(cublasDtpmv) \ - __macro(cublasCtpmv) \ - __macro(cublasZtpmv) \ - __macro(cublasStrsv) \ - __macro(cublasDtrsv) \ - __macro(cublasCtrsv) \ - __macro(cublasZtrsv) \ - __macro(cublasStpsv) \ - __macro(cublasDtpsv) \ - __macro(cublasCtpsv) \ - __macro(cublasZtpsv) \ - __macro(cublasStbsv) \ - __macro(cublasDtbsv) \ - __macro(cublasCtbsv) \ - __macro(cublasZtbsv) \ - __macro(cublasSsymv) \ - __macro(cublasDsymv) \ - __macro(cublasCsymv) \ - __macro(cublasZsymv) \ - __macro(cublasChemv) \ - __macro(cublasZhemv) \ - __macro(cublasSsbmv) \ - __macro(cublasDsbmv) \ - __macro(cublasChbmv) \ - __macro(cublasZhbmv) \ - __macro(cublasSspmv) \ - __macro(cublasDspmv) \ - __macro(cublasChpmv) \ - __macro(cublasZhpmv) \ - __macro(cublasSger) \ - __macro(cublasDger) \ - __macro(cublasCgeru) \ - __macro(cublasCgerc) \ - __macro(cublasZgeru) \ - __macro(cublasZgerc) \ - __macro(cublasSsyr) \ - __macro(cublasDsyr) \ - __macro(cublasCsyr) \ - __macro(cublasZsyr) \ - __macro(cublasCher) \ - __macro(cublasZher) \ - __macro(cublasSspr) \ - __macro(cublasDspr) \ - __macro(cublasChpr) \ - __macro(cublasZhpr) \ - __macro(cublasSsyr2) \ - __macro(cublasDsyr2) \ - __macro(cublasCsyr2) \ - __macro(cublasZsyr2) \ - __macro(cublasCher2) \ - __macro(cublasZher2) \ - __macro(cublasSspr2) \ - __macro(cublasDspr2) \ - __macro(cublasChpr2) \ - __macro(cublasZhpr2) \ - __macro(cublasSgemm) \ - __macro(cublasDgemm) \ - __macro(cublasCgemm) \ - __macro(cublasZgemm) \ - __macro(cublasSsyrk) \ - __macro(cublasDsyrk) \ - __macro(cublasCsyrk) \ - __macro(cublasZsyrk) \ - __macro(cublasCherk) \ - __macro(cublasZherk) \ - __macro(cublasSsyr2k) \ - __macro(cublasDsyr2k) \ - __macro(cublasCsyr2k) \ - __macro(cublasZsyr2k) \ - __macro(cublasCher2k) \ - __macro(cublasZher2k) \ - __macro(cublasSsyrkx) \ - __macro(cublasDsyrkx) \ - __macro(cublasCsyrkx) \ - __macro(cublasZsyrkx) \ - __macro(cublasCherkx) \ - __macro(cublasZherkx) \ - __macro(cublasSsymm) \ - __macro(cublasDsymm) \ - __macro(cublasCsymm) \ - __macro(cublasZsymm) \ - __macro(cublasChemm) \ - __macro(cublasZhemm) \ - __macro(cublasStrsm) \ - __macro(cublasDtrsm) \ - __macro(cublasCtrsm) \ - __macro(cublasZtrsm) \ - __macro(cublasStrmm) \ - __macro(cublasDtrmm) \ - __macro(cublasCtrmm) \ - __macro(cublasZtrmm) \ - __macro(cublasSgeam) \ - __macro(cublasDgeam) \ - __macro(cublasCgeam) \ - __macro(cublasZgeam) \ - __macro(cublasSdgmm) \ - __macro(cublasDdgmm) \ - __macro(cublasCdgmm) \ - __macro(cublasZdgmm) - -// clang-format off - -#ifdef PLATFORM_GOOGLE -#define STREAM_EXECUTOR_CUBLAS_WRAP(__name) \ - struct WrapperShim__##__name { \ - static const char *kName; \ - template \ - cublasStatus_t operator()(GpuExecutor *parent, Args... args) { \ - gpu::ScopedActivateExecutorContext sac{parent}; \ - return ::__name(args...); \ - } \ - } __name; \ - const char *WrapperShim__##__name::kName = #__name; - -#define STREAM_EXECUTOR_CUBLAS_V2_WRAP(__name) \ - STREAM_EXECUTOR_CUBLAS_WRAP(__name) - -#else - -#define STREAM_EXECUTOR_CUBLAS_WRAP(__name) \ - struct DynLoadShim__##__name { \ - static const char* kName; \ - using FuncPtrT = std::add_pointer::type; \ - static void* GetDsoHandle() { \ - auto s = internal::CachedDsoLoader::GetCublasDsoHandle(); \ - return s.ValueOrDie(); \ - } \ - static FuncPtrT LoadOrDie() { \ - void* f; \ - auto s = port::Env::Default()->GetSymbolFromLibrary(GetDsoHandle(), \ - kName, &f); \ - CHECK(s.ok()) << "could not find " << kName \ - << " in cublas DSO; dlerror: " << s.error_message(); \ - return reinterpret_cast(f); \ - } \ - static FuncPtrT DynLoad() { \ - static FuncPtrT f = LoadOrDie(); \ - return f; \ - } \ - template \ - cublasStatus_t operator()(GpuExecutor* parent, Args... args) { \ - gpu::ScopedActivateExecutorContext sac{parent}; \ - return DynLoad()(args...); \ - } \ - } __name; \ - const char* DynLoadShim__##__name::kName = #__name; - -#define STREAM_EXECUTOR_CUBLAS_V2_WRAP(__name) \ - STREAM_EXECUTOR_CUBLAS_WRAP(__name) - -#endif - -STREAM_EXECUTOR_CUBLAS_V2_WRAP(cublasCreate) -STREAM_EXECUTOR_CUBLAS_V2_WRAP(cublasDestroy) -STREAM_EXECUTOR_CUBLAS_V2_WRAP(cublasSetStream) -STREAM_EXECUTOR_CUBLAS_V2_WRAP(cublasSetPointerMode) -STREAM_EXECUTOR_CUBLAS_V2_WRAP(cublasGetPointerMode) -STREAM_EXECUTOR_CUBLAS_WRAP(cublasSgemmBatched) -STREAM_EXECUTOR_CUBLAS_WRAP(cublasDgemmBatched) -STREAM_EXECUTOR_CUBLAS_WRAP(cublasCgemmBatched) -STREAM_EXECUTOR_CUBLAS_WRAP(cublasZgemmBatched) -CUBLAS_ROUTINE_EACH(STREAM_EXECUTOR_CUBLAS_V2_WRAP) - -#if CUDA_VERSION >= 7050 -STREAM_EXECUTOR_CUBLAS_WRAP(cublasSgemmEx) -#endif - -#if CUDA_VERSION >= 8000 -STREAM_EXECUTOR_CUBLAS_WRAP(cublasGemmEx) -STREAM_EXECUTOR_CUBLAS_WRAP(cublasSgemmStridedBatched) -STREAM_EXECUTOR_CUBLAS_WRAP(cublasDgemmStridedBatched) -STREAM_EXECUTOR_CUBLAS_WRAP(cublasCgemmStridedBatched) -STREAM_EXECUTOR_CUBLAS_WRAP(cublasZgemmStridedBatched) -#endif - -#if CUDA_VERSION >= 9000 -STREAM_EXECUTOR_CUBLAS_WRAP(cublasGetMathMode) -STREAM_EXECUTOR_CUBLAS_WRAP(cublasSetMathMode) -#endif - -#if CUDA_VERSION >= 9010 -STREAM_EXECUTOR_CUBLAS_WRAP(cublasGemmBatchedEx) -STREAM_EXECUTOR_CUBLAS_WRAP(cublasGemmStridedBatchedEx) -#endif - -} // namespace wrap - static string ToString(cublasStatus_t status) { switch (status) { case CUBLAS_STATUS_SUCCESS: @@ -395,22 +132,21 @@ class ScopedCublasPointerMode { // // Parameters: // handle: The cublas library handle to act upon in setting the pointer mode. - explicit ScopedCublasPointerMode(GpuExecutor *parent, cublasHandle_t handle) - : parent_(parent), handle_(handle), ok_(false) {} + explicit ScopedCublasPointerMode(cublasHandle_t handle) + : handle_(handle), ok_(false) {} // Attempts the switch to the requested scoped pointer mode, new_mode. // // Note that when false is returned, an appropriate error has already been // logged. bool Init(cublasPointerMode_t new_mode) { - cublasStatus_t ret = - wrap::cublasGetPointerMode(parent_, handle_, &old_mode_); + cublasStatus_t ret = cublasGetPointerMode(handle_, &old_mode_); if (ret != CUBLAS_STATUS_SUCCESS) { LOG(ERROR) << "failed to get old cublas pointer mode: " << ToString(ret); return ok_ = false; } - ret = wrap::cublasSetPointerMode(parent_, handle_, new_mode); + ret = cublasSetPointerMode(handle_, new_mode); if (ret != CUBLAS_STATUS_SUCCESS) { LOG(ERROR) << "failed to set new cublas pointer mode: " << ToString(ret); return ok_ = false; @@ -423,8 +159,7 @@ class ScopedCublasPointerMode { // successful in the first place. ~ScopedCublasPointerMode() { if (ok_) { - cublasStatus_t ret = - wrap::cublasSetPointerMode(parent_, handle_, old_mode_); + cublasStatus_t ret = cublasSetPointerMode(handle_, old_mode_); if (ret != CUBLAS_STATUS_SUCCESS) { LOG(ERROR) << "failed to set former cublas pointer mode: " << ToString(ret); @@ -433,7 +168,6 @@ class ScopedCublasPointerMode { } private: - GpuExecutor *parent_; // Executor establishing this pointer mode for. cublasHandle_t handle_; // Handle to the cuBLAS instance of interest. cublasPointerMode_t old_mode_; // Prior cuBLAS pointer mode, to be restored. bool ok_; // Whether the change was successful. @@ -456,22 +190,21 @@ class ScopedCublasMathMode { // // Parameters: // handle: The cublas library handle to act upon in setting the math mode. - explicit ScopedCublasMathMode(GpuExecutor *parent, cublasHandle_t handle) - : parent_(parent), handle_(handle), ok_(false) {} + explicit ScopedCublasMathMode(cublasHandle_t handle) + : handle_(handle), ok_(false) {} // Attempts the switch to the requested scoped math mode, new_mode. // // Note that when false is returned, an appropriate error has already been // logged. bool Init(cublasMath_t new_mode) { - cublasStatus_t ret = - wrap::cublasGetMathMode(parent_, handle_, &old_mode_); + cublasStatus_t ret = cublasGetMathMode(handle_, &old_mode_); if (ret != CUBLAS_STATUS_SUCCESS) { LOG(ERROR) << "failed to get old cublas math mode: " << ToString(ret); return ok_ = false; } - ret = wrap::cublasSetMathMode(parent_, handle_, new_mode); + ret = cublasSetMathMode(handle_, new_mode); if (ret != CUBLAS_STATUS_SUCCESS) { LOG(ERROR) << "failed to set new cublas math mode: " << ToString(ret); return ok_ = false; @@ -483,8 +216,7 @@ class ScopedCublasMathMode { // successful in the first place. ~ScopedCublasMathMode() { if (ok_) { - cublasStatus_t ret = - wrap::cublasSetMathMode(parent_, handle_, old_mode_); + cublasStatus_t ret = cublasSetMathMode(handle_, old_mode_); if (ret != CUBLAS_STATUS_SUCCESS) { LOG(ERROR) << "failed to set former cublas math mode: " << ToString(ret); @@ -493,7 +225,6 @@ class ScopedCublasMathMode { } private: - GpuExecutor *parent_; // Executor establishing this math mode for. cublasHandle_t handle_; // Handle to the cuBLAS instance of interest. cublasMath_t old_mode_; // Prior cuBLAS math mode, to be restored. bool ok_; // Whether the change was successful. @@ -501,7 +232,8 @@ class ScopedCublasMathMode { #endif // CUDA_VERSION >= 9000 bool CUDABlas::Init() { - cublasStatus_t ret = wrap::cublasCreate(parent_, &blas_); + gpu::ScopedActivateExecutorContext sac{parent_}; + cublasStatus_t ret = cublasCreate(&blas_); if (ret != CUBLAS_STATUS_SUCCESS) { LOG(ERROR) << "failed to create cublas handle: " << ToString(ret); return false; @@ -515,7 +247,8 @@ CUDABlas::CUDABlas(gpu::GpuExecutor *parent) CUDABlas::~CUDABlas() { if (blas_ != nullptr) { - wrap::cublasDestroy(parent_, blas_); + gpu::ScopedActivateExecutorContext sac{parent_}; + cublasDestroy(blas_); } } @@ -523,8 +256,8 @@ bool CUDABlas::SetStream(Stream *stream) { CHECK(stream != nullptr); CHECK(AsGpuStreamValue(stream) != nullptr); CHECK(blas_ != nullptr); - cublasStatus_t ret = - wrap::cublasSetStream(parent_, blas_, AsGpuStreamValue(stream)); + gpu::ScopedActivateExecutorContext sac{parent_}; + cublasStatus_t ret = cublasSetStream(blas_, AsGpuStreamValue(stream)); if (ret != CUBLAS_STATUS_SUCCESS) { LOG(ERROR) << "failed to set stream for cuBLAS calls: " << ToString(ret); return false; @@ -676,23 +409,23 @@ bool CUDABlas::DoBlasInternalImpl(FuncT cublas_func, Stream *stream, return false; } - ScopedCublasPointerMode pointer_mode{parent_, blas_}; + gpu::ScopedActivateExecutorContext sac{parent_}; + ScopedCublasPointerMode pointer_mode{blas_}; if (!pointer_mode.Init(pointer_mode_host ? CUBLAS_POINTER_MODE_HOST : CUBLAS_POINTER_MODE_DEVICE)) { return false; } #if CUDA_VERSION >= 9000 - ScopedCublasMathMode math_mode{parent_, blas_}; + ScopedCublasMathMode math_mode{blas_}; if (use_tensor_op_math) { if (!math_mode.Init(CUBLAS_TENSOR_OP_MATH)) { return false; } } #endif - cublasStatus_t ret = cublas_func(parent_, blas_, args...); + cublasStatus_t ret = cublas_func(blas_, args...); if ((err_on_failure || VLOG_IS_ON(3)) && ret != CUBLAS_STATUS_SUCCESS) { - LOG(ERROR) << "failed to run cuBLAS routine " << cublas_func.kName << ": " - << ToString(ret); + LOG(ERROR) << "failed to run cuBLAS routine: " << ToString(ret); } return ret == CUBLAS_STATUS_SUCCESS; } @@ -700,58 +433,58 @@ bool CUDABlas::DoBlasInternalImpl(FuncT cublas_func, Stream *stream, bool CUDABlas::DoBlasAsum(Stream *stream, uint64 elem_count, const DeviceMemory &x, int incx, DeviceMemory *result) { - return DoBlasInternal(wrap::cublasSasum, stream, - false /* = pointer_mode_host */, elem_count, - GpuMemory(x), incx, GpuMemoryMutable(result)); + return DoBlasInternal(cublasSasum, stream, false /* = pointer_mode_host */, + elem_count, GpuMemory(x), incx, + GpuMemoryMutable(result)); } bool CUDABlas::DoBlasAsum(Stream *stream, uint64 elem_count, const DeviceMemory &x, int incx, DeviceMemory *result) { - return DoBlasInternal(wrap::cublasDasum, stream, - false /* = pointer_mode_host */, elem_count, - GpuMemory(x), incx, GpuMemoryMutable(result)); + return DoBlasInternal(cublasDasum, stream, false /* = pointer_mode_host */, + elem_count, GpuMemory(x), incx, + GpuMemoryMutable(result)); } bool CUDABlas::DoBlasAsum(Stream *stream, uint64 elem_count, const DeviceMemory> &x, int incx, DeviceMemory *result) { - return DoBlasInternal( - wrap::cublasScasum, stream, false /* = pointer_mode_host */, - elem_count, GpuComplex(GpuMemory(x)), incx, GpuMemoryMutable(result)); + return DoBlasInternal(cublasScasum, stream, false /* = pointer_mode_host */, + elem_count, GpuComplex(GpuMemory(x)), incx, + GpuMemoryMutable(result)); } bool CUDABlas::DoBlasAsum(Stream *stream, uint64 elem_count, const DeviceMemory> &x, int incx, DeviceMemory *result) { - return DoBlasInternal( - wrap::cublasDzasum, stream, false /* = pointer_mode_host */, - elem_count, GpuComplex(GpuMemory(x)), incx, GpuMemoryMutable(result)); + return DoBlasInternal(cublasDzasum, stream, false /* = pointer_mode_host */, + elem_count, GpuComplex(GpuMemory(x)), incx, + GpuMemoryMutable(result)); } bool CUDABlas::DoBlasAxpy(Stream *stream, uint64 elem_count, float alpha, const DeviceMemory &x, int incx, DeviceMemory *y, int incy) { - return DoBlasInternal(wrap::cublasSaxpy, stream, - true /* = pointer_mode_host */, elem_count, &alpha, - GpuMemory(x), incx, GpuMemoryMutable(y), incy); + return DoBlasInternal(cublasSaxpy, stream, true /* = pointer_mode_host */, + elem_count, &alpha, GpuMemory(x), incx, + GpuMemoryMutable(y), incy); } bool CUDABlas::DoBlasAxpy(Stream *stream, uint64 elem_count, double alpha, const DeviceMemory &x, int incx, DeviceMemory *y, int incy) { - return DoBlasInternal(wrap::cublasDaxpy, stream, - true /* = pointer_mode_host */, elem_count, &alpha, - GpuMemory(x), incx, GpuMemoryMutable(y), incy); + return DoBlasInternal(cublasDaxpy, stream, true /* = pointer_mode_host */, + elem_count, &alpha, GpuMemory(x), incx, + GpuMemoryMutable(y), incy); } bool CUDABlas::DoBlasAxpy(Stream *stream, uint64 elem_count, std::complex alpha, const DeviceMemory> &x, int incx, DeviceMemory> *y, int incy) { - return DoBlasInternal(wrap::cublasCaxpy, stream, - true /* = pointer_mode_host */, elem_count, - GpuComplex(&alpha), GpuComplex(GpuMemory(x)), incx, + return DoBlasInternal(cublasCaxpy, stream, true /* = pointer_mode_host */, + elem_count, GpuComplex(&alpha), + GpuComplex(GpuMemory(x)), incx, GpuComplex(GpuMemoryMutable(y)), incy); } @@ -759,43 +492,41 @@ bool CUDABlas::DoBlasAxpy(Stream *stream, uint64 elem_count, std::complex alpha, const DeviceMemory> &x, int incx, DeviceMemory> *y, int incy) { - return DoBlasInternal(wrap::cublasZaxpy, stream, - true /* = pointer_mode_host */, elem_count, - GpuComplex(&alpha), GpuComplex(GpuMemory(x)), incx, + return DoBlasInternal(cublasZaxpy, stream, true /* = pointer_mode_host */, + elem_count, GpuComplex(&alpha), + GpuComplex(GpuMemory(x)), incx, GpuComplex(GpuMemoryMutable(y)), incy); } bool CUDABlas::DoBlasCopy(Stream *stream, uint64 elem_count, const DeviceMemory &x, int incx, DeviceMemory *y, int incy) { - return DoBlasInternal(wrap::cublasScopy, stream, - true /* = pointer_mode_host */, elem_count, - GpuMemory(x), incx, GpuMemoryMutable(y), incy); + return DoBlasInternal(cublasScopy, stream, true /* = pointer_mode_host */, + elem_count, GpuMemory(x), incx, GpuMemoryMutable(y), + incy); } bool CUDABlas::DoBlasCopy(Stream *stream, uint64 elem_count, const DeviceMemory &x, int incx, DeviceMemory *y, int incy) { - return DoBlasInternal(wrap::cublasDcopy, stream, - true /* = pointer_mode_host */, elem_count, - GpuMemory(x), incx, GpuMemoryMutable(y), incy); + return DoBlasInternal(cublasDcopy, stream, true /* = pointer_mode_host */, + elem_count, GpuMemory(x), incx, GpuMemoryMutable(y), + incy); } bool CUDABlas::DoBlasCopy(Stream *stream, uint64 elem_count, const DeviceMemory> &x, int incx, DeviceMemory> *y, int incy) { - return DoBlasInternal(wrap::cublasCcopy, stream, - true /* = pointer_mode_host */, elem_count, - GpuComplex(GpuMemory(x)), incx, + return DoBlasInternal(cublasCcopy, stream, true /* = pointer_mode_host */, + elem_count, GpuComplex(GpuMemory(x)), incx, GpuComplex(GpuMemoryMutable(y)), incy); } bool CUDABlas::DoBlasCopy(Stream *stream, uint64 elem_count, const DeviceMemory> &x, int incx, DeviceMemory> *y, int incy) { - return DoBlasInternal(wrap::cublasZcopy, stream, - true /* = pointer_mode_host */, elem_count, - GpuComplex(GpuMemory(x)), incx, + return DoBlasInternal(cublasZcopy, stream, true /* = pointer_mode_host */, + elem_count, GpuComplex(GpuMemory(x)), incx, GpuComplex(GpuMemoryMutable(y)), incy); } @@ -803,116 +534,115 @@ bool CUDABlas::DoBlasDot(Stream *stream, uint64 elem_count, const DeviceMemory &x, int incx, const DeviceMemory &y, int incy, DeviceMemory *result) { - return DoBlasInternal( - wrap::cublasSdot, stream, false /* = pointer_mode_host */, elem_count, - GpuMemory(x), incx, GpuMemory(y), incy, GpuMemoryMutable(result)); + return DoBlasInternal(cublasSdot, stream, false /* = pointer_mode_host */, + elem_count, GpuMemory(x), incx, GpuMemory(y), incy, + GpuMemoryMutable(result)); } bool CUDABlas::DoBlasDot(Stream *stream, uint64 elem_count, const DeviceMemory &x, int incx, const DeviceMemory &y, int incy, DeviceMemory *result) { - return DoBlasInternal( - wrap::cublasDdot, stream, false /* = pointer_mode_host */, elem_count, - GpuMemory(x), incx, GpuMemory(y), incy, GpuMemoryMutable(result)); + return DoBlasInternal(cublasDdot, stream, false /* = pointer_mode_host */, + elem_count, GpuMemory(x), incx, GpuMemory(y), incy, + GpuMemoryMutable(result)); } bool CUDABlas::DoBlasDotc(Stream *stream, uint64 elem_count, const DeviceMemory> &x, int incx, const DeviceMemory> &y, int incy, DeviceMemory> *result) { - return DoBlasInternal( - wrap::cublasCdotc, stream, false /* = pointer_mode_host */, elem_count, - GpuComplex(GpuMemory(x)), incx, GpuComplex(GpuMemory(y)), incy, - GpuComplex(GpuMemoryMutable(result))); + return DoBlasInternal(cublasCdotc, stream, false /* = pointer_mode_host */, + elem_count, GpuComplex(GpuMemory(x)), incx, + GpuComplex(GpuMemory(y)), incy, + GpuComplex(GpuMemoryMutable(result))); } bool CUDABlas::DoBlasDotc(Stream *stream, uint64 elem_count, const DeviceMemory> &x, int incx, const DeviceMemory> &y, int incy, DeviceMemory> *result) { - return DoBlasInternal( - wrap::cublasZdotc, stream, false /* = pointer_mode_host */, elem_count, - GpuComplex(GpuMemory(x)), incx, GpuComplex(GpuMemory(y)), incy, - GpuComplex(GpuMemoryMutable(result))); + return DoBlasInternal(cublasZdotc, stream, false /* = pointer_mode_host */, + elem_count, GpuComplex(GpuMemory(x)), incx, + GpuComplex(GpuMemory(y)), incy, + GpuComplex(GpuMemoryMutable(result))); } bool CUDABlas::DoBlasDotu(Stream *stream, uint64 elem_count, const DeviceMemory> &x, int incx, const DeviceMemory> &y, int incy, DeviceMemory> *result) { - return DoBlasInternal( - wrap::cublasCdotu, stream, false /* = pointer_mode_host */, elem_count, - GpuComplex(GpuMemory(x)), incx, GpuComplex(GpuMemory(y)), incy, - GpuComplex(GpuMemoryMutable(result))); + return DoBlasInternal(cublasCdotu, stream, false /* = pointer_mode_host */, + elem_count, GpuComplex(GpuMemory(x)), incx, + GpuComplex(GpuMemory(y)), incy, + GpuComplex(GpuMemoryMutable(result))); } bool CUDABlas::DoBlasDotu(Stream *stream, uint64 elem_count, const DeviceMemory> &x, int incx, const DeviceMemory> &y, int incy, DeviceMemory> *result) { - return DoBlasInternal( - wrap::cublasZdotu, stream, false /* = pointer_mode_host */, elem_count, - GpuComplex(GpuMemory(x)), incx, GpuComplex(GpuMemory(y)), incy, - GpuComplex(GpuMemoryMutable(result))); + return DoBlasInternal(cublasZdotu, stream, false /* = pointer_mode_host */, + elem_count, GpuComplex(GpuMemory(x)), incx, + GpuComplex(GpuMemory(y)), incy, + GpuComplex(GpuMemoryMutable(result))); } bool CUDABlas::DoBlasNrm2(Stream *stream, uint64 elem_count, const DeviceMemory &x, int incx, DeviceMemory *result) { - return DoBlasInternal(wrap::cublasSnrm2, stream, - false /* = pointer_mode_host */, elem_count, - GpuMemory(x), incx, GpuMemoryMutable(result)); + return DoBlasInternal(cublasSnrm2, stream, false /* = pointer_mode_host */, + elem_count, GpuMemory(x), incx, + GpuMemoryMutable(result)); } bool CUDABlas::DoBlasNrm2(Stream *stream, uint64 elem_count, const DeviceMemory &x, int incx, DeviceMemory *result) { - return DoBlasInternal(wrap::cublasDnrm2, stream, - false /* = pointer_mode_host */, elem_count, - GpuMemory(x), incx, GpuMemoryMutable(result)); + return DoBlasInternal(cublasDnrm2, stream, false /* = pointer_mode_host */, + elem_count, GpuMemory(x), incx, + GpuMemoryMutable(result)); } bool CUDABlas::DoBlasNrm2(Stream *stream, uint64 elem_count, const DeviceMemory> &x, int incx, DeviceMemory *result) { - return DoBlasInternal( - wrap::cublasScnrm2, stream, false /* = pointer_mode_host */, - elem_count, GpuComplex(GpuMemory(x)), incx, GpuMemoryMutable(result)); + return DoBlasInternal(cublasScnrm2, stream, false /* = pointer_mode_host */, + elem_count, GpuComplex(GpuMemory(x)), incx, + GpuMemoryMutable(result)); } bool CUDABlas::DoBlasNrm2(Stream *stream, uint64 elem_count, const DeviceMemory> &x, int incx, DeviceMemory *result) { - return DoBlasInternal( - wrap::cublasDznrm2, stream, false /* = pointer_mode_host */, - elem_count, GpuComplex(GpuMemory(x)), incx, GpuMemoryMutable(result)); + return DoBlasInternal(cublasDznrm2, stream, false /* = pointer_mode_host */, + elem_count, GpuComplex(GpuMemory(x)), incx, + GpuMemoryMutable(result)); } bool CUDABlas::DoBlasRot(Stream *stream, uint64 elem_count, DeviceMemory *x, int incx, DeviceMemory *y, int incy, float c, float s) { - return DoBlasInternal( - wrap::cublasSrot, stream, true /* = pointer_mode_host */, elem_count, - GpuMemoryMutable(x), incx, GpuMemoryMutable(y), incy, &c, &s); + return DoBlasInternal(cublasSrot, stream, true /* = pointer_mode_host */, + elem_count, GpuMemoryMutable(x), incx, + GpuMemoryMutable(y), incy, &c, &s); } bool CUDABlas::DoBlasRot(Stream *stream, uint64 elem_count, DeviceMemory *x, int incx, DeviceMemory *y, int incy, double c, double s) { - return DoBlasInternal( - wrap::cublasDrot, stream, true /* = pointer_mode_host */, elem_count, - GpuMemoryMutable(x), incx, GpuMemoryMutable(y), incy, &c, &s); + return DoBlasInternal(cublasDrot, stream, true /* = pointer_mode_host */, + elem_count, GpuMemoryMutable(x), incx, + GpuMemoryMutable(y), incy, &c, &s); } bool CUDABlas::DoBlasRot(Stream *stream, uint64 elem_count, DeviceMemory> *x, int incx, DeviceMemory> *y, int incy, float c, float s) { - return DoBlasInternal(wrap::cublasCsrot, stream, - true /* = pointer_mode_host */, elem_count, - GpuComplex(GpuMemoryMutable(x)), incx, + return DoBlasInternal(cublasCsrot, stream, true /* = pointer_mode_host */, + elem_count, GpuComplex(GpuMemoryMutable(x)), incx, GpuComplex(GpuMemoryMutable(y)), incy, &c, &s); } @@ -920,26 +650,23 @@ bool CUDABlas::DoBlasRot(Stream *stream, uint64 elem_count, DeviceMemory> *x, int incx, DeviceMemory> *y, int incy, double c, double s) { - return DoBlasInternal(wrap::cublasZdrot, stream, - true /* = pointer_mode_host */, elem_count, - GpuComplex(GpuMemoryMutable(x)), incx, + return DoBlasInternal(cublasZdrot, stream, true /* = pointer_mode_host */, + elem_count, GpuComplex(GpuMemoryMutable(x)), incx, GpuComplex(GpuMemoryMutable(y)), incy, &c, &s); } bool CUDABlas::DoBlasRotg(Stream *stream, DeviceMemory *a, DeviceMemory *b, DeviceMemory *c, DeviceMemory *s) { - return DoBlasInternal(wrap::cublasSrotg, stream, - false /* = pointer_mode_host */, GpuMemoryMutable(a), - GpuMemoryMutable(b), GpuMemoryMutable(c), - GpuMemoryMutable(s)); + return DoBlasInternal(cublasSrotg, stream, false /* = pointer_mode_host */, + GpuMemoryMutable(a), GpuMemoryMutable(b), + GpuMemoryMutable(c), GpuMemoryMutable(s)); } bool CUDABlas::DoBlasRotg(Stream *stream, DeviceMemory *a, DeviceMemory *b, DeviceMemory *c, DeviceMemory *s) { - return DoBlasInternal(wrap::cublasDrotg, stream, - false /* = pointer_mode_host */, + return DoBlasInternal(cublasDrotg, stream, false /* = pointer_mode_host */, GpuComplex(GpuMemoryMutable(a)), GpuMemoryMutable(b), GpuMemoryMutable(c), GpuMemoryMutable(s)); } @@ -949,7 +676,7 @@ bool CUDABlas::DoBlasRotg(Stream *stream, DeviceMemory> *a, DeviceMemory *c, DeviceMemory> *s) { return DoBlasInternal( - wrap::cublasCrotg, stream, false /* = pointer_mode_host */, + cublasCrotg, stream, false /* = pointer_mode_host */, GpuComplex(GpuMemoryMutable(a)), GpuComplex(GpuMemoryMutable(b)), GpuComplex(GpuMemoryMutable(c)), GpuComplex(GpuMemoryMutable(s))); } @@ -959,7 +686,7 @@ bool CUDABlas::DoBlasRotg(Stream *stream, DeviceMemory> *a, DeviceMemory *c, DeviceMemory> *s) { return DoBlasInternal( - wrap::cublasZrotg, stream, false /* = pointer_mode_host */, + cublasZrotg, stream, false /* = pointer_mode_host */, GpuComplex(GpuMemoryMutable(a)), GpuComplex(GpuMemoryMutable(b)), GpuComplex(GpuMemoryMutable(c)), GpuComplex(GpuMemoryMutable(s))); } @@ -968,182 +695,176 @@ bool CUDABlas::DoBlasRotm(Stream *stream, uint64 elem_count, DeviceMemory *x, int incx, DeviceMemory *y, int incy, const DeviceMemory ¶m) { - return DoBlasInternal(wrap::cublasSrotm, stream, - false /* = pointer_mode_host */, elem_count, - GpuMemoryMutable(x), incx, GpuMemoryMutable(y), incy, - GpuMemory(param)); + return DoBlasInternal(cublasSrotm, stream, false /* = pointer_mode_host */, + elem_count, GpuMemoryMutable(x), incx, + GpuMemoryMutable(y), incy, GpuMemory(param)); } bool CUDABlas::DoBlasRotm(Stream *stream, uint64 elem_count, DeviceMemory *x, int incx, DeviceMemory *y, int incy, const DeviceMemory ¶m) { - return DoBlasInternal(wrap::cublasDrotm, stream, - false /* = pointer_mode_host */, elem_count, - GpuMemoryMutable(x), incx, GpuMemoryMutable(y), incy, - GpuMemory(param)); + return DoBlasInternal(cublasDrotm, stream, false /* = pointer_mode_host */, + elem_count, GpuMemoryMutable(x), incx, + GpuMemoryMutable(y), incy, GpuMemory(param)); } bool CUDABlas::DoBlasRotmg(Stream *stream, DeviceMemory *d1, DeviceMemory *d2, DeviceMemory *x1, const DeviceMemory &y1, DeviceMemory *param) { - return DoBlasInternal(wrap::cublasSrotmg, stream, - false /* = pointer_mode_host */, GpuMemoryMutable(d1), - GpuMemoryMutable(d2), GpuMemoryMutable(x1), - GpuMemory(y1), GpuMemoryMutable(param)); + return DoBlasInternal(cublasSrotmg, stream, false /* = pointer_mode_host */, + GpuMemoryMutable(d1), GpuMemoryMutable(d2), + GpuMemoryMutable(x1), GpuMemory(y1), + GpuMemoryMutable(param)); } bool CUDABlas::DoBlasRotmg(Stream *stream, DeviceMemory *d1, DeviceMemory *d2, DeviceMemory *x1, const DeviceMemory &y1, DeviceMemory *param) { - return DoBlasInternal(wrap::cublasDrotmg, stream, - false /* = pointer_mode_host */, GpuMemoryMutable(d1), - GpuMemoryMutable(d2), GpuMemoryMutable(x1), - GpuMemory(y1), GpuMemoryMutable(param)); + return DoBlasInternal(cublasDrotmg, stream, false /* = pointer_mode_host */, + GpuMemoryMutable(d1), GpuMemoryMutable(d2), + GpuMemoryMutable(x1), GpuMemory(y1), + GpuMemoryMutable(param)); } bool CUDABlas::DoBlasScal(Stream *stream, uint64 elem_count, float alpha, DeviceMemory *x, int incx) { - return DoBlasInternal(wrap::cublasSscal, stream, - true /* = pointer_mode_host */, elem_count, &alpha, - GpuMemoryMutable(x), incx); + return DoBlasInternal(cublasSscal, stream, true /* = pointer_mode_host */, + elem_count, &alpha, GpuMemoryMutable(x), incx); } bool CUDABlas::DoBlasScal(Stream *stream, uint64 elem_count, double alpha, DeviceMemory *x, int incx) { - return DoBlasInternal(wrap::cublasDscal, stream, - true /* = pointer_mode_host */, elem_count, &alpha, - GpuMemoryMutable(x), incx); + return DoBlasInternal(cublasDscal, stream, true /* = pointer_mode_host */, + elem_count, &alpha, GpuMemoryMutable(x), incx); } bool CUDABlas::DoBlasScal(Stream *stream, uint64 elem_count, float alpha, DeviceMemory> *x, int incx) { - return DoBlasInternal( - wrap::cublasCsscal, stream, true /* = pointer_mode_host */, elem_count, - GpuComplex(&alpha), GpuComplex(GpuMemoryMutable(x)), incx); + return DoBlasInternal(cublasCsscal, stream, true /* = pointer_mode_host */, + elem_count, GpuComplex(&alpha), + GpuComplex(GpuMemoryMutable(x)), incx); } bool CUDABlas::DoBlasScal(Stream *stream, uint64 elem_count, double alpha, DeviceMemory> *x, int incx) { - return DoBlasInternal( - wrap::cublasZdscal, stream, true /* = pointer_mode_host */, elem_count, - GpuComplex(&alpha), GpuComplex(GpuMemoryMutable(x)), incx); + return DoBlasInternal(cublasZdscal, stream, true /* = pointer_mode_host */, + elem_count, GpuComplex(&alpha), + GpuComplex(GpuMemoryMutable(x)), incx); } bool CUDABlas::DoBlasScal(Stream *stream, uint64 elem_count, std::complex alpha, DeviceMemory> *x, int incx) { - return DoBlasInternal( - wrap::cublasCscal, stream, true /* = pointer_mode_host */, elem_count, - GpuComplex(&alpha), GpuComplex(GpuMemoryMutable(x)), incx); + return DoBlasInternal(cublasCscal, stream, true /* = pointer_mode_host */, + elem_count, GpuComplex(&alpha), + GpuComplex(GpuMemoryMutable(x)), incx); } bool CUDABlas::DoBlasScal(Stream *stream, uint64 elem_count, std::complex alpha, DeviceMemory> *x, int incx) { - return DoBlasInternal( - wrap::cublasZscal, stream, true /* = pointer_mode_host */, elem_count, - GpuComplex(&alpha), GpuComplex(GpuMemoryMutable(x)), incx); + return DoBlasInternal(cublasZscal, stream, true /* = pointer_mode_host */, + elem_count, GpuComplex(&alpha), + GpuComplex(GpuMemoryMutable(x)), incx); } bool CUDABlas::DoBlasSwap(Stream *stream, uint64 elem_count, DeviceMemory *x, int incx, DeviceMemory *y, int incy) { - return DoBlasInternal(wrap::cublasSswap, stream, - true /* = pointer_mode_host */, elem_count, - GpuMemoryMutable(x), incx, GpuMemoryMutable(y), incy); + return DoBlasInternal(cublasSswap, stream, true /* = pointer_mode_host */, + elem_count, GpuMemoryMutable(x), incx, + GpuMemoryMutable(y), incy); } bool CUDABlas::DoBlasSwap(Stream *stream, uint64 elem_count, DeviceMemory *x, int incx, DeviceMemory *y, int incy) { - return DoBlasInternal(wrap::cublasDswap, stream, - true /* = pointer_mode_host */, elem_count, - GpuMemoryMutable(x), incx, GpuMemoryMutable(y), incy); + return DoBlasInternal(cublasDswap, stream, true /* = pointer_mode_host */, + elem_count, GpuMemoryMutable(x), incx, + GpuMemoryMutable(y), incy); } bool CUDABlas::DoBlasSwap(Stream *stream, uint64 elem_count, DeviceMemory> *x, int incx, DeviceMemory> *y, int incy) { - return DoBlasInternal(wrap::cublasCswap, stream, - true /* = pointer_mode_host */, elem_count, - GpuComplex(GpuMemoryMutable(x)), incx, + return DoBlasInternal(cublasCswap, stream, true /* = pointer_mode_host */, + elem_count, GpuComplex(GpuMemoryMutable(x)), incx, GpuComplex(GpuMemoryMutable(y)), incy); } bool CUDABlas::DoBlasSwap(Stream *stream, uint64 elem_count, DeviceMemory> *x, int incx, DeviceMemory> *y, int incy) { - return DoBlasInternal(wrap::cublasZswap, stream, - true /* = pointer_mode_host */, elem_count, - GpuComplex(GpuMemoryMutable(x)), incx, + return DoBlasInternal(cublasZswap, stream, true /* = pointer_mode_host */, + elem_count, GpuComplex(GpuMemoryMutable(x)), incx, GpuComplex(GpuMemoryMutable(y)), incy); } bool CUDABlas::DoBlasIamax(Stream *stream, uint64 elem_count, const DeviceMemory &x, int incx, DeviceMemory *result) { - return DoBlasInternal(wrap::cublasIsamax, stream, - false /* = pointer_mode_host */, elem_count, - GpuMemory(x), incx, GpuMemoryMutable(result)); + return DoBlasInternal(cublasIsamax, stream, false /* = pointer_mode_host */, + elem_count, GpuMemory(x), incx, + GpuMemoryMutable(result)); } bool CUDABlas::DoBlasIamax(Stream *stream, uint64 elem_count, const DeviceMemory &x, int incx, DeviceMemory *result) { - return DoBlasInternal(wrap::cublasIdamax, stream, - false /* = pointer_mode_host */, elem_count, - GpuMemory(x), incx, GpuMemoryMutable(result)); + return DoBlasInternal(cublasIdamax, stream, false /* = pointer_mode_host */, + elem_count, GpuMemory(x), incx, + GpuMemoryMutable(result)); } bool CUDABlas::DoBlasIamax(Stream *stream, uint64 elem_count, const DeviceMemory> &x, int incx, DeviceMemory *result) { - return DoBlasInternal( - wrap::cublasIcamax, stream, false /* = pointer_mode_host */, - elem_count, GpuComplex(GpuMemory(x)), incx, GpuMemoryMutable(result)); + return DoBlasInternal(cublasIcamax, stream, false /* = pointer_mode_host */, + elem_count, GpuComplex(GpuMemory(x)), incx, + GpuMemoryMutable(result)); } bool CUDABlas::DoBlasIamax(Stream *stream, uint64 elem_count, const DeviceMemory> &x, int incx, DeviceMemory *result) { - return DoBlasInternal( - wrap::cublasIzamax, stream, false /* = pointer_mode_host */, - elem_count, GpuComplex(GpuMemory(x)), incx, GpuMemoryMutable(result)); + return DoBlasInternal(cublasIzamax, stream, false /* = pointer_mode_host */, + elem_count, GpuComplex(GpuMemory(x)), incx, + GpuMemoryMutable(result)); } bool CUDABlas::DoBlasIamin(Stream *stream, uint64 elem_count, const DeviceMemory &x, int incx, DeviceMemory *result) { - return DoBlasInternal( - wrap::cublasIsamin, stream, false /* = pointer_mode_host */, - elem_count, GpuComplex(GpuMemory(x)), incx, GpuMemoryMutable(result)); + return DoBlasInternal(cublasIsamin, stream, false /* = pointer_mode_host */, + elem_count, GpuComplex(GpuMemory(x)), incx, + GpuMemoryMutable(result)); } bool CUDABlas::DoBlasIamin(Stream *stream, uint64 elem_count, const DeviceMemory &x, int incx, DeviceMemory *result) { - return DoBlasInternal( - wrap::cublasIdamin, stream, false /* = pointer_mode_host */, - elem_count, GpuComplex(GpuMemory(x)), incx, GpuMemoryMutable(result)); + return DoBlasInternal(cublasIdamin, stream, false /* = pointer_mode_host */, + elem_count, GpuComplex(GpuMemory(x)), incx, + GpuMemoryMutable(result)); } bool CUDABlas::DoBlasIamin(Stream *stream, uint64 elem_count, const DeviceMemory> &x, int incx, DeviceMemory *result) { - return DoBlasInternal( - wrap::cublasIcamin, stream, false /* = pointer_mode_host */, - elem_count, GpuComplex(GpuMemory(x)), incx, GpuMemoryMutable(result)); + return DoBlasInternal(cublasIcamin, stream, false /* = pointer_mode_host */, + elem_count, GpuComplex(GpuMemory(x)), incx, + GpuMemoryMutable(result)); } bool CUDABlas::DoBlasIamin(Stream *stream, uint64 elem_count, const DeviceMemory> &x, int incx, DeviceMemory *result) { - return DoBlasInternal( - wrap::cublasIzamin, stream, false /* = pointer_mode_host */, - elem_count, GpuComplex(GpuMemory(x)), incx, GpuMemoryMutable(result)); + return DoBlasInternal(cublasIzamin, stream, false /* = pointer_mode_host */, + elem_count, GpuComplex(GpuMemory(x)), incx, + GpuMemoryMutable(result)); } bool CUDABlas::DoBlasGbmv(Stream *stream, blas::Transpose trans, uint64 m, @@ -1151,10 +872,10 @@ bool CUDABlas::DoBlasGbmv(Stream *stream, blas::Transpose trans, uint64 m, const DeviceMemory &a, int lda, const DeviceMemory &x, int incx, float beta, DeviceMemory *y, int incy) { - return DoBlasInternal( - wrap::cublasSgbmv, stream, true /* = pointer_mode_host */, - CUDABlasTranspose(trans), m, n, kl, ku, &alpha, GpuMemory(a), lda, - GpuMemory(x), incx, &beta, GpuMemoryMutable(y), incy); + return DoBlasInternal(cublasSgbmv, stream, true /* = pointer_mode_host */, + CUDABlasTranspose(trans), m, n, kl, ku, &alpha, + GpuMemory(a), lda, GpuMemory(x), incx, &beta, + GpuMemoryMutable(y), incy); } bool CUDABlas::DoBlasGbmv(Stream *stream, blas::Transpose trans, uint64 m, @@ -1162,10 +883,10 @@ bool CUDABlas::DoBlasGbmv(Stream *stream, blas::Transpose trans, uint64 m, const DeviceMemory &a, int lda, const DeviceMemory &x, int incx, double beta, DeviceMemory *y, int incy) { - return DoBlasInternal( - wrap::cublasDgbmv, stream, true /* = pointer_mode_host */, - CUDABlasTranspose(trans), m, n, kl, ku, &alpha, GpuMemory(a), lda, - GpuMemory(x), incx, &beta, GpuMemoryMutable(y), incy); + return DoBlasInternal(cublasDgbmv, stream, true /* = pointer_mode_host */, + CUDABlasTranspose(trans), m, n, kl, ku, &alpha, + GpuMemory(a), lda, GpuMemory(x), incx, &beta, + GpuMemoryMutable(y), incy); } bool CUDABlas::DoBlasGbmv(Stream *stream, blas::Transpose trans, uint64 m, @@ -1175,11 +896,11 @@ bool CUDABlas::DoBlasGbmv(Stream *stream, blas::Transpose trans, uint64 m, const DeviceMemory> &x, int incx, std::complex beta, DeviceMemory> *y, int incy) { - return DoBlasInternal( - wrap::cublasCgbmv, stream, true /* = pointer_mode_host */, - CUDABlasTranspose(trans), m, n, kl, ku, GpuComplex(&alpha), - GpuComplex(GpuMemory(a)), lda, GpuComplex(GpuMemory(x)), incx, - GpuComplex(&beta), GpuComplex(GpuMemoryMutable(y)), incy); + return DoBlasInternal(cublasCgbmv, stream, true /* = pointer_mode_host */, + CUDABlasTranspose(trans), m, n, kl, ku, + GpuComplex(&alpha), GpuComplex(GpuMemory(a)), lda, + GpuComplex(GpuMemory(x)), incx, GpuComplex(&beta), + GpuComplex(GpuMemoryMutable(y)), incy); } bool CUDABlas::DoBlasGbmv(Stream *stream, blas::Transpose trans, uint64 m, @@ -1189,31 +910,31 @@ bool CUDABlas::DoBlasGbmv(Stream *stream, blas::Transpose trans, uint64 m, const DeviceMemory> &x, int incx, std::complex beta, DeviceMemory> *y, int incy) { - return DoBlasInternal( - wrap::cublasZgbmv, stream, true /* = pointer_mode_host */, - CUDABlasTranspose(trans), m, n, kl, ku, GpuComplex(&alpha), - GpuComplex(GpuMemory(a)), lda, GpuComplex(GpuMemory(x)), incx, - GpuComplex(&beta), GpuComplex(GpuMemoryMutable(y)), incy); + return DoBlasInternal(cublasZgbmv, stream, true /* = pointer_mode_host */, + CUDABlasTranspose(trans), m, n, kl, ku, + GpuComplex(&alpha), GpuComplex(GpuMemory(a)), lda, + GpuComplex(GpuMemory(x)), incx, GpuComplex(&beta), + GpuComplex(GpuMemoryMutable(y)), incy); } bool CUDABlas::DoBlasGemv(Stream *stream, blas::Transpose trans, uint64 m, uint64 n, float alpha, const DeviceMemory &a, int lda, const DeviceMemory &x, int incx, float beta, DeviceMemory *y, int incy) { - return DoBlasInternal( - wrap::cublasSgemv, stream, true /* = pointer_mode_host */, - CUDABlasTranspose(trans), m, n, &alpha, GpuMemory(a), lda, GpuMemory(x), - incx, &beta, GpuMemoryMutable(y), incy); + return DoBlasInternal(cublasSgemv, stream, true /* = pointer_mode_host */, + CUDABlasTranspose(trans), m, n, &alpha, GpuMemory(a), + lda, GpuMemory(x), incx, &beta, GpuMemoryMutable(y), + incy); } bool CUDABlas::DoBlasGemv(Stream *stream, blas::Transpose trans, uint64 m, uint64 n, double alpha, const DeviceMemory &a, int lda, const DeviceMemory &x, int incx, double beta, DeviceMemory *y, int incy) { - return DoBlasInternal( - wrap::cublasDgemv, stream, true /* = pointer_mode_host */, - CUDABlasTranspose(trans), m, n, &alpha, GpuMemory(a), lda, GpuMemory(x), - incx, &beta, GpuMemoryMutable(y), incy); + return DoBlasInternal(cublasDgemv, stream, true /* = pointer_mode_host */, + CUDABlasTranspose(trans), m, n, &alpha, GpuMemory(a), + lda, GpuMemory(x), incx, &beta, GpuMemoryMutable(y), + incy); } bool CUDABlas::DoBlasGemv(Stream *stream, blas::Transpose trans, uint64 m, @@ -1222,11 +943,11 @@ bool CUDABlas::DoBlasGemv(Stream *stream, blas::Transpose trans, uint64 m, const DeviceMemory> &x, int incx, std::complex beta, DeviceMemory> *y, int incy) { - return DoBlasInternal( - wrap::cublasCgemv, stream, true /* = pointer_mode_host */, - CUDABlasTranspose(trans), m, n, GpuComplex(&alpha), - GpuComplex(GpuMemory(a)), lda, GpuComplex(GpuMemory(x)), incx, - GpuComplex(&beta), GpuComplex(GpuMemoryMutable(y)), incy); + return DoBlasInternal(cublasCgemv, stream, true /* = pointer_mode_host */, + CUDABlasTranspose(trans), m, n, GpuComplex(&alpha), + GpuComplex(GpuMemory(a)), lda, GpuComplex(GpuMemory(x)), + incx, GpuComplex(&beta), + GpuComplex(GpuMemoryMutable(y)), incy); } bool CUDABlas::DoBlasGemv(Stream *stream, blas::Transpose trans, uint64 m, @@ -1235,29 +956,29 @@ bool CUDABlas::DoBlasGemv(Stream *stream, blas::Transpose trans, uint64 m, const DeviceMemory> &x, int incx, std::complex beta, DeviceMemory> *y, int incy) { - return DoBlasInternal( - wrap::cublasZgemv, stream, true /* = pointer_mode_host */, - CUDABlasTranspose(trans), m, n, GpuComplex(&alpha), - GpuComplex(GpuMemory(a)), lda, GpuComplex(GpuMemory(x)), incx, - GpuComplex(&beta), GpuComplex(GpuMemoryMutable(y)), incy); + return DoBlasInternal(cublasZgemv, stream, true /* = pointer_mode_host */, + CUDABlasTranspose(trans), m, n, GpuComplex(&alpha), + GpuComplex(GpuMemory(a)), lda, GpuComplex(GpuMemory(x)), + incx, GpuComplex(&beta), + GpuComplex(GpuMemoryMutable(y)), incy); } bool CUDABlas::DoBlasGer(Stream *stream, uint64 m, uint64 n, float alpha, const DeviceMemory &x, int incx, const DeviceMemory &y, int incy, DeviceMemory *a, int lda) { - return DoBlasInternal( - wrap::cublasSger, stream, true /* = pointer_mode_host */, m, n, &alpha, - GpuMemory(x), incx, GpuMemory(y), incy, GpuMemoryMutable(a), lda); + return DoBlasInternal(cublasSger, stream, true /* = pointer_mode_host */, m, + n, &alpha, GpuMemory(x), incx, GpuMemory(y), incy, + GpuMemoryMutable(a), lda); } bool CUDABlas::DoBlasGer(Stream *stream, uint64 m, uint64 n, double alpha, const DeviceMemory &x, int incx, const DeviceMemory &y, int incy, DeviceMemory *a, int lda) { - return DoBlasInternal( - wrap::cublasDger, stream, true /* = pointer_mode_host */, m, n, &alpha, - GpuMemory(x), incx, GpuMemory(y), incy, GpuMemoryMutable(a), lda); + return DoBlasInternal(cublasDger, stream, true /* = pointer_mode_host */, m, + n, &alpha, GpuMemory(x), incx, GpuMemory(y), incy, + GpuMemoryMutable(a), lda); } bool CUDABlas::DoBlasGerc(Stream *stream, uint64 m, uint64 n, @@ -1265,10 +986,10 @@ bool CUDABlas::DoBlasGerc(Stream *stream, uint64 m, uint64 n, const DeviceMemory> &x, int incx, const DeviceMemory> &y, int incy, DeviceMemory> *a, int lda) { - return DoBlasInternal( - wrap::cublasCgerc, stream, true /* = pointer_mode_host */, m, n, - GpuComplex(&alpha), GpuComplex(GpuMemory(x)), incx, - GpuComplex(GpuMemory(y)), incy, GpuComplex(GpuMemoryMutable(a)), lda); + return DoBlasInternal(cublasCgerc, stream, true /* = pointer_mode_host */, m, + n, GpuComplex(&alpha), GpuComplex(GpuMemory(x)), incx, + GpuComplex(GpuMemory(y)), incy, + GpuComplex(GpuMemoryMutable(a)), lda); } bool CUDABlas::DoBlasGerc(Stream *stream, uint64 m, uint64 n, @@ -1276,10 +997,10 @@ bool CUDABlas::DoBlasGerc(Stream *stream, uint64 m, uint64 n, const DeviceMemory> &x, int incx, const DeviceMemory> &y, int incy, DeviceMemory> *a, int lda) { - return DoBlasInternal( - wrap::cublasZgerc, stream, true /* = pointer_mode_host */, m, n, - GpuComplex(&alpha), GpuComplex(GpuMemory(x)), incx, - GpuComplex(GpuMemory(y)), incy, GpuComplex(GpuMemoryMutable(a)), lda); + return DoBlasInternal(cublasZgerc, stream, true /* = pointer_mode_host */, m, + n, GpuComplex(&alpha), GpuComplex(GpuMemory(x)), incx, + GpuComplex(GpuMemory(y)), incy, + GpuComplex(GpuMemoryMutable(a)), lda); } bool CUDABlas::DoBlasGeru(Stream *stream, uint64 m, uint64 n, @@ -1287,10 +1008,10 @@ bool CUDABlas::DoBlasGeru(Stream *stream, uint64 m, uint64 n, const DeviceMemory> &x, int incx, const DeviceMemory> &y, int incy, DeviceMemory> *a, int lda) { - return DoBlasInternal( - wrap::cublasCgeru, stream, true /* = pointer_mode_host */, m, n, - GpuComplex(&alpha), GpuComplex(GpuMemory(x)), incx, - GpuComplex(GpuMemory(y)), incy, GpuComplex(GpuMemoryMutable(a)), lda); + return DoBlasInternal(cublasCgeru, stream, true /* = pointer_mode_host */, m, + n, GpuComplex(&alpha), GpuComplex(GpuMemory(x)), incx, + GpuComplex(GpuMemory(y)), incy, + GpuComplex(GpuMemoryMutable(a)), lda); } bool CUDABlas::DoBlasGeru(Stream *stream, uint64 m, uint64 n, @@ -1298,10 +1019,10 @@ bool CUDABlas::DoBlasGeru(Stream *stream, uint64 m, uint64 n, const DeviceMemory> &x, int incx, const DeviceMemory> &y, int incy, DeviceMemory> *a, int lda) { - return DoBlasInternal( - wrap::cublasZgeru, stream, true /* = pointer_mode_host */, m, n, - GpuComplex(&alpha), GpuComplex(GpuMemory(x)), incx, - GpuComplex(GpuMemory(y)), incy, GpuComplex(GpuMemoryMutable(a)), lda); + return DoBlasInternal(cublasZgeru, stream, true /* = pointer_mode_host */, m, + n, GpuComplex(&alpha), GpuComplex(GpuMemory(x)), incx, + GpuComplex(GpuMemory(y)), incy, + GpuComplex(GpuMemoryMutable(a)), lda); } bool CUDABlas::DoBlasHbmv(Stream *stream, blas::UpperLower uplo, uint64 n, @@ -1310,11 +1031,11 @@ bool CUDABlas::DoBlasHbmv(Stream *stream, blas::UpperLower uplo, uint64 n, const DeviceMemory> &x, int incx, std::complex beta, DeviceMemory> *y, int incy) { - return DoBlasInternal( - wrap::cublasChbmv, stream, true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), n, k, GpuComplex(&alpha), - GpuComplex(GpuMemory(a)), lda, GpuComplex(GpuMemory(x)), incx, - GpuComplex(&beta), GpuComplex(GpuMemoryMutable(y)), incy); + return DoBlasInternal(cublasChbmv, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), n, k, GpuComplex(&alpha), + GpuComplex(GpuMemory(a)), lda, GpuComplex(GpuMemory(x)), + incx, GpuComplex(&beta), + GpuComplex(GpuMemoryMutable(y)), incy); } bool CUDABlas::DoBlasHbmv(Stream *stream, blas::UpperLower uplo, uint64 n, @@ -1323,11 +1044,11 @@ bool CUDABlas::DoBlasHbmv(Stream *stream, blas::UpperLower uplo, uint64 n, const DeviceMemory> &x, int incx, std::complex beta, DeviceMemory> *y, int incy) { - return DoBlasInternal( - wrap::cublasZhbmv, stream, true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), n, k, GpuComplex(&alpha), - GpuComplex(GpuMemory(a)), lda, GpuComplex(GpuMemory(x)), incx, - GpuComplex(&beta), GpuComplex(GpuMemoryMutable(y)), incy); + return DoBlasInternal(cublasZhbmv, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), n, k, GpuComplex(&alpha), + GpuComplex(GpuMemory(a)), lda, GpuComplex(GpuMemory(x)), + incx, GpuComplex(&beta), + GpuComplex(GpuMemoryMutable(y)), incy); } bool CUDABlas::DoBlasHemv(Stream *stream, blas::UpperLower uplo, uint64 n, @@ -1336,11 +1057,11 @@ bool CUDABlas::DoBlasHemv(Stream *stream, blas::UpperLower uplo, uint64 n, const DeviceMemory> &x, int incx, std::complex beta, DeviceMemory> *y, int incy) { - return DoBlasInternal( - wrap::cublasChemv, stream, true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), n, GpuComplex(&alpha), - GpuComplex(GpuMemory(a)), lda, GpuComplex(GpuMemory(x)), incx, - GpuComplex(&beta), GpuComplex(GpuMemoryMutable(y)), incy); + return DoBlasInternal(cublasChemv, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), n, GpuComplex(&alpha), + GpuComplex(GpuMemory(a)), lda, GpuComplex(GpuMemory(x)), + incx, GpuComplex(&beta), + GpuComplex(GpuMemoryMutable(y)), incy); } bool CUDABlas::DoBlasHemv(Stream *stream, blas::UpperLower uplo, uint64 n, @@ -1349,31 +1070,31 @@ bool CUDABlas::DoBlasHemv(Stream *stream, blas::UpperLower uplo, uint64 n, const DeviceMemory> &x, int incx, std::complex beta, DeviceMemory> *y, int incy) { - return DoBlasInternal( - wrap::cublasZhemv, stream, true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), n, GpuComplex(&alpha), - GpuComplex(GpuMemory(a)), lda, GpuComplex(GpuMemory(x)), incx, - GpuComplex(&beta), GpuComplex(GpuMemoryMutable(y)), incy); + return DoBlasInternal(cublasZhemv, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), n, GpuComplex(&alpha), + GpuComplex(GpuMemory(a)), lda, GpuComplex(GpuMemory(x)), + incx, GpuComplex(&beta), + GpuComplex(GpuMemoryMutable(y)), incy); } bool CUDABlas::DoBlasHer(Stream *stream, blas::UpperLower uplo, uint64 n, float alpha, const DeviceMemory> &x, int incx, DeviceMemory> *a, int lda) { - return DoBlasInternal( - wrap::cublasCher, stream, true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), n, &alpha, GpuComplex(GpuMemory(x)), incx, - GpuComplex(GpuMemoryMutable(a)), lda); + return DoBlasInternal(cublasCher, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), n, &alpha, + GpuComplex(GpuMemory(x)), incx, + GpuComplex(GpuMemoryMutable(a)), lda); } bool CUDABlas::DoBlasHer(Stream *stream, blas::UpperLower uplo, uint64 n, double alpha, const DeviceMemory> &x, int incx, DeviceMemory> *a, int lda) { - return DoBlasInternal( - wrap::cublasZher, stream, true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), n, &alpha, GpuComplex(GpuMemory(x)), incx, - GpuComplex(GpuMemoryMutable(a)), lda); + return DoBlasInternal(cublasZher, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), n, &alpha, + GpuComplex(GpuMemory(x)), incx, + GpuComplex(GpuMemoryMutable(a)), lda); } bool CUDABlas::DoBlasHer2(Stream *stream, blas::UpperLower uplo, uint64 n, @@ -1381,11 +1102,11 @@ bool CUDABlas::DoBlasHer2(Stream *stream, blas::UpperLower uplo, uint64 n, const DeviceMemory> &x, int incx, const DeviceMemory> &y, int incy, DeviceMemory> *a, int lda) { - return DoBlasInternal( - wrap::cublasCher2, stream, true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), n, GpuComplex(&alpha), - GpuComplex(GpuMemory(x)), incx, GpuComplex(GpuMemory(y)), incy, - GpuComplex(GpuMemoryMutable(a)), lda); + return DoBlasInternal(cublasCher2, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), n, GpuComplex(&alpha), + GpuComplex(GpuMemory(x)), incx, + GpuComplex(GpuMemory(y)), incy, + GpuComplex(GpuMemoryMutable(a)), lda); } bool CUDABlas::DoBlasHer2(Stream *stream, blas::UpperLower uplo, uint64 n, @@ -1393,11 +1114,11 @@ bool CUDABlas::DoBlasHer2(Stream *stream, blas::UpperLower uplo, uint64 n, const DeviceMemory> &x, int incx, const DeviceMemory> &y, int incy, DeviceMemory> *a, int lda) { - return DoBlasInternal( - wrap::cublasZher2, stream, true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), n, GpuComplex(&alpha), - GpuComplex(GpuMemory(x)), incx, GpuComplex(GpuMemory(y)), incy, - GpuComplex(GpuMemoryMutable(a)), lda); + return DoBlasInternal(cublasZher2, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), n, GpuComplex(&alpha), + GpuComplex(GpuMemory(x)), incx, + GpuComplex(GpuMemory(y)), incy, + GpuComplex(GpuMemoryMutable(a)), lda); } bool CUDABlas::DoBlasHpmv(Stream *stream, blas::UpperLower uplo, uint64 n, @@ -1406,11 +1127,11 @@ bool CUDABlas::DoBlasHpmv(Stream *stream, blas::UpperLower uplo, uint64 n, const DeviceMemory> &x, int incx, std::complex beta, DeviceMemory> *y, int incy) { - return DoBlasInternal( - wrap::cublasChpmv, stream, true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), n, GpuComplex(&alpha), - GpuComplex(GpuMemory(ap)), GpuComplex(GpuMemory(x)), incx, - GpuComplex(&beta), GpuComplex(GpuMemoryMutable(y)), incy); + return DoBlasInternal(cublasChpmv, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), n, GpuComplex(&alpha), + GpuComplex(GpuMemory(ap)), GpuComplex(GpuMemory(x)), + incx, GpuComplex(&beta), + GpuComplex(GpuMemoryMutable(y)), incy); } bool CUDABlas::DoBlasHpmv(Stream *stream, blas::UpperLower uplo, uint64 n, @@ -1419,31 +1140,31 @@ bool CUDABlas::DoBlasHpmv(Stream *stream, blas::UpperLower uplo, uint64 n, const DeviceMemory> &x, int incx, std::complex beta, DeviceMemory> *y, int incy) { - return DoBlasInternal( - wrap::cublasZhpmv, stream, true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), n, GpuComplex(&alpha), - GpuComplex(GpuMemory(ap)), GpuComplex(GpuMemory(x)), incx, - GpuComplex(&beta), GpuComplex(GpuMemoryMutable(y)), incy); + return DoBlasInternal(cublasZhpmv, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), n, GpuComplex(&alpha), + GpuComplex(GpuMemory(ap)), GpuComplex(GpuMemory(x)), + incx, GpuComplex(&beta), + GpuComplex(GpuMemoryMutable(y)), incy); } bool CUDABlas::DoBlasHpr(Stream *stream, blas::UpperLower uplo, uint64 n, float alpha, const DeviceMemory> &x, int incx, DeviceMemory> *ap) { - return DoBlasInternal( - wrap::cublasChpr, stream, true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), n, GpuComplex(&alpha), - GpuComplex(GpuMemory(x)), incx, GpuComplex(GpuMemoryMutable(ap))); + return DoBlasInternal(cublasChpr, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), n, GpuComplex(&alpha), + GpuComplex(GpuMemory(x)), incx, + GpuComplex(GpuMemoryMutable(ap))); } bool CUDABlas::DoBlasHpr(Stream *stream, blas::UpperLower uplo, uint64 n, double alpha, const DeviceMemory> &x, int incx, DeviceMemory> *ap) { - return DoBlasInternal( - wrap::cublasZhpr, stream, true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), n, GpuComplex(&alpha), - GpuComplex(GpuMemory(x)), incx, GpuComplex(GpuMemoryMutable(ap))); + return DoBlasInternal(cublasZhpr, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), n, GpuComplex(&alpha), + GpuComplex(GpuMemory(x)), incx, + GpuComplex(GpuMemoryMutable(ap))); } bool CUDABlas::DoBlasHpr2(Stream *stream, blas::UpperLower uplo, uint64 n, @@ -1452,10 +1173,9 @@ bool CUDABlas::DoBlasHpr2(Stream *stream, blas::UpperLower uplo, uint64 n, const DeviceMemory> &y, int incy, DeviceMemory> *ap) { return DoBlasInternal( - wrap::cublasChpr2, stream, true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), n, GpuComplex(&alpha), - GpuComplex(GpuMemory(x)), incx, GpuComplex(GpuMemory(y)), incy, - GpuComplex(GpuMemoryMutable(ap))); + cublasChpr2, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), n, GpuComplex(&alpha), GpuComplex(GpuMemory(x)), + incx, GpuComplex(GpuMemory(y)), incy, GpuComplex(GpuMemoryMutable(ap))); } bool CUDABlas::DoBlasHpr2(Stream *stream, blas::UpperLower uplo, uint64 n, @@ -1464,38 +1184,36 @@ bool CUDABlas::DoBlasHpr2(Stream *stream, blas::UpperLower uplo, uint64 n, const DeviceMemory> &y, int incy, DeviceMemory> *ap) { return DoBlasInternal( - wrap::cublasZhpr2, stream, true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), n, GpuComplex(&alpha), - GpuComplex(GpuMemory(x)), incx, GpuComplex(GpuMemory(y)), incy, - GpuComplex(GpuMemoryMutable(ap))); + cublasZhpr2, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), n, GpuComplex(&alpha), GpuComplex(GpuMemory(x)), + incx, GpuComplex(GpuMemory(y)), incy, GpuComplex(GpuMemoryMutable(ap))); } bool CUDABlas::DoBlasSbmv(Stream *stream, blas::UpperLower uplo, uint64 n, uint64 k, float alpha, const DeviceMemory &a, int lda, const DeviceMemory &x, int incx, float beta, DeviceMemory *y, int incy) { - return DoBlasInternal( - wrap::cublasSsbmv, stream, true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), n, k, &alpha, GpuMemory(a), lda, GpuMemory(x), - incx, &beta, GpuMemoryMutable(y), incy); + return DoBlasInternal(cublasSsbmv, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), n, k, &alpha, GpuMemory(a), + lda, GpuMemory(x), incx, &beta, GpuMemoryMutable(y), + incy); } bool CUDABlas::DoBlasSbmv(Stream *stream, blas::UpperLower uplo, uint64 n, uint64 k, double alpha, const DeviceMemory &a, int lda, const DeviceMemory &x, int incx, double beta, DeviceMemory *y, int incy) { - return DoBlasInternal( - wrap::cublasDsbmv, stream, true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), n, k, &alpha, GpuMemory(a), lda, GpuMemory(x), - incx, &beta, GpuMemoryMutable(y), incy); + return DoBlasInternal(cublasDsbmv, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), n, k, &alpha, GpuMemory(a), + lda, GpuMemory(x), incx, &beta, GpuMemoryMutable(y), + incy); } bool CUDABlas::DoBlasSpmv(Stream *stream, blas::UpperLower uplo, uint64 n, float alpha, const DeviceMemory &ap, const DeviceMemory &x, int incx, float beta, DeviceMemory *y, int incy) { - return DoBlasInternal(wrap::cublasSspmv, stream, - true /* = pointer_mode_host */, + return DoBlasInternal(cublasSspmv, stream, true /* = pointer_mode_host */, CUDABlasUpperLower(uplo), n, &alpha, GpuMemory(ap), GpuMemory(x), incx, &beta, GpuMemoryMutable(y), incy); } @@ -1504,8 +1222,7 @@ bool CUDABlas::DoBlasSpmv(Stream *stream, blas::UpperLower uplo, uint64 n, double alpha, const DeviceMemory &ap, const DeviceMemory &x, int incx, double beta, DeviceMemory *y, int incy) { - return DoBlasInternal(wrap::cublasDspmv, stream, - true /* = pointer_mode_host */, + return DoBlasInternal(cublasDspmv, stream, true /* = pointer_mode_host */, CUDABlasUpperLower(uplo), n, &alpha, GpuMemory(ap), GpuMemory(x), incx, &beta, GpuMemoryMutable(y), incy); } @@ -1513,47 +1230,42 @@ bool CUDABlas::DoBlasSpmv(Stream *stream, blas::UpperLower uplo, uint64 n, bool CUDABlas::DoBlasSpr(Stream *stream, blas::UpperLower uplo, uint64 n, float alpha, const DeviceMemory &x, int incx, DeviceMemory *ap) { - return DoBlasInternal(wrap::cublasSspr, stream, - true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), n, &alpha, GpuMemory(x), - incx, GpuMemoryMutable(ap)); + return DoBlasInternal(cublasSspr, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), n, &alpha, GpuMemory(x), incx, + GpuMemoryMutable(ap)); } bool CUDABlas::DoBlasSpr(Stream *stream, blas::UpperLower uplo, uint64 n, double alpha, const DeviceMemory &x, int incx, DeviceMemory *ap) { - return DoBlasInternal(wrap::cublasDspr, stream, - true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), n, &alpha, GpuMemory(x), - incx, GpuMemoryMutable(ap)); + return DoBlasInternal(cublasDspr, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), n, &alpha, GpuMemory(x), incx, + GpuMemoryMutable(ap)); } bool CUDABlas::DoBlasSpr2(Stream *stream, blas::UpperLower uplo, uint64 n, float alpha, const DeviceMemory &x, int incx, const DeviceMemory &y, int incy, DeviceMemory *ap) { - return DoBlasInternal(wrap::cublasSspr2, stream, - true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), n, &alpha, GpuMemory(x), - incx, GpuMemory(y), incy, GpuMemoryMutable(ap)); + return DoBlasInternal(cublasSspr2, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), n, &alpha, GpuMemory(x), incx, + GpuMemory(y), incy, GpuMemoryMutable(ap)); } bool CUDABlas::DoBlasSpr2(Stream *stream, blas::UpperLower uplo, uint64 n, double alpha, const DeviceMemory &x, int incx, const DeviceMemory &y, int incy, DeviceMemory *ap) { - return DoBlasInternal(wrap::cublasDspr2, stream, - true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), n, &alpha, GpuMemory(x), - incx, GpuMemory(y), incy, GpuMemoryMutable(ap)); + return DoBlasInternal(cublasDspr2, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), n, &alpha, GpuMemory(x), incx, + GpuMemory(y), incy, GpuMemoryMutable(ap)); } bool CUDABlas::DoBlasSymv(Stream *stream, blas::UpperLower uplo, uint64 n, float alpha, const DeviceMemory &a, int lda, const DeviceMemory &x, int incx, float beta, DeviceMemory *y, int incy) { - return DoBlasInternal(wrap::cublasSsymv, stream, - true /* = pointer_mode_host */, + return DoBlasInternal(cublasSsymv, stream, true /* = pointer_mode_host */, CUDABlasUpperLower(uplo), n, &alpha, GpuMemory(a), lda, GpuMemory(x), incx, &beta, GpuMemoryMutable(y), incy); } @@ -1562,8 +1274,7 @@ bool CUDABlas::DoBlasSymv(Stream *stream, blas::UpperLower uplo, uint64 n, double alpha, const DeviceMemory &a, int lda, const DeviceMemory &x, int incx, double beta, DeviceMemory *y, int incy) { - return DoBlasInternal(wrap::cublasDsymv, stream, - true /* = pointer_mode_host */, + return DoBlasInternal(cublasDsymv, stream, true /* = pointer_mode_host */, CUDABlasUpperLower(uplo), n, &alpha, GpuMemory(a), lda, GpuMemory(x), incx, &beta, GpuMemoryMutable(y), incy); } @@ -1571,47 +1282,42 @@ bool CUDABlas::DoBlasSymv(Stream *stream, blas::UpperLower uplo, uint64 n, bool CUDABlas::DoBlasSyr(Stream *stream, blas::UpperLower uplo, uint64 n, float alpha, const DeviceMemory &x, int incx, DeviceMemory *a, int lda) { - return DoBlasInternal(wrap::cublasSsyr, stream, - true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), n, &alpha, GpuMemory(x), - incx, GpuMemoryMutable(a), lda); + return DoBlasInternal(cublasSsyr, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), n, &alpha, GpuMemory(x), incx, + GpuMemoryMutable(a), lda); } bool CUDABlas::DoBlasSyr(Stream *stream, blas::UpperLower uplo, uint64 n, double alpha, const DeviceMemory &x, int incx, DeviceMemory *a, int lda) { - return DoBlasInternal(wrap::cublasDsyr, stream, - true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), n, &alpha, GpuMemory(x), - incx, GpuMemoryMutable(a), lda); + return DoBlasInternal(cublasDsyr, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), n, &alpha, GpuMemory(x), incx, + GpuMemoryMutable(a), lda); } bool CUDABlas::DoBlasSyr2(Stream *stream, blas::UpperLower uplo, uint64 n, float alpha, const DeviceMemory &x, int incx, const DeviceMemory &y, int incy, DeviceMemory *a, int lda) { - return DoBlasInternal(wrap::cublasSsyr2, stream, - true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), n, &alpha, GpuMemory(x), - incx, GpuMemory(y), incy, GpuMemoryMutable(a), lda); + return DoBlasInternal(cublasSsyr2, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), n, &alpha, GpuMemory(x), incx, + GpuMemory(y), incy, GpuMemoryMutable(a), lda); } bool CUDABlas::DoBlasSyr2(Stream *stream, blas::UpperLower uplo, uint64 n, double alpha, const DeviceMemory &x, int incx, const DeviceMemory &y, int incy, DeviceMemory *a, int lda) { - return DoBlasInternal(wrap::cublasDsyr2, stream, - true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), n, &alpha, GpuMemory(x), - incx, GpuMemory(y), incy, GpuMemoryMutable(a), lda); + return DoBlasInternal(cublasDsyr2, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), n, &alpha, GpuMemory(x), incx, + GpuMemory(y), incy, GpuMemoryMutable(a), lda); } bool CUDABlas::DoBlasTbmv(Stream *stream, blas::UpperLower uplo, blas::Transpose trans, blas::Diagonal diag, uint64 n, uint64 k, const DeviceMemory &a, int lda, DeviceMemory *x, int incx) { - return DoBlasInternal(wrap::cublasStbmv, stream, - true /* = pointer_mode_host */, + return DoBlasInternal(cublasStbmv, stream, true /* = pointer_mode_host */, CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), CUDABlasDiagonal(diag), n, k, GpuMemory(a), lda, GpuMemoryMutable(x), incx); @@ -1621,8 +1327,7 @@ bool CUDABlas::DoBlasTbmv(Stream *stream, blas::UpperLower uplo, blas::Transpose trans, blas::Diagonal diag, uint64 n, uint64 k, const DeviceMemory &a, int lda, DeviceMemory *x, int incx) { - return DoBlasInternal(wrap::cublasDtbmv, stream, - true /* = pointer_mode_host */, + return DoBlasInternal(cublasDtbmv, stream, true /* = pointer_mode_host */, CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), CUDABlasDiagonal(diag), n, k, GpuMemory(a), lda, GpuMemoryMutable(x), incx); @@ -1633,11 +1338,10 @@ bool CUDABlas::DoBlasTbmv(Stream *stream, blas::UpperLower uplo, uint64 k, const DeviceMemory> &a, int lda, DeviceMemory> *x, int incx) { - return DoBlasInternal( - wrap::cublasCtbmv, stream, true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), - CUDABlasDiagonal(diag), n, k, GpuComplex(GpuMemory(a)), lda, - GpuComplex(GpuMemoryMutable(x)), incx); + return DoBlasInternal(cublasCtbmv, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), + CUDABlasDiagonal(diag), n, k, GpuComplex(GpuMemory(a)), + lda, GpuComplex(GpuMemoryMutable(x)), incx); } bool CUDABlas::DoBlasTbmv(Stream *stream, blas::UpperLower uplo, @@ -1645,19 +1349,17 @@ bool CUDABlas::DoBlasTbmv(Stream *stream, blas::UpperLower uplo, uint64 k, const DeviceMemory> &a, int lda, DeviceMemory> *x, int incx) { - return DoBlasInternal( - wrap::cublasZtbmv, stream, true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), - CUDABlasDiagonal(diag), n, k, GpuComplex(GpuMemory(a)), lda, - GpuComplex(GpuMemoryMutable(x)), incx); + return DoBlasInternal(cublasZtbmv, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), + CUDABlasDiagonal(diag), n, k, GpuComplex(GpuMemory(a)), + lda, GpuComplex(GpuMemoryMutable(x)), incx); } bool CUDABlas::DoBlasTbsv(Stream *stream, blas::UpperLower uplo, blas::Transpose trans, blas::Diagonal diag, uint64 n, uint64 k, const DeviceMemory &a, int lda, DeviceMemory *x, int incx) { - return DoBlasInternal(wrap::cublasStbsv, stream, - true /* = pointer_mode_host */, + return DoBlasInternal(cublasStbsv, stream, true /* = pointer_mode_host */, CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), CUDABlasDiagonal(diag), n, k, GpuMemory(a), lda, GpuMemoryMutable(x), incx); @@ -1667,8 +1369,7 @@ bool CUDABlas::DoBlasTbsv(Stream *stream, blas::UpperLower uplo, blas::Transpose trans, blas::Diagonal diag, uint64 n, uint64 k, const DeviceMemory &a, int lda, DeviceMemory *x, int incx) { - return DoBlasInternal(wrap::cublasDtbsv, stream, - true /* = pointer_mode_host */, + return DoBlasInternal(cublasDtbsv, stream, true /* = pointer_mode_host */, CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), CUDABlasDiagonal(diag), n, k, GpuMemory(a), lda, GpuMemoryMutable(x), incx); @@ -1679,11 +1380,10 @@ bool CUDABlas::DoBlasTbsv(Stream *stream, blas::UpperLower uplo, uint64 k, const DeviceMemory> &a, int lda, DeviceMemory> *x, int incx) { - return DoBlasInternal( - wrap::cublasCtbsv, stream, true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), - CUDABlasDiagonal(diag), n, k, GpuComplex(GpuMemory(a)), lda, - GpuComplex(GpuMemoryMutable(x)), incx); + return DoBlasInternal(cublasCtbsv, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), + CUDABlasDiagonal(diag), n, k, GpuComplex(GpuMemory(a)), + lda, GpuComplex(GpuMemoryMutable(x)), incx); } bool CUDABlas::DoBlasTbsv(Stream *stream, blas::UpperLower uplo, @@ -1691,39 +1391,37 @@ bool CUDABlas::DoBlasTbsv(Stream *stream, blas::UpperLower uplo, uint64 k, const DeviceMemory> &a, int lda, DeviceMemory> *x, int incx) { - return DoBlasInternal( - wrap::cublasZtbsv, stream, true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), - CUDABlasDiagonal(diag), n, k, GpuComplex(GpuMemory(a)), lda, - GpuComplex(GpuMemoryMutable(x)), incx); + return DoBlasInternal(cublasZtbsv, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), + CUDABlasDiagonal(diag), n, k, GpuComplex(GpuMemory(a)), + lda, GpuComplex(GpuMemoryMutable(x)), incx); } bool CUDABlas::DoBlasTpmv(Stream *stream, blas::UpperLower uplo, blas::Transpose trans, blas::Diagonal diag, uint64 n, const DeviceMemory &ap, DeviceMemory *x, int incx) { - return DoBlasInternal( - wrap::cublasStpmv, stream, true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), - CUDABlasDiagonal(diag), n, GpuMemory(ap), GpuMemoryMutable(x), incx); + return DoBlasInternal(cublasStpmv, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), + CUDABlasDiagonal(diag), n, GpuMemory(ap), + GpuMemoryMutable(x), incx); } bool CUDABlas::DoBlasTpmv(Stream *stream, blas::UpperLower uplo, blas::Transpose trans, blas::Diagonal diag, uint64 n, const DeviceMemory &ap, DeviceMemory *x, int incx) { - return DoBlasInternal( - wrap::cublasDtpmv, stream, true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), - CUDABlasDiagonal(diag), n, GpuMemory(ap), GpuMemoryMutable(x), incx); + return DoBlasInternal(cublasDtpmv, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), + CUDABlasDiagonal(diag), n, GpuMemory(ap), + GpuMemoryMutable(x), incx); } bool CUDABlas::DoBlasTpmv(Stream *stream, blas::UpperLower uplo, blas::Transpose trans, blas::Diagonal diag, uint64 n, const DeviceMemory> &ap, DeviceMemory> *x, int incx) { - return DoBlasInternal(wrap::cublasCtpmv, stream, - true /* = pointer_mode_host */, + return DoBlasInternal(cublasCtpmv, stream, true /* = pointer_mode_host */, CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), CUDABlasDiagonal(diag), n, GpuComplex(GpuMemory(ap)), GpuComplex(GpuMemoryMutable(x)), incx); @@ -1733,8 +1431,7 @@ bool CUDABlas::DoBlasTpmv(Stream *stream, blas::UpperLower uplo, blas::Transpose trans, blas::Diagonal diag, uint64 n, const DeviceMemory> &ap, DeviceMemory> *x, int incx) { - return DoBlasInternal(wrap::cublasZtpmv, stream, - true /* = pointer_mode_host */, + return DoBlasInternal(cublasZtpmv, stream, true /* = pointer_mode_host */, CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), CUDABlasDiagonal(diag), n, GpuComplex(GpuMemory(ap)), GpuComplex(GpuMemoryMutable(x)), incx); @@ -1744,28 +1441,27 @@ bool CUDABlas::DoBlasTpsv(Stream *stream, blas::UpperLower uplo, blas::Transpose trans, blas::Diagonal diag, uint64 n, const DeviceMemory &ap, DeviceMemory *x, int incx) { - return DoBlasInternal( - wrap::cublasStpsv, stream, true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), - CUDABlasDiagonal(diag), n, GpuMemory(ap), GpuMemoryMutable(x), incx); + return DoBlasInternal(cublasStpsv, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), + CUDABlasDiagonal(diag), n, GpuMemory(ap), + GpuMemoryMutable(x), incx); } bool CUDABlas::DoBlasTpsv(Stream *stream, blas::UpperLower uplo, blas::Transpose trans, blas::Diagonal diag, uint64 n, const DeviceMemory &ap, DeviceMemory *x, int incx) { - return DoBlasInternal( - wrap::cublasDtpsv, stream, true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), - CUDABlasDiagonal(diag), n, GpuMemory(ap), GpuMemoryMutable(x), incx); + return DoBlasInternal(cublasDtpsv, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), + CUDABlasDiagonal(diag), n, GpuMemory(ap), + GpuMemoryMutable(x), incx); } bool CUDABlas::DoBlasTpsv(Stream *stream, blas::UpperLower uplo, blas::Transpose trans, blas::Diagonal diag, uint64 n, const DeviceMemory> &ap, DeviceMemory> *x, int incx) { - return DoBlasInternal(wrap::cublasCtpsv, stream, - true /* = pointer_mode_host */, + return DoBlasInternal(cublasCtpsv, stream, true /* = pointer_mode_host */, CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), CUDABlasDiagonal(diag), n, GpuComplex(GpuMemory(ap)), GpuComplex(GpuMemoryMutable(x)), incx); @@ -1775,8 +1471,7 @@ bool CUDABlas::DoBlasTpsv(Stream *stream, blas::UpperLower uplo, blas::Transpose trans, blas::Diagonal diag, uint64 n, const DeviceMemory> &ap, DeviceMemory> *x, int incx) { - return DoBlasInternal(wrap::cublasZtpsv, stream, - true /* = pointer_mode_host */, + return DoBlasInternal(cublasZtpsv, stream, true /* = pointer_mode_host */, CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), CUDABlasDiagonal(diag), n, GpuComplex(GpuMemory(ap)), GpuComplex(GpuMemoryMutable(x)), incx); @@ -1786,8 +1481,7 @@ bool CUDABlas::DoBlasTrmv(Stream *stream, blas::UpperLower uplo, blas::Transpose trans, blas::Diagonal diag, uint64 n, const DeviceMemory &a, int lda, DeviceMemory *x, int incx) { - return DoBlasInternal(wrap::cublasStrmv, stream, - true /* = pointer_mode_host */, + return DoBlasInternal(cublasStrmv, stream, true /* = pointer_mode_host */, CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), CUDABlasDiagonal(diag), n, GpuMemory(a), lda, GpuMemoryMutable(x), incx); @@ -1797,8 +1491,7 @@ bool CUDABlas::DoBlasTrmv(Stream *stream, blas::UpperLower uplo, blas::Transpose trans, blas::Diagonal diag, uint64 n, const DeviceMemory &a, int lda, DeviceMemory *x, int incx) { - return DoBlasInternal(wrap::cublasDtrmv, stream, - true /* = pointer_mode_host */, + return DoBlasInternal(cublasDtrmv, stream, true /* = pointer_mode_host */, CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), CUDABlasDiagonal(diag), n, GpuMemory(a), lda, GpuMemoryMutable(x), incx); @@ -1808,8 +1501,7 @@ bool CUDABlas::DoBlasTrmv(Stream *stream, blas::UpperLower uplo, blas::Transpose trans, blas::Diagonal diag, uint64 n, const DeviceMemory> &a, int lda, DeviceMemory> *x, int incx) { - return DoBlasInternal(wrap::cublasCtrmv, stream, - true /* = pointer_mode_host */, + return DoBlasInternal(cublasCtrmv, stream, true /* = pointer_mode_host */, CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), CUDABlasDiagonal(diag), n, GpuComplex(GpuMemory(a)), lda, GpuComplex(GpuMemoryMutable(x)), incx); @@ -1819,8 +1511,7 @@ bool CUDABlas::DoBlasTrmv(Stream *stream, blas::UpperLower uplo, blas::Transpose trans, blas::Diagonal diag, uint64 n, const DeviceMemory> &a, int lda, DeviceMemory> *x, int incx) { - return DoBlasInternal(wrap::cublasZtrmv, stream, - true /* = pointer_mode_host */, + return DoBlasInternal(cublasZtrmv, stream, true /* = pointer_mode_host */, CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), CUDABlasDiagonal(diag), n, GpuComplex(GpuMemory(a)), lda, GpuComplex(GpuMemoryMutable(x)), incx); @@ -1830,8 +1521,7 @@ bool CUDABlas::DoBlasTrsv(Stream *stream, blas::UpperLower uplo, blas::Transpose trans, blas::Diagonal diag, uint64 n, const DeviceMemory &a, int lda, DeviceMemory *x, int incx) { - return DoBlasInternal(wrap::cublasStrsv, stream, - true /* = pointer_mode_host */, + return DoBlasInternal(cublasStrsv, stream, true /* = pointer_mode_host */, CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), CUDABlasDiagonal(diag), n, GpuMemory(a), lda, GpuMemoryMutable(x), incx); @@ -1841,8 +1531,7 @@ bool CUDABlas::DoBlasTrsv(Stream *stream, blas::UpperLower uplo, blas::Transpose trans, blas::Diagonal diag, uint64 n, const DeviceMemory &a, int lda, DeviceMemory *x, int incx) { - return DoBlasInternal(wrap::cublasDtrsv, stream, - true /* = pointer_mode_host */, + return DoBlasInternal(cublasDtrsv, stream, true /* = pointer_mode_host */, CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), CUDABlasDiagonal(diag), n, GpuMemory(a), lda, GpuMemoryMutable(x), incx); @@ -1852,8 +1541,7 @@ bool CUDABlas::DoBlasTrsv(Stream *stream, blas::UpperLower uplo, blas::Transpose trans, blas::Diagonal diag, uint64 n, const DeviceMemory> &a, int lda, DeviceMemory> *x, int incx) { - return DoBlasInternal(wrap::cublasCtrsv, stream, - true /* = pointer_mode_host */, + return DoBlasInternal(cublasCtrsv, stream, true /* = pointer_mode_host */, CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), CUDABlasDiagonal(diag), n, GpuComplex(GpuMemory(a)), lda, GpuComplex(GpuMemoryMutable(x)), incx); @@ -1863,8 +1551,7 @@ bool CUDABlas::DoBlasTrsv(Stream *stream, blas::UpperLower uplo, blas::Transpose trans, blas::Diagonal diag, uint64 n, const DeviceMemory> &a, int lda, DeviceMemory> *x, int incx) { - return DoBlasInternal(wrap::cublasZtrsv, stream, - true /* = pointer_mode_host */, + return DoBlasInternal(cublasZtrsv, stream, true /* = pointer_mode_host */, CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), CUDABlasDiagonal(diag), n, GpuComplex(GpuMemory(a)), lda, GpuComplex(GpuMemoryMutable(x)), incx); @@ -1919,7 +1606,7 @@ bool CUDABlas::DoBlasGemm( #endif return DoBlasInternalImpl( - wrap::cublasSgemmEx, stream, true /* = pointer_mode_host */, + cublasSgemmEx, stream, true /* = pointer_mode_host */, true /* = err_on_failure= */, use_tensor_ops, CUDABlasTranspose(transa), CUDABlasTranspose(transb), m, n, k, &alpha, GpuMemory(a), SE_CUDA_DATA_HALF, lda, GpuMemory(b), SE_CUDA_DATA_HALF, ldb, &beta, @@ -1965,10 +1652,10 @@ bool CUDABlas::DoBlasGemm(Stream *stream, blas::Transpose transa, "precondition violation"; } } - return DoBlasInternal( - wrap::cublasSgemm, stream, true /* = pointer_mode_host */, - CUDABlasTranspose(transa), CUDABlasTranspose(transb), m, n, k, &alpha, - GpuMemory(a), lda, GpuMemory(b), ldb, &beta, GpuMemoryMutable(c), ldc); + return DoBlasInternal(cublasSgemm, stream, true /* = pointer_mode_host */, + CUDABlasTranspose(transa), CUDABlasTranspose(transb), m, + n, k, &alpha, GpuMemory(a), lda, GpuMemory(b), ldb, + &beta, GpuMemoryMutable(c), ldc); } bool CUDABlas::DoBlasGemm(Stream *stream, blas::Transpose transa, @@ -1976,10 +1663,10 @@ bool CUDABlas::DoBlasGemm(Stream *stream, blas::Transpose transa, double alpha, const DeviceMemory &a, int lda, const DeviceMemory &b, int ldb, double beta, DeviceMemory *c, int ldc) { - return DoBlasInternal( - wrap::cublasDgemm, stream, true /* = pointer_mode_host */, - CUDABlasTranspose(transa), CUDABlasTranspose(transb), m, n, k, &alpha, - GpuMemory(a), lda, GpuMemory(b), ldb, &beta, GpuMemoryMutable(c), ldc); + return DoBlasInternal(cublasDgemm, stream, true /* = pointer_mode_host */, + CUDABlasTranspose(transa), CUDABlasTranspose(transb), m, + n, k, &alpha, GpuMemory(a), lda, GpuMemory(b), ldb, + &beta, GpuMemoryMutable(c), ldc); } bool CUDABlas::DoBlasGemm(Stream *stream, blas::Transpose transa, @@ -1989,12 +1676,11 @@ bool CUDABlas::DoBlasGemm(Stream *stream, blas::Transpose transa, const DeviceMemory> &b, int ldb, std::complex beta, DeviceMemory> *c, int ldc) { - return DoBlasInternal( - wrap::cublasCgemm, stream, true /* = pointer_mode_host */, - CUDABlasTranspose(transa), CUDABlasTranspose(transb), m, n, k, - GpuComplex(&alpha), GpuComplex(GpuMemory(a)), lda, - GpuComplex(GpuMemory(b)), ldb, GpuComplex(&beta), - GpuComplex(GpuMemoryMutable(c)), ldc); + return DoBlasInternal(cublasCgemm, stream, true /* = pointer_mode_host */, + CUDABlasTranspose(transa), CUDABlasTranspose(transb), m, + n, k, GpuComplex(&alpha), GpuComplex(GpuMemory(a)), lda, + GpuComplex(GpuMemory(b)), ldb, GpuComplex(&beta), + GpuComplex(GpuMemoryMutable(c)), ldc); } bool CUDABlas::DoBlasGemm(Stream *stream, blas::Transpose transa, @@ -2004,12 +1690,11 @@ bool CUDABlas::DoBlasGemm(Stream *stream, blas::Transpose transa, const DeviceMemory> &b, int ldb, std::complex beta, DeviceMemory> *c, int ldc) { - return DoBlasInternal( - wrap::cublasZgemm, stream, true /* = pointer_mode_host */, - CUDABlasTranspose(transa), CUDABlasTranspose(transb), m, n, k, - GpuComplex(&alpha), GpuComplex(GpuMemory(a)), lda, - GpuComplex(GpuMemory(b)), ldb, GpuComplex(&beta), - GpuComplex(GpuMemoryMutable(c)), ldc); + return DoBlasInternal(cublasZgemm, stream, true /* = pointer_mode_host */, + CUDABlasTranspose(transa), CUDABlasTranspose(transb), m, + n, k, GpuComplex(&alpha), GpuComplex(GpuMemory(a)), lda, + GpuComplex(GpuMemory(b)), ldb, GpuComplex(&beta), + GpuComplex(GpuMemoryMutable(c)), ldc); } bool CUDABlas::DoBlasGemvWithProfiling( @@ -2268,7 +1953,7 @@ bool CUDABlas::DoBlasGemmWithAlgorithmImpl( // essentially reinterpet_cast to __half, which is safe because Eigen::half // inherits from __half. bool result = DoBlasInternalFailureOK( - wrap::cublasGemmEx, stream, /* pointer_mode_host = */ !alpha.is_pointer(), + cublasGemmEx, stream, /* pointer_mode_host = */ !alpha.is_pointer(), CUDABlasTranspose(transa), CUDABlasTranspose(transb), m, n, k, alpha.is_pointer() ? GpuMemory(alpha.pointer()) : &alpha.value(), GpuMemory(a), cuda_in_type, lda, GpuMemory(b), cuda_in_type, ldb, @@ -2329,7 +2014,7 @@ bool CUDABlas::GetBlasGemmAlgorithms( CUBLAS_GEMM_ALGO3_TENSOR_OP, CUBLAS_GEMM_ALGO4_TENSOR_OP, #endif -#if CUDA_VERSION >= 9200 +#if CUDA_VERSION >= 9020 CUBLAS_GEMM_ALGO18, CUBLAS_GEMM_ALGO19, CUBLAS_GEMM_ALGO20, @@ -2542,7 +2227,7 @@ port::Status CUDABlas::DoBlasGemmBatchedInternal( reinterpret_cast(const_cast(GpuMemory(c))); bool ok; ok = DoBlasInternalImpl( - wrap::cublasGemmBatchedEx, stream, true /* = pointer_mode_host */, + cublasGemmBatchedEx, stream, true /* = pointer_mode_host */, true /* = err_on_failure */, use_tensor_ops, CUDABlasTranspose(transa), CUDABlasTranspose(transb), m, n, k, &alpha, a_void_ptrs, data_type, lda, b_void_ptrs, data_type, ldb, &beta, c_void_ptrs, data_type, ldc, @@ -2594,8 +2279,8 @@ bool CUDABlas::DoBlasGemmBatched( // Note: The func passed here (cublasSgemmBatched) is not actually called, // due to special handling of fp16 inside DoBlasGemmBatchedInternal. port::Status status = DoBlasGemmBatchedInternal( - wrap::cublasSgemmBatched, stream, transa, transb, m, n, k, alpha, a_array, - lda, b_array, ldb, beta, c_array, ldc, batch_count, scratch_allocator); + cublasSgemmBatched, stream, transa, transb, m, n, k, alpha, a_array, lda, + b_array, ldb, beta, c_array, ldc, batch_count, scratch_allocator); if (!status.ok()) { LOG(ERROR) << status; } @@ -2610,8 +2295,8 @@ bool CUDABlas::DoBlasGemmBatched( const port::ArraySlice *> &c_array, int ldc, int batch_count, ScratchAllocator *scratch_allocator) { port::Status status = DoBlasGemmBatchedInternal( - wrap::cublasSgemmBatched, stream, transa, transb, m, n, k, alpha, a_array, - lda, b_array, ldb, beta, c_array, ldc, batch_count, scratch_allocator); + cublasSgemmBatched, stream, transa, transb, m, n, k, alpha, a_array, lda, + b_array, ldb, beta, c_array, ldc, batch_count, scratch_allocator); if (!status.ok()) { LOG(ERROR) << status; } @@ -2626,8 +2311,8 @@ bool CUDABlas::DoBlasGemmBatched( double beta, const port::ArraySlice *> &c_array, int ldc, int batch_count, ScratchAllocator *scratch_allocator) { port::Status status = DoBlasGemmBatchedInternal( - wrap::cublasDgemmBatched, stream, transa, transb, m, n, k, alpha, a_array, - lda, b_array, ldb, beta, c_array, ldc, batch_count, scratch_allocator); + cublasDgemmBatched, stream, transa, transb, m, n, k, alpha, a_array, lda, + b_array, ldb, beta, c_array, ldc, batch_count, scratch_allocator); if (!status.ok()) { LOG(ERROR) << status; } @@ -2644,8 +2329,8 @@ bool CUDABlas::DoBlasGemmBatched( const port::ArraySlice> *> &c_array, int ldc, int batch_count, ScratchAllocator *scratch_allocator) { port::Status status = DoBlasGemmBatchedInternal( - wrap::cublasCgemmBatched, stream, transa, transb, m, n, k, alpha, a_array, - lda, b_array, ldb, beta, c_array, ldc, batch_count, scratch_allocator); + cublasCgemmBatched, stream, transa, transb, m, n, k, alpha, a_array, lda, + b_array, ldb, beta, c_array, ldc, batch_count, scratch_allocator); if (!status.ok()) { LOG(ERROR) << status; } @@ -2662,8 +2347,8 @@ bool CUDABlas::DoBlasGemmBatched( const port::ArraySlice> *> &c_array, int ldc, int batch_count, ScratchAllocator *scratch_allocator) { port::Status status = DoBlasGemmBatchedInternal( - wrap::cublasZgemmBatched, stream, transa, transb, m, n, k, alpha, a_array, - lda, b_array, ldb, beta, c_array, ldc, batch_count, scratch_allocator); + cublasZgemmBatched, stream, transa, transb, m, n, k, alpha, a_array, lda, + b_array, ldb, beta, c_array, ldc, batch_count, scratch_allocator); if (!status.ok()) { LOG(ERROR) << status; } @@ -2690,12 +2375,12 @@ bool CUDABlas::DoBlasGemmStridedBatched( cublasGemmAlgo_t algo = (use_tensor_ops ? CUBLAS_GEMM_DFALT_TENSOR_OP : CUBLAS_GEMM_DFALT); bool ok = DoBlasInternalImpl( - wrap::cublasGemmStridedBatchedEx, stream, - true /* = pointer_mode_host */, true /* = err_on_failure */, - use_tensor_ops, CUDABlasTranspose(transa), CUDABlasTranspose(transb), - m, n, k, &alpha, GpuMemory(a), CUDA_R_16F, lda, stride_a, - GpuMemory(b), CUDA_R_16F, ldb, stride_b, &beta, GpuMemoryMutable(c), - CUDA_R_16F, ldc, stride_c, batch_count, CUDA_R_32F, algo); + cublasGemmStridedBatchedEx, stream, true /* = pointer_mode_host */, + true /* = err_on_failure */, use_tensor_ops, + CUDABlasTranspose(transa), CUDABlasTranspose(transb), m, n, k, &alpha, + GpuMemory(a), CUDA_R_16F, lda, stride_a, GpuMemory(b), CUDA_R_16F, + ldb, stride_b, &beta, GpuMemoryMutable(c), CUDA_R_16F, ldc, stride_c, + batch_count, CUDA_R_32F, algo); if (ok) { return true; } @@ -2714,7 +2399,7 @@ bool CUDABlas::DoBlasGemmStridedBatched( auto *c_matrix = reinterpret_cast<__half *>(GpuMemoryMutable(c) + batch * stride_c); bool ok = DoBlasInternalImpl( - wrap::cublasSgemmEx, stream, true /* = pointer_mode_host */, + cublasSgemmEx, stream, true /* = pointer_mode_host */, true /* = err_on_failure= */, use_tensor_ops, CUDABlasTranspose(transa), CUDABlasTranspose(transb), m, n, k, &alpha, a_matrix, SE_CUDA_DATA_HALF, lda, b_matrix, SE_CUDA_DATA_HALF, ldb, &beta, c_matrix, @@ -2734,7 +2419,7 @@ bool CUDABlas::DoBlasGemmStridedBatched( float beta, DeviceMemory *c, int ldc, int64 stride_c, int batch_count) { return DoBlasInternal( - wrap::cublasSgemmStridedBatched, stream, true /* = pointer_mode_host */, + cublasSgemmStridedBatched, stream, true /* = pointer_mode_host */, CUDABlasTranspose(transa), CUDABlasTranspose(transb), m, n, k, &alpha, GpuMemory(a), lda, stride_a, GpuMemory(b), ldb, stride_b, &beta, GpuMemoryMutable(c), ldc, stride_c, batch_count); @@ -2747,7 +2432,7 @@ bool CUDABlas::DoBlasGemmStridedBatched( double beta, DeviceMemory *c, int ldc, int64 stride_c, int batch_count) { return DoBlasInternal( - wrap::cublasDgemmStridedBatched, stream, true /* = pointer_mode_host */, + cublasDgemmStridedBatched, stream, true /* = pointer_mode_host */, CUDABlasTranspose(transa), CUDABlasTranspose(transb), m, n, k, &alpha, GpuMemory(a), lda, stride_a, GpuMemory(b), ldb, stride_b, &beta, GpuMemoryMutable(c), ldc, stride_c, batch_count); @@ -2761,7 +2446,7 @@ bool CUDABlas::DoBlasGemmStridedBatched( std::complex beta, DeviceMemory> *c, int ldc, int64 stride_c, int batch_count) { return DoBlasInternal( - wrap::cublasCgemmStridedBatched, stream, true /* = pointer_mode_host */, + cublasCgemmStridedBatched, stream, true /* = pointer_mode_host */, CUDABlasTranspose(transa), CUDABlasTranspose(transb), m, n, k, GpuComplex(&alpha), GpuComplex(GpuMemory(a)), lda, stride_a, GpuComplex(GpuMemory(b)), ldb, stride_b, GpuComplex(&beta), @@ -2776,7 +2461,7 @@ bool CUDABlas::DoBlasGemmStridedBatched( std::complex beta, DeviceMemory> *c, int ldc, int64 stride_c, int batch_count) { return DoBlasInternal( - wrap::cublasZgemmStridedBatched, stream, true /* = pointer_mode_host */, + cublasZgemmStridedBatched, stream, true /* = pointer_mode_host */, CUDABlasTranspose(transa), CUDABlasTranspose(transb), m, n, k, GpuComplex(&alpha), GpuComplex(GpuMemory(a)), lda, stride_a, GpuComplex(GpuMemory(b)), ldb, stride_b, GpuComplex(&beta), @@ -2790,11 +2475,11 @@ bool CUDABlas::DoBlasHemm(Stream *stream, blas::Side side, const DeviceMemory> &b, int ldb, std::complex beta, DeviceMemory> *c, int ldc) { - return DoBlasInternal( - wrap::cublasChemm, stream, true /* = pointer_mode_host */, - CUDABlasSide(side), CUDABlasUpperLower(uplo), m, n, GpuComplex(&alpha), - GpuComplex(GpuMemory(a)), lda, GpuComplex(GpuMemory(b)), ldb, - GpuComplex(&beta), GpuComplex(GpuMemoryMutable(c)), ldc); + return DoBlasInternal(cublasChemm, stream, true /* = pointer_mode_host */, + CUDABlasSide(side), CUDABlasUpperLower(uplo), m, n, + GpuComplex(&alpha), GpuComplex(GpuMemory(a)), lda, + GpuComplex(GpuMemory(b)), ldb, GpuComplex(&beta), + GpuComplex(GpuMemoryMutable(c)), ldc); } bool CUDABlas::DoBlasHemm(Stream *stream, blas::Side side, @@ -2804,11 +2489,11 @@ bool CUDABlas::DoBlasHemm(Stream *stream, blas::Side side, const DeviceMemory> &b, int ldb, std::complex beta, DeviceMemory> *c, int ldc) { - return DoBlasInternal( - wrap::cublasZhemm, stream, true /* = pointer_mode_host */, - CUDABlasSide(side), CUDABlasUpperLower(uplo), m, n, GpuComplex(&alpha), - GpuComplex(GpuMemory(a)), lda, GpuComplex(GpuMemory(b)), ldb, - GpuComplex(&beta), GpuComplex(GpuMemoryMutable(c)), ldc); + return DoBlasInternal(cublasZhemm, stream, true /* = pointer_mode_host */, + CUDABlasSide(side), CUDABlasUpperLower(uplo), m, n, + GpuComplex(&alpha), GpuComplex(GpuMemory(a)), lda, + GpuComplex(GpuMemory(b)), ldb, GpuComplex(&beta), + GpuComplex(GpuMemoryMutable(c)), ldc); } bool CUDABlas::DoBlasHerk(Stream *stream, blas::UpperLower uplo, @@ -2817,8 +2502,7 @@ bool CUDABlas::DoBlasHerk(Stream *stream, blas::UpperLower uplo, const DeviceMemory> &a, int lda, float beta, DeviceMemory> *c, int ldc) { - return DoBlasInternal(wrap::cublasCherk, stream, - true /* = pointer_mode_host */, + return DoBlasInternal(cublasCherk, stream, true /* = pointer_mode_host */, CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), n, k, GpuComplex(&alpha), GpuComplex(GpuMemory(a)), lda, &beta, GpuComplex(GpuMemoryMutable(c)), ldc); @@ -2830,8 +2514,7 @@ bool CUDABlas::DoBlasHerk(Stream *stream, blas::UpperLower uplo, const DeviceMemory> &a, int lda, double beta, DeviceMemory> *c, int ldc) { - return DoBlasInternal(wrap::cublasZherk, stream, - true /* = pointer_mode_host */, + return DoBlasInternal(cublasZherk, stream, true /* = pointer_mode_host */, CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), n, k, GpuComplex(&alpha), GpuComplex(GpuMemory(a)), lda, &beta, GpuComplex(GpuMemoryMutable(c)), ldc); @@ -2844,8 +2527,7 @@ bool CUDABlas::DoBlasHer2k(Stream *stream, blas::UpperLower uplo, const DeviceMemory> &b, int ldb, float beta, DeviceMemory> *c, int ldc) { - return DoBlasInternal(wrap::cublasCher2k, stream, - true /* = pointer_mode_host */, + return DoBlasInternal(cublasCher2k, stream, true /* = pointer_mode_host */, CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), n, k, GpuComplex(&alpha), GpuComplex(GpuMemory(a)), lda, GpuComplex(GpuMemory(b)), ldb, &beta, @@ -2859,8 +2541,7 @@ bool CUDABlas::DoBlasHer2k(Stream *stream, blas::UpperLower uplo, const DeviceMemory> &b, int ldb, double beta, DeviceMemory> *c, int ldc) { - return DoBlasInternal(wrap::cublasZher2k, stream, - true /* = pointer_mode_host */, + return DoBlasInternal(cublasZher2k, stream, true /* = pointer_mode_host */, CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), n, k, GpuComplex(&alpha), GpuComplex(GpuMemory(a)), lda, GpuComplex(GpuMemory(b)), ldb, &beta, @@ -2872,10 +2553,10 @@ bool CUDABlas::DoBlasSymm(Stream *stream, blas::Side side, float alpha, const DeviceMemory &a, int lda, const DeviceMemory &b, int ldb, float beta, DeviceMemory *c, int ldc) { - return DoBlasInternal( - wrap::cublasSsymm, stream, true /* = pointer_mode_host */, - CUDABlasSide(side), CUDABlasUpperLower(uplo), m, n, &alpha, GpuMemory(a), - lda, GpuMemory(b), ldb, &beta, GpuMemoryMutable(c), ldc); + return DoBlasInternal(cublasSsymm, stream, true /* = pointer_mode_host */, + CUDABlasSide(side), CUDABlasUpperLower(uplo), m, n, + &alpha, GpuMemory(a), lda, GpuMemory(b), ldb, &beta, + GpuMemoryMutable(c), ldc); } bool CUDABlas::DoBlasSymm(Stream *stream, blas::Side side, @@ -2883,10 +2564,10 @@ bool CUDABlas::DoBlasSymm(Stream *stream, blas::Side side, double alpha, const DeviceMemory &a, int lda, const DeviceMemory &b, int ldb, double beta, DeviceMemory *c, int ldc) { - return DoBlasInternal( - wrap::cublasDsymm, stream, true /* = pointer_mode_host */, - CUDABlasSide(side), CUDABlasUpperLower(uplo), m, n, &alpha, GpuMemory(a), - lda, GpuMemory(b), ldb, &beta, GpuMemoryMutable(c), ldc); + return DoBlasInternal(cublasDsymm, stream, true /* = pointer_mode_host */, + CUDABlasSide(side), CUDABlasUpperLower(uplo), m, n, + &alpha, GpuMemory(a), lda, GpuMemory(b), ldb, &beta, + GpuMemoryMutable(c), ldc); } bool CUDABlas::DoBlasSymm(Stream *stream, blas::Side side, @@ -2896,11 +2577,11 @@ bool CUDABlas::DoBlasSymm(Stream *stream, blas::Side side, const DeviceMemory> &b, int ldb, std::complex beta, DeviceMemory> *c, int ldc) { - return DoBlasInternal( - wrap::cublasCsymm, stream, true /* = pointer_mode_host */, - CUDABlasSide(side), CUDABlasUpperLower(uplo), m, n, GpuComplex(&alpha), - GpuComplex(GpuMemory(a)), lda, GpuComplex(GpuMemory(b)), ldb, - GpuComplex(&beta), GpuComplex(GpuMemoryMutable(c)), ldc); + return DoBlasInternal(cublasCsymm, stream, true /* = pointer_mode_host */, + CUDABlasSide(side), CUDABlasUpperLower(uplo), m, n, + GpuComplex(&alpha), GpuComplex(GpuMemory(a)), lda, + GpuComplex(GpuMemory(b)), ldb, GpuComplex(&beta), + GpuComplex(GpuMemoryMutable(c)), ldc); } bool CUDABlas::DoBlasSymm(Stream *stream, blas::Side side, @@ -2910,31 +2591,31 @@ bool CUDABlas::DoBlasSymm(Stream *stream, blas::Side side, const DeviceMemory> &b, int ldb, std::complex beta, DeviceMemory> *c, int ldc) { - return DoBlasInternal( - wrap::cublasZsymm, stream, true /* = pointer_mode_host */, - CUDABlasSide(side), CUDABlasUpperLower(uplo), m, n, GpuComplex(&alpha), - GpuComplex(GpuMemory(a)), lda, GpuComplex(GpuMemory(b)), ldb, - GpuComplex(&beta), GpuComplex(GpuMemoryMutable(c)), ldc); + return DoBlasInternal(cublasZsymm, stream, true /* = pointer_mode_host */, + CUDABlasSide(side), CUDABlasUpperLower(uplo), m, n, + GpuComplex(&alpha), GpuComplex(GpuMemory(a)), lda, + GpuComplex(GpuMemory(b)), ldb, GpuComplex(&beta), + GpuComplex(GpuMemoryMutable(c)), ldc); } bool CUDABlas::DoBlasSyrk(Stream *stream, blas::UpperLower uplo, blas::Transpose trans, uint64 n, uint64 k, float alpha, const DeviceMemory &a, int lda, float beta, DeviceMemory *c, int ldc) { - return DoBlasInternal( - wrap::cublasSsyrk, stream, true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), n, k, &alpha, - GpuMemory(a), lda, &beta, GpuMemoryMutable(c), ldc); + return DoBlasInternal(cublasSsyrk, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), n, + k, &alpha, GpuMemory(a), lda, &beta, + GpuMemoryMutable(c), ldc); } bool CUDABlas::DoBlasSyrk(Stream *stream, blas::UpperLower uplo, blas::Transpose trans, uint64 n, uint64 k, double alpha, const DeviceMemory &a, int lda, double beta, DeviceMemory *c, int ldc) { - return DoBlasInternal( - wrap::cublasDsyrk, stream, true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), n, k, &alpha, - GpuMemory(a), lda, &beta, GpuMemoryMutable(c), ldc); + return DoBlasInternal(cublasDsyrk, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), n, + k, &alpha, GpuMemory(a), lda, &beta, + GpuMemoryMutable(c), ldc); } bool CUDABlas::DoBlasSyrk(Stream *stream, blas::UpperLower uplo, @@ -2943,11 +2624,11 @@ bool CUDABlas::DoBlasSyrk(Stream *stream, blas::UpperLower uplo, const DeviceMemory> &a, int lda, std::complex beta, DeviceMemory> *c, int ldc) { - return DoBlasInternal( - wrap::cublasCsyrk, stream, true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), n, k, - GpuComplex(&alpha), GpuComplex(GpuMemory(a)), lda, GpuComplex(&beta), - GpuComplex(GpuMemoryMutable(c)), ldc); + return DoBlasInternal(cublasCsyrk, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), n, + k, GpuComplex(&alpha), GpuComplex(GpuMemory(a)), lda, + GpuComplex(&beta), GpuComplex(GpuMemoryMutable(c)), + ldc); } bool CUDABlas::DoBlasSyrk(Stream *stream, blas::UpperLower uplo, @@ -2956,11 +2637,11 @@ bool CUDABlas::DoBlasSyrk(Stream *stream, blas::UpperLower uplo, const DeviceMemory> &a, int lda, std::complex beta, DeviceMemory> *c, int ldc) { - return DoBlasInternal( - wrap::cublasZsyrk, stream, true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), n, k, - GpuComplex(&alpha), GpuComplex(GpuMemory(a)), lda, GpuComplex(&beta), - GpuComplex(GpuMemoryMutable(c)), ldc); + return DoBlasInternal(cublasZsyrk, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), n, + k, GpuComplex(&alpha), GpuComplex(GpuMemory(a)), lda, + GpuComplex(&beta), GpuComplex(GpuMemoryMutable(c)), + ldc); } bool CUDABlas::DoBlasSyr2k(Stream *stream, blas::UpperLower uplo, @@ -2968,10 +2649,10 @@ bool CUDABlas::DoBlasSyr2k(Stream *stream, blas::UpperLower uplo, float alpha, const DeviceMemory &a, int lda, const DeviceMemory &b, int ldb, float beta, DeviceMemory *c, int ldc) { - return DoBlasInternal( - wrap::cublasSsyr2k, stream, true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), n, k, &alpha, - GpuMemory(a), lda, GpuMemory(b), ldb, &beta, GpuMemoryMutable(c), ldc); + return DoBlasInternal(cublasSsyr2k, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), n, + k, &alpha, GpuMemory(a), lda, GpuMemory(b), ldb, &beta, + GpuMemoryMutable(c), ldc); } bool CUDABlas::DoBlasSyr2k(Stream *stream, blas::UpperLower uplo, @@ -2979,10 +2660,10 @@ bool CUDABlas::DoBlasSyr2k(Stream *stream, blas::UpperLower uplo, double alpha, const DeviceMemory &a, int lda, const DeviceMemory &b, int ldb, double beta, DeviceMemory *c, int ldc) { - return DoBlasInternal( - wrap::cublasDsyr2k, stream, true /* = pointer_mode_host */, - CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), n, k, &alpha, - GpuMemory(a), lda, GpuMemory(b), ldb, &beta, GpuMemoryMutable(c), ldc); + return DoBlasInternal(cublasDsyr2k, stream, true /* = pointer_mode_host */, + CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), n, + k, &alpha, GpuMemory(a), lda, GpuMemory(b), ldb, &beta, + GpuMemoryMutable(c), ldc); } bool CUDABlas::DoBlasSyr2k(Stream *stream, blas::UpperLower uplo, @@ -2992,8 +2673,7 @@ bool CUDABlas::DoBlasSyr2k(Stream *stream, blas::UpperLower uplo, const DeviceMemory> &b, int ldb, std::complex beta, DeviceMemory> *c, int ldc) { - return DoBlasInternal(wrap::cublasCsyr2k, stream, - true /* = pointer_mode_host */, + return DoBlasInternal(cublasCsyr2k, stream, true /* = pointer_mode_host */, CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), n, k, GpuComplex(&alpha), GpuComplex(GpuMemory(a)), lda, GpuComplex(GpuMemory(b)), ldb, GpuComplex(&beta), @@ -3007,8 +2687,7 @@ bool CUDABlas::DoBlasSyr2k(Stream *stream, blas::UpperLower uplo, const DeviceMemory> &b, int ldb, std::complex beta, DeviceMemory> *c, int ldc) { - return DoBlasInternal(wrap::cublasZsyr2k, stream, - true /* = pointer_mode_host */, + return DoBlasInternal(cublasZsyr2k, stream, true /* = pointer_mode_host */, CUDABlasUpperLower(uplo), CUDABlasTranspose(trans), n, k, GpuComplex(&alpha), GpuComplex(GpuMemory(a)), lda, GpuComplex(GpuMemory(b)), ldb, GpuComplex(&beta), @@ -3020,11 +2699,11 @@ bool CUDABlas::DoBlasTrmm(Stream *stream, blas::Side side, blas::Diagonal diag, uint64 m, uint64 n, float alpha, const DeviceMemory &a, int lda, DeviceMemory *b, int ldb) { - return DoBlasInternal( - wrap::cublasStrmm, stream, true /* = pointer_mode_host */, - CUDABlasSide(side), CUDABlasUpperLower(uplo), CUDABlasTranspose(transa), - CUDABlasDiagonal(diag), m, n, &alpha, GpuMemory(a), lda, - GpuMemoryMutable(b), ldb, GpuMemoryMutable(b), ldb); + return DoBlasInternal(cublasStrmm, stream, true /* = pointer_mode_host */, + CUDABlasSide(side), CUDABlasUpperLower(uplo), + CUDABlasTranspose(transa), CUDABlasDiagonal(diag), m, n, + &alpha, GpuMemory(a), lda, GpuMemoryMutable(b), ldb, + GpuMemoryMutable(b), ldb); } bool CUDABlas::DoBlasTrmm(Stream *stream, blas::Side side, @@ -3032,11 +2711,11 @@ bool CUDABlas::DoBlasTrmm(Stream *stream, blas::Side side, blas::Diagonal diag, uint64 m, uint64 n, double alpha, const DeviceMemory &a, int lda, DeviceMemory *b, int ldb) { - return DoBlasInternal( - wrap::cublasDtrmm, stream, true /* = pointer_mode_host */, - CUDABlasSide(side), CUDABlasUpperLower(uplo), CUDABlasTranspose(transa), - CUDABlasDiagonal(diag), m, n, &alpha, GpuMemory(a), lda, - GpuMemoryMutable(b), ldb, GpuMemoryMutable(b), ldb); + return DoBlasInternal(cublasDtrmm, stream, true /* = pointer_mode_host */, + CUDABlasSide(side), CUDABlasUpperLower(uplo), + CUDABlasTranspose(transa), CUDABlasDiagonal(diag), m, n, + &alpha, GpuMemory(a), lda, GpuMemoryMutable(b), ldb, + GpuMemoryMutable(b), ldb); } bool CUDABlas::DoBlasTrmm(Stream *stream, blas::Side side, @@ -3045,12 +2724,12 @@ bool CUDABlas::DoBlasTrmm(Stream *stream, blas::Side side, std::complex alpha, const DeviceMemory> &a, int lda, DeviceMemory> *b, int ldb) { - return DoBlasInternal( - wrap::cublasCtrmm, stream, true /* = pointer_mode_host */, - CUDABlasSide(side), CUDABlasUpperLower(uplo), CUDABlasTranspose(transa), - CUDABlasDiagonal(diag), m, n, GpuComplex(&alpha), - GpuComplex(GpuMemory(a)), lda, GpuComplex(GpuMemoryMutable(b)), ldb, - GpuComplex(GpuMemoryMutable(b)), ldb); + return DoBlasInternal(cublasCtrmm, stream, true /* = pointer_mode_host */, + CUDABlasSide(side), CUDABlasUpperLower(uplo), + CUDABlasTranspose(transa), CUDABlasDiagonal(diag), m, n, + GpuComplex(&alpha), GpuComplex(GpuMemory(a)), lda, + GpuComplex(GpuMemoryMutable(b)), ldb, + GpuComplex(GpuMemoryMutable(b)), ldb); } bool CUDABlas::DoBlasTrmm(Stream *stream, blas::Side side, @@ -3059,12 +2738,12 @@ bool CUDABlas::DoBlasTrmm(Stream *stream, blas::Side side, std::complex alpha, const DeviceMemory> &a, int lda, DeviceMemory> *b, int ldb) { - return DoBlasInternal( - wrap::cublasZtrmm, stream, true /* = pointer_mode_host */, - CUDABlasSide(side), CUDABlasUpperLower(uplo), CUDABlasTranspose(transa), - CUDABlasDiagonal(diag), m, n, GpuComplex(&alpha), - GpuComplex(GpuMemory(a)), lda, GpuComplex(GpuMemoryMutable(b)), ldb, - GpuComplex(GpuMemoryMutable(b)), ldb); + return DoBlasInternal(cublasZtrmm, stream, true /* = pointer_mode_host */, + CUDABlasSide(side), CUDABlasUpperLower(uplo), + CUDABlasTranspose(transa), CUDABlasDiagonal(diag), m, n, + GpuComplex(&alpha), GpuComplex(GpuMemory(a)), lda, + GpuComplex(GpuMemoryMutable(b)), ldb, + GpuComplex(GpuMemoryMutable(b)), ldb); } bool CUDABlas::DoBlasTrsm(Stream *stream, blas::Side side, @@ -3072,11 +2751,10 @@ bool CUDABlas::DoBlasTrsm(Stream *stream, blas::Side side, blas::Diagonal diag, uint64 m, uint64 n, float alpha, const DeviceMemory &a, int lda, DeviceMemory *b, int ldb) { - return DoBlasInternal(wrap::cublasStrsm, stream, - true /* = pointer_mode_host */, CUDABlasSide(side), - CUDABlasUpperLower(uplo), CUDABlasTranspose(transa), - CUDABlasDiagonal(diag), m, n, &alpha, GpuMemory(a), - lda, GpuMemoryMutable(b), ldb); + return DoBlasInternal(cublasStrsm, stream, true /* = pointer_mode_host */, + CUDABlasSide(side), CUDABlasUpperLower(uplo), + CUDABlasTranspose(transa), CUDABlasDiagonal(diag), m, n, + &alpha, GpuMemory(a), lda, GpuMemoryMutable(b), ldb); } bool CUDABlas::DoBlasTrsm(Stream *stream, blas::Side side, @@ -3084,11 +2762,10 @@ bool CUDABlas::DoBlasTrsm(Stream *stream, blas::Side side, blas::Diagonal diag, uint64 m, uint64 n, double alpha, const DeviceMemory &a, int lda, DeviceMemory *b, int ldb) { - return DoBlasInternal(wrap::cublasDtrsm, stream, - true /* = pointer_mode_host */, CUDABlasSide(side), - CUDABlasUpperLower(uplo), CUDABlasTranspose(transa), - CUDABlasDiagonal(diag), m, n, &alpha, GpuMemory(a), - lda, GpuMemoryMutable(b), ldb); + return DoBlasInternal(cublasDtrsm, stream, true /* = pointer_mode_host */, + CUDABlasSide(side), CUDABlasUpperLower(uplo), + CUDABlasTranspose(transa), CUDABlasDiagonal(diag), m, n, + &alpha, GpuMemory(a), lda, GpuMemoryMutable(b), ldb); } bool CUDABlas::DoBlasTrsm(Stream *stream, blas::Side side, @@ -3097,11 +2774,11 @@ bool CUDABlas::DoBlasTrsm(Stream *stream, blas::Side side, std::complex alpha, const DeviceMemory> &a, int lda, DeviceMemory> *b, int ldb) { - return DoBlasInternal( - wrap::cublasCtrsm, stream, true /* = pointer_mode_host */, - CUDABlasSide(side), CUDABlasUpperLower(uplo), CUDABlasTranspose(transa), - CUDABlasDiagonal(diag), m, n, GpuComplex(&alpha), - GpuComplex(GpuMemory(a)), lda, GpuComplex(GpuMemoryMutable(b)), ldb); + return DoBlasInternal(cublasCtrsm, stream, true /* = pointer_mode_host */, + CUDABlasSide(side), CUDABlasUpperLower(uplo), + CUDABlasTranspose(transa), CUDABlasDiagonal(diag), m, n, + GpuComplex(&alpha), GpuComplex(GpuMemory(a)), lda, + GpuComplex(GpuMemoryMutable(b)), ldb); } bool CUDABlas::DoBlasTrsm(Stream *stream, blas::Side side, @@ -3110,11 +2787,11 @@ bool CUDABlas::DoBlasTrsm(Stream *stream, blas::Side side, std::complex alpha, const DeviceMemory> &a, int lda, DeviceMemory> *b, int ldb) { - return DoBlasInternal( - wrap::cublasZtrsm, stream, true /* = pointer_mode_host */, - CUDABlasSide(side), CUDABlasUpperLower(uplo), CUDABlasTranspose(transa), - CUDABlasDiagonal(diag), m, n, GpuComplex(&alpha), - GpuComplex(GpuMemory(a)), lda, GpuComplex(GpuMemoryMutable(b)), ldb); + return DoBlasInternal(cublasZtrsm, stream, true /* = pointer_mode_host */, + CUDABlasSide(side), CUDABlasUpperLower(uplo), + CUDABlasTranspose(transa), CUDABlasDiagonal(diag), m, n, + GpuComplex(&alpha), GpuComplex(GpuMemory(a)), lda, + GpuComplex(GpuMemoryMutable(b)), ldb); } } // namespace gpu diff --git a/tensorflow/stream_executor/cuda/cuda_dnn.cc b/tensorflow/stream_executor/cuda/cuda_dnn.cc index bae71b4e8d..7e80367ef9 100644 --- a/tensorflow/stream_executor/cuda/cuda_dnn.cc +++ b/tensorflow/stream_executor/cuda/cuda_dnn.cc @@ -40,7 +40,6 @@ limitations under the License. #include "tensorflow/stream_executor/lib/mathutil.h" #include "tensorflow/stream_executor/lib/threadpool.h" #include "tensorflow/stream_executor/logging.pb.h" -#include "tensorflow/stream_executor/platform/dso_loader.h" #include "tensorflow/stream_executor/platform/logging.h" #include "tensorflow/stream_executor/plugin_registry.h" #include "tensorflow/stream_executor/scratch_allocator.h" @@ -153,156 +152,6 @@ class CudnnHandle { } // namespace -#ifdef PLATFORM_GOOGLE -// This macro wraps a global identifier, given by __name, in a callable -// structure that loads the DLL symbol out of the DSO handle in a thread-safe -// manner on first use. This dynamic loading technique is used to avoid DSO -// dependencies on vendor libraries which may or may not be available in the -// deployed binary environment. -#define STREAM_EXECUTOR_CUDNN_WRAP(__name) \ - struct WrapperShim__##__name { \ - template \ - cudnnStatus_t operator()(Args... args) { \ - return ::__name(args...); \ - } \ - } __name; - -#else -#define STREAM_EXECUTOR_CUDNN_WRAP(__name) \ - struct DynLoadShim__##__name { \ - static const char* kName; \ - using FuncPtrT = std::add_pointer::type; \ - static void* GetDsoHandle() { \ - auto s = internal::CachedDsoLoader::GetCudnnDsoHandle(); \ - return s.ValueOrDie(); \ - } \ - static FuncPtrT LoadOrDie() { \ - void* f; \ - auto s = port::Env::Default()->GetSymbolFromLibrary(GetDsoHandle(), \ - kName, &f); \ - CHECK(s.ok()) << "could not find " << kName \ - << " in cudnn DSO; dlerror: " << s.error_message(); \ - return reinterpret_cast(f); \ - } \ - static FuncPtrT DynLoad() { \ - static FuncPtrT f = LoadOrDie(); \ - return f; \ - } \ - template \ - cudnnStatus_t operator()(Args... args) { \ - return DynLoad()(args...); \ - } \ - } __name; \ - const char* DynLoadShim__##__name::kName = #__name; -#endif - -// clang-format off -#define CUDNN_ROUTINE_EACH_V7000_UNDER(__macro) \ - __macro(cudnnActivationForward) \ - __macro(cudnnAddTensor) \ - __macro(cudnnBatchNormalizationBackward) \ - __macro(cudnnBatchNormalizationForwardInference) \ - __macro(cudnnBatchNormalizationForwardTraining) \ - __macro(cudnnConvolutionBackwardBias) \ - __macro(cudnnConvolutionBackwardData) \ - __macro(cudnnConvolutionBackwardFilter) \ - __macro(cudnnConvolutionBiasActivationForward) \ - __macro(cudnnConvolutionForward) \ - __macro(cudnnCreate) \ - __macro(cudnnCreateActivationDescriptor) \ - __macro(cudnnCreateConvolutionDescriptor) \ - __macro(cudnnCreateDropoutDescriptor) \ - __macro(cudnnCreateFilterDescriptor) \ - __macro(cudnnCreateLRNDescriptor) \ - __macro(cudnnCreatePersistentRNNPlan) \ - __macro(cudnnCreatePoolingDescriptor) \ - __macro(cudnnCreateRNNDescriptor) \ - __macro(cudnnCreateTensorDescriptor) \ - __macro(cudnnDestroy) \ - __macro(cudnnDestroyActivationDescriptor) \ - __macro(cudnnDestroyConvolutionDescriptor) \ - __macro(cudnnDestroyDropoutDescriptor) \ - __macro(cudnnDestroyFilterDescriptor) \ - __macro(cudnnDestroyLRNDescriptor) \ - __macro(cudnnDestroyPersistentRNNPlan) \ - __macro(cudnnDestroyPoolingDescriptor) \ - __macro(cudnnDestroyRNNDescriptor) \ - __macro(cudnnDestroyTensorDescriptor) \ - __macro(cudnnDropoutGetStatesSize) \ - __macro(cudnnGetActivationDescriptor) \ - __macro(cudnnGetConvolutionBackwardDataAlgorithm) \ - __macro(cudnnGetConvolutionBackwardDataWorkspaceSize) \ - __macro(cudnnGetConvolutionBackwardFilterAlgorithm) \ - __macro(cudnnGetConvolutionBackwardFilterWorkspaceSize) \ - __macro(cudnnGetConvolutionForwardAlgorithm) \ - __macro(cudnnGetConvolutionForwardWorkspaceSize) \ - __macro(cudnnGetConvolutionNdDescriptor) \ - __macro(cudnnGetConvolutionNdForwardOutputDim) \ - __macro(cudnnGetFilterNdDescriptor) \ - __macro(cudnnGetProperty) \ - __macro(cudnnGetRNNLinLayerBiasParams) \ - __macro(cudnnGetRNNLinLayerMatrixParams) \ - __macro(cudnnGetRNNParamsSize) \ - __macro(cudnnGetRNNTrainingReserveSize) \ - __macro(cudnnGetRNNWorkspaceSize) \ - __macro(cudnnLRNCrossChannelBackward) \ - __macro(cudnnLRNCrossChannelForward) \ - __macro(cudnnPoolingBackward) \ - __macro(cudnnPoolingForward) \ - __macro(cudnnRNNBackwardData) \ - __macro(cudnnRNNBackwardWeights) \ - __macro(cudnnRNNForwardInference) \ - __macro(cudnnRNNForwardTraining) \ - __macro(cudnnSetActivationDescriptor) \ - __macro(cudnnSetConvolutionNdDescriptor) \ - __macro(cudnnSetDropoutDescriptor) \ - __macro(cudnnSetFilterNdDescriptor) \ - __macro(cudnnSetLRNDescriptor) \ - __macro(cudnnSetPersistentRNNPlan) \ - __macro(cudnnSetPoolingNdDescriptor) \ - __macro(cudnnSetRNNDescriptor) \ - __macro(cudnnSetRNNDescriptor_v6) \ - __macro(cudnnSetStream) \ - __macro(cudnnSetTensor4dDescriptor) \ - __macro(cudnnSetTensorNdDescriptor) \ - __macro(cudnnTransformTensor) - -// clang-format on - -CUDNN_ROUTINE_EACH_V7000_UNDER(STREAM_EXECUTOR_CUDNN_WRAP) -#undef CUDNN_ROUTINE_EACH_V7000_UNDER - -#if CUDNN_VERSION >= 7000 -// clang-format off -#define CUDNN_ROUTINE_EACH_V7000(__macro) \ - __macro(cudnnSetRNNMatrixMathType) \ - __macro(cudnnSetConvolutionMathType) \ - __macro(cudnnSetConvolutionGroupCount) - -// clang-format on - -CUDNN_ROUTINE_EACH_V7000(STREAM_EXECUTOR_CUDNN_WRAP) -#undef CUDNN_ROUTINE_EACH_V7000 -#endif - -#if CUDNN_VERSION >= 7201 -// clang-format off -#define CUDNN_ROUTINE_EACH_V7210(__macro) \ - __macro(cudnnCreateRNNDataDescriptor) \ - __macro(cudnnDestroyRNNDataDescriptor) \ - __macro(cudnnRNNBackwardDataEx) \ - __macro(cudnnRNNBackwardWeightsEx) \ - __macro(cudnnRNNForwardInferenceEx) \ - __macro(cudnnRNNForwardTrainingEx) \ - __macro(cudnnSetRNNDataDescriptor) \ - __macro(cudnnSetRNNPaddingMode) - -// clang-format on - -CUDNN_ROUTINE_EACH_V7210(STREAM_EXECUTOR_CUDNN_WRAP) -#undef CUDNN_ROUTINE_EACH_V7210 -#endif - // Wraps a cuDNN handle and provides access to it through CudnnHandle // instances, which also locks a mutex, acquires the CUDA context, and sets // the stream that cuDNN should use to enqueue any work. diff --git a/tensorflow/stream_executor/cuda/cuda_driver.cc b/tensorflow/stream_executor/cuda/cuda_driver.cc index 5ef821a4f1..d3c4f18084 100644 --- a/tensorflow/stream_executor/cuda/cuda_driver.cc +++ b/tensorflow/stream_executor/cuda/cuda_driver.cc @@ -26,7 +26,6 @@ limitations under the License. #include "absl/strings/str_cat.h" #include "cuda/include/cuda_runtime_api.h" #include "tensorflow/stream_executor/cuda/cuda_diagnostics.h" -#include "tensorflow/stream_executor/cuda/cuda_driver_wrapper.h" #include "tensorflow/stream_executor/lib/env.h" #include "tensorflow/stream_executor/lib/error.h" #include "tensorflow/stream_executor/lib/human_readable.h" @@ -109,11 +108,11 @@ class CreatedContexts { // Formats CUresult to output prettified values into a log stream. string ToString(CUresult result) { const char* error_name; - if (tensorflow::wrap::cuGetErrorName(result, &error_name)) { + if (cuGetErrorName(result, &error_name)) { return absl::StrCat("UNKNOWN ERROR (", static_cast(result), ")"); } const char* error_string; - if (tensorflow::wrap::cuGetErrorString(result, &error_string)) { + if (cuGetErrorString(result, &error_string)) { return error_name; } return absl::StrCat(error_name, ": ", error_string); @@ -186,7 +185,7 @@ bool PointerIsValid(const PtrT ptr) { // Call cuCtxtSynchronize and crash if it doesn't succeed. void SynchronizeOrDie() { - auto res = tensorflow::wrap::cuCtxSynchronize(); + auto res = cuCtxSynchronize(); if (res != CUDA_SUCCESS) { LOG(FATAL) << "Synchronize found " << ToString(res) << " :: " << port::CurrentStackTrace(); @@ -222,8 +221,7 @@ ScopedActivateContext::ScopedActivateContext(GpuContext* cuda_context) { to_restore_ = (tls->depth == 1 ? nullptr : tls->context); // Set the context and update thread local. - CHECK_EQ(CUDA_SUCCESS, - tensorflow::wrap::cuCtxSetCurrent(cuda_context->context())); + CHECK_EQ(CUDA_SUCCESS, cuCtxSetCurrent(cuda_context->context())); tls->id = cuda_context->id(); tls->context = cuda_context; } @@ -248,8 +246,7 @@ ScopedActivateContext::~ScopedActivateContext() { } // Set context and update thread local. - CHECK_EQ(CUDA_SUCCESS, - tensorflow::wrap::cuCtxSetCurrent(to_restore_->context())); + CHECK_EQ(CUDA_SUCCESS, cuCtxSetCurrent(to_restore_->context())); tls->id = to_restore_->id(); tls->context = to_restore_; } @@ -310,7 +307,7 @@ static port::Status InternalInit() { if (FLAGS_gpuexec_cuda_driver_inject_init_error) { LOG(ERROR) << "injecting CUDA init error; initialization will fail"; } else { - res = tensorflow::wrap::cuInit(0 /* = flags */); + res = cuInit(0 /* = flags */); } if (res == CUDA_SUCCESS) { @@ -343,7 +340,7 @@ static port::Status InternalInit() { /* static */ port::Status GpuDriver::GetDevice(int device_ordinal, CUdevice* device) { - CUresult res = tensorflow::wrap::cuDeviceGet(device, device_ordinal); + CUresult res = cuDeviceGet(device, device_ordinal); if (res == CUDA_SUCCESS) { return port::Status::OK(); } @@ -357,8 +354,7 @@ static port::Status InternalInit() { string* device_name) { static const size_t kCharLimit = 64; absl::InlinedVector chars(kCharLimit); - CUresult res = - tensorflow::wrap::cuDeviceGetName(chars.begin(), kCharLimit - 1, device); + CUresult res = cuDeviceGetName(chars.begin(), kCharLimit - 1, device); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to get device name for " << device << ": " << ToString(res); @@ -409,9 +405,9 @@ bool DeviceOptionsToContextFlags(const DeviceOptions& device_options, unsigned int former_primary_context_flags; int former_primary_context_is_active; - CHECK_EQ(CUDA_SUCCESS, tensorflow::wrap::cuDevicePrimaryCtxGetState( - device, &former_primary_context_flags, - &former_primary_context_is_active)); + CHECK_EQ(CUDA_SUCCESS, + cuDevicePrimaryCtxGetState(device, &former_primary_context_flags, + &former_primary_context_is_active)); if (former_primary_context_flags != flags) { if (former_primary_context_is_active) { LOG(ERROR) @@ -419,16 +415,15 @@ bool DeviceOptionsToContextFlags(const DeviceOptions& device_options, << former_primary_context_flags << ") than the desired flag set (" << flags << ")."; } else { - CHECK_EQ(CUDA_SUCCESS, - tensorflow::wrap::cuDevicePrimaryCtxSetFlags(device, flags)); + CHECK_EQ(CUDA_SUCCESS, cuDevicePrimaryCtxSetFlags(device, flags)); } } former_context = cuda::CurrentContextOrDie(); - res = tensorflow::wrap::cuDevicePrimaryCtxRetain(&new_context, device); + res = cuDevicePrimaryCtxRetain(&new_context, device); if (former_context != nullptr) { CUdevice former_device; - if (tensorflow::wrap::cuCtxGetDevice(&former_device) == CUDA_SUCCESS) { + if (cuCtxGetDevice(&former_device) == CUDA_SUCCESS) { if (former_device == device) { if (former_context == new_context) { VLOG(2) << "The primary context " << former_context << " for device " @@ -447,7 +442,7 @@ bool DeviceOptionsToContextFlags(const DeviceOptions& device_options, << former_context; } } - CHECK_EQ(CUDA_SUCCESS, tensorflow::wrap::cuCtxSetCurrent(former_context)); + CHECK_EQ(CUDA_SUCCESS, cuCtxSetCurrent(former_context)); if (res == CUDA_SUCCESS) { *context = CreatedContexts::Add(new_context); @@ -476,12 +471,12 @@ bool DeviceOptionsToContextFlags(const DeviceOptions& device_options, return; } CUcontext former_context = CurrentContext(); - CUresult res = tensorflow::wrap::cuCtxSetCurrent(context->context()); + CUresult res = cuCtxSetCurrent(context->context()); CUdevice device; - tensorflow::wrap::cuCtxGetDevice(&device); - tensorflow::wrap::cuCtxSetCurrent(former_context); + cuCtxGetDevice(&device); + cuCtxSetCurrent(former_context); - res = tensorflow::wrap::cuDevicePrimaryCtxRelease(device); + res = cuDevicePrimaryCtxRelease(device); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to release CUDA context; leaking: " << ToString(res); @@ -493,8 +488,7 @@ bool DeviceOptionsToContextFlags(const DeviceOptions& device_options, /* static */ bool GpuDriver::FuncGetAttribute(CUfunction_attribute attribute, CUfunction func, int* attribute_value) { - CUresult res = - tensorflow::wrap::cuFuncGetAttribute(attribute_value, attribute, func); + CUresult res = cuFuncGetAttribute(attribute_value, attribute, func); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to query kernel attribute. kernel: " << func << ", attribute: " << attribute; @@ -505,7 +499,7 @@ bool DeviceOptionsToContextFlags(const DeviceOptions& device_options, /* static */ bool GpuDriver::FuncSetCacheConfig(CUfunction function, CUfunc_cache cache_config) { - CUresult res = tensorflow::wrap::cuFuncSetCacheConfig(function, cache_config); + CUresult res = cuFuncSetCacheConfig(function, cache_config); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to set CUDA kernel cache config. kernel: " << function << ", config: " << cache_config << ", result: " << ToString(res); @@ -519,11 +513,10 @@ bool DeviceOptionsToContextFlags(const DeviceOptions& device_options, GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { CUsharedconfig shared_mem_config; ScopedActivateContext activation(context); - CUresult result = - tensorflow::wrap::cuCtxGetSharedMemConfig(&shared_mem_config); + CUresult result = cuCtxGetSharedMemConfig(&shared_mem_config); if (result != CUDA_SUCCESS) { CUdevice device; - tensorflow::wrap::cuCtxGetDevice(&device); + cuCtxGetDevice(&device); LOG(ERROR) << "failed to get CUDA device shared memory config. " << "Context device ID: " << device << ", result: " << ToString(result); @@ -537,11 +530,10 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { /* static */ port::Status GpuDriver::ContextSetSharedMemConfig( GpuContext* context, CUsharedconfig shared_mem_config) { ScopedActivateContext activation(context); - CUresult result = - tensorflow::wrap::cuCtxSetSharedMemConfig(shared_mem_config); + CUresult result = cuCtxSetSharedMemConfig(shared_mem_config); if (result != CUDA_SUCCESS) { CUdevice device; - tensorflow::wrap::cuCtxGetDevice(&device); + cuCtxGetDevice(&device); LOG(ERROR) << "failed to set CUDA device shared memory config. " << "Context device ID: " << device << ", config: " << shared_mem_config @@ -564,9 +556,9 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { << " gdy: " << grid_dim_y << " gdz: " << grid_dim_z << " bdx: " << block_dim_x << " bdy: " << block_dim_y << " bdz: " << block_dim_z; - CUresult res = tensorflow::wrap::cuLaunchKernel( - function, grid_dim_x, grid_dim_y, grid_dim_z, block_dim_x, block_dim_y, - block_dim_z, shared_mem_bytes, stream, kernel_params, extra); + CUresult res = cuLaunchKernel(function, grid_dim_x, grid_dim_y, grid_dim_z, + block_dim_x, block_dim_y, block_dim_z, + shared_mem_bytes, stream, kernel_params, extra); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to launch CUDA kernel: " << function << "; result: " << ToString(res); @@ -580,8 +572,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { const char* cubin_bytes, CUmodule* module) { ScopedActivateContext activation(context); - CUresult result = - tensorflow::wrap::cuModuleLoadFatBinary(module, cubin_bytes); + CUresult result = cuModuleLoadFatBinary(module, cubin_bytes); if (result != CUDA_SUCCESS) { return port::Status(port::error::INTERNAL, "failed to load in-memory CUBIN: " + ToString(result)); @@ -624,8 +615,8 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { // TODO(leary) Need to see if NVIDIA can expunge the leakiness in their // module loading: see http://b/13248943 - res = tensorflow::wrap::cuModuleLoadDataEx( - module, ptx_data, TF_ARRAYSIZE(options), options, option_values); + res = cuModuleLoadDataEx(module, ptx_data, TF_ARRAYSIZE(options), options, + option_values); } // The PTX JIT mutates the values in the option values array to reflect the @@ -671,7 +662,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { CUdeviceptr location, uint8 value, size_t size) { ScopedActivateContext activation(context); - CUresult res = tensorflow::wrap::cuMemsetD8(location, value, size); + CUresult res = cuMemsetD8(location, value, size); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to memset memory: " << ToString(res); return false; @@ -684,7 +675,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { uint32 value, size_t uint32_count) { ScopedActivateContext activation(context); - CUresult res = tensorflow::wrap::cuMemsetD32(location, value, uint32_count); + CUresult res = cuMemsetD32(location, value, uint32_count); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to memset memory: " << ToString(res); return false; @@ -698,8 +689,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { size_t uint32_count, CUstream stream) { ScopedActivateContext activation(context); - CUresult res = - tensorflow::wrap::cuMemsetD8Async(location, value, uint32_count, stream); + CUresult res = cuMemsetD8Async(location, value, uint32_count, stream); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to enqueue async memset operation: " << ToString(res); return false; @@ -714,8 +704,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { size_t uint32_count, CUstream stream) { ScopedActivateContext activation(context); - CUresult res = - tensorflow::wrap::cuMemsetD32Async(location, value, uint32_count, stream); + CUresult res = cuMemsetD32Async(location, value, uint32_count, stream); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to enqueue async memset operation: " << ToString(res); return false; @@ -729,8 +718,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { StreamCallback callback, void* data) { // Note: flags param is required to be zero according to CUDA 6.0. - CUresult res = tensorflow::wrap::cuStreamAddCallback(stream, callback, data, - 0 /* = flags */); + CUresult res = cuStreamAddCallback(stream, callback, data, 0 /* = flags */); if (res != CUDA_SUCCESS) { LOG(ERROR) << "unable to add host callback: " << ToString(res); return false; @@ -744,8 +732,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { CUfunction* function) { ScopedActivateContext activated{context}; CHECK(module != nullptr && kernel_name != nullptr); - CUresult res = - tensorflow::wrap::cuModuleGetFunction(function, module, kernel_name); + CUresult res = cuModuleGetFunction(function, module, kernel_name); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to get PTX kernel \"" << kernel_name << "\" from module: " << ToString(res); @@ -762,8 +749,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { ScopedActivateContext activated{context}; CHECK(module != nullptr && symbol_name != nullptr && (dptr != nullptr || bytes != nullptr)); - CUresult res = - tensorflow::wrap::cuModuleGetGlobal(dptr, bytes, module, symbol_name); + CUresult res = cuModuleGetGlobal(dptr, bytes, module, symbol_name); if (res != CUDA_SUCCESS) { // symbol may not be found in the current module, but it may reside in // another module. @@ -778,7 +764,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { /* static */ void GpuDriver::UnloadModule(GpuContext* context, CUmodule module) { ScopedActivateContext activated{context}; - CUresult res = tensorflow::wrap::cuModuleUnload(module); + CUresult res = cuModuleUnload(module); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to unload module " << module << "; leaking: " << ToString(res); @@ -789,7 +775,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { GpuContext* context) { ScopedActivateContext activated{context}; CUdevice device = -1; - CUresult result = tensorflow::wrap::cuCtxGetDevice(&device); + CUresult result = cuCtxGetDevice(&device); if (result == CUDA_SUCCESS) { return device; } @@ -805,7 +791,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { // up synchronization with respect to memsets and any other things that have // to occur on the default stream? ScopedActivateContext activated{context}; - CUresult res = tensorflow::wrap::cuStreamCreate(stream, 0); + CUresult res = cuStreamCreate(stream, 0); if (res != CUDA_SUCCESS) { LOG(ERROR) << "could not allocate CUDA stream for context " << context->context() << ": " << ToString(res); @@ -824,7 +810,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { } ScopedActivateContext activated{context}; - CUresult res = tensorflow::wrap::cuStreamDestroy(*stream); + CUresult res = cuStreamDestroy(*stream); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to destroy CUDA stream for context " << context->context() << ": " << ToString(res); @@ -843,7 +829,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { ScopedActivateContext activated{context}; CUdeviceptr result = 0; - CUresult res = tensorflow::wrap::cuMemAlloc(&result, bytes); + CUresult res = cuMemAlloc(&result, bytes); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to allocate " << port::HumanReadableNumBytes::ToString(bytes) << " (" << bytes @@ -860,7 +846,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { void* location) { ScopedActivateContext activation(context); CUdeviceptr pointer = absl::bit_cast(location); - CUresult res = tensorflow::wrap::cuMemFree(pointer); + CUresult res = cuMemFree(pointer); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to free device memory at " << location << "; result: " << ToString(res); @@ -875,8 +861,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { ScopedActivateContext activation(context); CUdeviceptr result = 0; // "Portable" memory is visible to all CUDA contexts. Safe for our use model. - CUresult res = - tensorflow::wrap::cuMemAllocManaged(&result, bytes, CU_MEM_ATTACH_GLOBAL); + CUresult res = cuMemAllocManaged(&result, bytes, CU_MEM_ATTACH_GLOBAL); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to alloc " << bytes << " bytes unified memory; result: " << ToString(res); @@ -892,7 +877,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { void* location) { ScopedActivateContext activation(context); CUdeviceptr pointer = absl::bit_cast(location); - CUresult res = tensorflow::wrap::cuMemFree(pointer); + CUresult res = cuMemFree(pointer); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to free unified memory at " << location << "; result: " << ToString(res); @@ -906,8 +891,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { ScopedActivateContext activation(context); void* host_mem = nullptr; // "Portable" memory is visible to all CUDA contexts. Safe for our use model. - CUresult res = tensorflow::wrap::cuMemHostAlloc(&host_mem, bytes, - CU_MEMHOSTALLOC_PORTABLE); + CUresult res = cuMemHostAlloc(&host_mem, bytes, CU_MEMHOSTALLOC_PORTABLE); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to alloc " << bytes << " bytes on host: " << ToString(res); @@ -918,7 +902,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { /* static */ void GpuDriver::HostDeallocate(GpuContext* context, void* location) { ScopedActivateContext activation(context); - CUresult res = tensorflow::wrap::cuMemFreeHost(location); + CUresult res = cuMemFreeHost(location); if (res != CUDA_SUCCESS) { LOG(ERROR) << "error deallocating host memory at " << location << ": " << ToString(res); @@ -929,8 +913,8 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { uint64 bytes) { ScopedActivateContext activation(context); // "Portable" memory is visible to all CUDA contexts. Safe for our use model. - CUresult res = tensorflow::wrap::cuMemHostRegister( - location, bytes, CU_MEMHOSTREGISTER_PORTABLE); + CUresult res = + cuMemHostRegister(location, bytes, CU_MEMHOSTREGISTER_PORTABLE); if (res != CUDA_SUCCESS) { LOG(ERROR) << "error registering host memory at " << location << ": " << ToString(res); @@ -942,7 +926,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { /* static */ bool GpuDriver::HostUnregister(GpuContext* context, void* location) { ScopedActivateContext activation(context); - CUresult res = tensorflow::wrap::cuMemHostUnregister(location); + CUresult res = cuMemHostUnregister(location); if (res != CUDA_SUCCESS) { LOG(ERROR) << "error unregistering host memory at " << location << ": " << ToString(res); @@ -959,7 +943,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { } ScopedActivateContext activated{context}; - CUresult res = tensorflow::wrap::cuEventDestroy(*event); + CUresult res = cuEventDestroy(*event); *event = nullptr; switch (res) { @@ -983,7 +967,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { CUevent event, CUstream stream) { ScopedActivateContext activated{context}; - CUresult res = tensorflow::wrap::cuEventRecord(event, stream); + CUresult res = cuEventRecord(event, stream); switch (res) { case CUDA_SUCCESS: return port::Status::OK(); @@ -1004,7 +988,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { /* static */ port::StatusOr GpuDriver::QueryEvent(GpuContext* context, CUevent event) { ScopedActivateContext activated{context}; - CUresult res = tensorflow::wrap::cuEventQuery(event); + CUresult res = cuEventQuery(event); if (res != CUDA_SUCCESS && res != CUDA_ERROR_NOT_READY) { return port::Status( port::error::INTERNAL, @@ -1020,12 +1004,12 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { ScopedActivateContext activated{context}; // The stop event must have completed in order for cuEventElapsedTime to // work. - CUresult res = tensorflow::wrap::cuEventSynchronize(stop); + CUresult res = cuEventSynchronize(stop); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to synchronize the stop event: " << ToString(res); return false; } - res = tensorflow::wrap::cuEventElapsedTime(elapsed_milliseconds, start, stop); + res = cuEventElapsedTime(elapsed_milliseconds, start, stop); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to get elapsed time between events: " << ToString(res); @@ -1038,8 +1022,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { /* static */ bool GpuDriver::WaitStreamOnEvent(GpuContext* context, CUstream stream, CUevent event) { ScopedActivateContext activation(context); - CUresult res = - tensorflow::wrap::cuStreamWaitEvent(stream, event, 0 /* = flags */); + CUresult res = cuStreamWaitEvent(stream, event, 0 /* = flags */); if (res != CUDA_SUCCESS) { LOG(ERROR) << "could not wait stream on event: " << ToString(res); return false; @@ -1050,7 +1033,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { /* static */ bool GpuDriver::SynchronizeContext(GpuContext* context) { ScopedActivateContext activation(context); - CUresult res = tensorflow::wrap::cuCtxSynchronize(); + CUresult res = cuCtxSynchronize(); if (res != CUDA_SUCCESS) { LOG(ERROR) << "could not synchronize on CUDA context: " << ToString(res) << " :: " << port::CurrentStackTrace(); @@ -1064,7 +1047,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { CUstream stream) { ScopedActivateContext activated{context}; CHECK(stream != nullptr); - CUresult res = tensorflow::wrap::cuStreamSynchronize(stream); + CUresult res = cuStreamSynchronize(stream); if (res != CUDA_SUCCESS) { port::Status status = port::InternalError( absl::StrCat("could not synchronize on CUDA stream: ", ToString(res))); @@ -1080,7 +1063,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { CUstream stream) { ScopedActivateContext activated{context}; CHECK(stream != nullptr); - CUresult res = tensorflow::wrap::cuStreamQuery(stream); + CUresult res = cuStreamQuery(stream); if (res == CUDA_SUCCESS) { return true; } @@ -1102,7 +1085,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { CHECK(PointerIsValid(host_dst)) << "Destination pointer is not actually on CPU: " << host_dst; } - CUresult res = tensorflow::wrap::cuMemcpyDtoH(host_dst, gpu_src, size); + CUresult res = cuMemcpyDtoH(host_dst, gpu_src, size); if (res != CUDA_SUCCESS) { return port::InternalError( port::Printf("failed to synchronous memcpy from device to host: %s; " @@ -1126,7 +1109,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { CHECK(PointerIsValid(gpu_dst)) << "Destination pointer is not actually on GPU: " << gpu_dst; } - CUresult res = tensorflow::wrap::cuMemcpyHtoD(gpu_dst, host_src, size); + CUresult res = cuMemcpyHtoD(gpu_dst, host_src, size); if (res != CUDA_SUCCESS) { return port::InternalError(port::Printf( "failed to synchronous memcpy from host to device: %s; GPU dst: %p;" @@ -1149,7 +1132,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { CHECK(PointerIsValid(gpu_dst)) << "Destination pointer is not actually on GPU: " << gpu_dst; } - CUresult res = tensorflow::wrap::cuMemcpyDtoD(gpu_dst, gpu_src, size); + CUresult res = cuMemcpyDtoD(gpu_dst, gpu_src, size); if (res != CUDA_SUCCESS) { return port::InternalError(port::Printf( "failed to synchronous memcpy from host to device: %s; GPU dst: %p; " @@ -1173,8 +1156,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { CHECK(PointerIsValid(host_dst)) << "Destination pointer is not actually on CPU: " << host_dst; } - CUresult res = - tensorflow::wrap::cuMemcpyDtoHAsync(host_dst, gpu_src, size, stream); + CUresult res = cuMemcpyDtoHAsync(host_dst, gpu_src, size, stream); if (res != CUDA_SUCCESS) { LOG(ERROR) << port::Printf( "failed to enqueue async memcpy from device to host: %s; host dst: %p; " @@ -1201,8 +1183,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { CHECK(PointerIsValid(gpu_dst)) << "Destination pointer is not actually on GPU: " << gpu_dst; } - CUresult res = - tensorflow::wrap::cuMemcpyHtoDAsync(gpu_dst, host_src, size, stream); + CUresult res = cuMemcpyHtoDAsync(gpu_dst, host_src, size, stream); if (res != CUDA_SUCCESS) { LOG(ERROR) << port::Printf( "failed to enqueue async memcpy from host to device: %s; GPU dst: %p; " @@ -1228,8 +1209,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { CHECK(PointerIsValid(gpu_dst)) << "Destination pointer is not actually on GPU: " << gpu_dst; } - CUresult result = - tensorflow::wrap::cuMemcpyDtoDAsync(gpu_dst, gpu_src, size, stream); + CUresult result = cuMemcpyDtoDAsync(gpu_dst, gpu_src, size, stream); if (result != CUDA_SUCCESS) { LOG(ERROR) << port::Printf( "failed to enqueue async memcpy from device to device: %s" @@ -1266,7 +1246,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { } ScopedActivateContext activated{context}; - CUresult res = tensorflow::wrap::cuEventCreate(result, cuflags); + CUresult res = cuEventCreate(result, cuflags); if (res == CUDA_SUCCESS) { return port::Status::OK(); @@ -1282,7 +1262,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { /* static */ int GpuDriver::GetDeviceCount() { int device_count = 0; - CUresult res = tensorflow::wrap::cuDeviceGetCount(&device_count); + CUresult res = cuDeviceGetCount(&device_count); if (res != CUDA_SUCCESS) { LOG(ERROR) << "could not retrieve CUDA device count: " << ToString(res); return 0; @@ -1297,8 +1277,8 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { /* static */ port::StatusOr GpuDriver::GetPointerContext( CUdeviceptr pointer) { GpuContext* context = nullptr; - CUresult result = tensorflow::wrap::cuPointerGetAttribute( - &context, CU_POINTER_ATTRIBUTE_CONTEXT, pointer); + CUresult result = + cuPointerGetAttribute(&context, CU_POINTER_ATTRIBUTE_CONTEXT, pointer); if (result == CUDA_SUCCESS) { CHECK(context != nullptr) << "success should entail non-null context"; return context; @@ -1313,8 +1293,8 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { /* static */ port::StatusOr GpuDriver::GetPointerMemorySpace( CUdeviceptr pointer) { unsigned int value; - CUresult result = tensorflow::wrap::cuPointerGetAttribute( - &value, CU_POINTER_ATTRIBUTE_MEMORY_TYPE, pointer); + CUresult result = + cuPointerGetAttribute(&value, CU_POINTER_ATTRIBUTE_MEMORY_TYPE, pointer); if (result == CUDA_SUCCESS) { switch (value) { case CU_MEMORYTYPE_DEVICE: @@ -1337,7 +1317,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { /* static */ port::Status GpuDriver::GetPointerAddressRange(CUdeviceptr dptr, CUdeviceptr* base, size_t* size) { - CUresult result = tensorflow::wrap::cuMemGetAddressRange(base, size, dptr); + CUresult result = cuMemGetAddressRange(base, size, dptr); if (result == CUDA_SUCCESS) { return port::Status::OK(); } else if (result == CUDA_ERROR_NOT_FOUND) { @@ -1372,7 +1352,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { *cc_major = 0; *cc_minor = 0; - CUresult res = tensorflow::wrap::cuDeviceGetAttribute( + CUresult res = cuDeviceGetAttribute( cc_major, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR, device); if (res != CUDA_SUCCESS) { return port::Status( @@ -1382,7 +1362,7 @@ GpuDriver::ContextGetSharedMemConfig(GpuContext* context) { ToString(res).c_str(), device)); } - res = tensorflow::wrap::cuDeviceGetAttribute( + res = cuDeviceGetAttribute( cc_minor, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR, device); if (res != CUDA_SUCCESS) { return port::Status( @@ -1408,8 +1388,7 @@ template static port::StatusOr GetSimpleAttribute(CUdevice device, CUdevice_attribute attribute) { int value = -1; - CUresult result = - tensorflow::wrap::cuDeviceGetAttribute(&value, attribute, device); + CUresult result = cuDeviceGetAttribute(&value, attribute, device); if (result != CUDA_SUCCESS) { return port::Status( port::error::NOT_FOUND, @@ -1464,24 +1443,24 @@ static port::StatusOr GetSimpleAttribute(CUdevice device, /* static */ bool GpuDriver::GetGridLimits(int* x, int* y, int* z, CUdevice device) { int value; - CUresult res = tensorflow::wrap::cuDeviceGetAttribute( - &value, CU_DEVICE_ATTRIBUTE_MAX_GRID_DIM_X, device); + CUresult res = + cuDeviceGetAttribute(&value, CU_DEVICE_ATTRIBUTE_MAX_GRID_DIM_X, device); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to query max grid dim x: " << ToString(res); return false; } *x = value; - res = tensorflow::wrap::cuDeviceGetAttribute( - &value, CU_DEVICE_ATTRIBUTE_MAX_GRID_DIM_Y, device); + res = + cuDeviceGetAttribute(&value, CU_DEVICE_ATTRIBUTE_MAX_GRID_DIM_Y, device); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to query max grid dim y: " << ToString(res); return false; } *y = value; - res = tensorflow::wrap::cuDeviceGetAttribute( - &value, CU_DEVICE_ATTRIBUTE_MAX_GRID_DIM_Z, device); + res = + cuDeviceGetAttribute(&value, CU_DEVICE_ATTRIBUTE_MAX_GRID_DIM_Z, device); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to query max grid dim z: " << ToString(res); return false; @@ -1491,7 +1470,7 @@ static port::StatusOr GetSimpleAttribute(CUdevice device, } /* static */ bool GpuDriver::GetDriverVersion(int* driver_version) { - CUresult res = tensorflow::wrap::cuDriverGetVersion(driver_version); + CUresult res = cuDriverGetVersion(driver_version); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to query driver version: " << ToString(res); return false; @@ -1502,8 +1481,7 @@ static port::StatusOr GetSimpleAttribute(CUdevice device, /* static */ bool GpuDriver::GetDeviceProperties(CUdevprop* device_properties, int device_ordinal) { - CUresult res = tensorflow::wrap::cuDeviceGetProperties(device_properties, - device_ordinal); + CUresult res = cuDeviceGetProperties(device_properties, device_ordinal); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to query device properties: " << ToString(res); return false; @@ -1515,8 +1493,7 @@ static port::StatusOr GetSimpleAttribute(CUdevice device, /* static */ port::StatusOr GpuDriver::GetDeviceAttribute( CUdevice_attribute attribute, CUdevice device) { int val; - CUresult res = - tensorflow::wrap::cuDeviceGetAttribute(&val, attribute, device); + CUresult res = cuDeviceGetAttribute(&val, attribute, device); if (res != CUDA_SUCCESS) { return port::Status( port::error::INTERNAL, @@ -1528,8 +1505,8 @@ static port::StatusOr GetSimpleAttribute(CUdevice device, /* static */ bool GpuDriver::IsEccEnabled(CUdevice device, bool* result) { int value = -1; - CUresult res = tensorflow::wrap::cuDeviceGetAttribute( - &value, CU_DEVICE_ATTRIBUTE_ECC_ENABLED, device); + CUresult res = + cuDeviceGetAttribute(&value, CU_DEVICE_ATTRIBUTE_ECC_ENABLED, device); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to query ECC status: " << ToString(res); return false; @@ -1545,7 +1522,7 @@ static port::StatusOr GetSimpleAttribute(CUdevice device, ScopedActivateContext activation(context); size_t free = 0; size_t total = 0; - CUresult res = tensorflow::wrap::cuMemGetInfo(&free, &total); + CUresult res = cuMemGetInfo(&free, &total); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to query device memory info: " << ToString(res); return false; @@ -1559,7 +1536,7 @@ static port::StatusOr GetSimpleAttribute(CUdevice device, /* static */ bool GpuDriver::GetDeviceTotalMemory(CUdevice device, uint64* result) { size_t value = -1; - CUresult res = tensorflow::wrap::cuDeviceTotalMem(&value, device); + CUresult res = cuDeviceTotalMem(&value, device); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to query total available memory: " << ToString(res); return false; @@ -1574,8 +1551,7 @@ static port::StatusOr GetSimpleAttribute(CUdevice device, static const int kBufferSize = 64; absl::InlinedVector chars(kBufferSize); chars[kBufferSize - 1] = '\0'; - CUresult res = tensorflow::wrap::cuDeviceGetPCIBusId(chars.begin(), - kBufferSize - 1, device); + CUresult res = cuDeviceGetPCIBusId(chars.begin(), kBufferSize - 1, device); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to query PCI bus id for device: " << ToString(res); return pci_bus_id; @@ -1603,7 +1579,7 @@ static port::StatusOr GetSimpleAttribute(CUdevice device, << to_device.status(); return false; } - CUresult res = tensorflow::wrap::cuDeviceCanAccessPeer( + CUresult res = cuDeviceCanAccessPeer( &can_access_peer, from_device.ValueOrDie(), to_device.ValueOrDie()); if (res != CUDA_SUCCESS) { LOG(ERROR) << "failed to detect peer access capability: " << ToString(res); @@ -1620,8 +1596,7 @@ static port::StatusOr GetSimpleAttribute(CUdevice device, } ScopedActivateContext activated{from}; - CUresult result = - tensorflow::wrap::cuCtxEnablePeerAccess(to->context(), 0 /* = flags */); + CUresult result = cuCtxEnablePeerAccess(to->context(), 0 /* = flags */); if (result != CUDA_SUCCESS && result != CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED) { return port::Status( @@ -1639,9 +1614,8 @@ static port::StatusOr GetSimpleAttribute(CUdevice device, ScopedActivateContext activation(context); int max_blocks; - CUresult result = - tensorflow::wrap::cuOccupancyMaxActiveBlocksPerMultiprocessor( - &max_blocks, kernel, threads_per_block, dynamic_shared_memory_bytes); + CUresult result = cuOccupancyMaxActiveBlocksPerMultiprocessor( + &max_blocks, kernel, threads_per_block, dynamic_shared_memory_bytes); if (result != CUDA_SUCCESS) { return port::Status( port::error::INTERNAL, @@ -1658,7 +1632,7 @@ namespace cuda { CUcontext CurrentContextOrDie() { CUcontext current = nullptr; - CUresult result = tensorflow::wrap::cuCtxGetCurrent(¤t); + CUresult result = cuCtxGetCurrent(¤t); if (result != CUDA_SUCCESS) { LOG(FATAL) << "failed to query current context: " << gpu::ToString(result); } diff --git a/tensorflow/stream_executor/cuda/cuda_driver_wrapper.h b/tensorflow/stream_executor/cuda/cuda_driver_wrapper.h deleted file mode 100644 index 0de27d5cd3..0000000000 --- a/tensorflow/stream_executor/cuda/cuda_driver_wrapper.h +++ /dev/null @@ -1,144 +0,0 @@ -/* 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. -==============================================================================*/ - -// This file wraps cuda driver calls with dso loader so that we don't need to -// have explicit linking to libcuda. All TF cuda driver usage should route -// through this wrapper. - -#ifndef TENSORFLOW_STREAM_EXECUTOR_CUDA_CUDA_DRIVER_WRAPPER_H_ -#define TENSORFLOW_STREAM_EXECUTOR_CUDA_CUDA_DRIVER_WRAPPER_H_ - -#include "cuda/include/cuda.h" -#include "tensorflow/stream_executor/lib/env.h" -#include "tensorflow/stream_executor/platform/dso_loader.h" -#include "tensorflow/stream_executor/platform/port.h" - -namespace tensorflow { -namespace wrap { -#ifdef PLATFORM_GOOGLE -// Use static linked library -#define STREAM_EXECUTOR_LIBCUDA_WRAP(cudaSymbolName) \ - template \ - auto cudaSymbolName(Args... args)->decltype(::cudaSymbolName(args...)) { \ - return ::cudaSymbolName(args...); \ - } - -// This macro wraps a global identifier, given by cudaSymbolName, in a callable -// structure that loads the DLL symbol out of the DSO handle in a thread-safe -// manner on first use. This dynamic loading technique is used to avoid DSO -// dependencies on vendor libraries which may or may not be available in the -// deployed binary environment. -#else -#define TO_STR_(x) #x -#define TO_STR(x) TO_STR_(x) - -#define STREAM_EXECUTOR_LIBCUDA_WRAP(cudaSymbolName) \ - template \ - auto cudaSymbolName(Args... args)->decltype(::cudaSymbolName(args...)) { \ - using FuncPtrT = std::add_pointer::type; \ - static FuncPtrT loaded = []() -> FuncPtrT { \ - static const char *kName = TO_STR(cudaSymbolName); \ - void *f; \ - auto s = stream_executor::port::Env::Default()->GetSymbolFromLibrary( \ - stream_executor::internal::CachedDsoLoader::GetCudaDriverDsoHandle() \ - .ValueOrDie(), \ - kName, &f); \ - CHECK(s.ok()) << "could not find " << kName \ - << " in libcuda DSO; dlerror: " << s.error_message(); \ - return reinterpret_cast(f); \ - }(); \ - return loaded(args...); \ - } -#endif - -// clang-format off -#define LIBCUDA_ROUTINE_EACH(__macro) \ - __macro(cuCtxEnablePeerAccess) \ - __macro(cuCtxGetCurrent) \ - __macro(cuCtxGetDevice) \ - __macro(cuCtxGetSharedMemConfig) \ - __macro(cuCtxSetCurrent) \ - __macro(cuCtxSetSharedMemConfig) \ - __macro(cuCtxSynchronize) \ - __macro(cuDeviceCanAccessPeer) \ - __macro(cuDeviceGet) \ - __macro(cuDeviceGetAttribute) \ - __macro(cuDeviceGetCount) \ - __macro(cuDeviceGetName) \ - __macro(cuDeviceGetPCIBusId) \ - __macro(cuDeviceGetProperties) \ - __macro(cuDevicePrimaryCtxGetState) \ - __macro(cuDevicePrimaryCtxRelease) \ - __macro(cuDevicePrimaryCtxRetain) \ - __macro(cuDevicePrimaryCtxSetFlags) \ - __macro(cuDeviceTotalMem) \ - __macro(cuDriverGetVersion) \ - __macro(cuEventCreate) \ - __macro(cuEventDestroy) \ - __macro(cuEventElapsedTime) \ - __macro(cuEventQuery) \ - __macro(cuEventRecord) \ - __macro(cuEventSynchronize) \ - __macro(cuFuncGetAttribute) \ - __macro(cuFuncSetCacheConfig) \ - __macro(cuGetErrorName) \ - __macro(cuGetErrorString) \ - __macro(cuInit) \ - __macro(cuLaunchKernel) \ - __macro(cuMemAlloc) \ - __macro(cuMemAllocManaged) \ - __macro(cuMemFree) \ - __macro(cuMemFreeHost) \ - __macro(cuMemGetAddressRange) \ - __macro(cuMemGetInfo) \ - __macro(cuMemHostAlloc) \ - __macro(cuMemHostRegister) \ - __macro(cuMemHostUnregister) \ - __macro(cuMemcpyDtoD) \ - __macro(cuMemcpyDtoDAsync) \ - __macro(cuMemcpyDtoH) \ - __macro(cuMemcpyDtoHAsync) \ - __macro(cuMemcpyHtoD) \ - __macro(cuMemcpyHtoDAsync) \ - __macro(cuMemsetD32) \ - __macro(cuMemsetD32Async) \ - __macro(cuMemsetD8) \ - __macro(cuMemsetD8Async) \ - __macro(cuModuleGetFunction) \ - __macro(cuModuleGetGlobal) \ - __macro(cuModuleLoadDataEx) \ - __macro(cuModuleLoadFatBinary) \ - __macro(cuModuleUnload) \ - __macro(cuOccupancyMaxActiveBlocksPerMultiprocessor) \ - __macro(cuOccupancyMaxPotentialBlockSize) \ - __macro(cuPointerGetAttribute) \ - __macro(cuStreamAddCallback) \ - __macro(cuStreamCreate) \ - __macro(cuStreamDestroy) \ - __macro(cuStreamQuery) \ - __macro(cuStreamSynchronize) \ - __macro(cuStreamWaitEvent) - -// clang-format on - -LIBCUDA_ROUTINE_EACH(STREAM_EXECUTOR_LIBCUDA_WRAP) -#undef LIBCUDA_ROUTINE_EACH -#undef STREAM_EXECUTOR_LIBCUDA_WRAP -#undef TO_STR -#undef TO_STR_ -} // namespace wrap -} // namespace tensorflow - -#endif // TENSORFLOW_STREAM_EXECUTOR_CUDA_CUDA_DRIVER_WRAPPER_H_ diff --git a/tensorflow/stream_executor/cuda/cuda_fft.cc b/tensorflow/stream_executor/cuda/cuda_fft.cc index 054b43b5b7..3bf2f5b974 100644 --- a/tensorflow/stream_executor/cuda/cuda_fft.cc +++ b/tensorflow/stream_executor/cuda/cuda_fft.cc @@ -26,7 +26,6 @@ limitations under the License. #include "tensorflow/stream_executor/lib/env.h" #include "tensorflow/stream_executor/lib/initialize.h" #include "tensorflow/stream_executor/lib/status.h" -#include "tensorflow/stream_executor/platform/dso_loader.h" #include "tensorflow/stream_executor/platform/logging.h" #include "tensorflow/stream_executor/platform/port.h" #include "tensorflow/stream_executor/plugin_registry.h" @@ -37,89 +36,6 @@ namespace gpu { PLUGIN_REGISTRY_DEFINE_PLUGIN_ID(kCuFftPlugin); -namespace wrap { - -#ifdef PLATFORM_GOOGLE -// This macro wraps a global identifier, given by __name, in a callable -// structure that loads the DLL symbol out of the DSO handle in a thread-safe -// manner on first use. This dynamic loading technique is used to avoid DSO -// dependencies on vendor libraries which may or may not be available in the -// deployed binary environment. -#define STREAM_EXECUTOR_CUFFT_WRAP(__name) \ - struct WrapperShim__##__name { \ - template \ - cufftResult operator()(GpuExecutor *parent, Args... args) { \ - gpu::ScopedActivateExecutorContext sac{parent}; \ - return ::__name(args...); \ - } \ - } __name; - -#else - -#define STREAM_EXECUTOR_CUFFT_WRAP(__name) \ - struct DynLoadShim__##__name { \ - static const char *kName; \ - using FuncPtrT = std::add_pointer::type; \ - static void *GetDsoHandle() { \ - auto s = internal::CachedDsoLoader::GetCufftDsoHandle(); \ - return s.ValueOrDie(); \ - } \ - static FuncPtrT LoadOrDie() { \ - void *f; \ - auto s = port::Env::Default()->GetSymbolFromLibrary(GetDsoHandle(), \ - kName, &f); \ - CHECK(s.ok()) << "could not find " << kName \ - << " in cufft DSO; dlerror: " << s.error_message(); \ - return reinterpret_cast(f); \ - } \ - static FuncPtrT DynLoad() { \ - static FuncPtrT f = LoadOrDie(); \ - return f; \ - } \ - template \ - cufftResult operator()(GpuExecutor *parent, Args... args) { \ - gpu::ScopedActivateExecutorContext sac{parent}; \ - return DynLoad()(args...); \ - } \ - } __name; \ - const char *DynLoadShim__##__name::kName = #__name; - -#endif - -// clang-format off - -#define CUFFT_ROUTINE_EACH(__macro) \ - __macro(cufftDestroy) \ - __macro(cufftSetStream) \ - __macro(cufftPlan1d) \ - __macro(cufftPlan2d) \ - __macro(cufftPlan3d) \ - __macro(cufftPlanMany) \ - __macro(cufftExecD2Z) \ - __macro(cufftExecZ2D) \ - __macro(cufftExecC2C) \ - __macro(cufftExecC2R) \ - __macro(cufftExecZ2Z) \ - __macro(cufftExecR2C) \ - __macro(cufftCreate) \ - __macro(cufftSetAutoAllocation) \ - __macro(cufftSetWorkArea) \ - __macro(cufftGetSize1d) \ - __macro(cufftMakePlan1d) \ - __macro(cufftGetSize2d) \ - __macro(cufftMakePlan2d) \ - __macro(cufftGetSize3d) \ - __macro(cufftMakePlan3d) \ - __macro(cufftGetSizeMany) \ - __macro(cufftMakePlanMany) - -// clang-format on - -CUFFT_ROUTINE_EACH(STREAM_EXECUTOR_CUFFT_WRAP) -#undef CUFFT_ROUTINE_EACH - -} // namespace wrap - namespace { // A helper function transforming gpu_fft arguments into cuFFT arguments. @@ -146,7 +62,8 @@ cufftType CUDAFftType(fft::Type type) { // Associates the given stream with the given cuFFT plan. bool SetStream(GpuExecutor *parent, cufftHandle plan, Stream *stream) { - auto ret = wrap::cufftSetStream(parent, plan, AsGpuStreamValue(stream)); + cuda::ScopedActivateExecutorContext sac(parent); + auto ret = cufftSetStream(plan, AsGpuStreamValue(stream)); if (ret != CUFFT_SUCCESS) { LOG(ERROR) << "failed to run cuFFT routine cufftSetStream: " << ret; return false; @@ -165,6 +82,7 @@ port::Status CUDAFftPlan::Initialize( LOG(FATAL) << "Try to repeatedly initialize."; } is_initialized_ = true; + cuda::ScopedActivateExecutorContext sac(parent); int elem_count_[3], input_embed_[3], output_embed_[3]; for (int i = 0; i < rank; ++i) { elem_count_[i] = elem_count[i]; @@ -183,8 +101,8 @@ port::Status CUDAFftPlan::Initialize( switch (rank) { case 1: // cufftPlan1d - ret = wrap::cufftPlan1d(parent, &plan_, elem_count_[0], - CUDAFftType(type), 1 /* = batch */); + ret = cufftPlan1d(&plan_, elem_count_[0], CUDAFftType(type), + 1 /* = batch */); if (ret != CUFFT_SUCCESS) { LOG(ERROR) << "failed to create cuFFT 1d plan:" << ret; return port::Status(port::error::INTERNAL, @@ -193,8 +111,8 @@ port::Status CUDAFftPlan::Initialize( return port::Status::OK(); case 2: // cufftPlan2d - ret = wrap::cufftPlan2d(parent, &plan_, elem_count_[0], - elem_count_[1], CUDAFftType(type)); + ret = cufftPlan2d(&plan_, elem_count_[0], elem_count_[1], + CUDAFftType(type)); if (ret != CUFFT_SUCCESS) { LOG(ERROR) << "failed to create cuFFT 2d plan:" << ret; return port::Status(port::error::INTERNAL, @@ -203,9 +121,8 @@ port::Status CUDAFftPlan::Initialize( return port::Status::OK(); case 3: // cufftPlan3d - ret = - wrap::cufftPlan3d(parent, &plan_, elem_count_[0], elem_count_[1], - elem_count_[2], CUDAFftType(type)); + ret = cufftPlan3d(&plan_, elem_count_[0], elem_count_[1], + elem_count_[2], CUDAFftType(type)); if (ret != CUFFT_SUCCESS) { LOG(ERROR) << "failed to create cuFFT 3d plan:" << ret; return port::Status(port::error::INTERNAL, @@ -220,13 +137,13 @@ port::Status CUDAFftPlan::Initialize( "cufftPlan only takes rank 1, 2, or 3."); } } else { - ret = wrap::cufftCreate(parent, &plan_); + ret = cufftCreate(&plan_); if (ret != CUFFT_SUCCESS) { LOG(ERROR) << "failed to create cuFFT plan:" << ret; return port::Status(port::error::INTERNAL, "Failed to create cuFFT plan."); } - ret = wrap::cufftSetAutoAllocation(parent, plan_, 0); + ret = cufftSetAutoAllocation(plan_, 0); if (ret != CUFFT_SUCCESS) { LOG(ERROR) << "failed to set auto allocation for cuFFT plan:" << ret; return port::Status(port::error::INTERNAL, @@ -234,9 +151,8 @@ port::Status CUDAFftPlan::Initialize( } switch (rank) { case 1: - ret = wrap::cufftMakePlan1d(parent, plan_, elem_count_[0], - CUDAFftType(type), /*batch=*/1, - &scratch_size_bytes_); + ret = cufftMakePlan1d(plan_, elem_count_[0], CUDAFftType(type), + /*batch=*/1, &scratch_size_bytes_); if (ret != CUFFT_SUCCESS) { LOG(ERROR) << "failed to make cuFFT 1d plan:" << ret; return port::Status(port::error::INTERNAL, @@ -244,9 +160,8 @@ port::Status CUDAFftPlan::Initialize( } break; case 2: - ret = wrap::cufftMakePlan2d(parent, plan_, elem_count_[0], - elem_count_[1], CUDAFftType(type), - &scratch_size_bytes_); + ret = cufftMakePlan2d(plan_, elem_count_[0], elem_count_[1], + CUDAFftType(type), &scratch_size_bytes_); if (ret != CUFFT_SUCCESS) { LOG(ERROR) << "failed to make cuFFT 2d plan:" << ret; return port::Status(port::error::INTERNAL, @@ -254,9 +169,9 @@ port::Status CUDAFftPlan::Initialize( } break; case 3: - ret = wrap::cufftMakePlan3d(parent, plan_, elem_count_[0], - elem_count_[1], elem_count_[2], - CUDAFftType(type), &scratch_size_bytes_); + ret = cufftMakePlan3d(plan_, elem_count_[0], elem_count_[1], + elem_count_[2], CUDAFftType(type), + &scratch_size_bytes_); if (ret != CUFFT_SUCCESS) { LOG(ERROR) << "failed to make cuFFT 3d plan:" << ret; return port::Status(port::error::INTERNAL, @@ -275,24 +190,23 @@ port::Status CUDAFftPlan::Initialize( } else { // For either multiple batches or rank higher than 3, use cufftPlanMany(). if (scratch_allocator == nullptr) { - auto ret = wrap::cufftPlanMany( - parent, &plan_, rank, elem_count_, - input_embed ? input_embed_ : nullptr, input_stride, input_distance, - output_embed ? output_embed_ : nullptr, output_stride, - output_distance, CUDAFftType(type), batch_count); + auto ret = cufftPlanMany( + &plan_, rank, elem_count_, input_embed ? input_embed_ : nullptr, + input_stride, input_distance, output_embed ? output_embed_ : nullptr, + output_stride, output_distance, CUDAFftType(type), batch_count); if (ret != CUFFT_SUCCESS) { LOG(ERROR) << "failed to create cuFFT batched plan:" << ret; return port::Status(port::error::INTERNAL, "Failed to create cuFFT batched plan."); } } else { - auto ret = wrap::cufftCreate(parent, &plan_); + auto ret = cufftCreate(&plan_); if (ret != CUFFT_SUCCESS) { LOG(ERROR) << "failed to create cuFFT batched plan:" << ret; return port::Status(port::error::INTERNAL, "Failed to create cuFFT batched plan."); } - ret = wrap::cufftSetAutoAllocation(parent, plan_, 0); + ret = cufftSetAutoAllocation(plan_, 0); if (ret != CUFFT_SUCCESS) { LOG(ERROR) << "failed to set auto allocation for cuFFT batched plan:" << ret; @@ -300,11 +214,10 @@ port::Status CUDAFftPlan::Initialize( port::error::INTERNAL, "Failed to set auto allocation for cuFFT batched plan."); } - ret = wrap::cufftMakePlanMany( - parent, plan_, rank, elem_count_, - input_embed ? input_embed_ : nullptr, input_stride, input_distance, - output_embed ? output_embed_ : nullptr, output_stride, - output_distance, CUDAFftType(type), batch_count, + ret = cufftMakePlanMany( + plan_, rank, elem_count_, input_embed ? input_embed_ : nullptr, + input_stride, input_distance, output_embed ? output_embed_ : nullptr, + output_stride, output_distance, CUDAFftType(type), batch_count, &scratch_size_bytes_); if (ret != CUFFT_SUCCESS) { LOG(ERROR) << "failed to make cuFFT batched plan:" << ret; @@ -339,7 +252,8 @@ port::Status CUDAFftPlan::UpdateScratchAllocator( } } // Connect work area with allocated space. - cufftResult_t ret = wrap::cufftSetWorkArea(parent_, plan_, scratch_.opaque()); + cuda::ScopedActivateExecutorContext sac(parent_); + cufftResult_t ret = cufftSetWorkArea(plan_, scratch_.opaque()); if (ret != CUFFT_SUCCESS) { LOG(ERROR) << "failed to set work area for cuFFT plan:" << ret; return port::Status(port::error::INTERNAL, @@ -348,7 +262,10 @@ port::Status CUDAFftPlan::UpdateScratchAllocator( return port::Status::OK(); } -CUDAFftPlan::~CUDAFftPlan() { wrap::cufftDestroy(parent_, plan_); } +CUDAFftPlan::~CUDAFftPlan() { + cuda::ScopedActivateExecutorContext sac(parent_); + cufftDestroy(plan_); +} int CUDAFftPlan::GetFftDirection() const { if (!IsInitialized()) { @@ -548,7 +465,8 @@ bool CUDAFft::DoFftInternal(Stream *stream, fft::Plan *plan, FuncT cufftExec, return false; } - auto ret = cufftExec(parent_, cuda_fft_plan->GetPlan(), + cuda::ScopedActivateExecutorContext sac(parent_); + auto ret = cufftExec(cuda_fft_plan->GetPlan(), GpuComplex(const_cast(GpuMemory(input))), GpuComplex(GpuMemoryMutable(output))); @@ -575,7 +493,8 @@ bool CUDAFft::DoFftWithDirectionInternal(Stream *stream, fft::Plan *plan, return false; } - auto ret = cufftExec(parent_, cuda_fft_plan->GetPlan(), + cuda::ScopedActivateExecutorContext sac(parent_); + auto ret = cufftExec(cuda_fft_plan->GetPlan(), GpuComplex(const_cast(GpuMemory(input))), GpuComplex(GpuMemoryMutable(output)), cuda_fft_plan->GetFftDirection()); @@ -588,25 +507,23 @@ bool CUDAFft::DoFftWithDirectionInternal(Stream *stream, fft::Plan *plan, return true; } -#define STREAM_EXECUTOR_CUDA_DEFINE_FFT(__type, __fft_type1, __fft_type2, \ - __fft_type3) \ - bool CUDAFft::DoFft(Stream *stream, fft::Plan *plan, \ - const DeviceMemory> &input, \ - DeviceMemory> *output) { \ - return DoFftWithDirectionInternal( \ - stream, plan, wrap::cufftExec##__fft_type1, input, output); \ - } \ - bool CUDAFft::DoFft(Stream *stream, fft::Plan *plan, \ - const DeviceMemory<__type> &input, \ - DeviceMemory> *output) { \ - return DoFftInternal(stream, plan, wrap::cufftExec##__fft_type2, input, \ - output); \ - } \ - bool CUDAFft::DoFft(Stream *stream, fft::Plan *plan, \ - const DeviceMemory> &input, \ - DeviceMemory<__type> *output) { \ - return DoFftInternal(stream, plan, wrap::cufftExec##__fft_type3, input, \ - output); \ +#define STREAM_EXECUTOR_CUDA_DEFINE_FFT(__type, __fft_type1, __fft_type2, \ + __fft_type3) \ + bool CUDAFft::DoFft(Stream *stream, fft::Plan *plan, \ + const DeviceMemory> &input, \ + DeviceMemory> *output) { \ + return DoFftWithDirectionInternal(stream, plan, cufftExec##__fft_type1, \ + input, output); \ + } \ + bool CUDAFft::DoFft(Stream *stream, fft::Plan *plan, \ + const DeviceMemory<__type> &input, \ + DeviceMemory> *output) { \ + return DoFftInternal(stream, plan, cufftExec##__fft_type2, input, output); \ + } \ + bool CUDAFft::DoFft(Stream *stream, fft::Plan *plan, \ + const DeviceMemory> &input, \ + DeviceMemory<__type> *output) { \ + return DoFftInternal(stream, plan, cufftExec##__fft_type3, input, output); \ } STREAM_EXECUTOR_CUDA_DEFINE_FFT(float, C2C, R2C, C2R) diff --git a/tensorflow/stream_executor/cuda/cuda_gpu_executor.cc b/tensorflow/stream_executor/cuda/cuda_gpu_executor.cc index 420f2591b8..5d3a9a5a06 100644 --- a/tensorflow/stream_executor/cuda/cuda_gpu_executor.cc +++ b/tensorflow/stream_executor/cuda/cuda_gpu_executor.cc @@ -28,7 +28,6 @@ limitations under the License. #include "absl/strings/string_view.h" #include "tensorflow/stream_executor/cuda/cuda_diagnostics.h" #include "tensorflow/stream_executor/cuda/cuda_driver.h" -#include "tensorflow/stream_executor/cuda/cuda_driver_wrapper.h" #include "tensorflow/stream_executor/cuda/cuda_event.h" #include "tensorflow/stream_executor/cuda/cuda_platform_id.h" #include "tensorflow/stream_executor/cuda/cuda_stream.h" @@ -513,7 +512,7 @@ int GpuExecutor::CalculateOccupancy(const DeviceDescription& device_description, CUfunction func) { int suggested_blocks = 0; int suggested_threads = 0; - CUresult err = tensorflow::wrap::cuOccupancyMaxPotentialBlockSize( + CUresult err = cuOccupancyMaxPotentialBlockSize( &suggested_blocks, &suggested_threads, func, nullptr, shared_memory_per_block, 0); CHECK_EQ(err, CUDA_SUCCESS); @@ -530,7 +529,7 @@ int GpuExecutor::CompareOccupancy(int* initial_blocks, CUfunction func) { int suggested_blocks = 0; int suggested_threads = 0; - CUresult err = tensorflow::wrap::cuOccupancyMaxPotentialBlockSize( + CUresult err = cuOccupancyMaxPotentialBlockSize( &suggested_blocks, &suggested_threads, func, nullptr, shared_memory_per_block, 0); CHECK_EQ(err, CUDA_SUCCESS); diff --git a/tensorflow/stream_executor/cuda/cuda_rng.cc b/tensorflow/stream_executor/cuda/cuda_rng.cc index 395b30b491..36eef0a07a 100644 --- a/tensorflow/stream_executor/cuda/cuda_rng.cc +++ b/tensorflow/stream_executor/cuda/cuda_rng.cc @@ -24,7 +24,6 @@ limitations under the License. #include "tensorflow/stream_executor/lib/env.h" #include "tensorflow/stream_executor/lib/initialize.h" #include "tensorflow/stream_executor/lib/status.h" -#include "tensorflow/stream_executor/platform/dso_loader.h" #include "tensorflow/stream_executor/platform/logging.h" #include "tensorflow/stream_executor/rng.h" // clang-format off @@ -62,65 +61,12 @@ namespace gpu { PLUGIN_REGISTRY_DEFINE_PLUGIN_ID(kGpuRandPlugin); -namespace wrap { - -#ifdef PLATFORM_GOOGLE -#define STREAM_EXECUTOR_CURAND_WRAP(__name) \ - struct WrapperShim__##__name { \ - template \ - curandStatus_t operator()(GpuExecutor* parent, Args... args) { \ - gpu::ScopedActivateExecutorContext sac{parent}; \ - return ::__name(args...); \ - } \ - } __name; - -#else -#define STREAM_EXECUTOR_CURAND_WRAP(__name) \ - struct DynLoadShim__##__name { \ - static const char* kName; \ - using FuncPtrT = std::add_pointer::type; \ - static void* GetDsoHandle() { \ - auto s = internal::CachedDsoLoader::GetCurandDsoHandle(); \ - return s.ValueOrDie(); \ - } \ - static FuncPtrT LoadOrDie() { \ - void* f; \ - auto s = port::Env::Default()->GetSymbolFromLibrary(GetDsoHandle(), \ - kName, &f); \ - CHECK(s.ok()) << "could not find " << kName \ - << " in curand DSO; dlerror: " << s.error_message(); \ - return reinterpret_cast(f); \ - } \ - static FuncPtrT DynLoad() { \ - static FuncPtrT f = LoadOrDie(); \ - return f; \ - } \ - template \ - curandStatus_t operator()(GpuExecutor* parent, Args... args) { \ - gpu::ScopedActivateExecutorContext sac{parent}; \ - return DynLoad()(args...); \ - } \ - } __name; \ - const char* DynLoadShim__##__name::kName = #__name; -#endif - -STREAM_EXECUTOR_CURAND_WRAP(curandCreateGenerator); -STREAM_EXECUTOR_CURAND_WRAP(curandDestroyGenerator); -STREAM_EXECUTOR_CURAND_WRAP(curandSetStream); -STREAM_EXECUTOR_CURAND_WRAP(curandGenerateUniform); -STREAM_EXECUTOR_CURAND_WRAP(curandGenerateUniformDouble); -STREAM_EXECUTOR_CURAND_WRAP(curandSetPseudoRandomGeneratorSeed); -STREAM_EXECUTOR_CURAND_WRAP(curandSetGeneratorOffset); -STREAM_EXECUTOR_CURAND_WRAP(curandGenerateNormal); -STREAM_EXECUTOR_CURAND_WRAP(curandGenerateNormalDouble); - -} // namespace wrap - GpuRng::GpuRng(GpuExecutor* parent) : parent_(parent), rng_(nullptr) {} GpuRng::~GpuRng() { if (rng_ != nullptr) { - wrap::curandDestroyGenerator(parent_, rng_); + cuda::ScopedActivateExecutorContext sac(parent_); + curandDestroyGenerator(rng_); } } @@ -128,8 +74,8 @@ bool GpuRng::Init() { mutex_lock lock(mu_); CHECK(rng_ == nullptr); - curandStatus_t ret = - wrap::curandCreateGenerator(parent_, &rng_, CURAND_RNG_PSEUDO_DEFAULT); + cuda::ScopedActivateExecutorContext sac(parent_); + curandStatus_t ret = curandCreateGenerator(&rng_, CURAND_RNG_PSEUDO_DEFAULT); if (ret != CURAND_STATUS_SUCCESS) { LOG(ERROR) << "failed to create random number generator: " << ret; return false; @@ -140,8 +86,8 @@ bool GpuRng::Init() { } bool GpuRng::SetStream(Stream* stream) { - curandStatus_t ret = - wrap::curandSetStream(parent_, rng_, AsGpuStreamValue(stream)); + cuda::ScopedActivateExecutorContext sac(parent_); + curandStatus_t ret = curandSetStream(rng_, AsGpuStreamValue(stream)); if (ret != CURAND_STATUS_SUCCESS) { LOG(ERROR) << "failed to set stream for random generation: " << ret; return false; @@ -175,16 +121,15 @@ bool GpuRng::DoPopulateRandUniformInternal(Stream* stream, DeviceMemory* v) { element_count *= 2; } + cuda::ScopedActivateExecutorContext sac(parent_); curandStatus_t ret; if (std::is_same::value || std::is_same>::value) { - ret = wrap::curandGenerateUniform( - parent_, rng_, reinterpret_cast(GpuMemoryMutable(v)), - element_count); + ret = curandGenerateUniform( + rng_, reinterpret_cast(GpuMemoryMutable(v)), element_count); } else { - ret = wrap::curandGenerateUniformDouble( - parent_, rng_, reinterpret_cast(GpuMemoryMutable(v)), - element_count); + ret = curandGenerateUniformDouble( + rng_, reinterpret_cast(GpuMemoryMutable(v)), element_count); } if (ret != CURAND_STATUS_SUCCESS) { LOG(ERROR) << "failed to do uniform generation of " << v->ElementCount() @@ -225,9 +170,10 @@ bool GpuRng::DoPopulateRandGaussianInternal(Stream* stream, ElemT mean, return false; } + cuda::ScopedActivateExecutorContext sac(parent_); uint64 element_count = v->ElementCount(); curandStatus_t ret = - func(parent_, rng_, GpuMemoryMutable(v), element_count, mean, stddev); + func(rng_, GpuMemoryMutable(v), element_count, mean, stddev); if (ret != CURAND_STATUS_SUCCESS) { LOG(ERROR) << "failed to do gaussian generation of " << v->ElementCount() @@ -241,13 +187,13 @@ bool GpuRng::DoPopulateRandGaussianInternal(Stream* stream, ElemT mean, bool GpuRng::DoPopulateRandGaussian(Stream* stream, float mean, float stddev, DeviceMemory* v) { return DoPopulateRandGaussianInternal(stream, mean, stddev, v, - wrap::curandGenerateNormal); + curandGenerateNormal); } bool GpuRng::DoPopulateRandGaussian(Stream* stream, double mean, double stddev, DeviceMemory* v) { return DoPopulateRandGaussianInternal(stream, mean, stddev, v, - wrap::curandGenerateNormalDouble); + curandGenerateNormalDouble); } bool GpuRng::SetSeed(Stream* stream, const uint8* seed, uint64 seed_bytes) { @@ -262,16 +208,17 @@ bool GpuRng::SetSeed(Stream* stream, const uint8* seed, uint64 seed_bytes) { return false; } + cuda::ScopedActivateExecutorContext sac(parent_); // Requires 8 bytes of seed data; checked in RngSupport::CheckSeed (above) // (which itself requires 16 for API consistency with host RNG fallbacks). - curandStatus_t ret = wrap::curandSetPseudoRandomGeneratorSeed( - parent_, rng_, *(reinterpret_cast(seed))); + curandStatus_t ret = curandSetPseudoRandomGeneratorSeed( + rng_, *(reinterpret_cast(seed))); if (ret != CURAND_STATUS_SUCCESS) { LOG(ERROR) << "failed to set rng seed: " << ret; return false; } - ret = wrap::curandSetGeneratorOffset(parent_, rng_, 0); + ret = curandSetGeneratorOffset(rng_, 0); if (ret != CURAND_STATUS_SUCCESS) { LOG(ERROR) << "failed to reset rng position: " << ret; return false; diff --git a/tensorflow/stream_executor/cuda/cuda_stub.cc b/tensorflow/stream_executor/cuda/cuda_stub.cc new file mode 100644 index 0000000000..f7777cae5b --- /dev/null +++ b/tensorflow/stream_executor/cuda/cuda_stub.cc @@ -0,0 +1,99 @@ +/* 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 "cuda/include/cuda.h" +#include "tensorflow/stream_executor/lib/env.h" +#include "tensorflow/stream_executor/platform/dso_loader.h" + +// Implements the CUDA driver API by forwarding to CUDA loaded from the DSO. + +namespace { +// Returns DSO handle or null if loading the DSO fails. +void* GetDsoHandle() { +#ifdef PLATFORM_GOOGLE + return nullptr; +#else + static auto handle = []() -> void* { + auto handle_or = + stream_executor::internal::DsoLoader::GetCudaDriverDsoHandle(); + if (!handle_or.ok()) return nullptr; + return handle_or.ValueOrDie(); + }(); + return handle; +#endif +} + +template +T LoadSymbol(const char* symbol_name) { + void* symbol = nullptr; + if (auto handle = GetDsoHandle()) { + stream_executor::port::Env::Default() + ->GetSymbolFromLibrary(handle, symbol_name, &symbol) + .IgnoreError(); + } + return reinterpret_cast(symbol); +} + +CUresult GetSymbolNotFoundError() { + return CUDA_ERROR_SHARED_OBJECT_INIT_FAILED; +} +} // namespace + +#if CUDA_VERSION < 8000 +#error CUDA version earlier than 8 is not supported. +#endif + +// Forward-declare types introduced in CUDA 9.0. +typedef struct CUDA_LAUNCH_PARAMS_st CUDA_LAUNCH_PARAMS; + +#ifndef __CUDA_DEPRECATED +#define __CUDA_DEPRECATED +#endif + +#if CUDA_VERSION < 10000 +// Define fake enums introduced in CUDA 10.0. +typedef enum CUgraphNodeType_enum {} CUgraphNodeType; +typedef enum CUstreamCaptureStatus_enum {} CUstreamCaptureStatus; +typedef enum CUexternalMemoryHandleType_enum {} CUexternalMemoryHandleType; +typedef enum CUexternalSemaphoreHandleType_enum { +} CUexternalSemaphoreHandleType; +#endif + +// Forward-declare types introduced in CUDA 10.0. +typedef struct CUextMemory_st* CUexternalMemory; +typedef struct CUextSemaphore_st* CUexternalSemaphore; +typedef struct CUgraph_st* CUgraph; +typedef struct CUgraphNode_st* CUgraphNode; +typedef struct CUgraphExec_st* CUgraphExec; +typedef struct CUDA_KERNEL_NODE_PARAMS_st CUDA_KERNEL_NODE_PARAMS; +typedef struct CUDA_MEMSET_NODE_PARAMS_st CUDA_MEMSET_NODE_PARAMS; +typedef struct CUDA_HOST_NODE_PARAMS_st CUDA_HOST_NODE_PARAMS; +typedef struct CUDA_EXTERNAL_MEMORY_HANDLE_DESC_st + CUDA_EXTERNAL_MEMORY_HANDLE_DESC; +typedef struct CUDA_EXTERNAL_MEMORY_BUFFER_DESC_st + CUDA_EXTERNAL_MEMORY_BUFFER_DESC; +typedef struct CUDA_EXTERNAL_MEMORY_MIPMAPPED_ARRAY_DESC_st + CUDA_EXTERNAL_MEMORY_MIPMAPPED_ARRAY_DESC; +typedef struct CUDA_EXTERNAL_SEMAPHORE_HANDLE_DESC_st + CUDA_EXTERNAL_SEMAPHORE_HANDLE_DESC; +typedef struct CUDA_EXTERNAL_SEMAPHORE_SIGNAL_PARAMS_st + CUDA_EXTERNAL_SEMAPHORE_SIGNAL_PARAMS; +typedef struct CUDA_EXTERNAL_SEMAPHORE_WAIT_PARAMS_st + CUDA_EXTERNAL_SEMAPHORE_WAIT_PARAMS; +typedef void(CUDA_CB* CUhostFn)(void* userData); + +// We only need one stub implementation. Calling a function that is not yet +// available in the given CUDA version will return +// CUDA_ERROR_SHARED_OBJECT_INIT_FAILED. +#include "tensorflow/stream_executor/cuda/cuda_10_0.inc" diff --git a/tensorflow/stream_executor/cuda/cudnn_6_0.inc b/tensorflow/stream_executor/cuda/cudnn_6_0.inc new file mode 100644 index 0000000000..e9c51d6057 --- /dev/null +++ b/tensorflow/stream_executor/cuda/cudnn_6_0.inc @@ -0,0 +1,1773 @@ +// Auto-generated, do not edit. + +extern "C" { + +size_t CUDNNWINAPI cudnnGetVersion(void) { + using FuncPtr = size_t (CUDNNWINAPI *)(); + static auto func_ptr = LoadSymbol("cudnnGetVersion"); + if (!func_ptr) return 0; + return func_ptr(); +} + +size_t CUDNNWINAPI cudnnGetCudartVersion(void) { + using FuncPtr = size_t (CUDNNWINAPI *)(); + static auto func_ptr = LoadSymbol("cudnnGetCudartVersion"); + if (!func_ptr) return 0; + return func_ptr(); +} + +const char * CUDNNWINAPI cudnnGetErrorString(cudnnStatus_t status) { + using FuncPtr = const char * (CUDNNWINAPI *)(cudnnStatus_t); + static auto func_ptr = LoadSymbol("cudnnGetErrorString"); + if (!func_ptr) return "cudnnGetErrorString symbol not found."; + return func_ptr(status); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetProperty(libraryPropertyType type, int *value) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(libraryPropertyType, int *); + static auto func_ptr = LoadSymbol("cudnnGetProperty"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(type, value); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreate (cudnnHandle_t *handle) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t *); + static auto func_ptr = LoadSymbol("cudnnCreate"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroy (cudnnHandle_t handle) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t); + static auto func_ptr = LoadSymbol("cudnnDestroy"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetStream (cudnnHandle_t handle, cudaStream_t streamId) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudaStream_t); + static auto func_ptr = LoadSymbol("cudnnSetStream"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, streamId); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetStream (cudnnHandle_t handle, cudaStream_t *streamId) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudaStream_t *); + static auto func_ptr = LoadSymbol("cudnnGetStream"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, streamId); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateTensorDescriptor( + cudnnTensorDescriptor_t *tensorDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetTensor4dDescriptor( + cudnnTensorDescriptor_t tensorDesc, + cudnnTensorFormat_t format, + cudnnDataType_t dataType, // image data type + int n, // number of inputs (batch size) + int c, // number of input feature maps + int h, // height of input section + int w ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t, cudnnTensorFormat_t, cudnnDataType_t, int, int, int, int); + static auto func_ptr = LoadSymbol("cudnnSetTensor4dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, format, dataType, n, c, h, w); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetTensor4dDescriptorEx( + cudnnTensorDescriptor_t tensorDesc, + cudnnDataType_t dataType, // image data type + int n, // number of inputs (batch size) + int c, // number of input feature maps + int h, // height of input section + int w, // width of input section + int nStride, + int cStride, + int hStride, + int wStride ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t, cudnnDataType_t, int, int, int, int, int, int, int, int); + static auto func_ptr = LoadSymbol("cudnnSetTensor4dDescriptorEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, dataType, n, c, h, w, nStride, cStride, hStride, wStride); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetTensor4dDescriptor( + const cudnnTensorDescriptor_t tensorDesc, + cudnnDataType_t *dataType, // image data type + int *n, // number of inputs (batch size) + int *c, // number of input feature maps + int *h, // height of input section + int *w, // width of input section + int *nStride, + int *cStride, + int *hStride, + int *wStride ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnTensorDescriptor_t, cudnnDataType_t *, int *, int *, int *, int *, int *, int *, int *, int *); + static auto func_ptr = LoadSymbol("cudnnGetTensor4dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, dataType, n, c, h, w, nStride, cStride, hStride, wStride); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetTensorNdDescriptor( + cudnnTensorDescriptor_t tensorDesc, + cudnnDataType_t dataType, + int nbDims, + const int dimA[], + const int strideA[] ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t, cudnnDataType_t, int, const int [], const int []); + static auto func_ptr = LoadSymbol("cudnnSetTensorNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, dataType, nbDims, dimA, strideA); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetTensorNdDescriptorEx( + cudnnTensorDescriptor_t tensorDesc, + cudnnTensorFormat_t format, + cudnnDataType_t dataType, + int nbDims, + const int dimA[] ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t, cudnnTensorFormat_t, cudnnDataType_t, int, const int []); + static auto func_ptr = LoadSymbol("cudnnSetTensorNdDescriptorEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, format, dataType, nbDims, dimA); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetTensorNdDescriptor( + const cudnnTensorDescriptor_t tensorDesc, + int nbDimsRequested, + cudnnDataType_t *dataType, + int *nbDims, + int dimA[], + int strideA[] ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnTensorDescriptor_t, int, cudnnDataType_t *, int *, int [], int []); + static auto func_ptr = LoadSymbol("cudnnGetTensorNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, nbDimsRequested, dataType, nbDims, dimA, strideA); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetTensorSizeInBytes( + const cudnnTensorDescriptor_t tensorDesc, + size_t *size) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnTensorDescriptor_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetTensorSizeInBytes"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, size); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyTensorDescriptor( + cudnnTensorDescriptor_t tensorDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnTransformTensor( + cudnnHandle_t handle, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnTransformTensor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha, xDesc, x, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI cudnnAddTensor( + cudnnHandle_t handle, + const void *alpha, + const cudnnTensorDescriptor_t aDesc, + const void *A, + const void *beta, + const cudnnTensorDescriptor_t cDesc, + void *C ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnAddTensor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha, aDesc, A, beta, cDesc, C); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateOpTensorDescriptor( + cudnnOpTensorDescriptor_t *opTensorDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnOpTensorDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateOpTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(opTensorDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetOpTensorDescriptor( + cudnnOpTensorDescriptor_t opTensorDesc, + cudnnOpTensorOp_t opTensorOp, + cudnnDataType_t opTensorCompType, + cudnnNanPropagation_t opTensorNanOpt ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnOpTensorDescriptor_t, cudnnOpTensorOp_t, cudnnDataType_t, cudnnNanPropagation_t); + static auto func_ptr = LoadSymbol("cudnnSetOpTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(opTensorDesc, opTensorOp, opTensorCompType, opTensorNanOpt); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetOpTensorDescriptor( + const cudnnOpTensorDescriptor_t opTensorDesc, + cudnnOpTensorOp_t *opTensorOp, + cudnnDataType_t *opTensorCompType, + cudnnNanPropagation_t *opTensorNanOpt ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnOpTensorDescriptor_t, cudnnOpTensorOp_t *, cudnnDataType_t *, cudnnNanPropagation_t *); + static auto func_ptr = LoadSymbol("cudnnGetOpTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(opTensorDesc, opTensorOp, opTensorCompType, opTensorNanOpt); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyOpTensorDescriptor( + cudnnOpTensorDescriptor_t opTensorDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnOpTensorDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyOpTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(opTensorDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnOpTensor( + cudnnHandle_t handle, + const cudnnOpTensorDescriptor_t opTensorDesc, + const void *alpha1, + const cudnnTensorDescriptor_t aDesc, + const void *A, + const void *alpha2, + const cudnnTensorDescriptor_t bDesc, + const void *B, + const void *beta, + const cudnnTensorDescriptor_t cDesc, + void *C ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnOpTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnOpTensor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, opTensorDesc, alpha1, aDesc, A, alpha2, bDesc, B, beta, cDesc, C); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateReduceTensorDescriptor( + cudnnReduceTensorDescriptor_t *reduceTensorDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnReduceTensorDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateReduceTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(reduceTensorDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetReduceTensorDescriptor( + cudnnReduceTensorDescriptor_t reduceTensorDesc, + cudnnReduceTensorOp_t reduceTensorOp, + cudnnDataType_t reduceTensorCompType, + cudnnNanPropagation_t reduceTensorNanOpt, + cudnnReduceTensorIndices_t reduceTensorIndices, + cudnnIndicesType_t reduceTensorIndicesType ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnReduceTensorDescriptor_t, cudnnReduceTensorOp_t, cudnnDataType_t, cudnnNanPropagation_t, cudnnReduceTensorIndices_t, cudnnIndicesType_t); + static auto func_ptr = LoadSymbol("cudnnSetReduceTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(reduceTensorDesc, reduceTensorOp, reduceTensorCompType, reduceTensorNanOpt, reduceTensorIndices, reduceTensorIndicesType); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetReduceTensorDescriptor( + const cudnnReduceTensorDescriptor_t reduceTensorDesc, + cudnnReduceTensorOp_t *reduceTensorOp, + cudnnDataType_t *reduceTensorCompType, + cudnnNanPropagation_t *reduceTensorNanOpt, + cudnnReduceTensorIndices_t *reduceTensorIndices, + cudnnIndicesType_t *reduceTensorIndicesType ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnReduceTensorDescriptor_t, cudnnReduceTensorOp_t *, cudnnDataType_t *, cudnnNanPropagation_t *, cudnnReduceTensorIndices_t *, cudnnIndicesType_t *); + static auto func_ptr = LoadSymbol("cudnnGetReduceTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(reduceTensorDesc, reduceTensorOp, reduceTensorCompType, reduceTensorNanOpt, reduceTensorIndices, reduceTensorIndicesType); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyReduceTensorDescriptor( + cudnnReduceTensorDescriptor_t reduceTensorDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnReduceTensorDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyReduceTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(reduceTensorDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetReductionIndicesSize( + cudnnHandle_t handle, + const cudnnReduceTensorDescriptor_t reduceTensorDesc, + const cudnnTensorDescriptor_t aDesc, + const cudnnTensorDescriptor_t cDesc, + size_t *sizeInBytes ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnReduceTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetReductionIndicesSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, reduceTensorDesc, aDesc, cDesc, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetReductionWorkspaceSize( + cudnnHandle_t handle, + const cudnnReduceTensorDescriptor_t reduceTensorDesc, + const cudnnTensorDescriptor_t aDesc, + const cudnnTensorDescriptor_t cDesc, + size_t *sizeInBytes ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnReduceTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetReductionWorkspaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, reduceTensorDesc, aDesc, cDesc, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnReduceTensor( + cudnnHandle_t handle, + const cudnnReduceTensorDescriptor_t reduceTensorDesc, + void *indices, + size_t indicesSizeInBytes, + void *workspace, + size_t workspaceSizeInBytes, + const void *alpha, + const cudnnTensorDescriptor_t aDesc, + const void *A, + const void *beta, + const cudnnTensorDescriptor_t cDesc, + void *C ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnReduceTensorDescriptor_t, void *, size_t, void *, size_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnReduceTensor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, reduceTensorDesc, indices, indicesSizeInBytes, workspace, workspaceSizeInBytes, alpha, aDesc, A, beta, cDesc, C); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetTensor( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t yDesc, + void *y, + const void *valuePtr ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, void *, const void *); + static auto func_ptr = LoadSymbol("cudnnSetTensor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, yDesc, y, valuePtr); +} + +cudnnStatus_t CUDNNWINAPI cudnnScaleTensor( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t yDesc, + void *y, + const void *alpha ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, void *, const void *); + static auto func_ptr = LoadSymbol("cudnnScaleTensor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, yDesc, y, alpha); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateFilterDescriptor( + cudnnFilterDescriptor_t *filterDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnFilterDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateFilterDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(filterDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetFilter4dDescriptor( + cudnnFilterDescriptor_t filterDesc, + cudnnDataType_t dataType, // image data type + cudnnTensorFormat_t format, + int k, // number of output feature maps + int c, // number of input feature maps + int h, // height of each input filter + int w ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnFilterDescriptor_t, cudnnDataType_t, cudnnTensorFormat_t, int, int, int, int); + static auto func_ptr = LoadSymbol("cudnnSetFilter4dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(filterDesc, dataType, format, k, c, h, w); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetFilter4dDescriptor( + const cudnnFilterDescriptor_t filterDesc, + cudnnDataType_t *dataType, // image data type + cudnnTensorFormat_t *format, + int *k, // number of output feature maps + int *c, // number of input feature maps + int *h, // height of each input filter + int *w ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnFilterDescriptor_t, cudnnDataType_t *, cudnnTensorFormat_t *, int *, int *, int *, int *); + static auto func_ptr = LoadSymbol("cudnnGetFilter4dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(filterDesc, dataType, format, k, c, h, w); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetFilterNdDescriptor( + cudnnFilterDescriptor_t filterDesc, + cudnnDataType_t dataType, // image data type + cudnnTensorFormat_t format, + int nbDims, + const int filterDimA[] ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnFilterDescriptor_t, cudnnDataType_t, cudnnTensorFormat_t, int, const int []); + static auto func_ptr = LoadSymbol("cudnnSetFilterNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(filterDesc, dataType, format, nbDims, filterDimA); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetFilterNdDescriptor( + const cudnnFilterDescriptor_t filterDesc, + int nbDimsRequested, + cudnnDataType_t *dataType, // image data type + cudnnTensorFormat_t *format, + int *nbDims, + int filterDimA[] ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnFilterDescriptor_t, int, cudnnDataType_t *, cudnnTensorFormat_t *, int *, int []); + static auto func_ptr = LoadSymbol("cudnnGetFilterNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(filterDesc, nbDimsRequested, dataType, format, nbDims, filterDimA); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyFilterDescriptor( + cudnnFilterDescriptor_t filterDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnFilterDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyFilterDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(filterDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateConvolutionDescriptor( + cudnnConvolutionDescriptor_t *convDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateConvolutionDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetConvolution2dDescriptor( cudnnConvolutionDescriptor_t convDesc, + int pad_h, // zero-padding height + int pad_w, // zero-padding width + int u, // vertical filter stride + int v, // horizontal filter stride + int dilation_h, // filter dilation in the vertical dimension + int dilation_w, // filter dilation in the horizontal dimension + cudnnConvolutionMode_t mode, + cudnnDataType_t computeType + ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t, int, int, int, int, int, int, cudnnConvolutionMode_t, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnSetConvolution2dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, pad_h, pad_w, u, v, dilation_h, dilation_w, mode, computeType); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolution2dDescriptor( const cudnnConvolutionDescriptor_t convDesc, + int* pad_h, // zero-padding height + int* pad_w, // zero-padding width + int* u, // vertical filter stride + int* v, // horizontal filter stride + int* dilation_h, // filter dilation in the vertical dimension + int* dilation_w, // filter dilation in the horizontal dimension + cudnnConvolutionMode_t* mode, + cudnnDataType_t *computeType + ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnConvolutionDescriptor_t, int *, int *, int *, int *, int *, int *, cudnnConvolutionMode_t *, cudnnDataType_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolution2dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, pad_h, pad_w, u, v, dilation_h, dilation_w, mode, computeType); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolution2dForwardOutputDim( + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t inputTensorDesc, + const cudnnFilterDescriptor_t filterDesc, + int *n, + int *c, + int *h, + int *w ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, int *, int *, int *, int *); + static auto func_ptr = LoadSymbol("cudnnGetConvolution2dForwardOutputDim"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, inputTensorDesc, filterDesc, n, c, h, w); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetConvolutionNdDescriptor( + cudnnConvolutionDescriptor_t convDesc, + int arrayLength, /* nbDims-2 size */ + const int padA[], + const int filterStrideA[], + const int dilationA[], + cudnnConvolutionMode_t mode, + cudnnDataType_t computeType ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t, int, const int [], const int [], const int [], cudnnConvolutionMode_t, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnSetConvolutionNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, arrayLength, padA, filterStrideA, dilationA, mode, computeType); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionNdDescriptor( + const cudnnConvolutionDescriptor_t convDesc, + int arrayLengthRequested, + int *arrayLength, + int padA[], + int strideA[], + int dilationA[], + cudnnConvolutionMode_t *mode, + cudnnDataType_t *computeType ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnConvolutionDescriptor_t, int, int *, int [], int [], int [], cudnnConvolutionMode_t *, cudnnDataType_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, arrayLengthRequested, arrayLength, padA, strideA, dilationA, mode, computeType); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionNdForwardOutputDim( + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t inputTensorDesc, + const cudnnFilterDescriptor_t filterDesc, + int nbDims, + int tensorOuputDimA[] ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, int, int []); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionNdForwardOutputDim"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, inputTensorDesc, filterDesc, nbDims, tensorOuputDimA); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyConvolutionDescriptor( + cudnnConvolutionDescriptor_t convDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyConvolutionDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnFindConvolutionForwardAlgorithm( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnFilterDescriptor_t wDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t yDesc, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionFwdAlgoPerf_t *perfResults ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, const int, int *, cudnnConvolutionFwdAlgoPerf_t *); + static auto func_ptr = LoadSymbol("cudnnFindConvolutionForwardAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, wDesc, convDesc, yDesc, requestedAlgoCount, returnedAlgoCount, perfResults); +} + +cudnnStatus_t CUDNNWINAPI cudnnFindConvolutionForwardAlgorithmEx( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t yDesc, + void *y, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionFwdAlgoPerf_t *perfResults, + void *workSpace, + size_t workSpaceSizeInBytes ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, void *, const int, int *, cudnnConvolutionFwdAlgoPerf_t *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnFindConvolutionForwardAlgorithmEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, x, wDesc, w, convDesc, yDesc, y, requestedAlgoCount, returnedAlgoCount, perfResults, workSpace, workSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionForwardAlgorithm( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnFilterDescriptor_t wDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t yDesc, + cudnnConvolutionFwdPreference_t preference, + size_t memoryLimitInBytes, + cudnnConvolutionFwdAlgo_t *algo ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, cudnnConvolutionFwdPreference_t, size_t, cudnnConvolutionFwdAlgo_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionForwardAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, wDesc, convDesc, yDesc, preference, memoryLimitInBytes, algo); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionForwardWorkspaceSize( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnFilterDescriptor_t wDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t yDesc, + cudnnConvolutionFwdAlgo_t algo, + size_t *sizeInBytes ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, cudnnConvolutionFwdAlgo_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionForwardWorkspaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, wDesc, convDesc, yDesc, algo, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnConvolutionForward( + cudnnHandle_t handle, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnConvolutionDescriptor_t convDesc, + cudnnConvolutionFwdAlgo_t algo, + void *workSpace, + size_t workSpaceSizeInBytes, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, cudnnConvolutionFwdAlgo_t, void *, size_t, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnConvolutionForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha, xDesc, x, wDesc, w, convDesc, algo, workSpace, workSpaceSizeInBytes, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI cudnnConvolutionBiasActivationForward( + cudnnHandle_t handle, + const void *alpha1, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnConvolutionDescriptor_t convDesc, + cudnnConvolutionFwdAlgo_t algo, + void *workSpace, + size_t workSpaceSizeInBytes, + const void *alpha2, + const cudnnTensorDescriptor_t zDesc, + const void *z, + const cudnnTensorDescriptor_t biasDesc, + const void *bias, + const cudnnActivationDescriptor_t activationDesc, + const cudnnTensorDescriptor_t yDesc, + void *y ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, cudnnConvolutionFwdAlgo_t, void *, size_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnActivationDescriptor_t, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnConvolutionBiasActivationForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha1, xDesc, x, wDesc, w, convDesc, algo, workSpace, workSpaceSizeInBytes, alpha2, zDesc, z, biasDesc, bias, activationDesc, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI cudnnConvolutionBackwardBias( + cudnnHandle_t handle, + const void *alpha, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const void *beta, + const cudnnTensorDescriptor_t dbDesc, + void *db ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnConvolutionBackwardBias"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha, dyDesc, dy, beta, dbDesc, db); +} + +cudnnStatus_t CUDNNWINAPI cudnnFindConvolutionBackwardFilterAlgorithm( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnFilterDescriptor_t dwDesc, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionBwdFilterAlgoPerf_t *perfResults ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnFilterDescriptor_t, const int, int *, cudnnConvolutionBwdFilterAlgoPerf_t *); + static auto func_ptr = LoadSymbol("cudnnFindConvolutionBackwardFilterAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, dyDesc, convDesc, dwDesc, requestedAlgoCount, returnedAlgoCount, perfResults); +} + +cudnnStatus_t CUDNNWINAPI cudnnFindConvolutionBackwardFilterAlgorithmEx( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnTensorDescriptor_t dyDesc, + const void *y, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnFilterDescriptor_t dwDesc, + void *dw, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionBwdFilterAlgoPerf_t *perfResults, + void *workSpace, + size_t workSpaceSizeInBytes ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, const cudnnFilterDescriptor_t, void *, const int, int *, cudnnConvolutionBwdFilterAlgoPerf_t *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnFindConvolutionBackwardFilterAlgorithmEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, x, dyDesc, y, convDesc, dwDesc, dw, requestedAlgoCount, returnedAlgoCount, perfResults, workSpace, workSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionBackwardFilterAlgorithm( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnFilterDescriptor_t dwDesc, + cudnnConvolutionBwdFilterPreference_t preference, + size_t memoryLimitInBytes, + cudnnConvolutionBwdFilterAlgo_t *algo ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnFilterDescriptor_t, cudnnConvolutionBwdFilterPreference_t, size_t, cudnnConvolutionBwdFilterAlgo_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardFilterAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, dyDesc, convDesc, dwDesc, preference, memoryLimitInBytes, algo); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionBackwardFilterWorkspaceSize( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnFilterDescriptor_t gradDesc, + cudnnConvolutionBwdFilterAlgo_t algo, + size_t *sizeInBytes ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnFilterDescriptor_t, cudnnConvolutionBwdFilterAlgo_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardFilterWorkspaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, dyDesc, convDesc, gradDesc, algo, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnConvolutionBackwardFilter( + cudnnHandle_t handle, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnConvolutionDescriptor_t convDesc, + cudnnConvolutionBwdFilterAlgo_t algo, + void *workSpace, + size_t workSpaceSizeInBytes, + const void *beta, + const cudnnFilterDescriptor_t dwDesc, + void *dw ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, cudnnConvolutionBwdFilterAlgo_t, void *, size_t, const void *, const cudnnFilterDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnConvolutionBackwardFilter"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha, xDesc, x, dyDesc, dy, convDesc, algo, workSpace, workSpaceSizeInBytes, beta, dwDesc, dw); +} + +cudnnStatus_t CUDNNWINAPI cudnnFindConvolutionBackwardDataAlgorithm( + cudnnHandle_t handle, + const cudnnFilterDescriptor_t wDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t dxDesc, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionBwdDataAlgoPerf_t *perfResults ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnFilterDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, const int, int *, cudnnConvolutionBwdDataAlgoPerf_t *); + static auto func_ptr = LoadSymbol("cudnnFindConvolutionBackwardDataAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, wDesc, dyDesc, convDesc, dxDesc, requestedAlgoCount, returnedAlgoCount, perfResults); +} + +cudnnStatus_t CUDNNWINAPI cudnnFindConvolutionBackwardDataAlgorithmEx( + cudnnHandle_t handle, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t dxDesc, + void *dx, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionBwdDataAlgoPerf_t *perfResults, + void *workSpace, + size_t workSpaceSizeInBytes ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, void *, const int, int *, cudnnConvolutionBwdDataAlgoPerf_t *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnFindConvolutionBackwardDataAlgorithmEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, wDesc, w, dyDesc, dy, convDesc, dxDesc, dx, requestedAlgoCount, returnedAlgoCount, perfResults, workSpace, workSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionBackwardDataAlgorithm( + cudnnHandle_t handle, + const cudnnFilterDescriptor_t wDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t dxDesc, + cudnnConvolutionBwdDataPreference_t preference, + size_t memoryLimitInBytes, + cudnnConvolutionBwdDataAlgo_t *algo ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnFilterDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, cudnnConvolutionBwdDataPreference_t, size_t, cudnnConvolutionBwdDataAlgo_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardDataAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, wDesc, dyDesc, convDesc, dxDesc, preference, memoryLimitInBytes, algo); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionBackwardDataWorkspaceSize( + cudnnHandle_t handle, + const cudnnFilterDescriptor_t wDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t dxDesc, + cudnnConvolutionBwdDataAlgo_t algo, + size_t *sizeInBytes ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnFilterDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, cudnnConvolutionBwdDataAlgo_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardDataWorkspaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, wDesc, dyDesc, convDesc, dxDesc, algo, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnConvolutionBackwardData( + cudnnHandle_t handle, + const void *alpha, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnConvolutionDescriptor_t convDesc, + cudnnConvolutionBwdDataAlgo_t algo, + void *workSpace, + size_t workSpaceSizeInBytes, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, cudnnConvolutionBwdDataAlgo_t, void *, size_t, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnConvolutionBackwardData"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha, wDesc, w, dyDesc, dy, convDesc, algo, workSpace, workSpaceSizeInBytes, beta, dxDesc, dx); +} + +cudnnStatus_t CUDNNWINAPI cudnnIm2Col( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnFilterDescriptor_t wDesc, + const cudnnConvolutionDescriptor_t convDesc, + void *colBuffer ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const cudnnConvolutionDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnIm2Col"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, x, wDesc, convDesc, colBuffer); +} + +cudnnStatus_t CUDNNWINAPI cudnnSoftmaxForward( + cudnnHandle_t handle, + cudnnSoftmaxAlgorithm_t algo, + cudnnSoftmaxMode_t mode, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnSoftmaxAlgorithm_t, cudnnSoftmaxMode_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnSoftmaxForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, algo, mode, alpha, xDesc, x, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI cudnnSoftmaxBackward( + cudnnHandle_t handle, + cudnnSoftmaxAlgorithm_t algo, + cudnnSoftmaxMode_t mode, + const void *alpha, + const cudnnTensorDescriptor_t yDesc, + const void *y, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnSoftmaxAlgorithm_t, cudnnSoftmaxMode_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnSoftmaxBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, algo, mode, alpha, yDesc, y, dyDesc, dy, beta, dxDesc, dx); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreatePoolingDescriptor( + cudnnPoolingDescriptor_t *poolingDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnPoolingDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreatePoolingDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetPooling2dDescriptor( + cudnnPoolingDescriptor_t poolingDesc, + cudnnPoolingMode_t mode, + cudnnNanPropagation_t maxpoolingNanOpt, + int windowHeight, + int windowWidth, + int verticalPadding, + int horizontalPadding, + int verticalStride, + int horizontalStride ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnPoolingDescriptor_t, cudnnPoolingMode_t, cudnnNanPropagation_t, int, int, int, int, int, int); + static auto func_ptr = LoadSymbol("cudnnSetPooling2dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc, mode, maxpoolingNanOpt, windowHeight, windowWidth, verticalPadding, horizontalPadding, verticalStride, horizontalStride); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetPooling2dDescriptor( + const cudnnPoolingDescriptor_t poolingDesc, + cudnnPoolingMode_t *mode, + cudnnNanPropagation_t *maxpoolingNanOpt, + int *windowHeight, + int *windowWidth, + int *verticalPadding, + int *horizontalPadding, + int *verticalStride, + int *horizontalStride ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnPoolingDescriptor_t, cudnnPoolingMode_t *, cudnnNanPropagation_t *, int *, int *, int *, int *, int *, int *); + static auto func_ptr = LoadSymbol("cudnnGetPooling2dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc, mode, maxpoolingNanOpt, windowHeight, windowWidth, verticalPadding, horizontalPadding, verticalStride, horizontalStride); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetPoolingNdDescriptor( + cudnnPoolingDescriptor_t poolingDesc, + const cudnnPoolingMode_t mode, + const cudnnNanPropagation_t maxpoolingNanOpt, + int nbDims, + const int windowDimA[], + const int paddingA[], + const int strideA[] ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnPoolingDescriptor_t, const cudnnPoolingMode_t, const cudnnNanPropagation_t, int, const int [], const int [], const int []); + static auto func_ptr = LoadSymbol("cudnnSetPoolingNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc, mode, maxpoolingNanOpt, nbDims, windowDimA, paddingA, strideA); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetPoolingNdDescriptor( + const cudnnPoolingDescriptor_t poolingDesc, + int nbDimsRequested, + cudnnPoolingMode_t *mode, + cudnnNanPropagation_t *maxpoolingNanOpt, + int *nbDims, + int windowDimA[], + int paddingA[], + int strideA[] ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnPoolingDescriptor_t, int, cudnnPoolingMode_t *, cudnnNanPropagation_t *, int *, int [], int [], int []); + static auto func_ptr = LoadSymbol("cudnnGetPoolingNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc, nbDimsRequested, mode, maxpoolingNanOpt, nbDims, windowDimA, paddingA, strideA); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetPoolingNdForwardOutputDim( + const cudnnPoolingDescriptor_t poolingDesc, + const cudnnTensorDescriptor_t inputTensorDesc, + int nbDims, + int outputTensorDimA[] ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnPoolingDescriptor_t, const cudnnTensorDescriptor_t, int, int []); + static auto func_ptr = LoadSymbol("cudnnGetPoolingNdForwardOutputDim"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc, inputTensorDesc, nbDims, outputTensorDimA); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetPooling2dForwardOutputDim( + const cudnnPoolingDescriptor_t poolingDesc, + const cudnnTensorDescriptor_t inputTensorDesc, + int *n, + int *c, + int *h, + int *w ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnPoolingDescriptor_t, const cudnnTensorDescriptor_t, int *, int *, int *, int *); + static auto func_ptr = LoadSymbol("cudnnGetPooling2dForwardOutputDim"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc, inputTensorDesc, n, c, h, w); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyPoolingDescriptor( + cudnnPoolingDescriptor_t poolingDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnPoolingDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyPoolingDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnPoolingForward( + cudnnHandle_t handle, + const cudnnPoolingDescriptor_t poolingDesc, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnPoolingDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnPoolingForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, poolingDesc, alpha, xDesc, x, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI cudnnPoolingBackward( + cudnnHandle_t handle, + const cudnnPoolingDescriptor_t poolingDesc, + const void *alpha, + const cudnnTensorDescriptor_t yDesc, + const void *y, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnPoolingDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnPoolingBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, poolingDesc, alpha, yDesc, y, dyDesc, dy, xDesc, x, beta, dxDesc, dx); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateActivationDescriptor( + cudnnActivationDescriptor_t *activationDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnActivationDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateActivationDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(activationDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetActivationDescriptor( + cudnnActivationDescriptor_t activationDesc, + cudnnActivationMode_t mode, + cudnnNanPropagation_t reluNanOpt, + double coef ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnActivationDescriptor_t, cudnnActivationMode_t, cudnnNanPropagation_t, double); + static auto func_ptr = LoadSymbol("cudnnSetActivationDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(activationDesc, mode, reluNanOpt, coef); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetActivationDescriptor( + const cudnnActivationDescriptor_t activationDesc, + cudnnActivationMode_t *mode, + cudnnNanPropagation_t *reluNanOpt, + double* coef ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnActivationDescriptor_t, cudnnActivationMode_t *, cudnnNanPropagation_t *, double *); + static auto func_ptr = LoadSymbol("cudnnGetActivationDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(activationDesc, mode, reluNanOpt, coef); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyActivationDescriptor( + cudnnActivationDescriptor_t activationDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnActivationDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyActivationDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(activationDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnActivationForward( + cudnnHandle_t handle, + cudnnActivationDescriptor_t activationDesc, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnActivationDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnActivationForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, activationDesc, alpha, xDesc, x, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI cudnnActivationBackward( + cudnnHandle_t handle, + cudnnActivationDescriptor_t activationDesc, + const void *alpha, + const cudnnTensorDescriptor_t yDesc, + const void *y, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnActivationDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnActivationBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, activationDesc, alpha, yDesc, y, dyDesc, dy, xDesc, x, beta, dxDesc, dx); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateLRNDescriptor( + cudnnLRNDescriptor_t *normDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnLRNDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateLRNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(normDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetLRNDescriptor( + cudnnLRNDescriptor_t normDesc, + unsigned lrnN, + double lrnAlpha, + double lrnBeta, + double lrnK ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnLRNDescriptor_t, unsigned int, double, double, double); + static auto func_ptr = LoadSymbol("cudnnSetLRNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(normDesc, lrnN, lrnAlpha, lrnBeta, lrnK); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetLRNDescriptor( + cudnnLRNDescriptor_t normDesc, + unsigned* lrnN, + double* lrnAlpha, + double* lrnBeta, + double* lrnK ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnLRNDescriptor_t, unsigned int *, double *, double *, double *); + static auto func_ptr = LoadSymbol("cudnnGetLRNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(normDesc, lrnN, lrnAlpha, lrnBeta, lrnK); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyLRNDescriptor( cudnnLRNDescriptor_t lrnDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnLRNDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyLRNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(lrnDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnLRNCrossChannelForward( + cudnnHandle_t handle, + cudnnLRNDescriptor_t normDesc, + cudnnLRNMode_t lrnMode, + const void* alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnLRNDescriptor_t, cudnnLRNMode_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnLRNCrossChannelForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, normDesc, lrnMode, alpha, xDesc, x, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI cudnnLRNCrossChannelBackward( + cudnnHandle_t handle, + cudnnLRNDescriptor_t normDesc, + cudnnLRNMode_t lrnMode, + const void* alpha, + const cudnnTensorDescriptor_t yDesc, + const void *y, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnLRNDescriptor_t, cudnnLRNMode_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnLRNCrossChannelBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, normDesc, lrnMode, alpha, yDesc, y, dyDesc, dy, xDesc, x, beta, dxDesc, dx); +} + +cudnnStatus_t CUDNNWINAPI cudnnDivisiveNormalizationForward( + cudnnHandle_t handle, + cudnnLRNDescriptor_t normDesc, + cudnnDivNormMode_t mode, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, // same desc for means, temp, temp2 + const void *x, + const void *means, // if NULL, means are assumed to be zero + void *temp, + void *temp2, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnLRNDescriptor_t, cudnnDivNormMode_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, void *, void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnDivisiveNormalizationForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, normDesc, mode, alpha, xDesc, x, means, temp, temp2, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI cudnnDivisiveNormalizationBackward( + cudnnHandle_t handle, + cudnnLRNDescriptor_t normDesc, + cudnnDivNormMode_t mode, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, // same desc for x, means, dy, temp, temp2 + const void *x, + const void *means, // if NULL, means are assumed to be zero + const void *dy, + void *temp, + void *temp2, + const void *beta, + const cudnnTensorDescriptor_t dXdMeansDesc, // same desc for dx, dMeans + void *dx, // output x differential + void *dMeans ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnLRNDescriptor_t, cudnnDivNormMode_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const void *, void *, void *, const void *, const cudnnTensorDescriptor_t, void *, void *); + static auto func_ptr = LoadSymbol("cudnnDivisiveNormalizationBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, normDesc, mode, alpha, xDesc, x, means, dy, temp, temp2, beta, dXdMeansDesc, dx, dMeans); +} + +cudnnStatus_t CUDNNWINAPI cudnnDeriveBNTensorDescriptor( + cudnnTensorDescriptor_t derivedBnDesc, + const cudnnTensorDescriptor_t xDesc, + cudnnBatchNormMode_t mode ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, cudnnBatchNormMode_t); + static auto func_ptr = LoadSymbol("cudnnDeriveBNTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(derivedBnDesc, xDesc, mode); +} + +cudnnStatus_t CUDNNWINAPI cudnnBatchNormalizationForwardTraining( + cudnnHandle_t handle, + cudnnBatchNormMode_t mode, + + const void *alpha, // alpha[0] = result blend factor + const void *beta, // beta[0] = dest layer blend factor + + const cudnnTensorDescriptor_t xDesc, + const void *x, // NxCxHxW + const cudnnTensorDescriptor_t yDesc, + void *y, // NxCxHxW + + /* Shared desc for the next 6 tensors in the argument list. + Data type to be set as follows: + type = (typeOf(x) == double) ? double : float + Dimensions for this descriptor depend on normalization mode + - Spatial Normalization : tensors are expected to have dims 1xCx1x1 + (normalization is performed across NxHxW) + - Per-Activation Normalization : tensors are expected to have dims of 1xCxHxW + (normalization is performed across N) */ + const cudnnTensorDescriptor_t bnScaleBiasMeanVarDesc, + + // 'Gamma' and 'Beta' respectively in Ioffe and Szegedy's paper's notation + const void *bnScale, + const void *bnBias, + + /* MUST use factor=1 in the very first call of a complete training cycle. + Use a factor=1/(1+n) at N-th call to the function to get + Cumulative Moving Average (CMA) behavior + CMA[n] = (x[1]+...+x[n])/n + Since CMA[n+1] = (n*CMA[n]+x[n+1])/(n+1) = + ((n+1)*CMA[n]-CMA[n])/(n+1) + x[n+1]/(n+1) = + CMA[n]*(1-1/(n+1)) + x[n+1]*1/(n+1) */ + double exponentialAverageFactor, + + /* Used in Training phase only. + runningMean = newMean*factor + runningMean*(1-factor) */ + void *resultRunningMean, + /* Output in training mode, input in inference. Is the moving average + of variance[x] (factor is applied in the same way as for runningMean) */ + void *resultRunningVariance, + + /* Has to be >= CUDNN_BN_MIN_EPSILON. Should be the same in forward and backward functions. */ + double epsilon, + + /* Optionally save intermediate results from the forward pass here + - can be reused to speed up backward pass. NULL if unused */ + void *resultSaveMean, + void *resultSaveInvVariance ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnBatchNormMode_t, const void *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, const void *, const void *, double, void *, void *, double, void *, void *); + static auto func_ptr = LoadSymbol("cudnnBatchNormalizationForwardTraining"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode, alpha, beta, xDesc, x, yDesc, y, bnScaleBiasMeanVarDesc, bnScale, bnBias, exponentialAverageFactor, resultRunningMean, resultRunningVariance, epsilon, resultSaveMean, resultSaveInvVariance); +} + +cudnnStatus_t CUDNNWINAPI cudnnBatchNormalizationForwardInference( + cudnnHandle_t handle, + cudnnBatchNormMode_t mode, + const void *alpha, // alpha[0] = result blend factor + const void *beta, // beta[0] = dest layer blend factor + const cudnnTensorDescriptor_t xDesc, + const void *x, // NxCxHxW + const cudnnTensorDescriptor_t yDesc, + void *y, // NxCxHxW + const cudnnTensorDescriptor_t bnScaleBiasMeanVarDesc, + const void *bnScale, + const void *bnBias, + const void *estimatedMean, + const void *estimatedVariance, + double epsilon ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnBatchNormMode_t, const void *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, const void *, const void *, const void *, const void *, double); + static auto func_ptr = LoadSymbol("cudnnBatchNormalizationForwardInference"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode, alpha, beta, xDesc, x, yDesc, y, bnScaleBiasMeanVarDesc, bnScale, bnBias, estimatedMean, estimatedVariance, epsilon); +} + +cudnnStatus_t CUDNNWINAPI cudnnBatchNormalizationBackward( + cudnnHandle_t handle, + cudnnBatchNormMode_t mode, + const void *alphaDataDiff, + const void *betaDataDiff, + const void *alphaParamDiff, + const void *betaParamDiff, + const cudnnTensorDescriptor_t xDesc, // same desc for x, dx, dy + const void *x, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnTensorDescriptor_t dxDesc, + void *dx, + /* Shared tensor desc for the 4 tensors below */ + const cudnnTensorDescriptor_t dBnScaleBiasDesc, + const void *bnScale, // bnBias doesn't affect backpropagation + /* scale and bias diff are not backpropagated below this layer */ + void *dBnScaleResult, + void *dBnBiasResult, + /* Same epsilon as forward pass */ + double epsilon, + + /* Optionally cached intermediate results from + forward pass */ + const void *savedMean, + const void *savedInvVariance ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnBatchNormMode_t, const void *, const void *, const void *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, const void *, void *, void *, double, const void *, const void *); + static auto func_ptr = LoadSymbol("cudnnBatchNormalizationBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode, alphaDataDiff, betaDataDiff, alphaParamDiff, betaParamDiff, xDesc, x, dyDesc, dy, dxDesc, dx, dBnScaleBiasDesc, bnScale, dBnScaleResult, dBnBiasResult, epsilon, savedMean, savedInvVariance); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateSpatialTransformerDescriptor( + + cudnnSpatialTransformerDescriptor_t *stDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnSpatialTransformerDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateSpatialTransformerDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(stDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetSpatialTransformerNdDescriptor( + cudnnSpatialTransformerDescriptor_t stDesc, + cudnnSamplerType_t samplerType, + cudnnDataType_t dataType, + const int nbDims, + const int dimA[]) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnSpatialTransformerDescriptor_t, cudnnSamplerType_t, cudnnDataType_t, const int, const int []); + static auto func_ptr = LoadSymbol("cudnnSetSpatialTransformerNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(stDesc, samplerType, dataType, nbDims, dimA); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroySpatialTransformerDescriptor( + cudnnSpatialTransformerDescriptor_t stDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnSpatialTransformerDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroySpatialTransformerDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(stDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSpatialTfGridGeneratorForward( + cudnnHandle_t handle, + const cudnnSpatialTransformerDescriptor_t stDesc, + const void *theta, + void *grid) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnSpatialTransformerDescriptor_t, const void *, void *); + static auto func_ptr = LoadSymbol("cudnnSpatialTfGridGeneratorForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, stDesc, theta, grid); +} + +cudnnStatus_t CUDNNWINAPI cudnnSpatialTfGridGeneratorBackward( + cudnnHandle_t handle, + const cudnnSpatialTransformerDescriptor_t stDesc, + const void *dgrid, + void *dtheta) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnSpatialTransformerDescriptor_t, const void *, void *); + static auto func_ptr = LoadSymbol("cudnnSpatialTfGridGeneratorBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, stDesc, dgrid, dtheta); +} + +cudnnStatus_t CUDNNWINAPI cudnnSpatialTfSamplerForward( + cudnnHandle_t handle, + cudnnSpatialTransformerDescriptor_t stDesc, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *grid, + const void *beta, + cudnnTensorDescriptor_t yDesc, + void *y) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnSpatialTransformerDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const void *, cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnSpatialTfSamplerForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, stDesc, alpha, xDesc, x, grid, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI cudnnSpatialTfSamplerBackward( + cudnnHandle_t handle, + cudnnSpatialTransformerDescriptor_t stDesc, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx, + const void *alphaDgrid, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const void *grid, + const void *betaDgrid, + void *dgrid) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnSpatialTransformerDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const void *, void *); + static auto func_ptr = LoadSymbol("cudnnSpatialTfSamplerBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, stDesc, alpha, xDesc, x, beta, dxDesc, dx, alphaDgrid, dyDesc, dy, grid, betaDgrid, dgrid); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateDropoutDescriptor(cudnnDropoutDescriptor_t * dropoutDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnDropoutDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateDropoutDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dropoutDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyDropoutDescriptor(cudnnDropoutDescriptor_t dropoutDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnDropoutDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyDropoutDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dropoutDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnDropoutGetStatesSize(cudnnHandle_t handle, size_t * sizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnDropoutGetStatesSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnDropoutGetReserveSpaceSize(cudnnTensorDescriptor_t xdesc, size_t * sizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnDropoutGetReserveSpaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(xdesc, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetDropoutDescriptor(cudnnDropoutDescriptor_t dropoutDesc, + cudnnHandle_t handle, + float dropout, + void * states, + size_t stateSizeInBytes, + unsigned long long seed) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnDropoutDescriptor_t, cudnnHandle_t, float, void *, size_t, unsigned long long); + static auto func_ptr = LoadSymbol("cudnnSetDropoutDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dropoutDesc, handle, dropout, states, stateSizeInBytes, seed); +} + +cudnnStatus_t CUDNNWINAPI cudnnDropoutForward(cudnnHandle_t handle, + const cudnnDropoutDescriptor_t dropoutDesc, + const cudnnTensorDescriptor_t xdesc, + const void * x, + const cudnnTensorDescriptor_t ydesc, + void * y, + void * reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnDropoutDescriptor_t, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, void *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnDropoutForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, dropoutDesc, xdesc, x, ydesc, y, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnDropoutBackward(cudnnHandle_t handle, + const cudnnDropoutDescriptor_t dropoutDesc, + const cudnnTensorDescriptor_t dydesc, + const void * dy, + const cudnnTensorDescriptor_t dxdesc, + void * dx, + void * reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnDropoutDescriptor_t, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, void *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnDropoutBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, dropoutDesc, dydesc, dy, dxdesc, dx, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateRNNDescriptor(cudnnRNNDescriptor_t * rnnDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateRNNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyRNNDescriptor(cudnnRNNDescriptor_t rnnDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyRNNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreatePersistentRNNPlan(cudnnRNNDescriptor_t rnnDesc, + const int minibatch, + const cudnnDataType_t dataType, + cudnnPersistentRNNPlan_t * plan) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t, const int, const cudnnDataType_t, cudnnPersistentRNNPlan_t *); + static auto func_ptr = LoadSymbol("cudnnCreatePersistentRNNPlan"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc, minibatch, dataType, plan); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetPersistentRNNPlan(cudnnRNNDescriptor_t rnnDesc, + cudnnPersistentRNNPlan_t plan) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t, cudnnPersistentRNNPlan_t); + static auto func_ptr = LoadSymbol("cudnnSetPersistentRNNPlan"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc, plan); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyPersistentRNNPlan(cudnnPersistentRNNPlan_t plan) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnPersistentRNNPlan_t); + static auto func_ptr = LoadSymbol("cudnnDestroyPersistentRNNPlan"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(plan); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetRNNDescriptor_v6(cudnnHandle_t handle, + cudnnRNNDescriptor_t rnnDesc, + const int hiddenSize, + const int numLayers, + cudnnDropoutDescriptor_t dropoutDesc, // Between layers, not between recurrent steps. + cudnnRNNInputMode_t inputMode, + cudnnDirectionMode_t direction, + cudnnRNNMode_t mode, + cudnnRNNAlgo_t algo, + cudnnDataType_t dataType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnRNNDescriptor_t, const int, const int, cudnnDropoutDescriptor_t, cudnnRNNInputMode_t, cudnnDirectionMode_t, cudnnRNNMode_t, cudnnRNNAlgo_t, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnSetRNNDescriptor_v6"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, hiddenSize, numLayers, dropoutDesc, inputMode, direction, mode, algo, dataType); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetRNNDescriptor(cudnnRNNDescriptor_t rnnDesc, + int hiddenSize, + int numLayers, + cudnnDropoutDescriptor_t dropoutDesc, // Between layers, not between recurrent steps. + cudnnRNNInputMode_t inputMode, + cudnnDirectionMode_t direction, + cudnnRNNMode_t mode, + cudnnDataType_t dataType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t, int, int, cudnnDropoutDescriptor_t, cudnnRNNInputMode_t, cudnnDirectionMode_t, cudnnRNNMode_t, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnSetRNNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc, hiddenSize, numLayers, dropoutDesc, inputMode, direction, mode, dataType); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetRNNWorkspaceSize( cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t *xDesc, + size_t *sizeInBytes + ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetRNNWorkspaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetRNNTrainingReserveSize( cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t *xDesc, + size_t *sizeInBytes + ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetRNNTrainingReserveSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetRNNParamsSize( cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const cudnnTensorDescriptor_t xDesc, + size_t *sizeInBytes, + cudnnDataType_t dataType + ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const cudnnTensorDescriptor_t, size_t *, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnGetRNNParamsSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, xDesc, sizeInBytes, dataType); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetRNNLinLayerMatrixParams( cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int layer, + const cudnnTensorDescriptor_t xDesc, + const cudnnFilterDescriptor_t wDesc, + const void * w, + const int linLayerID, + cudnnFilterDescriptor_t linLayerMatDesc, + void ** linLayerMat + ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, const void *, const int, cudnnFilterDescriptor_t, void **); + static auto func_ptr = LoadSymbol("cudnnGetRNNLinLayerMatrixParams"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, layer, xDesc, wDesc, w, linLayerID, linLayerMatDesc, linLayerMat); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetRNNLinLayerBiasParams( cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int layer, + const cudnnTensorDescriptor_t xDesc, + const cudnnFilterDescriptor_t wDesc, + const void * w, + const int linLayerID, + cudnnFilterDescriptor_t linLayerBiasDesc, + void ** linLayerBias + ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, const void *, const int, cudnnFilterDescriptor_t, void **); + static auto func_ptr = LoadSymbol("cudnnGetRNNLinLayerBiasParams"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, layer, xDesc, wDesc, w, linLayerID, linLayerBiasDesc, linLayerBias); +} + +cudnnStatus_t CUDNNWINAPI cudnnRNNForwardInference( cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t * xDesc, + const void * x, + const cudnnTensorDescriptor_t hxDesc, + const void * hx, + const cudnnTensorDescriptor_t cxDesc, + const void * cx, + const cudnnFilterDescriptor_t wDesc, + const void * w, + const cudnnTensorDescriptor_t *yDesc, + void * y, + const cudnnTensorDescriptor_t hyDesc, + void * hy, + const cudnnTensorDescriptor_t cyDesc, + void * cy, + void * workspace, + size_t workSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t *, void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnRNNForwardInference"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, x, hxDesc, hx, cxDesc, cx, wDesc, w, yDesc, y, hyDesc, hy, cyDesc, cy, workspace, workSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnRNNForwardTraining( cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t *xDesc, + const void * x, + const cudnnTensorDescriptor_t hxDesc, + const void * hx, + const cudnnTensorDescriptor_t cxDesc, + const void * cx, + const cudnnFilterDescriptor_t wDesc, + const void * w, + const cudnnTensorDescriptor_t *yDesc, + void * y, + const cudnnTensorDescriptor_t hyDesc, + void * hy, + const cudnnTensorDescriptor_t cyDesc, + void * cy, + void * workspace, + size_t workSpaceSizeInBytes, + void * reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t *, void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, void *, size_t, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnRNNForwardTraining"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, x, hxDesc, hx, cxDesc, cx, wDesc, w, yDesc, y, hyDesc, hy, cyDesc, cy, workspace, workSpaceSizeInBytes, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnRNNBackwardData( cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t * yDesc, + const void * y, + const cudnnTensorDescriptor_t * dyDesc, + const void * dy, + const cudnnTensorDescriptor_t dhyDesc, + const void * dhy, + const cudnnTensorDescriptor_t dcyDesc, + const void * dcy, + const cudnnFilterDescriptor_t wDesc, + const void * w, + const cudnnTensorDescriptor_t hxDesc, + const void * hx, + const cudnnTensorDescriptor_t cxDesc, + const void * cx, + const cudnnTensorDescriptor_t * dxDesc, + void * dx, + const cudnnTensorDescriptor_t dhxDesc, + void * dhx, + const cudnnTensorDescriptor_t dcxDesc, + void * dcx, + void * workspace, + size_t workSpaceSizeInBytes, + void * reserveSpace, + size_t reserveSpaceSizeInBytes ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t *, void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, void *, size_t, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnRNNBackwardData"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, yDesc, y, dyDesc, dy, dhyDesc, dhy, dcyDesc, dcy, wDesc, w, hxDesc, hx, cxDesc, cx, dxDesc, dx, dhxDesc, dhx, dcxDesc, dcx, workspace, workSpaceSizeInBytes, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnRNNBackwardWeights( cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t * xDesc, + const void * x, + const cudnnTensorDescriptor_t hxDesc, + const void * hx, + const cudnnTensorDescriptor_t * yDesc, + const void * y, + const void * workspace, + size_t workSpaceSizeInBytes, + const cudnnFilterDescriptor_t dwDesc, + void * dw, + const void * reserveSpace, + size_t reserveSpaceSizeInBytes ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t *, const void *, const void *, size_t, const cudnnFilterDescriptor_t, void *, const void *, size_t); + static auto func_ptr = LoadSymbol("cudnnRNNBackwardWeights"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, x, hxDesc, hx, yDesc, y, workspace, workSpaceSizeInBytes, dwDesc, dw, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetConvolution2dDescriptor_v4( + cudnnConvolutionDescriptor_t convDesc, + int pad_h, // zero-padding height + int pad_w, // zero-padding width + int u, // vertical filter stride + int v, // horizontal filter stride + int dilation_h, // filter dilation in the vertical dimension + int dilation_w, // filter dilation in the horizontal dimension + cudnnConvolutionMode_t mode ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t, int, int, int, int, int, int, cudnnConvolutionMode_t); + static auto func_ptr = LoadSymbol("cudnnSetConvolution2dDescriptor_v4"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, pad_h, pad_w, u, v, dilation_h, dilation_w, mode); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetConvolution2dDescriptor_v5( cudnnConvolutionDescriptor_t convDesc, + int pad_h, // zero-padding height + int pad_w, // zero-padding width + int u, // vertical filter stride + int v, // horizontal filter stride + int dilation_h, // filter dilation in the vertical dimension + int dilation_w, // filter dilation in the horizontal dimension + cudnnConvolutionMode_t mode, + cudnnDataType_t computeType + ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t, int, int, int, int, int, int, cudnnConvolutionMode_t, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnSetConvolution2dDescriptor_v5"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, pad_h, pad_w, u, v, dilation_h, dilation_w, mode, computeType); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolution2dDescriptor_v4( + const cudnnConvolutionDescriptor_t convDesc, + int *pad_h, // zero-padding height + int *pad_w, // zero-padding width + int *u, // vertical filter stride + int *v, // horizontal filter stride + int *dilation_h, // filter dilation in the vertical dimension + int *dilation_w, // filter dilation in the horizontal dimension + cudnnConvolutionMode_t *mode ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnConvolutionDescriptor_t, int *, int *, int *, int *, int *, int *, cudnnConvolutionMode_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolution2dDescriptor_v4"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, pad_h, pad_w, u, v, dilation_h, dilation_w, mode); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolution2dDescriptor_v5( const cudnnConvolutionDescriptor_t convDesc, + int* pad_h, // zero-padding height + int* pad_w, // zero-padding width + int* u, // vertical filter stride + int* v, // horizontal filter stride + int* dilation_h, // filter dilation in the vertical dimension + int* dilation_w, // filter dilation in the horizontal dimension + cudnnConvolutionMode_t* mode, + cudnnDataType_t *computeType + ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnConvolutionDescriptor_t, int *, int *, int *, int *, int *, int *, cudnnConvolutionMode_t *, cudnnDataType_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolution2dDescriptor_v5"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, pad_h, pad_w, u, v, dilation_h, dilation_w, mode, computeType); +} + +} // extern "C" diff --git a/tensorflow/stream_executor/cuda/cudnn_7_0.inc b/tensorflow/stream_executor/cuda/cudnn_7_0.inc new file mode 100644 index 0000000000..ac6b0dd823 --- /dev/null +++ b/tensorflow/stream_executor/cuda/cudnn_7_0.inc @@ -0,0 +1,1946 @@ +// Auto-generated, do not edit. + +extern "C" { + +size_t CUDNNWINAPI cudnnGetVersion(void) { + using FuncPtr = size_t (CUDNNWINAPI *)(); + static auto func_ptr = LoadSymbol("cudnnGetVersion"); + if (!func_ptr) return 0; + return func_ptr(); +} + +size_t CUDNNWINAPI cudnnGetCudartVersion(void) { + using FuncPtr = size_t (CUDNNWINAPI *)(); + static auto func_ptr = LoadSymbol("cudnnGetCudartVersion"); + if (!func_ptr) return 0; + return func_ptr(); +} + +const char * CUDNNWINAPI cudnnGetErrorString(cudnnStatus_t status) { + using FuncPtr = const char * (CUDNNWINAPI *)(cudnnStatus_t); + static auto func_ptr = LoadSymbol("cudnnGetErrorString"); + if (!func_ptr) return "cudnnGetErrorString symbol not found."; + return func_ptr(status); +} + +cudnnStatus_t CUDNNWINAPI cudnnQueryRuntimeError( + cudnnHandle_t handle, + cudnnStatus_t *rstatus, + cudnnErrQueryMode_t mode, + cudnnRuntimeTag_t *tag ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnStatus_t *, cudnnErrQueryMode_t, cudnnRuntimeTag_t *); + static auto func_ptr = LoadSymbol("cudnnQueryRuntimeError"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rstatus, mode, tag); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetProperty(libraryPropertyType type, int *value) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(libraryPropertyType, int *); + static auto func_ptr = LoadSymbol("cudnnGetProperty"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(type, value); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreate (cudnnHandle_t *handle) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t *); + static auto func_ptr = LoadSymbol("cudnnCreate"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroy (cudnnHandle_t handle) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t); + static auto func_ptr = LoadSymbol("cudnnDestroy"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetStream (cudnnHandle_t handle, cudaStream_t streamId) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudaStream_t); + static auto func_ptr = LoadSymbol("cudnnSetStream"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, streamId); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetStream (cudnnHandle_t handle, cudaStream_t *streamId) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudaStream_t *); + static auto func_ptr = LoadSymbol("cudnnGetStream"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, streamId); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateTensorDescriptor( + cudnnTensorDescriptor_t *tensorDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetTensor4dDescriptor( + cudnnTensorDescriptor_t tensorDesc, + cudnnTensorFormat_t format, + cudnnDataType_t dataType, /* image data type */ + int n, /* number of inputs (batch size) */ + int c, /* number of input feature maps */ + int h, /* height of input section */ + int w ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t, cudnnTensorFormat_t, cudnnDataType_t, int, int, int, int); + static auto func_ptr = LoadSymbol("cudnnSetTensor4dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, format, dataType, n, c, h, w); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetTensor4dDescriptorEx( + cudnnTensorDescriptor_t tensorDesc, + cudnnDataType_t dataType, /* image data type */ + int n, /* number of inputs (batch size) */ + int c, /* number of input feature maps */ + int h, /* height of input section */ + int w, /* width of input section */ + int nStride, + int cStride, + int hStride, + int wStride ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t, cudnnDataType_t, int, int, int, int, int, int, int, int); + static auto func_ptr = LoadSymbol("cudnnSetTensor4dDescriptorEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, dataType, n, c, h, w, nStride, cStride, hStride, wStride); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetTensor4dDescriptor( + const cudnnTensorDescriptor_t tensorDesc, + cudnnDataType_t *dataType, /* image data type */ + int *n, /* number of inputs (batch size) */ + int *c, /* number of input feature maps */ + int *h, /* height of input section */ + int *w, /* width of input section */ + int *nStride, + int *cStride, + int *hStride, + int *wStride ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnTensorDescriptor_t, cudnnDataType_t *, int *, int *, int *, int *, int *, int *, int *, int *); + static auto func_ptr = LoadSymbol("cudnnGetTensor4dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, dataType, n, c, h, w, nStride, cStride, hStride, wStride); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetTensorNdDescriptor( + cudnnTensorDescriptor_t tensorDesc, + cudnnDataType_t dataType, + int nbDims, + const int dimA[], + const int strideA[] ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t, cudnnDataType_t, int, const int [], const int []); + static auto func_ptr = LoadSymbol("cudnnSetTensorNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, dataType, nbDims, dimA, strideA); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetTensorNdDescriptorEx( + cudnnTensorDescriptor_t tensorDesc, + cudnnTensorFormat_t format, + cudnnDataType_t dataType, + int nbDims, + const int dimA[] ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t, cudnnTensorFormat_t, cudnnDataType_t, int, const int []); + static auto func_ptr = LoadSymbol("cudnnSetTensorNdDescriptorEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, format, dataType, nbDims, dimA); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetTensorNdDescriptor( + const cudnnTensorDescriptor_t tensorDesc, + int nbDimsRequested, + cudnnDataType_t *dataType, + int *nbDims, + int dimA[], + int strideA[] ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnTensorDescriptor_t, int, cudnnDataType_t *, int *, int [], int []); + static auto func_ptr = LoadSymbol("cudnnGetTensorNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, nbDimsRequested, dataType, nbDims, dimA, strideA); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetTensorSizeInBytes( + const cudnnTensorDescriptor_t tensorDesc, + size_t *size) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnTensorDescriptor_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetTensorSizeInBytes"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, size); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyTensorDescriptor( + cudnnTensorDescriptor_t tensorDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnTransformTensor( + cudnnHandle_t handle, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnTransformTensor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha, xDesc, x, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI cudnnAddTensor( + cudnnHandle_t handle, + const void *alpha, + const cudnnTensorDescriptor_t aDesc, + const void *A, + const void *beta, + const cudnnTensorDescriptor_t cDesc, + void *C ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnAddTensor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha, aDesc, A, beta, cDesc, C); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateOpTensorDescriptor( + cudnnOpTensorDescriptor_t *opTensorDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnOpTensorDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateOpTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(opTensorDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetOpTensorDescriptor( + cudnnOpTensorDescriptor_t opTensorDesc, + cudnnOpTensorOp_t opTensorOp, + cudnnDataType_t opTensorCompType, + cudnnNanPropagation_t opTensorNanOpt ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnOpTensorDescriptor_t, cudnnOpTensorOp_t, cudnnDataType_t, cudnnNanPropagation_t); + static auto func_ptr = LoadSymbol("cudnnSetOpTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(opTensorDesc, opTensorOp, opTensorCompType, opTensorNanOpt); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetOpTensorDescriptor( + const cudnnOpTensorDescriptor_t opTensorDesc, + cudnnOpTensorOp_t *opTensorOp, + cudnnDataType_t *opTensorCompType, + cudnnNanPropagation_t *opTensorNanOpt ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnOpTensorDescriptor_t, cudnnOpTensorOp_t *, cudnnDataType_t *, cudnnNanPropagation_t *); + static auto func_ptr = LoadSymbol("cudnnGetOpTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(opTensorDesc, opTensorOp, opTensorCompType, opTensorNanOpt); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyOpTensorDescriptor( + cudnnOpTensorDescriptor_t opTensorDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnOpTensorDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyOpTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(opTensorDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnOpTensor( + cudnnHandle_t handle, + const cudnnOpTensorDescriptor_t opTensorDesc, + const void *alpha1, + const cudnnTensorDescriptor_t aDesc, + const void *A, + const void *alpha2, + const cudnnTensorDescriptor_t bDesc, + const void *B, + const void *beta, + const cudnnTensorDescriptor_t cDesc, + void *C ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnOpTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnOpTensor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, opTensorDesc, alpha1, aDesc, A, alpha2, bDesc, B, beta, cDesc, C); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateReduceTensorDescriptor( + cudnnReduceTensorDescriptor_t *reduceTensorDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnReduceTensorDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateReduceTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(reduceTensorDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetReduceTensorDescriptor( + cudnnReduceTensorDescriptor_t reduceTensorDesc, + cudnnReduceTensorOp_t reduceTensorOp, + cudnnDataType_t reduceTensorCompType, + cudnnNanPropagation_t reduceTensorNanOpt, + cudnnReduceTensorIndices_t reduceTensorIndices, + cudnnIndicesType_t reduceTensorIndicesType ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnReduceTensorDescriptor_t, cudnnReduceTensorOp_t, cudnnDataType_t, cudnnNanPropagation_t, cudnnReduceTensorIndices_t, cudnnIndicesType_t); + static auto func_ptr = LoadSymbol("cudnnSetReduceTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(reduceTensorDesc, reduceTensorOp, reduceTensorCompType, reduceTensorNanOpt, reduceTensorIndices, reduceTensorIndicesType); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetReduceTensorDescriptor( + const cudnnReduceTensorDescriptor_t reduceTensorDesc, + cudnnReduceTensorOp_t *reduceTensorOp, + cudnnDataType_t *reduceTensorCompType, + cudnnNanPropagation_t *reduceTensorNanOpt, + cudnnReduceTensorIndices_t *reduceTensorIndices, + cudnnIndicesType_t *reduceTensorIndicesType ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnReduceTensorDescriptor_t, cudnnReduceTensorOp_t *, cudnnDataType_t *, cudnnNanPropagation_t *, cudnnReduceTensorIndices_t *, cudnnIndicesType_t *); + static auto func_ptr = LoadSymbol("cudnnGetReduceTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(reduceTensorDesc, reduceTensorOp, reduceTensorCompType, reduceTensorNanOpt, reduceTensorIndices, reduceTensorIndicesType); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyReduceTensorDescriptor( + cudnnReduceTensorDescriptor_t reduceTensorDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnReduceTensorDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyReduceTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(reduceTensorDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetReductionIndicesSize( + cudnnHandle_t handle, + const cudnnReduceTensorDescriptor_t reduceTensorDesc, + const cudnnTensorDescriptor_t aDesc, + const cudnnTensorDescriptor_t cDesc, + size_t *sizeInBytes ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnReduceTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetReductionIndicesSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, reduceTensorDesc, aDesc, cDesc, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetReductionWorkspaceSize( + cudnnHandle_t handle, + const cudnnReduceTensorDescriptor_t reduceTensorDesc, + const cudnnTensorDescriptor_t aDesc, + const cudnnTensorDescriptor_t cDesc, + size_t *sizeInBytes ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnReduceTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetReductionWorkspaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, reduceTensorDesc, aDesc, cDesc, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnReduceTensor( + cudnnHandle_t handle, + const cudnnReduceTensorDescriptor_t reduceTensorDesc, + void *indices, + size_t indicesSizeInBytes, + void *workspace, + size_t workspaceSizeInBytes, + const void *alpha, + const cudnnTensorDescriptor_t aDesc, + const void *A, + const void *beta, + const cudnnTensorDescriptor_t cDesc, + void *C ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnReduceTensorDescriptor_t, void *, size_t, void *, size_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnReduceTensor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, reduceTensorDesc, indices, indicesSizeInBytes, workspace, workspaceSizeInBytes, alpha, aDesc, A, beta, cDesc, C); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetTensor( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t yDesc, + void *y, + const void *valuePtr ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, void *, const void *); + static auto func_ptr = LoadSymbol("cudnnSetTensor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, yDesc, y, valuePtr); +} + +cudnnStatus_t CUDNNWINAPI cudnnScaleTensor( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t yDesc, + void *y, + const void *alpha ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, void *, const void *); + static auto func_ptr = LoadSymbol("cudnnScaleTensor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, yDesc, y, alpha); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateFilterDescriptor( + cudnnFilterDescriptor_t *filterDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnFilterDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateFilterDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(filterDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetFilter4dDescriptor( + cudnnFilterDescriptor_t filterDesc, + cudnnDataType_t dataType, /* image data type */ + cudnnTensorFormat_t format, + int k, /* number of output feature maps */ + int c, /* number of input feature maps */ + int h, /* height of each input filter */ + int w ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnFilterDescriptor_t, cudnnDataType_t, cudnnTensorFormat_t, int, int, int, int); + static auto func_ptr = LoadSymbol("cudnnSetFilter4dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(filterDesc, dataType, format, k, c, h, w); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetFilter4dDescriptor( + const cudnnFilterDescriptor_t filterDesc, + cudnnDataType_t *dataType, /* image data type */ + cudnnTensorFormat_t *format, + int *k, /* number of output feature maps */ + int *c, /* number of input feature maps */ + int *h, /* height of each input filter */ + int *w ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnFilterDescriptor_t, cudnnDataType_t *, cudnnTensorFormat_t *, int *, int *, int *, int *); + static auto func_ptr = LoadSymbol("cudnnGetFilter4dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(filterDesc, dataType, format, k, c, h, w); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetFilterNdDescriptor( + cudnnFilterDescriptor_t filterDesc, + cudnnDataType_t dataType, /* image data type */ + cudnnTensorFormat_t format, + int nbDims, + const int filterDimA[] ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnFilterDescriptor_t, cudnnDataType_t, cudnnTensorFormat_t, int, const int []); + static auto func_ptr = LoadSymbol("cudnnSetFilterNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(filterDesc, dataType, format, nbDims, filterDimA); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetFilterNdDescriptor( + const cudnnFilterDescriptor_t filterDesc, + int nbDimsRequested, + cudnnDataType_t *dataType, /* image data type */ + cudnnTensorFormat_t *format, + int *nbDims, + int filterDimA[] ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnFilterDescriptor_t, int, cudnnDataType_t *, cudnnTensorFormat_t *, int *, int []); + static auto func_ptr = LoadSymbol("cudnnGetFilterNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(filterDesc, nbDimsRequested, dataType, format, nbDims, filterDimA); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyFilterDescriptor( + cudnnFilterDescriptor_t filterDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnFilterDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyFilterDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(filterDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateConvolutionDescriptor( + cudnnConvolutionDescriptor_t *convDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateConvolutionDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetConvolutionMathType( cudnnConvolutionDescriptor_t convDesc, + cudnnMathType_t mathType ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t, cudnnMathType_t); + static auto func_ptr = LoadSymbol("cudnnSetConvolutionMathType"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, mathType); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionMathType( cudnnConvolutionDescriptor_t convDesc, + cudnnMathType_t *mathType ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t, cudnnMathType_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionMathType"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, mathType); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetConvolutionGroupCount( cudnnConvolutionDescriptor_t convDesc, + int groupCount ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t, int); + static auto func_ptr = LoadSymbol("cudnnSetConvolutionGroupCount"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, groupCount); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionGroupCount( cudnnConvolutionDescriptor_t convDesc, + int *groupCount ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t, int *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionGroupCount"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, groupCount); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetConvolution2dDescriptor( cudnnConvolutionDescriptor_t convDesc, + int pad_h, /* zero-padding height */ + int pad_w, /* zero-padding width */ + int u, /* vertical filter stride */ + int v, /* horizontal filter stride */ + int dilation_h, /* filter dilation in the vertical dimension */ + int dilation_w, /* filter dilation in the horizontal dimension */ + cudnnConvolutionMode_t mode, + cudnnDataType_t computeType + ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t, int, int, int, int, int, int, cudnnConvolutionMode_t, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnSetConvolution2dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, pad_h, pad_w, u, v, dilation_h, dilation_w, mode, computeType); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolution2dDescriptor( const cudnnConvolutionDescriptor_t convDesc, + int* pad_h, /* zero-padding height */ + int* pad_w, /* zero-padding width */ + int* u, /* vertical filter stride */ + int* v, /* horizontal filter stride */ + int* dilation_h, /* filter dilation in the vertical dimension */ + int* dilation_w, /* filter dilation in the horizontal dimension */ + cudnnConvolutionMode_t* mode, + cudnnDataType_t *computeType + ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnConvolutionDescriptor_t, int *, int *, int *, int *, int *, int *, cudnnConvolutionMode_t *, cudnnDataType_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolution2dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, pad_h, pad_w, u, v, dilation_h, dilation_w, mode, computeType); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolution2dForwardOutputDim( + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t inputTensorDesc, + const cudnnFilterDescriptor_t filterDesc, + int *n, + int *c, + int *h, + int *w ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, int *, int *, int *, int *); + static auto func_ptr = LoadSymbol("cudnnGetConvolution2dForwardOutputDim"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, inputTensorDesc, filterDesc, n, c, h, w); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetConvolutionNdDescriptor( + cudnnConvolutionDescriptor_t convDesc, + int arrayLength, /* nbDims-2 size */ + const int padA[], + const int filterStrideA[], + const int dilationA[], + cudnnConvolutionMode_t mode, + cudnnDataType_t computeType ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t, int, const int [], const int [], const int [], cudnnConvolutionMode_t, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnSetConvolutionNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, arrayLength, padA, filterStrideA, dilationA, mode, computeType); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionNdDescriptor( + const cudnnConvolutionDescriptor_t convDesc, + int arrayLengthRequested, + int *arrayLength, + int padA[], + int strideA[], + int dilationA[], + cudnnConvolutionMode_t *mode, + cudnnDataType_t *computeType ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnConvolutionDescriptor_t, int, int *, int [], int [], int [], cudnnConvolutionMode_t *, cudnnDataType_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, arrayLengthRequested, arrayLength, padA, strideA, dilationA, mode, computeType); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionNdForwardOutputDim( + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t inputTensorDesc, + const cudnnFilterDescriptor_t filterDesc, + int nbDims, + int tensorOuputDimA[] ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, int, int []); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionNdForwardOutputDim"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, inputTensorDesc, filterDesc, nbDims, tensorOuputDimA); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyConvolutionDescriptor( + cudnnConvolutionDescriptor_t convDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyConvolutionDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionForwardAlgorithmMaxCount( cudnnHandle_t handle, + int *count) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, int *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionForwardAlgorithmMaxCount"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, count); +} + +cudnnStatus_t CUDNNWINAPI cudnnFindConvolutionForwardAlgorithm( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnFilterDescriptor_t wDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t yDesc, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionFwdAlgoPerf_t *perfResults ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, const int, int *, cudnnConvolutionFwdAlgoPerf_t *); + static auto func_ptr = LoadSymbol("cudnnFindConvolutionForwardAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, wDesc, convDesc, yDesc, requestedAlgoCount, returnedAlgoCount, perfResults); +} + +cudnnStatus_t CUDNNWINAPI cudnnFindConvolutionForwardAlgorithmEx( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t yDesc, + void *y, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionFwdAlgoPerf_t *perfResults, + void *workSpace, + size_t workSpaceSizeInBytes ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, void *, const int, int *, cudnnConvolutionFwdAlgoPerf_t *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnFindConvolutionForwardAlgorithmEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, x, wDesc, w, convDesc, yDesc, y, requestedAlgoCount, returnedAlgoCount, perfResults, workSpace, workSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionForwardAlgorithm( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnFilterDescriptor_t wDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t yDesc, + cudnnConvolutionFwdPreference_t preference, + size_t memoryLimitInBytes, + cudnnConvolutionFwdAlgo_t *algo ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, cudnnConvolutionFwdPreference_t, size_t, cudnnConvolutionFwdAlgo_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionForwardAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, wDesc, convDesc, yDesc, preference, memoryLimitInBytes, algo); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionForwardAlgorithm_v7( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t srcDesc, + const cudnnFilterDescriptor_t filterDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t destDesc, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionFwdAlgoPerf_t *perfResults) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, const int, int *, cudnnConvolutionFwdAlgoPerf_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionForwardAlgorithm_v7"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, srcDesc, filterDesc, convDesc, destDesc, requestedAlgoCount, returnedAlgoCount, perfResults); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionForwardWorkspaceSize( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnFilterDescriptor_t wDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t yDesc, + cudnnConvolutionFwdAlgo_t algo, + size_t *sizeInBytes ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, cudnnConvolutionFwdAlgo_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionForwardWorkspaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, wDesc, convDesc, yDesc, algo, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnConvolutionForward( + cudnnHandle_t handle, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnConvolutionDescriptor_t convDesc, + cudnnConvolutionFwdAlgo_t algo, + void *workSpace, + size_t workSpaceSizeInBytes, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, cudnnConvolutionFwdAlgo_t, void *, size_t, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnConvolutionForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha, xDesc, x, wDesc, w, convDesc, algo, workSpace, workSpaceSizeInBytes, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI cudnnConvolutionBiasActivationForward( + cudnnHandle_t handle, + const void *alpha1, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnConvolutionDescriptor_t convDesc, + cudnnConvolutionFwdAlgo_t algo, + void *workSpace, + size_t workSpaceSizeInBytes, + const void *alpha2, + const cudnnTensorDescriptor_t zDesc, + const void *z, + const cudnnTensorDescriptor_t biasDesc, + const void *bias, + const cudnnActivationDescriptor_t activationDesc, + const cudnnTensorDescriptor_t yDesc, + void *y ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, cudnnConvolutionFwdAlgo_t, void *, size_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnActivationDescriptor_t, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnConvolutionBiasActivationForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha1, xDesc, x, wDesc, w, convDesc, algo, workSpace, workSpaceSizeInBytes, alpha2, zDesc, z, biasDesc, bias, activationDesc, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI cudnnConvolutionBackwardBias( + cudnnHandle_t handle, + const void *alpha, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const void *beta, + const cudnnTensorDescriptor_t dbDesc, + void *db ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnConvolutionBackwardBias"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha, dyDesc, dy, beta, dbDesc, db); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionBackwardFilterAlgorithmMaxCount( cudnnHandle_t handle, + int *count) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, int *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardFilterAlgorithmMaxCount"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, count); +} + +cudnnStatus_t CUDNNWINAPI cudnnFindConvolutionBackwardFilterAlgorithm( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnFilterDescriptor_t dwDesc, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionBwdFilterAlgoPerf_t *perfResults ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnFilterDescriptor_t, const int, int *, cudnnConvolutionBwdFilterAlgoPerf_t *); + static auto func_ptr = LoadSymbol("cudnnFindConvolutionBackwardFilterAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, dyDesc, convDesc, dwDesc, requestedAlgoCount, returnedAlgoCount, perfResults); +} + +cudnnStatus_t CUDNNWINAPI cudnnFindConvolutionBackwardFilterAlgorithmEx( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnTensorDescriptor_t dyDesc, + const void *y, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnFilterDescriptor_t dwDesc, + void *dw, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionBwdFilterAlgoPerf_t *perfResults, + void *workSpace, + size_t workSpaceSizeInBytes ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, const cudnnFilterDescriptor_t, void *, const int, int *, cudnnConvolutionBwdFilterAlgoPerf_t *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnFindConvolutionBackwardFilterAlgorithmEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, x, dyDesc, y, convDesc, dwDesc, dw, requestedAlgoCount, returnedAlgoCount, perfResults, workSpace, workSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionBackwardFilterAlgorithm( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnFilterDescriptor_t dwDesc, + cudnnConvolutionBwdFilterPreference_t preference, + size_t memoryLimitInBytes, + cudnnConvolutionBwdFilterAlgo_t *algo ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnFilterDescriptor_t, cudnnConvolutionBwdFilterPreference_t, size_t, cudnnConvolutionBwdFilterAlgo_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardFilterAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, dyDesc, convDesc, dwDesc, preference, memoryLimitInBytes, algo); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionBackwardFilterAlgorithm_v7( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t srcDesc, + const cudnnTensorDescriptor_t diffDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnFilterDescriptor_t gradDesc, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionBwdFilterAlgoPerf_t *perfResults) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnFilterDescriptor_t, const int, int *, cudnnConvolutionBwdFilterAlgoPerf_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardFilterAlgorithm_v7"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, srcDesc, diffDesc, convDesc, gradDesc, requestedAlgoCount, returnedAlgoCount, perfResults); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionBackwardFilterWorkspaceSize( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnFilterDescriptor_t gradDesc, + cudnnConvolutionBwdFilterAlgo_t algo, + size_t *sizeInBytes ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnFilterDescriptor_t, cudnnConvolutionBwdFilterAlgo_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardFilterWorkspaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, dyDesc, convDesc, gradDesc, algo, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnConvolutionBackwardFilter( + cudnnHandle_t handle, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnConvolutionDescriptor_t convDesc, + cudnnConvolutionBwdFilterAlgo_t algo, + void *workSpace, + size_t workSpaceSizeInBytes, + const void *beta, + const cudnnFilterDescriptor_t dwDesc, + void *dw ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, cudnnConvolutionBwdFilterAlgo_t, void *, size_t, const void *, const cudnnFilterDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnConvolutionBackwardFilter"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha, xDesc, x, dyDesc, dy, convDesc, algo, workSpace, workSpaceSizeInBytes, beta, dwDesc, dw); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionBackwardDataAlgorithmMaxCount( cudnnHandle_t handle, + int *count) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, int *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardDataAlgorithmMaxCount"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, count); +} + +cudnnStatus_t CUDNNWINAPI cudnnFindConvolutionBackwardDataAlgorithm( + cudnnHandle_t handle, + const cudnnFilterDescriptor_t wDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t dxDesc, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionBwdDataAlgoPerf_t *perfResults ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnFilterDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, const int, int *, cudnnConvolutionBwdDataAlgoPerf_t *); + static auto func_ptr = LoadSymbol("cudnnFindConvolutionBackwardDataAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, wDesc, dyDesc, convDesc, dxDesc, requestedAlgoCount, returnedAlgoCount, perfResults); +} + +cudnnStatus_t CUDNNWINAPI cudnnFindConvolutionBackwardDataAlgorithmEx( + cudnnHandle_t handle, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t dxDesc, + void *dx, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionBwdDataAlgoPerf_t *perfResults, + void *workSpace, + size_t workSpaceSizeInBytes ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, void *, const int, int *, cudnnConvolutionBwdDataAlgoPerf_t *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnFindConvolutionBackwardDataAlgorithmEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, wDesc, w, dyDesc, dy, convDesc, dxDesc, dx, requestedAlgoCount, returnedAlgoCount, perfResults, workSpace, workSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionBackwardDataAlgorithm( + cudnnHandle_t handle, + const cudnnFilterDescriptor_t wDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t dxDesc, + cudnnConvolutionBwdDataPreference_t preference, + size_t memoryLimitInBytes, + cudnnConvolutionBwdDataAlgo_t *algo ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnFilterDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, cudnnConvolutionBwdDataPreference_t, size_t, cudnnConvolutionBwdDataAlgo_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardDataAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, wDesc, dyDesc, convDesc, dxDesc, preference, memoryLimitInBytes, algo); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionBackwardDataAlgorithm_v7( + cudnnHandle_t handle, + const cudnnFilterDescriptor_t filterDesc, + const cudnnTensorDescriptor_t diffDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t gradDesc, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionBwdDataAlgoPerf_t *perfResults) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnFilterDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, const int, int *, cudnnConvolutionBwdDataAlgoPerf_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardDataAlgorithm_v7"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, filterDesc, diffDesc, convDesc, gradDesc, requestedAlgoCount, returnedAlgoCount, perfResults); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionBackwardDataWorkspaceSize( + cudnnHandle_t handle, + const cudnnFilterDescriptor_t wDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t dxDesc, + cudnnConvolutionBwdDataAlgo_t algo, + size_t *sizeInBytes ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnFilterDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, cudnnConvolutionBwdDataAlgo_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardDataWorkspaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, wDesc, dyDesc, convDesc, dxDesc, algo, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnConvolutionBackwardData( + cudnnHandle_t handle, + const void *alpha, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnConvolutionDescriptor_t convDesc, + cudnnConvolutionBwdDataAlgo_t algo, + void *workSpace, + size_t workSpaceSizeInBytes, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, cudnnConvolutionBwdDataAlgo_t, void *, size_t, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnConvolutionBackwardData"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha, wDesc, w, dyDesc, dy, convDesc, algo, workSpace, workSpaceSizeInBytes, beta, dxDesc, dx); +} + +cudnnStatus_t CUDNNWINAPI cudnnIm2Col( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnFilterDescriptor_t wDesc, + const cudnnConvolutionDescriptor_t convDesc, + void *colBuffer ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const cudnnConvolutionDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnIm2Col"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, x, wDesc, convDesc, colBuffer); +} + +cudnnStatus_t CUDNNWINAPI cudnnSoftmaxForward( + cudnnHandle_t handle, + cudnnSoftmaxAlgorithm_t algo, + cudnnSoftmaxMode_t mode, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnSoftmaxAlgorithm_t, cudnnSoftmaxMode_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnSoftmaxForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, algo, mode, alpha, xDesc, x, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI cudnnSoftmaxBackward( + cudnnHandle_t handle, + cudnnSoftmaxAlgorithm_t algo, + cudnnSoftmaxMode_t mode, + const void *alpha, + const cudnnTensorDescriptor_t yDesc, + const void *y, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnSoftmaxAlgorithm_t, cudnnSoftmaxMode_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnSoftmaxBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, algo, mode, alpha, yDesc, y, dyDesc, dy, beta, dxDesc, dx); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreatePoolingDescriptor( + cudnnPoolingDescriptor_t *poolingDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnPoolingDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreatePoolingDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetPooling2dDescriptor( + cudnnPoolingDescriptor_t poolingDesc, + cudnnPoolingMode_t mode, + cudnnNanPropagation_t maxpoolingNanOpt, + int windowHeight, + int windowWidth, + int verticalPadding, + int horizontalPadding, + int verticalStride, + int horizontalStride ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnPoolingDescriptor_t, cudnnPoolingMode_t, cudnnNanPropagation_t, int, int, int, int, int, int); + static auto func_ptr = LoadSymbol("cudnnSetPooling2dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc, mode, maxpoolingNanOpt, windowHeight, windowWidth, verticalPadding, horizontalPadding, verticalStride, horizontalStride); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetPooling2dDescriptor( + const cudnnPoolingDescriptor_t poolingDesc, + cudnnPoolingMode_t *mode, + cudnnNanPropagation_t *maxpoolingNanOpt, + int *windowHeight, + int *windowWidth, + int *verticalPadding, + int *horizontalPadding, + int *verticalStride, + int *horizontalStride ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnPoolingDescriptor_t, cudnnPoolingMode_t *, cudnnNanPropagation_t *, int *, int *, int *, int *, int *, int *); + static auto func_ptr = LoadSymbol("cudnnGetPooling2dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc, mode, maxpoolingNanOpt, windowHeight, windowWidth, verticalPadding, horizontalPadding, verticalStride, horizontalStride); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetPoolingNdDescriptor( + cudnnPoolingDescriptor_t poolingDesc, + const cudnnPoolingMode_t mode, + const cudnnNanPropagation_t maxpoolingNanOpt, + int nbDims, + const int windowDimA[], + const int paddingA[], + const int strideA[] ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnPoolingDescriptor_t, const cudnnPoolingMode_t, const cudnnNanPropagation_t, int, const int [], const int [], const int []); + static auto func_ptr = LoadSymbol("cudnnSetPoolingNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc, mode, maxpoolingNanOpt, nbDims, windowDimA, paddingA, strideA); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetPoolingNdDescriptor( + const cudnnPoolingDescriptor_t poolingDesc, + int nbDimsRequested, + cudnnPoolingMode_t *mode, + cudnnNanPropagation_t *maxpoolingNanOpt, + int *nbDims, + int windowDimA[], + int paddingA[], + int strideA[] ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnPoolingDescriptor_t, int, cudnnPoolingMode_t *, cudnnNanPropagation_t *, int *, int [], int [], int []); + static auto func_ptr = LoadSymbol("cudnnGetPoolingNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc, nbDimsRequested, mode, maxpoolingNanOpt, nbDims, windowDimA, paddingA, strideA); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetPoolingNdForwardOutputDim( + const cudnnPoolingDescriptor_t poolingDesc, + const cudnnTensorDescriptor_t inputTensorDesc, + int nbDims, + int outputTensorDimA[] ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnPoolingDescriptor_t, const cudnnTensorDescriptor_t, int, int []); + static auto func_ptr = LoadSymbol("cudnnGetPoolingNdForwardOutputDim"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc, inputTensorDesc, nbDims, outputTensorDimA); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetPooling2dForwardOutputDim( + const cudnnPoolingDescriptor_t poolingDesc, + const cudnnTensorDescriptor_t inputTensorDesc, + int *n, + int *c, + int *h, + int *w ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnPoolingDescriptor_t, const cudnnTensorDescriptor_t, int *, int *, int *, int *); + static auto func_ptr = LoadSymbol("cudnnGetPooling2dForwardOutputDim"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc, inputTensorDesc, n, c, h, w); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyPoolingDescriptor( + cudnnPoolingDescriptor_t poolingDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnPoolingDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyPoolingDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnPoolingForward( + cudnnHandle_t handle, + const cudnnPoolingDescriptor_t poolingDesc, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnPoolingDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnPoolingForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, poolingDesc, alpha, xDesc, x, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI cudnnPoolingBackward( + cudnnHandle_t handle, + const cudnnPoolingDescriptor_t poolingDesc, + const void *alpha, + const cudnnTensorDescriptor_t yDesc, + const void *y, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnPoolingDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnPoolingBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, poolingDesc, alpha, yDesc, y, dyDesc, dy, xDesc, x, beta, dxDesc, dx); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateActivationDescriptor( + cudnnActivationDescriptor_t *activationDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnActivationDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateActivationDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(activationDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetActivationDescriptor( + cudnnActivationDescriptor_t activationDesc, + cudnnActivationMode_t mode, + cudnnNanPropagation_t reluNanOpt, + double coef ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnActivationDescriptor_t, cudnnActivationMode_t, cudnnNanPropagation_t, double); + static auto func_ptr = LoadSymbol("cudnnSetActivationDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(activationDesc, mode, reluNanOpt, coef); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetActivationDescriptor( + const cudnnActivationDescriptor_t activationDesc, + cudnnActivationMode_t *mode, + cudnnNanPropagation_t *reluNanOpt, + double* coef ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnActivationDescriptor_t, cudnnActivationMode_t *, cudnnNanPropagation_t *, double *); + static auto func_ptr = LoadSymbol("cudnnGetActivationDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(activationDesc, mode, reluNanOpt, coef); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyActivationDescriptor( + cudnnActivationDescriptor_t activationDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnActivationDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyActivationDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(activationDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnActivationForward( + cudnnHandle_t handle, + cudnnActivationDescriptor_t activationDesc, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnActivationDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnActivationForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, activationDesc, alpha, xDesc, x, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI cudnnActivationBackward( + cudnnHandle_t handle, + cudnnActivationDescriptor_t activationDesc, + const void *alpha, + const cudnnTensorDescriptor_t yDesc, + const void *y, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnActivationDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnActivationBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, activationDesc, alpha, yDesc, y, dyDesc, dy, xDesc, x, beta, dxDesc, dx); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateLRNDescriptor( + cudnnLRNDescriptor_t *normDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnLRNDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateLRNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(normDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetLRNDescriptor( + cudnnLRNDescriptor_t normDesc, + unsigned lrnN, + double lrnAlpha, + double lrnBeta, + double lrnK ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnLRNDescriptor_t, unsigned int, double, double, double); + static auto func_ptr = LoadSymbol("cudnnSetLRNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(normDesc, lrnN, lrnAlpha, lrnBeta, lrnK); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetLRNDescriptor( + cudnnLRNDescriptor_t normDesc, + unsigned* lrnN, + double* lrnAlpha, + double* lrnBeta, + double* lrnK ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnLRNDescriptor_t, unsigned int *, double *, double *, double *); + static auto func_ptr = LoadSymbol("cudnnGetLRNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(normDesc, lrnN, lrnAlpha, lrnBeta, lrnK); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyLRNDescriptor( cudnnLRNDescriptor_t lrnDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnLRNDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyLRNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(lrnDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnLRNCrossChannelForward( + cudnnHandle_t handle, + cudnnLRNDescriptor_t normDesc, + cudnnLRNMode_t lrnMode, + const void* alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnLRNDescriptor_t, cudnnLRNMode_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnLRNCrossChannelForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, normDesc, lrnMode, alpha, xDesc, x, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI cudnnLRNCrossChannelBackward( + cudnnHandle_t handle, + cudnnLRNDescriptor_t normDesc, + cudnnLRNMode_t lrnMode, + const void* alpha, + const cudnnTensorDescriptor_t yDesc, + const void *y, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnLRNDescriptor_t, cudnnLRNMode_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnLRNCrossChannelBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, normDesc, lrnMode, alpha, yDesc, y, dyDesc, dy, xDesc, x, beta, dxDesc, dx); +} + +cudnnStatus_t CUDNNWINAPI cudnnDivisiveNormalizationForward( + cudnnHandle_t handle, + cudnnLRNDescriptor_t normDesc, + cudnnDivNormMode_t mode, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, /* same desc for means, temp, temp2 */ + const void *x, + const void *means, /* if NULL, means are assumed to be zero */ + void *temp, + void *temp2, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnLRNDescriptor_t, cudnnDivNormMode_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, void *, void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnDivisiveNormalizationForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, normDesc, mode, alpha, xDesc, x, means, temp, temp2, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI cudnnDivisiveNormalizationBackward( + cudnnHandle_t handle, + cudnnLRNDescriptor_t normDesc, + cudnnDivNormMode_t mode, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, /* same desc for x, means, dy, temp, temp2 */ + const void *x, + const void *means, /* if NULL, means are assumed to be zero */ + const void *dy, + void *temp, + void *temp2, + const void *beta, + const cudnnTensorDescriptor_t dXdMeansDesc, /* same desc for dx, dMeans */ + void *dx, /* output x differential */ + void *dMeans ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnLRNDescriptor_t, cudnnDivNormMode_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const void *, void *, void *, const void *, const cudnnTensorDescriptor_t, void *, void *); + static auto func_ptr = LoadSymbol("cudnnDivisiveNormalizationBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, normDesc, mode, alpha, xDesc, x, means, dy, temp, temp2, beta, dXdMeansDesc, dx, dMeans); +} + +cudnnStatus_t CUDNNWINAPI cudnnDeriveBNTensorDescriptor( + cudnnTensorDescriptor_t derivedBnDesc, + const cudnnTensorDescriptor_t xDesc, + cudnnBatchNormMode_t mode ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, cudnnBatchNormMode_t); + static auto func_ptr = LoadSymbol("cudnnDeriveBNTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(derivedBnDesc, xDesc, mode); +} + +cudnnStatus_t CUDNNWINAPI cudnnBatchNormalizationForwardTraining( + cudnnHandle_t handle, + cudnnBatchNormMode_t mode, + + const void *alpha, /* alpha[0] = result blend factor */ + const void *beta, /* beta[0] = dest layer blend factor */ + + const cudnnTensorDescriptor_t xDesc, + const void *x, /* NxCxHxW */ + const cudnnTensorDescriptor_t yDesc, + void *y, /* NxCxHxW */ + + /* Shared desc for the next 6 tensors in the argument list. + Data type to be set as follows: + type = (typeOf(x) == double) ? double : float + Dimensions for this descriptor depend on normalization mode + - Spatial Normalization : tensors are expected to have dims 1xCx1x1 + (normalization is performed across NxHxW) + - Per-Activation Normalization : tensors are expected to have dims of 1xCxHxW + (normalization is performed across N) */ + const cudnnTensorDescriptor_t bnScaleBiasMeanVarDesc, + + /* 'Gamma' and 'Beta' respectively in Ioffe and Szegedy's paper's notation */ + const void *bnScale, + const void *bnBias, + + /* MUST use factor=1 in the very first call of a complete training cycle. + Use a factor=1/(1+n) at N-th call to the function to get + Cumulative Moving Average (CMA) behavior + CMA[n] = (x[1]+...+x[n])/n + Since CMA[n+1] = (n*CMA[n]+x[n+1])/(n+1) = + ((n+1)*CMA[n]-CMA[n])/(n+1) + x[n+1]/(n+1) = + CMA[n]*(1-1/(n+1)) + x[n+1]*1/(n+1) */ + double exponentialAverageFactor, + + /* Used in Training phase only. + runningMean = newMean*factor + runningMean*(1-factor) */ + void *resultRunningMean, + /* Output in training mode, input in inference. Is the moving average + of variance[x] (factor is applied in the same way as for runningMean) */ + void *resultRunningVariance, + + /* Has to be >= CUDNN_BN_MIN_EPSILON. Should be the same in forward and backward functions. */ + double epsilon, + + /* Optionally save intermediate results from the forward pass here + - can be reused to speed up backward pass. NULL if unused */ + void *resultSaveMean, + void *resultSaveInvVariance ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnBatchNormMode_t, const void *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, const void *, const void *, double, void *, void *, double, void *, void *); + static auto func_ptr = LoadSymbol("cudnnBatchNormalizationForwardTraining"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode, alpha, beta, xDesc, x, yDesc, y, bnScaleBiasMeanVarDesc, bnScale, bnBias, exponentialAverageFactor, resultRunningMean, resultRunningVariance, epsilon, resultSaveMean, resultSaveInvVariance); +} + +cudnnStatus_t CUDNNWINAPI cudnnBatchNormalizationForwardInference( + cudnnHandle_t handle, + cudnnBatchNormMode_t mode, + const void *alpha, /* alpha[0] = result blend factor */ + const void *beta, /* beta[0] = dest layer blend factor */ + const cudnnTensorDescriptor_t xDesc, + const void *x, /* NxCxHxW */ + const cudnnTensorDescriptor_t yDesc, + void *y, /* NxCxHxW */ + const cudnnTensorDescriptor_t bnScaleBiasMeanVarDesc, + const void *bnScale, + const void *bnBias, + const void *estimatedMean, + const void *estimatedVariance, + double epsilon ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnBatchNormMode_t, const void *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, const void *, const void *, const void *, const void *, double); + static auto func_ptr = LoadSymbol("cudnnBatchNormalizationForwardInference"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode, alpha, beta, xDesc, x, yDesc, y, bnScaleBiasMeanVarDesc, bnScale, bnBias, estimatedMean, estimatedVariance, epsilon); +} + +cudnnStatus_t CUDNNWINAPI cudnnBatchNormalizationBackward( + cudnnHandle_t handle, + cudnnBatchNormMode_t mode, + const void *alphaDataDiff, + const void *betaDataDiff, + const void *alphaParamDiff, + const void *betaParamDiff, + const cudnnTensorDescriptor_t xDesc, /* same desc for x, dx, dy */ + const void *x, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnTensorDescriptor_t dxDesc, + void *dx, + /* Shared tensor desc for the 4 tensors below */ + const cudnnTensorDescriptor_t dBnScaleBiasDesc, + const void *bnScale, /* bnBias doesn't affect backpropagation */ + /* scale and bias diff are not backpropagated below this layer */ + void *dBnScaleResult, + void *dBnBiasResult, + /* Same epsilon as forward pass */ + double epsilon, + + /* Optionally cached intermediate results from + forward pass */ + const void *savedMean, + const void *savedInvVariance ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnBatchNormMode_t, const void *, const void *, const void *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, const void *, void *, void *, double, const void *, const void *); + static auto func_ptr = LoadSymbol("cudnnBatchNormalizationBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode, alphaDataDiff, betaDataDiff, alphaParamDiff, betaParamDiff, xDesc, x, dyDesc, dy, dxDesc, dx, dBnScaleBiasDesc, bnScale, dBnScaleResult, dBnBiasResult, epsilon, savedMean, savedInvVariance); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateSpatialTransformerDescriptor( + cudnnSpatialTransformerDescriptor_t *stDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnSpatialTransformerDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateSpatialTransformerDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(stDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetSpatialTransformerNdDescriptor( + cudnnSpatialTransformerDescriptor_t stDesc, + cudnnSamplerType_t samplerType, + cudnnDataType_t dataType, + const int nbDims, + const int dimA[]) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnSpatialTransformerDescriptor_t, cudnnSamplerType_t, cudnnDataType_t, const int, const int []); + static auto func_ptr = LoadSymbol("cudnnSetSpatialTransformerNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(stDesc, samplerType, dataType, nbDims, dimA); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroySpatialTransformerDescriptor( + cudnnSpatialTransformerDescriptor_t stDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnSpatialTransformerDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroySpatialTransformerDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(stDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSpatialTfGridGeneratorForward( + cudnnHandle_t handle, + const cudnnSpatialTransformerDescriptor_t stDesc, + const void *theta, + void *grid) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnSpatialTransformerDescriptor_t, const void *, void *); + static auto func_ptr = LoadSymbol("cudnnSpatialTfGridGeneratorForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, stDesc, theta, grid); +} + +cudnnStatus_t CUDNNWINAPI cudnnSpatialTfGridGeneratorBackward( + cudnnHandle_t handle, + const cudnnSpatialTransformerDescriptor_t stDesc, + const void *dgrid, + void *dtheta) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnSpatialTransformerDescriptor_t, const void *, void *); + static auto func_ptr = LoadSymbol("cudnnSpatialTfGridGeneratorBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, stDesc, dgrid, dtheta); +} + +cudnnStatus_t CUDNNWINAPI cudnnSpatialTfSamplerForward( + cudnnHandle_t handle, + cudnnSpatialTransformerDescriptor_t stDesc, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *grid, + const void *beta, + cudnnTensorDescriptor_t yDesc, + void *y) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnSpatialTransformerDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const void *, cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnSpatialTfSamplerForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, stDesc, alpha, xDesc, x, grid, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI cudnnSpatialTfSamplerBackward( + cudnnHandle_t handle, + cudnnSpatialTransformerDescriptor_t stDesc, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx, + const void *alphaDgrid, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const void *grid, + const void *betaDgrid, + void *dgrid) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnSpatialTransformerDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const void *, void *); + static auto func_ptr = LoadSymbol("cudnnSpatialTfSamplerBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, stDesc, alpha, xDesc, x, beta, dxDesc, dx, alphaDgrid, dyDesc, dy, grid, betaDgrid, dgrid); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateDropoutDescriptor(cudnnDropoutDescriptor_t * dropoutDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnDropoutDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateDropoutDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dropoutDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyDropoutDescriptor(cudnnDropoutDescriptor_t dropoutDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnDropoutDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyDropoutDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dropoutDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnDropoutGetStatesSize(cudnnHandle_t handle, size_t * sizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnDropoutGetStatesSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnDropoutGetReserveSpaceSize(cudnnTensorDescriptor_t xdesc, size_t * sizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnDropoutGetReserveSpaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(xdesc, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetDropoutDescriptor(cudnnDropoutDescriptor_t dropoutDesc, + cudnnHandle_t handle, + float dropout, + void * states, + size_t stateSizeInBytes, + unsigned long long seed) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnDropoutDescriptor_t, cudnnHandle_t, float, void *, size_t, unsigned long long); + static auto func_ptr = LoadSymbol("cudnnSetDropoutDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dropoutDesc, handle, dropout, states, stateSizeInBytes, seed); +} + +cudnnStatus_t CUDNNWINAPI cudnnRestoreDropoutDescriptor(cudnnDropoutDescriptor_t dropoutDesc, + cudnnHandle_t handle, + float dropout, + void * states, + size_t stateSizeInBytes, + unsigned long long seed) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnDropoutDescriptor_t, cudnnHandle_t, float, void *, size_t, unsigned long long); + static auto func_ptr = LoadSymbol("cudnnRestoreDropoutDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dropoutDesc, handle, dropout, states, stateSizeInBytes, seed); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetDropoutDescriptor(cudnnDropoutDescriptor_t dropoutDesc, + cudnnHandle_t handle, + float * dropout, + void ** states, + unsigned long long * seed) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnDropoutDescriptor_t, cudnnHandle_t, float *, void **, unsigned long long *); + static auto func_ptr = LoadSymbol("cudnnGetDropoutDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dropoutDesc, handle, dropout, states, seed); +} + +cudnnStatus_t CUDNNWINAPI cudnnDropoutForward(cudnnHandle_t handle, + const cudnnDropoutDescriptor_t dropoutDesc, + const cudnnTensorDescriptor_t xdesc, + const void * x, + const cudnnTensorDescriptor_t ydesc, + void * y, + void * reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnDropoutDescriptor_t, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, void *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnDropoutForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, dropoutDesc, xdesc, x, ydesc, y, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnDropoutBackward(cudnnHandle_t handle, + const cudnnDropoutDescriptor_t dropoutDesc, + const cudnnTensorDescriptor_t dydesc, + const void * dy, + const cudnnTensorDescriptor_t dxdesc, + void * dx, + void * reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnDropoutDescriptor_t, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, void *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnDropoutBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, dropoutDesc, dydesc, dy, dxdesc, dx, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateRNNDescriptor(cudnnRNNDescriptor_t * rnnDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateRNNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyRNNDescriptor(cudnnRNNDescriptor_t rnnDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyRNNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreatePersistentRNNPlan(cudnnRNNDescriptor_t rnnDesc, + const int minibatch, + const cudnnDataType_t dataType, + cudnnPersistentRNNPlan_t * plan) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t, const int, const cudnnDataType_t, cudnnPersistentRNNPlan_t *); + static auto func_ptr = LoadSymbol("cudnnCreatePersistentRNNPlan"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc, minibatch, dataType, plan); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetPersistentRNNPlan(cudnnRNNDescriptor_t rnnDesc, + cudnnPersistentRNNPlan_t plan) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t, cudnnPersistentRNNPlan_t); + static auto func_ptr = LoadSymbol("cudnnSetPersistentRNNPlan"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc, plan); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyPersistentRNNPlan(cudnnPersistentRNNPlan_t plan) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnPersistentRNNPlan_t); + static auto func_ptr = LoadSymbol("cudnnDestroyPersistentRNNPlan"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(plan); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetRNNDescriptor(cudnnHandle_t handle, + cudnnRNNDescriptor_t rnnDesc, + const int hiddenSize, + const int numLayers, + cudnnDropoutDescriptor_t dropoutDesc, /* Between layers, not between recurrent steps. */ + cudnnRNNInputMode_t inputMode, + cudnnDirectionMode_t direction, + cudnnRNNMode_t mode, + cudnnRNNAlgo_t algo, + cudnnDataType_t dataType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnRNNDescriptor_t, const int, const int, cudnnDropoutDescriptor_t, cudnnRNNInputMode_t, cudnnDirectionMode_t, cudnnRNNMode_t, cudnnRNNAlgo_t, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnSetRNNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, hiddenSize, numLayers, dropoutDesc, inputMode, direction, mode, algo, dataType); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetRNNDescriptor(cudnnHandle_t cudnnHandle, + cudnnRNNDescriptor_t rnnDesc, + int * hiddenSize, + int * numLayers, + cudnnDropoutDescriptor_t * dropoutDesc, + cudnnRNNInputMode_t * inputMode, + cudnnDirectionMode_t * direction, + cudnnRNNMode_t * mode, + cudnnRNNAlgo_t * algo, + cudnnDataType_t * dataType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnRNNDescriptor_t, int *, int *, cudnnDropoutDescriptor_t *, cudnnRNNInputMode_t *, cudnnDirectionMode_t *, cudnnRNNMode_t *, cudnnRNNAlgo_t *, cudnnDataType_t *); + static auto func_ptr = LoadSymbol("cudnnGetRNNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(cudnnHandle, rnnDesc, hiddenSize, numLayers, dropoutDesc, inputMode, direction, mode, algo, dataType); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetRNNMatrixMathType (cudnnRNNDescriptor_t desc, cudnnMathType_t math) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t, cudnnMathType_t); + static auto func_ptr = LoadSymbol("cudnnSetRNNMatrixMathType"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(desc, math); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetRNNWorkspaceSize( cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t *xDesc, + size_t *sizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetRNNWorkspaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetRNNTrainingReserveSize( cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t *xDesc, + size_t *sizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetRNNTrainingReserveSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetRNNParamsSize( cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const cudnnTensorDescriptor_t xDesc, + size_t *sizeInBytes, + cudnnDataType_t dataType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const cudnnTensorDescriptor_t, size_t *, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnGetRNNParamsSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, xDesc, sizeInBytes, dataType); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetRNNLinLayerMatrixParams( cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int layer, + const cudnnTensorDescriptor_t xDesc, + const cudnnFilterDescriptor_t wDesc, + const void * w, + const int linLayerID, + cudnnFilterDescriptor_t linLayerMatDesc, + void ** linLayerMat) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, const void *, const int, cudnnFilterDescriptor_t, void **); + static auto func_ptr = LoadSymbol("cudnnGetRNNLinLayerMatrixParams"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, layer, xDesc, wDesc, w, linLayerID, linLayerMatDesc, linLayerMat); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetRNNLinLayerBiasParams( cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int layer, + const cudnnTensorDescriptor_t xDesc, + const cudnnFilterDescriptor_t wDesc, + const void * w, + const int linLayerID, + cudnnFilterDescriptor_t linLayerBiasDesc, + void ** linLayerBias) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, const void *, const int, cudnnFilterDescriptor_t, void **); + static auto func_ptr = LoadSymbol("cudnnGetRNNLinLayerBiasParams"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, layer, xDesc, wDesc, w, linLayerID, linLayerBiasDesc, linLayerBias); +} + +cudnnStatus_t CUDNNWINAPI cudnnRNNForwardInference( cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t * xDesc, + const void * x, + const cudnnTensorDescriptor_t hxDesc, + const void * hx, + const cudnnTensorDescriptor_t cxDesc, + const void * cx, + const cudnnFilterDescriptor_t wDesc, + const void * w, + const cudnnTensorDescriptor_t *yDesc, + void * y, + const cudnnTensorDescriptor_t hyDesc, + void * hy, + const cudnnTensorDescriptor_t cyDesc, + void * cy, + void * workspace, + size_t workSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t *, void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnRNNForwardInference"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, x, hxDesc, hx, cxDesc, cx, wDesc, w, yDesc, y, hyDesc, hy, cyDesc, cy, workspace, workSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnRNNForwardTraining( cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t *xDesc, + const void * x, + const cudnnTensorDescriptor_t hxDesc, + const void * hx, + const cudnnTensorDescriptor_t cxDesc, + const void * cx, + const cudnnFilterDescriptor_t wDesc, + const void * w, + const cudnnTensorDescriptor_t *yDesc, + void * y, + const cudnnTensorDescriptor_t hyDesc, + void * hy, + const cudnnTensorDescriptor_t cyDesc, + void * cy, + void * workspace, + size_t workSpaceSizeInBytes, + void * reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t *, void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, void *, size_t, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnRNNForwardTraining"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, x, hxDesc, hx, cxDesc, cx, wDesc, w, yDesc, y, hyDesc, hy, cyDesc, cy, workspace, workSpaceSizeInBytes, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnRNNBackwardData( cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t * yDesc, + const void * y, + const cudnnTensorDescriptor_t * dyDesc, + const void * dy, + const cudnnTensorDescriptor_t dhyDesc, + const void * dhy, + const cudnnTensorDescriptor_t dcyDesc, + const void * dcy, + const cudnnFilterDescriptor_t wDesc, + const void * w, + const cudnnTensorDescriptor_t hxDesc, + const void * hx, + const cudnnTensorDescriptor_t cxDesc, + const void * cx, + const cudnnTensorDescriptor_t * dxDesc, + void * dx, + const cudnnTensorDescriptor_t dhxDesc, + void * dhx, + const cudnnTensorDescriptor_t dcxDesc, + void * dcx, + void * workspace, + size_t workSpaceSizeInBytes, + void * reserveSpace, + size_t reserveSpaceSizeInBytes ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t *, void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, void *, size_t, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnRNNBackwardData"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, yDesc, y, dyDesc, dy, dhyDesc, dhy, dcyDesc, dcy, wDesc, w, hxDesc, hx, cxDesc, cx, dxDesc, dx, dhxDesc, dhx, dcxDesc, dcx, workspace, workSpaceSizeInBytes, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnRNNBackwardWeights( cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t * xDesc, + const void * x, + const cudnnTensorDescriptor_t hxDesc, + const void * hx, + const cudnnTensorDescriptor_t * yDesc, + const void * y, + const void * workspace, + size_t workSpaceSizeInBytes, + const cudnnFilterDescriptor_t dwDesc, + void * dw, + const void * reserveSpace, + size_t reserveSpaceSizeInBytes ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t *, const void *, const void *, size_t, const cudnnFilterDescriptor_t, void *, const void *, size_t); + static auto func_ptr = LoadSymbol("cudnnRNNBackwardWeights"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, x, hxDesc, hx, yDesc, y, workspace, workSpaceSizeInBytes, dwDesc, dw, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateCTCLossDescriptor( cudnnCTCLossDescriptor_t* ctcLossDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnCTCLossDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateCTCLossDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(ctcLossDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetCTCLossDescriptor( + cudnnCTCLossDescriptor_t ctcLossDesc, + cudnnDataType_t compType ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnCTCLossDescriptor_t, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnSetCTCLossDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(ctcLossDesc, compType); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetCTCLossDescriptor( + cudnnCTCLossDescriptor_t ctcLossDesc, + cudnnDataType_t* compType ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnCTCLossDescriptor_t, cudnnDataType_t *); + static auto func_ptr = LoadSymbol("cudnnGetCTCLossDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(ctcLossDesc, compType); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyCTCLossDescriptor( cudnnCTCLossDescriptor_t ctcLossDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnCTCLossDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyCTCLossDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(ctcLossDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnCTCLoss( cudnnHandle_t handle, + const cudnnTensorDescriptor_t probsDesc, /* Tensor descriptor for probabilities, the dimensions are T,N,A (T is the timing steps, N is the mini batch size, A is the alphabet size) */ + const void * probs, /* probabilities after softmax, in GPU memory */ + const int * labels, /* labels, in CPU memory */ + const int * labelLengths, /* the length of each label, in CPU memory */ + const int * inputLengths, /* the lengths of timing steps in each batch, in CPU memory */ + void * costs, /* the returned costs of CTC, in GPU memory */ + const cudnnTensorDescriptor_t gradientsDesc, /* Tensor descriptor for gradients, the dimensions are T,N,A */ + const void * gradients, /* the returned CTC gradients, in GPU memory, to compute costs only, set it to NULL */ + cudnnCTCLossAlgo_t algo, /* algorithm selected, supported now 0 and 1 */ + cudnnCTCLossDescriptor_t ctcLossDesc, + void * workspace, /* pointer to the workspace, in GPU memory */ + size_t workSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const void *, const int *, const int *, const int *, void *, const cudnnTensorDescriptor_t, const void *, cudnnCTCLossAlgo_t, cudnnCTCLossDescriptor_t, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnCTCLoss"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, probsDesc, probs, labels, labelLengths, inputLengths, costs, gradientsDesc, gradients, algo, ctcLossDesc, workspace, workSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetCTCLossWorkspaceSize( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t probsDesc, /* Tensor descriptor for probabilities, the dimensions are T,N,A (T is the timing steps, N is the mini batch size, A is the alphabet size) */ + const cudnnTensorDescriptor_t gradientsDesc, /* Tensor descriptor for gradients, the dimensions are T,N,A. To compute costs only, set it to NULL */ + const int * labels, /* labels, in CPU memory */ + const int * labelLengths, /* the length of each label, in CPU memory */ + const int * inputLengths, /* the lengths of timing steps in each batch, in CPU memory */ + cudnnCTCLossAlgo_t algo, /* algorithm selected, supported now 0 and 1 */ + cudnnCTCLossDescriptor_t ctcLossDesc, + size_t *sizeInBytes ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, const int *, const int *, const int *, cudnnCTCLossAlgo_t, cudnnCTCLossDescriptor_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetCTCLossWorkspaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, probsDesc, gradientsDesc, labels, labelLengths, inputLengths, algo, ctcLossDesc, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetRNNDescriptor_v6(cudnnHandle_t handle, + cudnnRNNDescriptor_t rnnDesc, + const int hiddenSize, + const int numLayers, + cudnnDropoutDescriptor_t dropoutDesc, /* Between layers, not between recurrent steps. */ + cudnnRNNInputMode_t inputMode, + cudnnDirectionMode_t direction, + cudnnRNNMode_t mode, + cudnnRNNAlgo_t algo, + cudnnDataType_t dataType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnRNNDescriptor_t, const int, const int, cudnnDropoutDescriptor_t, cudnnRNNInputMode_t, cudnnDirectionMode_t, cudnnRNNMode_t, cudnnRNNAlgo_t, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnSetRNNDescriptor_v6"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, hiddenSize, numLayers, dropoutDesc, inputMode, direction, mode, algo, dataType); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetRNNDescriptor_v5(cudnnRNNDescriptor_t rnnDesc, + int hiddenSize, + int numLayers, + cudnnDropoutDescriptor_t dropoutDesc, /* Between layers, not between recurrent steps. */ + cudnnRNNInputMode_t inputMode, + cudnnDirectionMode_t direction, + cudnnRNNMode_t mode, + cudnnDataType_t dataType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t, int, int, cudnnDropoutDescriptor_t, cudnnRNNInputMode_t, cudnnDirectionMode_t, cudnnRNNMode_t, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnSetRNNDescriptor_v5"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc, hiddenSize, numLayers, dropoutDesc, inputMode, direction, mode, dataType); +} + +} // extern "C" diff --git a/tensorflow/stream_executor/cuda/cudnn_7_1.inc b/tensorflow/stream_executor/cuda/cudnn_7_1.inc new file mode 100644 index 0000000000..21abd7fdb1 --- /dev/null +++ b/tensorflow/stream_executor/cuda/cudnn_7_1.inc @@ -0,0 +1,2281 @@ +// Auto-generated, do not edit. + +extern "C" { + +size_t CUDNNWINAPI cudnnGetVersion(void) { + using FuncPtr = size_t (CUDNNWINAPI *)(); + static auto func_ptr = LoadSymbol("cudnnGetVersion"); + if (!func_ptr) return 0; + return func_ptr(); +} + +size_t CUDNNWINAPI cudnnGetCudartVersion(void) { + using FuncPtr = size_t (CUDNNWINAPI *)(); + static auto func_ptr = LoadSymbol("cudnnGetCudartVersion"); + if (!func_ptr) return 0; + return func_ptr(); +} + +const char * CUDNNWINAPI cudnnGetErrorString(cudnnStatus_t status) { + using FuncPtr = const char * (CUDNNWINAPI *)(cudnnStatus_t); + static auto func_ptr = LoadSymbol("cudnnGetErrorString"); + if (!func_ptr) return "cudnnGetErrorString symbol not found."; + return func_ptr(status); +} + +cudnnStatus_t CUDNNWINAPI cudnnQueryRuntimeError( + cudnnHandle_t handle, + cudnnStatus_t *rstatus, + cudnnErrQueryMode_t mode, + cudnnRuntimeTag_t *tag ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnStatus_t *, cudnnErrQueryMode_t, cudnnRuntimeTag_t *); + static auto func_ptr = LoadSymbol("cudnnQueryRuntimeError"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rstatus, mode, tag); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetProperty(libraryPropertyType type, int *value) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(libraryPropertyType, int *); + static auto func_ptr = LoadSymbol("cudnnGetProperty"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(type, value); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreate (cudnnHandle_t *handle) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t *); + static auto func_ptr = LoadSymbol("cudnnCreate"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroy (cudnnHandle_t handle) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t); + static auto func_ptr = LoadSymbol("cudnnDestroy"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetStream (cudnnHandle_t handle, cudaStream_t streamId) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudaStream_t); + static auto func_ptr = LoadSymbol("cudnnSetStream"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, streamId); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetStream (cudnnHandle_t handle, cudaStream_t *streamId) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudaStream_t *); + static auto func_ptr = LoadSymbol("cudnnGetStream"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, streamId); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateTensorDescriptor( + cudnnTensorDescriptor_t *tensorDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetTensor4dDescriptor( + cudnnTensorDescriptor_t tensorDesc, + cudnnTensorFormat_t format, + cudnnDataType_t dataType, /* image data type */ + int n, /* number of inputs (batch size) */ + int c, /* number of input feature maps */ + int h, /* height of input section */ + int w ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t, cudnnTensorFormat_t, cudnnDataType_t, int, int, int, int); + static auto func_ptr = LoadSymbol("cudnnSetTensor4dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, format, dataType, n, c, h, w); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetTensor4dDescriptorEx( + cudnnTensorDescriptor_t tensorDesc, + cudnnDataType_t dataType, /* image data type */ + int n, /* number of inputs (batch size) */ + int c, /* number of input feature maps */ + int h, /* height of input section */ + int w, /* width of input section */ + int nStride, + int cStride, + int hStride, + int wStride ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t, cudnnDataType_t, int, int, int, int, int, int, int, int); + static auto func_ptr = LoadSymbol("cudnnSetTensor4dDescriptorEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, dataType, n, c, h, w, nStride, cStride, hStride, wStride); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetTensor4dDescriptor( + const cudnnTensorDescriptor_t tensorDesc, + cudnnDataType_t *dataType, /* image data type */ + int *n, /* number of inputs (batch size) */ + int *c, /* number of input feature maps */ + int *h, /* height of input section */ + int *w, /* width of input section */ + int *nStride, + int *cStride, + int *hStride, + int *wStride ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnTensorDescriptor_t, cudnnDataType_t *, int *, int *, int *, int *, int *, int *, int *, int *); + static auto func_ptr = LoadSymbol("cudnnGetTensor4dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, dataType, n, c, h, w, nStride, cStride, hStride, wStride); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetTensorNdDescriptor( + cudnnTensorDescriptor_t tensorDesc, + cudnnDataType_t dataType, + int nbDims, + const int dimA[], + const int strideA[] ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t, cudnnDataType_t, int, const int [], const int []); + static auto func_ptr = LoadSymbol("cudnnSetTensorNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, dataType, nbDims, dimA, strideA); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetTensorNdDescriptorEx( + cudnnTensorDescriptor_t tensorDesc, + cudnnTensorFormat_t format, + cudnnDataType_t dataType, + int nbDims, + const int dimA[] ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t, cudnnTensorFormat_t, cudnnDataType_t, int, const int []); + static auto func_ptr = LoadSymbol("cudnnSetTensorNdDescriptorEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, format, dataType, nbDims, dimA); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetTensorNdDescriptor( + const cudnnTensorDescriptor_t tensorDesc, + int nbDimsRequested, + cudnnDataType_t *dataType, + int *nbDims, + int dimA[], + int strideA[] ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnTensorDescriptor_t, int, cudnnDataType_t *, int *, int [], int []); + static auto func_ptr = LoadSymbol("cudnnGetTensorNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, nbDimsRequested, dataType, nbDims, dimA, strideA); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetTensorSizeInBytes( + const cudnnTensorDescriptor_t tensorDesc, + size_t *size) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnTensorDescriptor_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetTensorSizeInBytes"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, size); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyTensorDescriptor( + cudnnTensorDescriptor_t tensorDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnTransformTensor( + cudnnHandle_t handle, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnTransformTensor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha, xDesc, x, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI cudnnAddTensor( + cudnnHandle_t handle, + const void *alpha, + const cudnnTensorDescriptor_t aDesc, + const void *A, + const void *beta, + const cudnnTensorDescriptor_t cDesc, + void *C ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnAddTensor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha, aDesc, A, beta, cDesc, C); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateOpTensorDescriptor( + cudnnOpTensorDescriptor_t *opTensorDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnOpTensorDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateOpTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(opTensorDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetOpTensorDescriptor( + cudnnOpTensorDescriptor_t opTensorDesc, + cudnnOpTensorOp_t opTensorOp, + cudnnDataType_t opTensorCompType, + cudnnNanPropagation_t opTensorNanOpt ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnOpTensorDescriptor_t, cudnnOpTensorOp_t, cudnnDataType_t, cudnnNanPropagation_t); + static auto func_ptr = LoadSymbol("cudnnSetOpTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(opTensorDesc, opTensorOp, opTensorCompType, opTensorNanOpt); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetOpTensorDescriptor( + const cudnnOpTensorDescriptor_t opTensorDesc, + cudnnOpTensorOp_t *opTensorOp, + cudnnDataType_t *opTensorCompType, + cudnnNanPropagation_t *opTensorNanOpt ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnOpTensorDescriptor_t, cudnnOpTensorOp_t *, cudnnDataType_t *, cudnnNanPropagation_t *); + static auto func_ptr = LoadSymbol("cudnnGetOpTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(opTensorDesc, opTensorOp, opTensorCompType, opTensorNanOpt); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyOpTensorDescriptor( + cudnnOpTensorDescriptor_t opTensorDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnOpTensorDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyOpTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(opTensorDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnOpTensor( + cudnnHandle_t handle, + const cudnnOpTensorDescriptor_t opTensorDesc, + const void *alpha1, + const cudnnTensorDescriptor_t aDesc, + const void *A, + const void *alpha2, + const cudnnTensorDescriptor_t bDesc, + const void *B, + const void *beta, + const cudnnTensorDescriptor_t cDesc, + void *C ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnOpTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnOpTensor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, opTensorDesc, alpha1, aDesc, A, alpha2, bDesc, B, beta, cDesc, C); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateReduceTensorDescriptor( + cudnnReduceTensorDescriptor_t *reduceTensorDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnReduceTensorDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateReduceTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(reduceTensorDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetReduceTensorDescriptor( + cudnnReduceTensorDescriptor_t reduceTensorDesc, + cudnnReduceTensorOp_t reduceTensorOp, + cudnnDataType_t reduceTensorCompType, + cudnnNanPropagation_t reduceTensorNanOpt, + cudnnReduceTensorIndices_t reduceTensorIndices, + cudnnIndicesType_t reduceTensorIndicesType ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnReduceTensorDescriptor_t, cudnnReduceTensorOp_t, cudnnDataType_t, cudnnNanPropagation_t, cudnnReduceTensorIndices_t, cudnnIndicesType_t); + static auto func_ptr = LoadSymbol("cudnnSetReduceTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(reduceTensorDesc, reduceTensorOp, reduceTensorCompType, reduceTensorNanOpt, reduceTensorIndices, reduceTensorIndicesType); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetReduceTensorDescriptor( + const cudnnReduceTensorDescriptor_t reduceTensorDesc, + cudnnReduceTensorOp_t *reduceTensorOp, + cudnnDataType_t *reduceTensorCompType, + cudnnNanPropagation_t *reduceTensorNanOpt, + cudnnReduceTensorIndices_t *reduceTensorIndices, + cudnnIndicesType_t *reduceTensorIndicesType ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnReduceTensorDescriptor_t, cudnnReduceTensorOp_t *, cudnnDataType_t *, cudnnNanPropagation_t *, cudnnReduceTensorIndices_t *, cudnnIndicesType_t *); + static auto func_ptr = LoadSymbol("cudnnGetReduceTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(reduceTensorDesc, reduceTensorOp, reduceTensorCompType, reduceTensorNanOpt, reduceTensorIndices, reduceTensorIndicesType); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyReduceTensorDescriptor( + cudnnReduceTensorDescriptor_t reduceTensorDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnReduceTensorDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyReduceTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(reduceTensorDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetReductionIndicesSize( + cudnnHandle_t handle, + const cudnnReduceTensorDescriptor_t reduceTensorDesc, + const cudnnTensorDescriptor_t aDesc, + const cudnnTensorDescriptor_t cDesc, + size_t *sizeInBytes ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnReduceTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetReductionIndicesSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, reduceTensorDesc, aDesc, cDesc, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetReductionWorkspaceSize( + cudnnHandle_t handle, + const cudnnReduceTensorDescriptor_t reduceTensorDesc, + const cudnnTensorDescriptor_t aDesc, + const cudnnTensorDescriptor_t cDesc, + size_t *sizeInBytes ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnReduceTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetReductionWorkspaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, reduceTensorDesc, aDesc, cDesc, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnReduceTensor( + cudnnHandle_t handle, + const cudnnReduceTensorDescriptor_t reduceTensorDesc, + void *indices, + size_t indicesSizeInBytes, + void *workspace, + size_t workspaceSizeInBytes, + const void *alpha, + const cudnnTensorDescriptor_t aDesc, + const void *A, + const void *beta, + const cudnnTensorDescriptor_t cDesc, + void *C ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnReduceTensorDescriptor_t, void *, size_t, void *, size_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnReduceTensor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, reduceTensorDesc, indices, indicesSizeInBytes, workspace, workspaceSizeInBytes, alpha, aDesc, A, beta, cDesc, C); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetTensor( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t yDesc, + void *y, + const void *valuePtr ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, void *, const void *); + static auto func_ptr = LoadSymbol("cudnnSetTensor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, yDesc, y, valuePtr); +} + +cudnnStatus_t CUDNNWINAPI cudnnScaleTensor( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t yDesc, + void *y, + const void *alpha ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, void *, const void *); + static auto func_ptr = LoadSymbol("cudnnScaleTensor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, yDesc, y, alpha); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateFilterDescriptor( + cudnnFilterDescriptor_t *filterDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnFilterDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateFilterDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(filterDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetFilter4dDescriptor( + cudnnFilterDescriptor_t filterDesc, + cudnnDataType_t dataType, /* image data type */ + cudnnTensorFormat_t format, + int k, /* number of output feature maps */ + int c, /* number of input feature maps */ + int h, /* height of each input filter */ + int w ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnFilterDescriptor_t, cudnnDataType_t, cudnnTensorFormat_t, int, int, int, int); + static auto func_ptr = LoadSymbol("cudnnSetFilter4dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(filterDesc, dataType, format, k, c, h, w); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetFilter4dDescriptor( + const cudnnFilterDescriptor_t filterDesc, + cudnnDataType_t *dataType, /* image data type */ + cudnnTensorFormat_t *format, + int *k, /* number of output feature maps */ + int *c, /* number of input feature maps */ + int *h, /* height of each input filter */ + int *w ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnFilterDescriptor_t, cudnnDataType_t *, cudnnTensorFormat_t *, int *, int *, int *, int *); + static auto func_ptr = LoadSymbol("cudnnGetFilter4dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(filterDesc, dataType, format, k, c, h, w); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetFilterNdDescriptor( + cudnnFilterDescriptor_t filterDesc, + cudnnDataType_t dataType, /* image data type */ + cudnnTensorFormat_t format, + int nbDims, + const int filterDimA[] ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnFilterDescriptor_t, cudnnDataType_t, cudnnTensorFormat_t, int, const int []); + static auto func_ptr = LoadSymbol("cudnnSetFilterNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(filterDesc, dataType, format, nbDims, filterDimA); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetFilterNdDescriptor( + const cudnnFilterDescriptor_t filterDesc, + int nbDimsRequested, + cudnnDataType_t *dataType, /* image data type */ + cudnnTensorFormat_t *format, + int *nbDims, + int filterDimA[] ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnFilterDescriptor_t, int, cudnnDataType_t *, cudnnTensorFormat_t *, int *, int []); + static auto func_ptr = LoadSymbol("cudnnGetFilterNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(filterDesc, nbDimsRequested, dataType, format, nbDims, filterDimA); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyFilterDescriptor( + cudnnFilterDescriptor_t filterDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnFilterDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyFilterDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(filterDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateConvolutionDescriptor( + cudnnConvolutionDescriptor_t *convDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateConvolutionDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetConvolutionMathType( cudnnConvolutionDescriptor_t convDesc, + cudnnMathType_t mathType ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t, cudnnMathType_t); + static auto func_ptr = LoadSymbol("cudnnSetConvolutionMathType"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, mathType); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionMathType( cudnnConvolutionDescriptor_t convDesc, + cudnnMathType_t *mathType ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t, cudnnMathType_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionMathType"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, mathType); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetConvolutionGroupCount( cudnnConvolutionDescriptor_t convDesc, + int groupCount ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t, int); + static auto func_ptr = LoadSymbol("cudnnSetConvolutionGroupCount"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, groupCount); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionGroupCount( cudnnConvolutionDescriptor_t convDesc, + int *groupCount ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t, int *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionGroupCount"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, groupCount); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetConvolution2dDescriptor( cudnnConvolutionDescriptor_t convDesc, + int pad_h, /* zero-padding height */ + int pad_w, /* zero-padding width */ + int u, /* vertical filter stride */ + int v, /* horizontal filter stride */ + int dilation_h, /* filter dilation in the vertical dimension */ + int dilation_w, /* filter dilation in the horizontal dimension */ + cudnnConvolutionMode_t mode, + cudnnDataType_t computeType + ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t, int, int, int, int, int, int, cudnnConvolutionMode_t, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnSetConvolution2dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, pad_h, pad_w, u, v, dilation_h, dilation_w, mode, computeType); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolution2dDescriptor( const cudnnConvolutionDescriptor_t convDesc, + int* pad_h, /* zero-padding height */ + int* pad_w, /* zero-padding width */ + int* u, /* vertical filter stride */ + int* v, /* horizontal filter stride */ + int* dilation_h, /* filter dilation in the vertical dimension */ + int* dilation_w, /* filter dilation in the horizontal dimension */ + cudnnConvolutionMode_t* mode, + cudnnDataType_t *computeType + ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnConvolutionDescriptor_t, int *, int *, int *, int *, int *, int *, cudnnConvolutionMode_t *, cudnnDataType_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolution2dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, pad_h, pad_w, u, v, dilation_h, dilation_w, mode, computeType); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolution2dForwardOutputDim( + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t inputTensorDesc, + const cudnnFilterDescriptor_t filterDesc, + int *n, + int *c, + int *h, + int *w ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, int *, int *, int *, int *); + static auto func_ptr = LoadSymbol("cudnnGetConvolution2dForwardOutputDim"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, inputTensorDesc, filterDesc, n, c, h, w); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetConvolutionNdDescriptor( + cudnnConvolutionDescriptor_t convDesc, + int arrayLength, /* nbDims-2 size */ + const int padA[], + const int filterStrideA[], + const int dilationA[], + cudnnConvolutionMode_t mode, + cudnnDataType_t computeType ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t, int, const int [], const int [], const int [], cudnnConvolutionMode_t, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnSetConvolutionNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, arrayLength, padA, filterStrideA, dilationA, mode, computeType); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionNdDescriptor( + const cudnnConvolutionDescriptor_t convDesc, + int arrayLengthRequested, + int *arrayLength, + int padA[], + int strideA[], + int dilationA[], + cudnnConvolutionMode_t *mode, + cudnnDataType_t *computeType ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnConvolutionDescriptor_t, int, int *, int [], int [], int [], cudnnConvolutionMode_t *, cudnnDataType_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, arrayLengthRequested, arrayLength, padA, strideA, dilationA, mode, computeType); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionNdForwardOutputDim( + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t inputTensorDesc, + const cudnnFilterDescriptor_t filterDesc, + int nbDims, + int tensorOuputDimA[] ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, int, int []); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionNdForwardOutputDim"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, inputTensorDesc, filterDesc, nbDims, tensorOuputDimA); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyConvolutionDescriptor( + cudnnConvolutionDescriptor_t convDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyConvolutionDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionForwardAlgorithmMaxCount( cudnnHandle_t handle, + int *count) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, int *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionForwardAlgorithmMaxCount"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, count); +} + +cudnnStatus_t CUDNNWINAPI cudnnFindConvolutionForwardAlgorithm( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnFilterDescriptor_t wDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t yDesc, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionFwdAlgoPerf_t *perfResults ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, const int, int *, cudnnConvolutionFwdAlgoPerf_t *); + static auto func_ptr = LoadSymbol("cudnnFindConvolutionForwardAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, wDesc, convDesc, yDesc, requestedAlgoCount, returnedAlgoCount, perfResults); +} + +cudnnStatus_t CUDNNWINAPI cudnnFindConvolutionForwardAlgorithmEx( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t yDesc, + void *y, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionFwdAlgoPerf_t *perfResults, + void *workSpace, + size_t workSpaceSizeInBytes ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, void *, const int, int *, cudnnConvolutionFwdAlgoPerf_t *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnFindConvolutionForwardAlgorithmEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, x, wDesc, w, convDesc, yDesc, y, requestedAlgoCount, returnedAlgoCount, perfResults, workSpace, workSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionForwardAlgorithm( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnFilterDescriptor_t wDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t yDesc, + cudnnConvolutionFwdPreference_t preference, + size_t memoryLimitInBytes, + cudnnConvolutionFwdAlgo_t *algo ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, cudnnConvolutionFwdPreference_t, size_t, cudnnConvolutionFwdAlgo_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionForwardAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, wDesc, convDesc, yDesc, preference, memoryLimitInBytes, algo); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionForwardAlgorithm_v7( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t srcDesc, + const cudnnFilterDescriptor_t filterDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t destDesc, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionFwdAlgoPerf_t *perfResults) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, const int, int *, cudnnConvolutionFwdAlgoPerf_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionForwardAlgorithm_v7"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, srcDesc, filterDesc, convDesc, destDesc, requestedAlgoCount, returnedAlgoCount, perfResults); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionForwardWorkspaceSize( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnFilterDescriptor_t wDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t yDesc, + cudnnConvolutionFwdAlgo_t algo, + size_t *sizeInBytes ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, cudnnConvolutionFwdAlgo_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionForwardWorkspaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, wDesc, convDesc, yDesc, algo, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnConvolutionForward( + cudnnHandle_t handle, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnConvolutionDescriptor_t convDesc, + cudnnConvolutionFwdAlgo_t algo, + void *workSpace, + size_t workSpaceSizeInBytes, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, cudnnConvolutionFwdAlgo_t, void *, size_t, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnConvolutionForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha, xDesc, x, wDesc, w, convDesc, algo, workSpace, workSpaceSizeInBytes, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI cudnnConvolutionBiasActivationForward( + cudnnHandle_t handle, + const void *alpha1, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnConvolutionDescriptor_t convDesc, + cudnnConvolutionFwdAlgo_t algo, + void *workSpace, + size_t workSpaceSizeInBytes, + const void *alpha2, + const cudnnTensorDescriptor_t zDesc, + const void *z, + const cudnnTensorDescriptor_t biasDesc, + const void *bias, + const cudnnActivationDescriptor_t activationDesc, + const cudnnTensorDescriptor_t yDesc, + void *y ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, cudnnConvolutionFwdAlgo_t, void *, size_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnActivationDescriptor_t, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnConvolutionBiasActivationForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha1, xDesc, x, wDesc, w, convDesc, algo, workSpace, workSpaceSizeInBytes, alpha2, zDesc, z, biasDesc, bias, activationDesc, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI cudnnConvolutionBackwardBias( + cudnnHandle_t handle, + const void *alpha, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const void *beta, + const cudnnTensorDescriptor_t dbDesc, + void *db ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnConvolutionBackwardBias"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha, dyDesc, dy, beta, dbDesc, db); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionBackwardFilterAlgorithmMaxCount( cudnnHandle_t handle, + int *count) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, int *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardFilterAlgorithmMaxCount"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, count); +} + +cudnnStatus_t CUDNNWINAPI cudnnFindConvolutionBackwardFilterAlgorithm( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnFilterDescriptor_t dwDesc, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionBwdFilterAlgoPerf_t *perfResults ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnFilterDescriptor_t, const int, int *, cudnnConvolutionBwdFilterAlgoPerf_t *); + static auto func_ptr = LoadSymbol("cudnnFindConvolutionBackwardFilterAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, dyDesc, convDesc, dwDesc, requestedAlgoCount, returnedAlgoCount, perfResults); +} + +cudnnStatus_t CUDNNWINAPI cudnnFindConvolutionBackwardFilterAlgorithmEx( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnTensorDescriptor_t dyDesc, + const void *y, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnFilterDescriptor_t dwDesc, + void *dw, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionBwdFilterAlgoPerf_t *perfResults, + void *workSpace, + size_t workSpaceSizeInBytes ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, const cudnnFilterDescriptor_t, void *, const int, int *, cudnnConvolutionBwdFilterAlgoPerf_t *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnFindConvolutionBackwardFilterAlgorithmEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, x, dyDesc, y, convDesc, dwDesc, dw, requestedAlgoCount, returnedAlgoCount, perfResults, workSpace, workSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionBackwardFilterAlgorithm( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnFilterDescriptor_t dwDesc, + cudnnConvolutionBwdFilterPreference_t preference, + size_t memoryLimitInBytes, + cudnnConvolutionBwdFilterAlgo_t *algo ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnFilterDescriptor_t, cudnnConvolutionBwdFilterPreference_t, size_t, cudnnConvolutionBwdFilterAlgo_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardFilterAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, dyDesc, convDesc, dwDesc, preference, memoryLimitInBytes, algo); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionBackwardFilterAlgorithm_v7( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t srcDesc, + const cudnnTensorDescriptor_t diffDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnFilterDescriptor_t gradDesc, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionBwdFilterAlgoPerf_t *perfResults) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnFilterDescriptor_t, const int, int *, cudnnConvolutionBwdFilterAlgoPerf_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardFilterAlgorithm_v7"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, srcDesc, diffDesc, convDesc, gradDesc, requestedAlgoCount, returnedAlgoCount, perfResults); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionBackwardFilterWorkspaceSize( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnFilterDescriptor_t gradDesc, + cudnnConvolutionBwdFilterAlgo_t algo, + size_t *sizeInBytes ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnFilterDescriptor_t, cudnnConvolutionBwdFilterAlgo_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardFilterWorkspaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, dyDesc, convDesc, gradDesc, algo, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnConvolutionBackwardFilter( + cudnnHandle_t handle, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnConvolutionDescriptor_t convDesc, + cudnnConvolutionBwdFilterAlgo_t algo, + void *workSpace, + size_t workSpaceSizeInBytes, + const void *beta, + const cudnnFilterDescriptor_t dwDesc, + void *dw ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, cudnnConvolutionBwdFilterAlgo_t, void *, size_t, const void *, const cudnnFilterDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnConvolutionBackwardFilter"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha, xDesc, x, dyDesc, dy, convDesc, algo, workSpace, workSpaceSizeInBytes, beta, dwDesc, dw); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionBackwardDataAlgorithmMaxCount( cudnnHandle_t handle, + int *count) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, int *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardDataAlgorithmMaxCount"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, count); +} + +cudnnStatus_t CUDNNWINAPI cudnnFindConvolutionBackwardDataAlgorithm( + cudnnHandle_t handle, + const cudnnFilterDescriptor_t wDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t dxDesc, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionBwdDataAlgoPerf_t *perfResults ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnFilterDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, const int, int *, cudnnConvolutionBwdDataAlgoPerf_t *); + static auto func_ptr = LoadSymbol("cudnnFindConvolutionBackwardDataAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, wDesc, dyDesc, convDesc, dxDesc, requestedAlgoCount, returnedAlgoCount, perfResults); +} + +cudnnStatus_t CUDNNWINAPI cudnnFindConvolutionBackwardDataAlgorithmEx( + cudnnHandle_t handle, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t dxDesc, + void *dx, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionBwdDataAlgoPerf_t *perfResults, + void *workSpace, + size_t workSpaceSizeInBytes ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, void *, const int, int *, cudnnConvolutionBwdDataAlgoPerf_t *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnFindConvolutionBackwardDataAlgorithmEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, wDesc, w, dyDesc, dy, convDesc, dxDesc, dx, requestedAlgoCount, returnedAlgoCount, perfResults, workSpace, workSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionBackwardDataAlgorithm( + cudnnHandle_t handle, + const cudnnFilterDescriptor_t wDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t dxDesc, + cudnnConvolutionBwdDataPreference_t preference, + size_t memoryLimitInBytes, + cudnnConvolutionBwdDataAlgo_t *algo ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnFilterDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, cudnnConvolutionBwdDataPreference_t, size_t, cudnnConvolutionBwdDataAlgo_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardDataAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, wDesc, dyDesc, convDesc, dxDesc, preference, memoryLimitInBytes, algo); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionBackwardDataAlgorithm_v7( + cudnnHandle_t handle, + const cudnnFilterDescriptor_t filterDesc, + const cudnnTensorDescriptor_t diffDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t gradDesc, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionBwdDataAlgoPerf_t *perfResults) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnFilterDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, const int, int *, cudnnConvolutionBwdDataAlgoPerf_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardDataAlgorithm_v7"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, filterDesc, diffDesc, convDesc, gradDesc, requestedAlgoCount, returnedAlgoCount, perfResults); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetConvolutionBackwardDataWorkspaceSize( + cudnnHandle_t handle, + const cudnnFilterDescriptor_t wDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t dxDesc, + cudnnConvolutionBwdDataAlgo_t algo, + size_t *sizeInBytes ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnFilterDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, cudnnConvolutionBwdDataAlgo_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardDataWorkspaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, wDesc, dyDesc, convDesc, dxDesc, algo, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnConvolutionBackwardData( + cudnnHandle_t handle, + const void *alpha, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnConvolutionDescriptor_t convDesc, + cudnnConvolutionBwdDataAlgo_t algo, + void *workSpace, + size_t workSpaceSizeInBytes, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, cudnnConvolutionBwdDataAlgo_t, void *, size_t, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnConvolutionBackwardData"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha, wDesc, w, dyDesc, dy, convDesc, algo, workSpace, workSpaceSizeInBytes, beta, dxDesc, dx); +} + +cudnnStatus_t CUDNNWINAPI cudnnIm2Col( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnFilterDescriptor_t wDesc, + const cudnnConvolutionDescriptor_t convDesc, + void *colBuffer ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const cudnnConvolutionDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnIm2Col"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, x, wDesc, convDesc, colBuffer); +} + +cudnnStatus_t CUDNNWINAPI cudnnSoftmaxForward( + cudnnHandle_t handle, + cudnnSoftmaxAlgorithm_t algo, + cudnnSoftmaxMode_t mode, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnSoftmaxAlgorithm_t, cudnnSoftmaxMode_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnSoftmaxForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, algo, mode, alpha, xDesc, x, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI cudnnSoftmaxBackward( + cudnnHandle_t handle, + cudnnSoftmaxAlgorithm_t algo, + cudnnSoftmaxMode_t mode, + const void *alpha, + const cudnnTensorDescriptor_t yDesc, + const void *y, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnSoftmaxAlgorithm_t, cudnnSoftmaxMode_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnSoftmaxBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, algo, mode, alpha, yDesc, y, dyDesc, dy, beta, dxDesc, dx); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreatePoolingDescriptor( + cudnnPoolingDescriptor_t *poolingDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnPoolingDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreatePoolingDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetPooling2dDescriptor( + cudnnPoolingDescriptor_t poolingDesc, + cudnnPoolingMode_t mode, + cudnnNanPropagation_t maxpoolingNanOpt, + int windowHeight, + int windowWidth, + int verticalPadding, + int horizontalPadding, + int verticalStride, + int horizontalStride ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnPoolingDescriptor_t, cudnnPoolingMode_t, cudnnNanPropagation_t, int, int, int, int, int, int); + static auto func_ptr = LoadSymbol("cudnnSetPooling2dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc, mode, maxpoolingNanOpt, windowHeight, windowWidth, verticalPadding, horizontalPadding, verticalStride, horizontalStride); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetPooling2dDescriptor( + const cudnnPoolingDescriptor_t poolingDesc, + cudnnPoolingMode_t *mode, + cudnnNanPropagation_t *maxpoolingNanOpt, + int *windowHeight, + int *windowWidth, + int *verticalPadding, + int *horizontalPadding, + int *verticalStride, + int *horizontalStride ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnPoolingDescriptor_t, cudnnPoolingMode_t *, cudnnNanPropagation_t *, int *, int *, int *, int *, int *, int *); + static auto func_ptr = LoadSymbol("cudnnGetPooling2dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc, mode, maxpoolingNanOpt, windowHeight, windowWidth, verticalPadding, horizontalPadding, verticalStride, horizontalStride); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetPoolingNdDescriptor( + cudnnPoolingDescriptor_t poolingDesc, + const cudnnPoolingMode_t mode, + const cudnnNanPropagation_t maxpoolingNanOpt, + int nbDims, + const int windowDimA[], + const int paddingA[], + const int strideA[] ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnPoolingDescriptor_t, const cudnnPoolingMode_t, const cudnnNanPropagation_t, int, const int [], const int [], const int []); + static auto func_ptr = LoadSymbol("cudnnSetPoolingNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc, mode, maxpoolingNanOpt, nbDims, windowDimA, paddingA, strideA); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetPoolingNdDescriptor( + const cudnnPoolingDescriptor_t poolingDesc, + int nbDimsRequested, + cudnnPoolingMode_t *mode, + cudnnNanPropagation_t *maxpoolingNanOpt, + int *nbDims, + int windowDimA[], + int paddingA[], + int strideA[] ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnPoolingDescriptor_t, int, cudnnPoolingMode_t *, cudnnNanPropagation_t *, int *, int [], int [], int []); + static auto func_ptr = LoadSymbol("cudnnGetPoolingNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc, nbDimsRequested, mode, maxpoolingNanOpt, nbDims, windowDimA, paddingA, strideA); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetPoolingNdForwardOutputDim( + const cudnnPoolingDescriptor_t poolingDesc, + const cudnnTensorDescriptor_t inputTensorDesc, + int nbDims, + int outputTensorDimA[] ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnPoolingDescriptor_t, const cudnnTensorDescriptor_t, int, int []); + static auto func_ptr = LoadSymbol("cudnnGetPoolingNdForwardOutputDim"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc, inputTensorDesc, nbDims, outputTensorDimA); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetPooling2dForwardOutputDim( + const cudnnPoolingDescriptor_t poolingDesc, + const cudnnTensorDescriptor_t inputTensorDesc, + int *n, + int *c, + int *h, + int *w ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnPoolingDescriptor_t, const cudnnTensorDescriptor_t, int *, int *, int *, int *); + static auto func_ptr = LoadSymbol("cudnnGetPooling2dForwardOutputDim"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc, inputTensorDesc, n, c, h, w); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyPoolingDescriptor( + cudnnPoolingDescriptor_t poolingDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnPoolingDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyPoolingDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnPoolingForward( + cudnnHandle_t handle, + const cudnnPoolingDescriptor_t poolingDesc, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnPoolingDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnPoolingForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, poolingDesc, alpha, xDesc, x, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI cudnnPoolingBackward( + cudnnHandle_t handle, + const cudnnPoolingDescriptor_t poolingDesc, + const void *alpha, + const cudnnTensorDescriptor_t yDesc, + const void *y, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnPoolingDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnPoolingBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, poolingDesc, alpha, yDesc, y, dyDesc, dy, xDesc, x, beta, dxDesc, dx); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateActivationDescriptor( + cudnnActivationDescriptor_t *activationDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnActivationDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateActivationDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(activationDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetActivationDescriptor( + cudnnActivationDescriptor_t activationDesc, + cudnnActivationMode_t mode, + cudnnNanPropagation_t reluNanOpt, + double coef ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnActivationDescriptor_t, cudnnActivationMode_t, cudnnNanPropagation_t, double); + static auto func_ptr = LoadSymbol("cudnnSetActivationDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(activationDesc, mode, reluNanOpt, coef); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetActivationDescriptor( + const cudnnActivationDescriptor_t activationDesc, + cudnnActivationMode_t *mode, + cudnnNanPropagation_t *reluNanOpt, + double* coef ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnActivationDescriptor_t, cudnnActivationMode_t *, cudnnNanPropagation_t *, double *); + static auto func_ptr = LoadSymbol("cudnnGetActivationDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(activationDesc, mode, reluNanOpt, coef); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyActivationDescriptor( + cudnnActivationDescriptor_t activationDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnActivationDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyActivationDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(activationDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnActivationForward( + cudnnHandle_t handle, + cudnnActivationDescriptor_t activationDesc, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnActivationDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnActivationForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, activationDesc, alpha, xDesc, x, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI cudnnActivationBackward( + cudnnHandle_t handle, + cudnnActivationDescriptor_t activationDesc, + const void *alpha, + const cudnnTensorDescriptor_t yDesc, + const void *y, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnActivationDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnActivationBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, activationDesc, alpha, yDesc, y, dyDesc, dy, xDesc, x, beta, dxDesc, dx); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateLRNDescriptor( + cudnnLRNDescriptor_t *normDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnLRNDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateLRNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(normDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetLRNDescriptor( + cudnnLRNDescriptor_t normDesc, + unsigned lrnN, + double lrnAlpha, + double lrnBeta, + double lrnK ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnLRNDescriptor_t, unsigned int, double, double, double); + static auto func_ptr = LoadSymbol("cudnnSetLRNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(normDesc, lrnN, lrnAlpha, lrnBeta, lrnK); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetLRNDescriptor( + cudnnLRNDescriptor_t normDesc, + unsigned* lrnN, + double* lrnAlpha, + double* lrnBeta, + double* lrnK ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnLRNDescriptor_t, unsigned int *, double *, double *, double *); + static auto func_ptr = LoadSymbol("cudnnGetLRNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(normDesc, lrnN, lrnAlpha, lrnBeta, lrnK); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyLRNDescriptor( cudnnLRNDescriptor_t lrnDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnLRNDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyLRNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(lrnDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnLRNCrossChannelForward( + cudnnHandle_t handle, + cudnnLRNDescriptor_t normDesc, + cudnnLRNMode_t lrnMode, + const void* alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnLRNDescriptor_t, cudnnLRNMode_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnLRNCrossChannelForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, normDesc, lrnMode, alpha, xDesc, x, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI cudnnLRNCrossChannelBackward( + cudnnHandle_t handle, + cudnnLRNDescriptor_t normDesc, + cudnnLRNMode_t lrnMode, + const void* alpha, + const cudnnTensorDescriptor_t yDesc, + const void *y, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnLRNDescriptor_t, cudnnLRNMode_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnLRNCrossChannelBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, normDesc, lrnMode, alpha, yDesc, y, dyDesc, dy, xDesc, x, beta, dxDesc, dx); +} + +cudnnStatus_t CUDNNWINAPI cudnnDivisiveNormalizationForward( + cudnnHandle_t handle, + cudnnLRNDescriptor_t normDesc, + cudnnDivNormMode_t mode, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, /* same desc for means, temp, temp2 */ + const void *x, + const void *means, /* if NULL, means are assumed to be zero */ + void *temp, + void *temp2, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnLRNDescriptor_t, cudnnDivNormMode_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, void *, void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnDivisiveNormalizationForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, normDesc, mode, alpha, xDesc, x, means, temp, temp2, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI cudnnDivisiveNormalizationBackward( + cudnnHandle_t handle, + cudnnLRNDescriptor_t normDesc, + cudnnDivNormMode_t mode, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, /* same desc for x, means, dy, temp, temp2 */ + const void *x, + const void *means, /* if NULL, means are assumed to be zero */ + const void *dy, + void *temp, + void *temp2, + const void *beta, + const cudnnTensorDescriptor_t dXdMeansDesc, /* same desc for dx, dMeans */ + void *dx, /* output x differential */ + void *dMeans ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnLRNDescriptor_t, cudnnDivNormMode_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const void *, void *, void *, const void *, const cudnnTensorDescriptor_t, void *, void *); + static auto func_ptr = LoadSymbol("cudnnDivisiveNormalizationBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, normDesc, mode, alpha, xDesc, x, means, dy, temp, temp2, beta, dXdMeansDesc, dx, dMeans); +} + +cudnnStatus_t CUDNNWINAPI cudnnDeriveBNTensorDescriptor( + cudnnTensorDescriptor_t derivedBnDesc, + const cudnnTensorDescriptor_t xDesc, + cudnnBatchNormMode_t mode ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, cudnnBatchNormMode_t); + static auto func_ptr = LoadSymbol("cudnnDeriveBNTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(derivedBnDesc, xDesc, mode); +} + +cudnnStatus_t CUDNNWINAPI cudnnBatchNormalizationForwardTraining( + cudnnHandle_t handle, + cudnnBatchNormMode_t mode, + + const void *alpha, /* alpha[0] = result blend factor */ + const void *beta, /* beta[0] = dest layer blend factor */ + + const cudnnTensorDescriptor_t xDesc, + const void *x, /* NxCxHxW */ + const cudnnTensorDescriptor_t yDesc, + void *y, /* NxCxHxW */ + + /* Shared desc for the next 6 tensors in the argument list. + Data type to be set as follows: + type = (typeOf(x) == double) ? double : float + Dimensions for this descriptor depend on normalization mode + - Spatial Normalization : tensors are expected to have dims 1xCx1x1 + (normalization is performed across NxHxW) + - Per-Activation Normalization : tensors are expected to have dims of 1xCxHxW + (normalization is performed across N) */ + const cudnnTensorDescriptor_t bnScaleBiasMeanVarDesc, + + /* 'Gamma' and 'Beta' respectively in Ioffe and Szegedy's paper's notation */ + const void *bnScale, + const void *bnBias, + + /* MUST use factor=1 in the very first call of a complete training cycle. + Use a factor=1/(1+n) at N-th call to the function to get + Cumulative Moving Average (CMA) behavior + CMA[n] = (x[1]+...+x[n])/n + Since CMA[n+1] = (n*CMA[n]+x[n+1])/(n+1) = + ((n+1)*CMA[n]-CMA[n])/(n+1) + x[n+1]/(n+1) = + CMA[n]*(1-1/(n+1)) + x[n+1]*1/(n+1) */ + double exponentialAverageFactor, + + /* Used in Training phase only. + runningMean = newMean*factor + runningMean*(1-factor) */ + void *resultRunningMean, + /* Output in training mode, input in inference. Is the moving average + of variance[x] (factor is applied in the same way as for runningMean) */ + void *resultRunningVariance, + + /* Has to be >= CUDNN_BN_MIN_EPSILON. Should be the same in forward and backward functions. */ + double epsilon, + + /* Optionally save intermediate results from the forward pass here + - can be reused to speed up backward pass. NULL if unused */ + void *resultSaveMean, + void *resultSaveInvVariance ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnBatchNormMode_t, const void *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, const void *, const void *, double, void *, void *, double, void *, void *); + static auto func_ptr = LoadSymbol("cudnnBatchNormalizationForwardTraining"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode, alpha, beta, xDesc, x, yDesc, y, bnScaleBiasMeanVarDesc, bnScale, bnBias, exponentialAverageFactor, resultRunningMean, resultRunningVariance, epsilon, resultSaveMean, resultSaveInvVariance); +} + +cudnnStatus_t CUDNNWINAPI cudnnBatchNormalizationForwardInference( + cudnnHandle_t handle, + cudnnBatchNormMode_t mode, + const void *alpha, /* alpha[0] = result blend factor */ + const void *beta, /* beta[0] = dest layer blend factor */ + const cudnnTensorDescriptor_t xDesc, + const void *x, /* NxCxHxW */ + const cudnnTensorDescriptor_t yDesc, + void *y, /* NxCxHxW */ + const cudnnTensorDescriptor_t bnScaleBiasMeanVarDesc, + const void *bnScale, + const void *bnBias, + const void *estimatedMean, + const void *estimatedVariance, + double epsilon ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnBatchNormMode_t, const void *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, const void *, const void *, const void *, const void *, double); + static auto func_ptr = LoadSymbol("cudnnBatchNormalizationForwardInference"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode, alpha, beta, xDesc, x, yDesc, y, bnScaleBiasMeanVarDesc, bnScale, bnBias, estimatedMean, estimatedVariance, epsilon); +} + +cudnnStatus_t CUDNNWINAPI cudnnBatchNormalizationBackward( + cudnnHandle_t handle, + cudnnBatchNormMode_t mode, + const void *alphaDataDiff, + const void *betaDataDiff, + const void *alphaParamDiff, + const void *betaParamDiff, + const cudnnTensorDescriptor_t xDesc, /* same desc for x, dx, dy */ + const void *x, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnTensorDescriptor_t dxDesc, + void *dx, + /* Shared tensor desc for the 4 tensors below */ + const cudnnTensorDescriptor_t dBnScaleBiasDesc, + const void *bnScale, /* bnBias doesn't affect backpropagation */ + /* scale and bias diff are not backpropagated below this layer */ + void *dBnScaleResult, + void *dBnBiasResult, + /* Same epsilon as forward pass */ + double epsilon, + + /* Optionally cached intermediate results from + forward pass */ + const void *savedMean, + const void *savedInvVariance ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnBatchNormMode_t, const void *, const void *, const void *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, const void *, void *, void *, double, const void *, const void *); + static auto func_ptr = LoadSymbol("cudnnBatchNormalizationBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode, alphaDataDiff, betaDataDiff, alphaParamDiff, betaParamDiff, xDesc, x, dyDesc, dy, dxDesc, dx, dBnScaleBiasDesc, bnScale, dBnScaleResult, dBnBiasResult, epsilon, savedMean, savedInvVariance); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateSpatialTransformerDescriptor( + cudnnSpatialTransformerDescriptor_t *stDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnSpatialTransformerDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateSpatialTransformerDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(stDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetSpatialTransformerNdDescriptor( + cudnnSpatialTransformerDescriptor_t stDesc, + cudnnSamplerType_t samplerType, + cudnnDataType_t dataType, + const int nbDims, + const int dimA[]) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnSpatialTransformerDescriptor_t, cudnnSamplerType_t, cudnnDataType_t, const int, const int []); + static auto func_ptr = LoadSymbol("cudnnSetSpatialTransformerNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(stDesc, samplerType, dataType, nbDims, dimA); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroySpatialTransformerDescriptor( + cudnnSpatialTransformerDescriptor_t stDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnSpatialTransformerDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroySpatialTransformerDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(stDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSpatialTfGridGeneratorForward( + cudnnHandle_t handle, + const cudnnSpatialTransformerDescriptor_t stDesc, + const void *theta, + void *grid) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnSpatialTransformerDescriptor_t, const void *, void *); + static auto func_ptr = LoadSymbol("cudnnSpatialTfGridGeneratorForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, stDesc, theta, grid); +} + +cudnnStatus_t CUDNNWINAPI cudnnSpatialTfGridGeneratorBackward( + cudnnHandle_t handle, + const cudnnSpatialTransformerDescriptor_t stDesc, + const void *dgrid, + void *dtheta) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnSpatialTransformerDescriptor_t, const void *, void *); + static auto func_ptr = LoadSymbol("cudnnSpatialTfGridGeneratorBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, stDesc, dgrid, dtheta); +} + +cudnnStatus_t CUDNNWINAPI cudnnSpatialTfSamplerForward( + cudnnHandle_t handle, + cudnnSpatialTransformerDescriptor_t stDesc, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *grid, + const void *beta, + cudnnTensorDescriptor_t yDesc, + void *y) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnSpatialTransformerDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const void *, cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnSpatialTfSamplerForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, stDesc, alpha, xDesc, x, grid, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI cudnnSpatialTfSamplerBackward( + cudnnHandle_t handle, + cudnnSpatialTransformerDescriptor_t stDesc, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx, + const void *alphaDgrid, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const void *grid, + const void *betaDgrid, + void *dgrid) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnSpatialTransformerDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const void *, void *); + static auto func_ptr = LoadSymbol("cudnnSpatialTfSamplerBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, stDesc, alpha, xDesc, x, beta, dxDesc, dx, alphaDgrid, dyDesc, dy, grid, betaDgrid, dgrid); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateDropoutDescriptor(cudnnDropoutDescriptor_t * dropoutDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnDropoutDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateDropoutDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dropoutDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyDropoutDescriptor(cudnnDropoutDescriptor_t dropoutDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnDropoutDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyDropoutDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dropoutDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnDropoutGetStatesSize(cudnnHandle_t handle, size_t * sizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnDropoutGetStatesSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnDropoutGetReserveSpaceSize(cudnnTensorDescriptor_t xdesc, size_t * sizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnDropoutGetReserveSpaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(xdesc, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetDropoutDescriptor(cudnnDropoutDescriptor_t dropoutDesc, + cudnnHandle_t handle, + float dropout, + void * states, + size_t stateSizeInBytes, + unsigned long long seed) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnDropoutDescriptor_t, cudnnHandle_t, float, void *, size_t, unsigned long long); + static auto func_ptr = LoadSymbol("cudnnSetDropoutDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dropoutDesc, handle, dropout, states, stateSizeInBytes, seed); +} + +cudnnStatus_t CUDNNWINAPI cudnnRestoreDropoutDescriptor(cudnnDropoutDescriptor_t dropoutDesc, + cudnnHandle_t handle, + float dropout, + void * states, + size_t stateSizeInBytes, + unsigned long long seed) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnDropoutDescriptor_t, cudnnHandle_t, float, void *, size_t, unsigned long long); + static auto func_ptr = LoadSymbol("cudnnRestoreDropoutDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dropoutDesc, handle, dropout, states, stateSizeInBytes, seed); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetDropoutDescriptor(cudnnDropoutDescriptor_t dropoutDesc, + cudnnHandle_t handle, + float * dropout, + void ** states, + unsigned long long * seed) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnDropoutDescriptor_t, cudnnHandle_t, float *, void **, unsigned long long *); + static auto func_ptr = LoadSymbol("cudnnGetDropoutDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dropoutDesc, handle, dropout, states, seed); +} + +cudnnStatus_t CUDNNWINAPI cudnnDropoutForward(cudnnHandle_t handle, + const cudnnDropoutDescriptor_t dropoutDesc, + const cudnnTensorDescriptor_t xdesc, + const void * x, + const cudnnTensorDescriptor_t ydesc, + void * y, + void * reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnDropoutDescriptor_t, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, void *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnDropoutForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, dropoutDesc, xdesc, x, ydesc, y, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnDropoutBackward(cudnnHandle_t handle, + const cudnnDropoutDescriptor_t dropoutDesc, + const cudnnTensorDescriptor_t dydesc, + const void * dy, + const cudnnTensorDescriptor_t dxdesc, + void * dx, + void * reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnDropoutDescriptor_t, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, void *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnDropoutBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, dropoutDesc, dydesc, dy, dxdesc, dx, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateRNNDescriptor(cudnnRNNDescriptor_t * rnnDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateRNNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyRNNDescriptor(cudnnRNNDescriptor_t rnnDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyRNNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetRNNForwardInferenceAlgorithmMaxCount( + cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + int *count) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, int *); + static auto func_ptr = LoadSymbol("cudnnGetRNNForwardInferenceAlgorithmMaxCount"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, count); +} + +cudnnStatus_t CUDNNWINAPI cudnnFindRNNForwardInferenceAlgorithmEx( cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t * xDesc, + const void * x, + const cudnnTensorDescriptor_t hxDesc, + const void * hx, + const cudnnTensorDescriptor_t cxDesc, + const void * cx, + const cudnnFilterDescriptor_t wDesc, + const void * w, + const cudnnTensorDescriptor_t *yDesc, + void * y, + const cudnnTensorDescriptor_t hyDesc, + void * hy, + const cudnnTensorDescriptor_t cyDesc, + void * cy, + const float findIntensity, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnAlgorithmPerformance_t *perfResults, + void * workspace, + size_t workSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t *, void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, const float, const int, int *, cudnnAlgorithmPerformance_t *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnFindRNNForwardInferenceAlgorithmEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, x, hxDesc, hx, cxDesc, cx, wDesc, w, yDesc, y, hyDesc, hy, cyDesc, cy, findIntensity, requestedAlgoCount, returnedAlgoCount, perfResults, workspace, workSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetRNNForwardTrainingAlgorithmMaxCount( + cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + int *count) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, int *); + static auto func_ptr = LoadSymbol("cudnnGetRNNForwardTrainingAlgorithmMaxCount"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, count); +} + +cudnnStatus_t CUDNNWINAPI cudnnFindRNNForwardTrainingAlgorithmEx( cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t * xDesc, + const void * x, + const cudnnTensorDescriptor_t hxDesc, + const void * hx, + const cudnnTensorDescriptor_t cxDesc, + const void * cx, + const cudnnFilterDescriptor_t wDesc, + const void * w, + const cudnnTensorDescriptor_t *yDesc, + void * y, + const cudnnTensorDescriptor_t hyDesc, + void * hy, + const cudnnTensorDescriptor_t cyDesc, + void * cy, + const float findIntensity, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnAlgorithmPerformance_t *perfResults, + void * workspace, + size_t workSpaceSizeInBytes, + void * reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t *, void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, const float, const int, int *, cudnnAlgorithmPerformance_t *, void *, size_t, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnFindRNNForwardTrainingAlgorithmEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, x, hxDesc, hx, cxDesc, cx, wDesc, w, yDesc, y, hyDesc, hy, cyDesc, cy, findIntensity, requestedAlgoCount, returnedAlgoCount, perfResults, workspace, workSpaceSizeInBytes, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetRNNBackwardDataAlgorithmMaxCount( + cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + int *count) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, int *); + static auto func_ptr = LoadSymbol("cudnnGetRNNBackwardDataAlgorithmMaxCount"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, count); +} + +cudnnStatus_t CUDNNWINAPI cudnnFindRNNBackwardDataAlgorithmEx( cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t * yDesc, + const void * y, + const cudnnTensorDescriptor_t * dyDesc, + const void * dy, + const cudnnTensorDescriptor_t dhyDesc, + const void * dhy, + const cudnnTensorDescriptor_t dcyDesc, + const void * dcy, + const cudnnFilterDescriptor_t wDesc, + const void * w, + const cudnnTensorDescriptor_t hxDesc, + const void * hx, + const cudnnTensorDescriptor_t cxDesc, + const void * cx, + const cudnnTensorDescriptor_t * dxDesc, + void * dx, + const cudnnTensorDescriptor_t dhxDesc, + void * dhx, + const cudnnTensorDescriptor_t dcxDesc, + void * dcx, + const float findIntensity, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnAlgorithmPerformance_t *perfResults, + void * workspace, + size_t workSpaceSizeInBytes, + void * reserveSpace, + size_t reserveSpaceSizeInBytes ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t *, void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, const float, const int, int *, cudnnAlgorithmPerformance_t *, void *, size_t, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnFindRNNBackwardDataAlgorithmEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, yDesc, y, dyDesc, dy, dhyDesc, dhy, dcyDesc, dcy, wDesc, w, hxDesc, hx, cxDesc, cx, dxDesc, dx, dhxDesc, dhx, dcxDesc, dcx, findIntensity, requestedAlgoCount, returnedAlgoCount, perfResults, workspace, workSpaceSizeInBytes, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetRNNBackwardWeightsAlgorithmMaxCount( + cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + int *count) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, int *); + static auto func_ptr = LoadSymbol("cudnnGetRNNBackwardWeightsAlgorithmMaxCount"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, count); +} + +cudnnStatus_t CUDNNWINAPI cudnnFindRNNBackwardWeightsAlgorithmEx( cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t * xDesc, + const void * x, + const cudnnTensorDescriptor_t hxDesc, + const void * hx, + const cudnnTensorDescriptor_t * yDesc, + const void * y, + const float findIntensity, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnAlgorithmPerformance_t *perfResults, + const void * workspace, + size_t workSpaceSizeInBytes, + const cudnnFilterDescriptor_t dwDesc, + void * dw, + const void * reserveSpace, + size_t reserveSpaceSizeInBytes ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t *, const void *, const float, const int, int *, cudnnAlgorithmPerformance_t *, const void *, size_t, const cudnnFilterDescriptor_t, void *, const void *, size_t); + static auto func_ptr = LoadSymbol("cudnnFindRNNBackwardWeightsAlgorithmEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, x, hxDesc, hx, yDesc, y, findIntensity, requestedAlgoCount, returnedAlgoCount, perfResults, workspace, workSpaceSizeInBytes, dwDesc, dw, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreatePersistentRNNPlan(cudnnRNNDescriptor_t rnnDesc, + const int minibatch, + const cudnnDataType_t dataType, + cudnnPersistentRNNPlan_t * plan) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t, const int, const cudnnDataType_t, cudnnPersistentRNNPlan_t *); + static auto func_ptr = LoadSymbol("cudnnCreatePersistentRNNPlan"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc, minibatch, dataType, plan); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetPersistentRNNPlan(cudnnRNNDescriptor_t rnnDesc, + cudnnPersistentRNNPlan_t plan) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t, cudnnPersistentRNNPlan_t); + static auto func_ptr = LoadSymbol("cudnnSetPersistentRNNPlan"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc, plan); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyPersistentRNNPlan(cudnnPersistentRNNPlan_t plan) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnPersistentRNNPlan_t); + static auto func_ptr = LoadSymbol("cudnnDestroyPersistentRNNPlan"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(plan); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetRNNDescriptor(cudnnHandle_t handle, + cudnnRNNDescriptor_t rnnDesc, + const int hiddenSize, + const int numLayers, + cudnnDropoutDescriptor_t dropoutDesc, /* Between layers, not between recurrent steps. */ + cudnnRNNInputMode_t inputMode, + cudnnDirectionMode_t direction, + cudnnRNNMode_t mode, + cudnnRNNAlgo_t algo, + cudnnDataType_t dataType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnRNNDescriptor_t, const int, const int, cudnnDropoutDescriptor_t, cudnnRNNInputMode_t, cudnnDirectionMode_t, cudnnRNNMode_t, cudnnRNNAlgo_t, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnSetRNNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, hiddenSize, numLayers, dropoutDesc, inputMode, direction, mode, algo, dataType); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetRNNProjectionLayers(cudnnHandle_t handle, + cudnnRNNDescriptor_t rnnDesc, + const int recProjSize, + const int outProjSize) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnRNNDescriptor_t, const int, const int); + static auto func_ptr = LoadSymbol("cudnnSetRNNProjectionLayers"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, recProjSize, outProjSize); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetRNNProjectionLayers(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + int *recProjSize, + int *outProjSize) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, int *, int *); + static auto func_ptr = LoadSymbol("cudnnGetRNNProjectionLayers"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, recProjSize, outProjSize); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetRNNAlgorithmDescriptor(cudnnHandle_t handle, + cudnnRNNDescriptor_t rnnDesc, + cudnnAlgorithmDescriptor_t algoDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnRNNDescriptor_t, cudnnAlgorithmDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnSetRNNAlgorithmDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, algoDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetRNNDescriptor(cudnnHandle_t handle, + cudnnRNNDescriptor_t rnnDesc, + int * hiddenSize, + int * numLayers, + cudnnDropoutDescriptor_t * dropoutDesc, + cudnnRNNInputMode_t * inputMode, + cudnnDirectionMode_t * direction, + cudnnRNNMode_t * mode, + cudnnRNNAlgo_t * algo, + cudnnDataType_t * dataType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnRNNDescriptor_t, int *, int *, cudnnDropoutDescriptor_t *, cudnnRNNInputMode_t *, cudnnDirectionMode_t *, cudnnRNNMode_t *, cudnnRNNAlgo_t *, cudnnDataType_t *); + static auto func_ptr = LoadSymbol("cudnnGetRNNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, hiddenSize, numLayers, dropoutDesc, inputMode, direction, mode, algo, dataType); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetRNNMatrixMathType(cudnnRNNDescriptor_t rnnDesc, cudnnMathType_t mType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t, cudnnMathType_t); + static auto func_ptr = LoadSymbol("cudnnSetRNNMatrixMathType"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc, mType); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetRNNMatrixMathType(cudnnRNNDescriptor_t rnnDesc, cudnnMathType_t* mType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t, cudnnMathType_t *); + static auto func_ptr = LoadSymbol("cudnnGetRNNMatrixMathType"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc, mType); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetRNNWorkspaceSize( cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t *xDesc, + size_t *sizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetRNNWorkspaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetRNNTrainingReserveSize( cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t *xDesc, + size_t *sizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetRNNTrainingReserveSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetRNNParamsSize(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const cudnnTensorDescriptor_t xDesc, + size_t *sizeInBytes, + cudnnDataType_t dataType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const cudnnTensorDescriptor_t, size_t *, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnGetRNNParamsSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, xDesc, sizeInBytes, dataType); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetRNNLinLayerMatrixParams( cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int pseudoLayer, + const cudnnTensorDescriptor_t xDesc, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const int linLayerID, + cudnnFilterDescriptor_t linLayerMatDesc, + void **linLayerMat) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, const void *, const int, cudnnFilterDescriptor_t, void **); + static auto func_ptr = LoadSymbol("cudnnGetRNNLinLayerMatrixParams"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, pseudoLayer, xDesc, wDesc, w, linLayerID, linLayerMatDesc, linLayerMat); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetRNNLinLayerBiasParams( cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int pseudoLayer, + const cudnnTensorDescriptor_t xDesc, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const int linLayerID, + cudnnFilterDescriptor_t linLayerBiasDesc, + void **linLayerBias) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, const void *, const int, cudnnFilterDescriptor_t, void **); + static auto func_ptr = LoadSymbol("cudnnGetRNNLinLayerBiasParams"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, pseudoLayer, xDesc, wDesc, w, linLayerID, linLayerBiasDesc, linLayerBias); +} + +cudnnStatus_t CUDNNWINAPI cudnnRNNForwardInference( cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t *xDesc, + const void *x, + const cudnnTensorDescriptor_t hxDesc, + const void *hx, + const cudnnTensorDescriptor_t cxDesc, + const void *cx, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnTensorDescriptor_t *yDesc, + void *y, + const cudnnTensorDescriptor_t hyDesc, + void *hy, + const cudnnTensorDescriptor_t cyDesc, + void *cy, + void *workspace, + size_t workSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t *, void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnRNNForwardInference"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, x, hxDesc, hx, cxDesc, cx, wDesc, w, yDesc, y, hyDesc, hy, cyDesc, cy, workspace, workSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnRNNForwardTraining( cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t *xDesc, + const void *x, + const cudnnTensorDescriptor_t hxDesc, + const void *hx, + const cudnnTensorDescriptor_t cxDesc, + const void *cx, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnTensorDescriptor_t *yDesc, + void *y, + const cudnnTensorDescriptor_t hyDesc, + void *hy, + const cudnnTensorDescriptor_t cyDesc, + void *cy, + void *workspace, + size_t workSpaceSizeInBytes, + void * reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t *, void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, void *, size_t, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnRNNForwardTraining"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, x, hxDesc, hx, cxDesc, cx, wDesc, w, yDesc, y, hyDesc, hy, cyDesc, cy, workspace, workSpaceSizeInBytes, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnRNNBackwardData( cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t *yDesc, + const void *y, + const cudnnTensorDescriptor_t *dyDesc, + const void *dy, + const cudnnTensorDescriptor_t dhyDesc, + const void *dhy, + const cudnnTensorDescriptor_t dcyDesc, + const void *dcy, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnTensorDescriptor_t hxDesc, + const void *hx, + const cudnnTensorDescriptor_t cxDesc, + const void *cx, + const cudnnTensorDescriptor_t *dxDesc, + void *dx, + const cudnnTensorDescriptor_t dhxDesc, + void *dhx, + const cudnnTensorDescriptor_t dcxDesc, + void *dcx, + void *workspace, + size_t workSpaceSizeInBytes, + void * reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t *, void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, void *, size_t, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnRNNBackwardData"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, yDesc, y, dyDesc, dy, dhyDesc, dhy, dcyDesc, dcy, wDesc, w, hxDesc, hx, cxDesc, cx, dxDesc, dx, dhxDesc, dhx, dcxDesc, dcx, workspace, workSpaceSizeInBytes, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnRNNBackwardWeights( cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t *xDesc, + const void *x, + const cudnnTensorDescriptor_t hxDesc, + const void *hx, + const cudnnTensorDescriptor_t *yDesc, + const void *y, + const void *workspace, + size_t workSpaceSizeInBytes, + const cudnnFilterDescriptor_t dwDesc, + void *dw, + const void *reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t *, const void *, const void *, size_t, const cudnnFilterDescriptor_t, void *, const void *, size_t); + static auto func_ptr = LoadSymbol("cudnnRNNBackwardWeights"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, x, hxDesc, hx, yDesc, y, workspace, workSpaceSizeInBytes, dwDesc, dw, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateCTCLossDescriptor( cudnnCTCLossDescriptor_t* ctcLossDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnCTCLossDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateCTCLossDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(ctcLossDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetCTCLossDescriptor( + cudnnCTCLossDescriptor_t ctcLossDesc, + cudnnDataType_t compType ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnCTCLossDescriptor_t, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnSetCTCLossDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(ctcLossDesc, compType); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetCTCLossDescriptor( + cudnnCTCLossDescriptor_t ctcLossDesc, + cudnnDataType_t* compType ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnCTCLossDescriptor_t, cudnnDataType_t *); + static auto func_ptr = LoadSymbol("cudnnGetCTCLossDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(ctcLossDesc, compType); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyCTCLossDescriptor( cudnnCTCLossDescriptor_t ctcLossDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnCTCLossDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyCTCLossDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(ctcLossDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnCTCLoss( cudnnHandle_t handle, + const cudnnTensorDescriptor_t probsDesc, /* Tensor descriptor for probabilities, the dimensions are T,N,A (T is the timing steps, N is the mini batch size, A is the alphabet size) */ + const void * probs, /* probabilities after softmax, in GPU memory */ + const int * labels, /* labels, in CPU memory */ + const int * labelLengths, /* the length of each label, in CPU memory */ + const int * inputLengths, /* the lengths of timing steps in each batch, in CPU memory */ + void * costs, /* the returned costs of CTC, in GPU memory */ + const cudnnTensorDescriptor_t gradientsDesc, /* Tensor descriptor for gradients, the dimensions are T,N,A */ + const void * gradients, /* the returned CTC gradients, in GPU memory, to compute costs only, set it to NULL */ + cudnnCTCLossAlgo_t algo, /* algorithm selected, supported now 0 and 1 */ + cudnnCTCLossDescriptor_t ctcLossDesc, + void * workspace, /* pointer to the workspace, in GPU memory */ + size_t workSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const void *, const int *, const int *, const int *, void *, const cudnnTensorDescriptor_t, const void *, cudnnCTCLossAlgo_t, cudnnCTCLossDescriptor_t, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnCTCLoss"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, probsDesc, probs, labels, labelLengths, inputLengths, costs, gradientsDesc, gradients, algo, ctcLossDesc, workspace, workSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetCTCLossWorkspaceSize( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t probsDesc, /* Tensor descriptor for probabilities, the dimensions are T,N,A (T is the timing steps, N is the mini batch size, A is the alphabet size) */ + const cudnnTensorDescriptor_t gradientsDesc, /* Tensor descriptor for gradients, the dimensions are T,N,A. To compute costs only, set it to NULL */ + const int * labels, /* labels, in CPU memory */ + const int * labelLengths, /* the length of each label, in CPU memory */ + const int * inputLengths, /* the lengths of timing steps in each batch, in CPU memory */ + cudnnCTCLossAlgo_t algo, /* algorithm selected, supported now 0 and 1 */ + cudnnCTCLossDescriptor_t ctcLossDesc, + size_t *sizeInBytes ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, const int *, const int *, const int *, cudnnCTCLossAlgo_t, cudnnCTCLossDescriptor_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetCTCLossWorkspaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, probsDesc, gradientsDesc, labels, labelLengths, inputLengths, algo, ctcLossDesc, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateAlgorithmDescriptor( + cudnnAlgorithmDescriptor_t *algoDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnAlgorithmDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateAlgorithmDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(algoDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetAlgorithmDescriptor( + cudnnAlgorithmDescriptor_t algoDesc, + cudnnAlgorithm_t algorithm) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnAlgorithmDescriptor_t, cudnnAlgorithm_t); + static auto func_ptr = LoadSymbol("cudnnSetAlgorithmDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(algoDesc, algorithm); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetAlgorithmDescriptor( + const cudnnAlgorithmDescriptor_t algoDesc, + cudnnAlgorithm_t* algorithm) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnAlgorithmDescriptor_t, cudnnAlgorithm_t *); + static auto func_ptr = LoadSymbol("cudnnGetAlgorithmDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(algoDesc, algorithm); +} + +cudnnStatus_t CUDNNWINAPI cudnnCopyAlgorithmDescriptor( + const cudnnAlgorithmDescriptor_t src, + cudnnAlgorithmDescriptor_t dest) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnAlgorithmDescriptor_t, cudnnAlgorithmDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnCopyAlgorithmDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(src, dest); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyAlgorithmDescriptor( + cudnnAlgorithmDescriptor_t algoDesc ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnAlgorithmDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyAlgorithmDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(algoDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnCreateAlgorithmPerformance( + cudnnAlgorithmPerformance_t* algoPerf, + int numberToCreate ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnAlgorithmPerformance_t *, int); + static auto func_ptr = LoadSymbol("cudnnCreateAlgorithmPerformance"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(algoPerf, numberToCreate); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetAlgorithmPerformance( + cudnnAlgorithmPerformance_t algoPerf, + cudnnAlgorithmDescriptor_t algoDesc, + cudnnStatus_t status, + float time, + size_t memory ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnAlgorithmPerformance_t, cudnnAlgorithmDescriptor_t, cudnnStatus_t, float, size_t); + static auto func_ptr = LoadSymbol("cudnnSetAlgorithmPerformance"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(algoPerf, algoDesc, status, time, memory); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetAlgorithmPerformance( + const cudnnAlgorithmPerformance_t algoPerf, + cudnnAlgorithmDescriptor_t* algoDesc, + cudnnStatus_t* status, + float* time, + size_t* memory ) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnAlgorithmPerformance_t, cudnnAlgorithmDescriptor_t *, cudnnStatus_t *, float *, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetAlgorithmPerformance"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(algoPerf, algoDesc, status, time, memory); +} + +cudnnStatus_t CUDNNWINAPI cudnnDestroyAlgorithmPerformance( + cudnnAlgorithmPerformance_t* algoPerf, + int numberToDestroy) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnAlgorithmPerformance_t *, int); + static auto func_ptr = LoadSymbol("cudnnDestroyAlgorithmPerformance"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(algoPerf, numberToDestroy); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetAlgorithmSpaceSize( + cudnnHandle_t handle, + cudnnAlgorithmDescriptor_t algoDesc, + size_t* algoSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnAlgorithmDescriptor_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetAlgorithmSpaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, algoDesc, algoSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnSaveAlgorithm( + cudnnHandle_t handle, + cudnnAlgorithmDescriptor_t algoDesc, + void* algoSpace, + size_t algoSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnAlgorithmDescriptor_t, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnSaveAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, algoDesc, algoSpace, algoSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI cudnnRestoreAlgorithm( + cudnnHandle_t handle, + void* algoSpace, + size_t algoSpaceSizeInBytes, + cudnnAlgorithmDescriptor_t algoDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, void *, size_t, cudnnAlgorithmDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnRestoreAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, algoSpace, algoSpaceSizeInBytes, algoDesc); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetCallback( + unsigned mask, + void *udata, + cudnnCallback_t fptr) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(unsigned int, void *, cudnnCallback_t); + static auto func_ptr = LoadSymbol("cudnnSetCallback"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(mask, udata, fptr); +} + +cudnnStatus_t CUDNNWINAPI cudnnGetCallback( + unsigned *mask, + void **udata, + cudnnCallback_t *fptr) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(unsigned int *, void **, cudnnCallback_t *); + static auto func_ptr = LoadSymbol("cudnnGetCallback"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(mask, udata, fptr); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetRNNDescriptor_v6(cudnnHandle_t handle, + cudnnRNNDescriptor_t rnnDesc, + const int hiddenSize, + const int numLayers, + cudnnDropoutDescriptor_t dropoutDesc, + cudnnRNNInputMode_t inputMode, + cudnnDirectionMode_t direction, + cudnnRNNMode_t mode, + cudnnRNNAlgo_t algo, + cudnnDataType_t dataType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnRNNDescriptor_t, const int, const int, cudnnDropoutDescriptor_t, cudnnRNNInputMode_t, cudnnDirectionMode_t, cudnnRNNMode_t, cudnnRNNAlgo_t, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnSetRNNDescriptor_v6"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, hiddenSize, numLayers, dropoutDesc, inputMode, direction, mode, algo, dataType); +} + +cudnnStatus_t CUDNNWINAPI cudnnSetRNNDescriptor_v5(cudnnRNNDescriptor_t rnnDesc, + int hiddenSize, + int numLayers, + cudnnDropoutDescriptor_t dropoutDesc, + cudnnRNNInputMode_t inputMode, + cudnnDirectionMode_t direction, + cudnnRNNMode_t mode, + cudnnDataType_t dataType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t, int, int, cudnnDropoutDescriptor_t, cudnnRNNInputMode_t, cudnnDirectionMode_t, cudnnRNNMode_t, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnSetRNNDescriptor_v5"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc, hiddenSize, numLayers, dropoutDesc, inputMode, direction, mode, dataType); +} + +} // extern "C" diff --git a/tensorflow/stream_executor/cuda/cudnn_7_3.inc b/tensorflow/stream_executor/cuda/cudnn_7_3.inc new file mode 100644 index 0000000000..1f8e997ab9 --- /dev/null +++ b/tensorflow/stream_executor/cuda/cudnn_7_3.inc @@ -0,0 +1,2510 @@ +// Auto-generated, do not edit. + +extern "C" { + +size_t CUDNNWINAPI +cudnnGetVersion(void) { + using FuncPtr = size_t (CUDNNWINAPI *)(); + static auto func_ptr = LoadSymbol("cudnnGetVersion"); + if (!func_ptr) return 0; + return func_ptr(); +} + +size_t CUDNNWINAPI +cudnnGetCudartVersion(void) { + using FuncPtr = size_t (CUDNNWINAPI *)(); + static auto func_ptr = LoadSymbol("cudnnGetCudartVersion"); + if (!func_ptr) return 0; + return func_ptr(); +} + +const char *CUDNNWINAPI +cudnnGetErrorString(cudnnStatus_t status) { + using FuncPtr = const char * (CUDNNWINAPI *)(cudnnStatus_t); + static auto func_ptr = LoadSymbol("cudnnGetErrorString"); + if (!func_ptr) return "cudnnGetErrorString symbol not found."; + return func_ptr(status); +} + +cudnnStatus_t CUDNNWINAPI +cudnnQueryRuntimeError(cudnnHandle_t handle, cudnnStatus_t *rstatus, cudnnErrQueryMode_t mode, cudnnRuntimeTag_t *tag) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnStatus_t *, cudnnErrQueryMode_t, cudnnRuntimeTag_t *); + static auto func_ptr = LoadSymbol("cudnnQueryRuntimeError"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rstatus, mode, tag); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetProperty(libraryPropertyType type, int *value) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(libraryPropertyType, int *); + static auto func_ptr = LoadSymbol("cudnnGetProperty"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(type, value); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreate(cudnnHandle_t *handle) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t *); + static auto func_ptr = LoadSymbol("cudnnCreate"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroy(cudnnHandle_t handle) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t); + static auto func_ptr = LoadSymbol("cudnnDestroy"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetStream(cudnnHandle_t handle, cudaStream_t streamId) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudaStream_t); + static auto func_ptr = LoadSymbol("cudnnSetStream"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, streamId); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetStream(cudnnHandle_t handle, cudaStream_t *streamId) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudaStream_t *); + static auto func_ptr = LoadSymbol("cudnnGetStream"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, streamId); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreateTensorDescriptor(cudnnTensorDescriptor_t *tensorDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetTensor4dDescriptor(cudnnTensorDescriptor_t tensorDesc, + cudnnTensorFormat_t format, + cudnnDataType_t dataType, /* image data type */ + int n, /* number of inputs (batch size) */ + int c, /* number of input feature maps */ + int h, /* height of input section */ + int w) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t, cudnnTensorFormat_t, cudnnDataType_t, int, int, int, int); + static auto func_ptr = LoadSymbol("cudnnSetTensor4dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, format, dataType, n, c, h, w); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetTensor4dDescriptorEx(cudnnTensorDescriptor_t tensorDesc, + cudnnDataType_t dataType, /* image data type */ + int n, /* number of inputs (batch size) */ + int c, /* number of input feature maps */ + int h, /* height of input section */ + int w, /* width of input section */ + int nStride, + int cStride, + int hStride, + int wStride) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t, cudnnDataType_t, int, int, int, int, int, int, int, int); + static auto func_ptr = LoadSymbol("cudnnSetTensor4dDescriptorEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, dataType, n, c, h, w, nStride, cStride, hStride, wStride); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetTensor4dDescriptor(const cudnnTensorDescriptor_t tensorDesc, + cudnnDataType_t *dataType, /* image data type */ + int *n, /* number of inputs (batch size) */ + int *c, /* number of input feature maps */ + int *h, /* height of input section */ + int *w, /* width of input section */ + int *nStride, + int *cStride, + int *hStride, + int *wStride) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnTensorDescriptor_t, cudnnDataType_t *, int *, int *, int *, int *, int *, int *, int *, int *); + static auto func_ptr = LoadSymbol("cudnnGetTensor4dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, dataType, n, c, h, w, nStride, cStride, hStride, wStride); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetTensorNdDescriptor(cudnnTensorDescriptor_t tensorDesc, + cudnnDataType_t dataType, + int nbDims, + const int dimA[], + const int strideA[]) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t, cudnnDataType_t, int, const int [], const int []); + static auto func_ptr = LoadSymbol("cudnnSetTensorNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, dataType, nbDims, dimA, strideA); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetTensorNdDescriptorEx(cudnnTensorDescriptor_t tensorDesc, + cudnnTensorFormat_t format, + cudnnDataType_t dataType, + int nbDims, + const int dimA[]) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t, cudnnTensorFormat_t, cudnnDataType_t, int, const int []); + static auto func_ptr = LoadSymbol("cudnnSetTensorNdDescriptorEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, format, dataType, nbDims, dimA); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetTensorNdDescriptor(const cudnnTensorDescriptor_t tensorDesc, + int nbDimsRequested, + cudnnDataType_t *dataType, + int *nbDims, + int dimA[], + int strideA[]) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnTensorDescriptor_t, int, cudnnDataType_t *, int *, int [], int []); + static auto func_ptr = LoadSymbol("cudnnGetTensorNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, nbDimsRequested, dataType, nbDims, dimA, strideA); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetTensorSizeInBytes(const cudnnTensorDescriptor_t tensorDesc, size_t *size) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnTensorDescriptor_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetTensorSizeInBytes"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, size); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroyTensorDescriptor(cudnnTensorDescriptor_t tensorDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnTransformTensor(cudnnHandle_t handle, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnTransformTensor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha, xDesc, x, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI +cudnnAddTensor(cudnnHandle_t handle, + const void *alpha, + const cudnnTensorDescriptor_t aDesc, + const void *A, + const void *beta, + const cudnnTensorDescriptor_t cDesc, + void *C) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnAddTensor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha, aDesc, A, beta, cDesc, C); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreateOpTensorDescriptor(cudnnOpTensorDescriptor_t *opTensorDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnOpTensorDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateOpTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(opTensorDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetOpTensorDescriptor(cudnnOpTensorDescriptor_t opTensorDesc, + cudnnOpTensorOp_t opTensorOp, + cudnnDataType_t opTensorCompType, + cudnnNanPropagation_t opTensorNanOpt) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnOpTensorDescriptor_t, cudnnOpTensorOp_t, cudnnDataType_t, cudnnNanPropagation_t); + static auto func_ptr = LoadSymbol("cudnnSetOpTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(opTensorDesc, opTensorOp, opTensorCompType, opTensorNanOpt); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetOpTensorDescriptor(const cudnnOpTensorDescriptor_t opTensorDesc, + cudnnOpTensorOp_t *opTensorOp, + cudnnDataType_t *opTensorCompType, + cudnnNanPropagation_t *opTensorNanOpt) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnOpTensorDescriptor_t, cudnnOpTensorOp_t *, cudnnDataType_t *, cudnnNanPropagation_t *); + static auto func_ptr = LoadSymbol("cudnnGetOpTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(opTensorDesc, opTensorOp, opTensorCompType, opTensorNanOpt); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroyOpTensorDescriptor(cudnnOpTensorDescriptor_t opTensorDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnOpTensorDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyOpTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(opTensorDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnOpTensor(cudnnHandle_t handle, + const cudnnOpTensorDescriptor_t opTensorDesc, + const void *alpha1, + const cudnnTensorDescriptor_t aDesc, + const void *A, + const void *alpha2, + const cudnnTensorDescriptor_t bDesc, + const void *B, + const void *beta, + const cudnnTensorDescriptor_t cDesc, + void *C) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnOpTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnOpTensor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, opTensorDesc, alpha1, aDesc, A, alpha2, bDesc, B, beta, cDesc, C); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreateReduceTensorDescriptor(cudnnReduceTensorDescriptor_t *reduceTensorDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnReduceTensorDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateReduceTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(reduceTensorDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetReduceTensorDescriptor(cudnnReduceTensorDescriptor_t reduceTensorDesc, + cudnnReduceTensorOp_t reduceTensorOp, + cudnnDataType_t reduceTensorCompType, + cudnnNanPropagation_t reduceTensorNanOpt, + cudnnReduceTensorIndices_t reduceTensorIndices, + cudnnIndicesType_t reduceTensorIndicesType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnReduceTensorDescriptor_t, cudnnReduceTensorOp_t, cudnnDataType_t, cudnnNanPropagation_t, cudnnReduceTensorIndices_t, cudnnIndicesType_t); + static auto func_ptr = LoadSymbol("cudnnSetReduceTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(reduceTensorDesc, reduceTensorOp, reduceTensorCompType, reduceTensorNanOpt, reduceTensorIndices, reduceTensorIndicesType); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetReduceTensorDescriptor(const cudnnReduceTensorDescriptor_t reduceTensorDesc, + cudnnReduceTensorOp_t *reduceTensorOp, + cudnnDataType_t *reduceTensorCompType, + cudnnNanPropagation_t *reduceTensorNanOpt, + cudnnReduceTensorIndices_t *reduceTensorIndices, + cudnnIndicesType_t *reduceTensorIndicesType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnReduceTensorDescriptor_t, cudnnReduceTensorOp_t *, cudnnDataType_t *, cudnnNanPropagation_t *, cudnnReduceTensorIndices_t *, cudnnIndicesType_t *); + static auto func_ptr = LoadSymbol("cudnnGetReduceTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(reduceTensorDesc, reduceTensorOp, reduceTensorCompType, reduceTensorNanOpt, reduceTensorIndices, reduceTensorIndicesType); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroyReduceTensorDescriptor(cudnnReduceTensorDescriptor_t reduceTensorDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnReduceTensorDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyReduceTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(reduceTensorDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetReductionIndicesSize(cudnnHandle_t handle, + const cudnnReduceTensorDescriptor_t reduceTensorDesc, + const cudnnTensorDescriptor_t aDesc, + const cudnnTensorDescriptor_t cDesc, + size_t *sizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnReduceTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetReductionIndicesSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, reduceTensorDesc, aDesc, cDesc, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetReductionWorkspaceSize(cudnnHandle_t handle, + const cudnnReduceTensorDescriptor_t reduceTensorDesc, + const cudnnTensorDescriptor_t aDesc, + const cudnnTensorDescriptor_t cDesc, + size_t *sizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnReduceTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetReductionWorkspaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, reduceTensorDesc, aDesc, cDesc, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnReduceTensor(cudnnHandle_t handle, + const cudnnReduceTensorDescriptor_t reduceTensorDesc, + void *indices, + size_t indicesSizeInBytes, + void *workspace, + size_t workspaceSizeInBytes, + const void *alpha, + const cudnnTensorDescriptor_t aDesc, + const void *A, + const void *beta, + const cudnnTensorDescriptor_t cDesc, + void *C) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnReduceTensorDescriptor_t, void *, size_t, void *, size_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnReduceTensor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, reduceTensorDesc, indices, indicesSizeInBytes, workspace, workspaceSizeInBytes, alpha, aDesc, A, beta, cDesc, C); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetTensor(cudnnHandle_t handle, const cudnnTensorDescriptor_t yDesc, void *y, const void *valuePtr) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, void *, const void *); + static auto func_ptr = LoadSymbol("cudnnSetTensor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, yDesc, y, valuePtr); +} + +cudnnStatus_t CUDNNWINAPI +cudnnScaleTensor(cudnnHandle_t handle, const cudnnTensorDescriptor_t yDesc, void *y, const void *alpha) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, void *, const void *); + static auto func_ptr = LoadSymbol("cudnnScaleTensor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, yDesc, y, alpha); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreateFilterDescriptor(cudnnFilterDescriptor_t *filterDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnFilterDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateFilterDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(filterDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetFilter4dDescriptor(cudnnFilterDescriptor_t filterDesc, + cudnnDataType_t dataType, /* image data type */ + cudnnTensorFormat_t format, + int k, /* number of output feature maps */ + int c, /* number of input feature maps */ + int h, /* height of each input filter */ + int w) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnFilterDescriptor_t, cudnnDataType_t, cudnnTensorFormat_t, int, int, int, int); + static auto func_ptr = LoadSymbol("cudnnSetFilter4dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(filterDesc, dataType, format, k, c, h, w); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetFilter4dDescriptor(const cudnnFilterDescriptor_t filterDesc, + cudnnDataType_t *dataType, /* image data type */ + cudnnTensorFormat_t *format, + int *k, /* number of output feature maps */ + int *c, /* number of input feature maps */ + int *h, /* height of each input filter */ + int *w) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnFilterDescriptor_t, cudnnDataType_t *, cudnnTensorFormat_t *, int *, int *, int *, int *); + static auto func_ptr = LoadSymbol("cudnnGetFilter4dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(filterDesc, dataType, format, k, c, h, w); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetFilterNdDescriptor(cudnnFilterDescriptor_t filterDesc, + cudnnDataType_t dataType, /* image data type */ + cudnnTensorFormat_t format, + int nbDims, + const int filterDimA[]) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnFilterDescriptor_t, cudnnDataType_t, cudnnTensorFormat_t, int, const int []); + static auto func_ptr = LoadSymbol("cudnnSetFilterNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(filterDesc, dataType, format, nbDims, filterDimA); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetFilterNdDescriptor(const cudnnFilterDescriptor_t filterDesc, + int nbDimsRequested, + cudnnDataType_t *dataType, /* image data type */ + cudnnTensorFormat_t *format, + int *nbDims, + int filterDimA[]) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnFilterDescriptor_t, int, cudnnDataType_t *, cudnnTensorFormat_t *, int *, int []); + static auto func_ptr = LoadSymbol("cudnnGetFilterNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(filterDesc, nbDimsRequested, dataType, format, nbDims, filterDimA); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroyFilterDescriptor(cudnnFilterDescriptor_t filterDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnFilterDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyFilterDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(filterDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreateConvolutionDescriptor(cudnnConvolutionDescriptor_t *convDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateConvolutionDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetConvolutionMathType(cudnnConvolutionDescriptor_t convDesc, cudnnMathType_t mathType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t, cudnnMathType_t); + static auto func_ptr = LoadSymbol("cudnnSetConvolutionMathType"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, mathType); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolutionMathType(cudnnConvolutionDescriptor_t convDesc, cudnnMathType_t *mathType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t, cudnnMathType_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionMathType"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, mathType); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetConvolutionGroupCount(cudnnConvolutionDescriptor_t convDesc, int groupCount) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t, int); + static auto func_ptr = LoadSymbol("cudnnSetConvolutionGroupCount"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, groupCount); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolutionGroupCount(cudnnConvolutionDescriptor_t convDesc, int *groupCount) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t, int *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionGroupCount"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, groupCount); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetConvolution2dDescriptor(cudnnConvolutionDescriptor_t convDesc, + int pad_h, /* zero-padding height */ + int pad_w, /* zero-padding width */ + int u, /* vertical filter stride */ + int v, /* horizontal filter stride */ + int dilation_h, /* filter dilation in the vertical dimension */ + int dilation_w, /* filter dilation in the horizontal dimension */ + cudnnConvolutionMode_t mode, + cudnnDataType_t computeType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t, int, int, int, int, int, int, cudnnConvolutionMode_t, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnSetConvolution2dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, pad_h, pad_w, u, v, dilation_h, dilation_w, mode, computeType); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolution2dDescriptor(const cudnnConvolutionDescriptor_t convDesc, + int *pad_h, /* zero-padding height */ + int *pad_w, /* zero-padding width */ + int *u, /* vertical filter stride */ + int *v, /* horizontal filter stride */ + int *dilation_h, /* filter dilation in the vertical dimension */ + int *dilation_w, /* filter dilation in the horizontal dimension */ + cudnnConvolutionMode_t *mode, + cudnnDataType_t *computeType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnConvolutionDescriptor_t, int *, int *, int *, int *, int *, int *, cudnnConvolutionMode_t *, cudnnDataType_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolution2dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, pad_h, pad_w, u, v, dilation_h, dilation_w, mode, computeType); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolution2dForwardOutputDim(const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t inputTensorDesc, + const cudnnFilterDescriptor_t filterDesc, + int *n, + int *c, + int *h, + int *w) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, int *, int *, int *, int *); + static auto func_ptr = LoadSymbol("cudnnGetConvolution2dForwardOutputDim"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, inputTensorDesc, filterDesc, n, c, h, w); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetConvolutionNdDescriptor(cudnnConvolutionDescriptor_t convDesc, + int arrayLength, /* nbDims-2 size */ + const int padA[], + const int filterStrideA[], + const int dilationA[], + cudnnConvolutionMode_t mode, + cudnnDataType_t computeType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t, int, const int [], const int [], const int [], cudnnConvolutionMode_t, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnSetConvolutionNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, arrayLength, padA, filterStrideA, dilationA, mode, computeType); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolutionNdDescriptor(const cudnnConvolutionDescriptor_t convDesc, + int arrayLengthRequested, + int *arrayLength, + int padA[], + int strideA[], + int dilationA[], + cudnnConvolutionMode_t *mode, + cudnnDataType_t *computeType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnConvolutionDescriptor_t, int, int *, int [], int [], int [], cudnnConvolutionMode_t *, cudnnDataType_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, arrayLengthRequested, arrayLength, padA, strideA, dilationA, mode, computeType); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolutionNdForwardOutputDim(const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t inputTensorDesc, + const cudnnFilterDescriptor_t filterDesc, + int nbDims, + int tensorOuputDimA[]) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, int, int []); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionNdForwardOutputDim"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, inputTensorDesc, filterDesc, nbDims, tensorOuputDimA); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroyConvolutionDescriptor(cudnnConvolutionDescriptor_t convDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyConvolutionDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolutionForwardAlgorithmMaxCount(cudnnHandle_t handle, int *count) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, int *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionForwardAlgorithmMaxCount"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, count); +} + +cudnnStatus_t CUDNNWINAPI +cudnnFindConvolutionForwardAlgorithm(cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnFilterDescriptor_t wDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t yDesc, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionFwdAlgoPerf_t *perfResults) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, const int, int *, cudnnConvolutionFwdAlgoPerf_t *); + static auto func_ptr = LoadSymbol("cudnnFindConvolutionForwardAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, wDesc, convDesc, yDesc, requestedAlgoCount, returnedAlgoCount, perfResults); +} + +cudnnStatus_t CUDNNWINAPI +cudnnFindConvolutionForwardAlgorithmEx(cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t yDesc, + void *y, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionFwdAlgoPerf_t *perfResults, + void *workSpace, + size_t workSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, void *, const int, int *, cudnnConvolutionFwdAlgoPerf_t *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnFindConvolutionForwardAlgorithmEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, x, wDesc, w, convDesc, yDesc, y, requestedAlgoCount, returnedAlgoCount, perfResults, workSpace, workSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolutionForwardAlgorithm(cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnFilterDescriptor_t wDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t yDesc, + cudnnConvolutionFwdPreference_t preference, + size_t memoryLimitInBytes, + cudnnConvolutionFwdAlgo_t *algo) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, cudnnConvolutionFwdPreference_t, size_t, cudnnConvolutionFwdAlgo_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionForwardAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, wDesc, convDesc, yDesc, preference, memoryLimitInBytes, algo); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolutionForwardAlgorithm_v7(cudnnHandle_t handle, + const cudnnTensorDescriptor_t srcDesc, + const cudnnFilterDescriptor_t filterDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t destDesc, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionFwdAlgoPerf_t *perfResults) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, const int, int *, cudnnConvolutionFwdAlgoPerf_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionForwardAlgorithm_v7"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, srcDesc, filterDesc, convDesc, destDesc, requestedAlgoCount, returnedAlgoCount, perfResults); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolutionForwardWorkspaceSize(cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnFilterDescriptor_t wDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t yDesc, + cudnnConvolutionFwdAlgo_t algo, + size_t *sizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, cudnnConvolutionFwdAlgo_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionForwardWorkspaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, wDesc, convDesc, yDesc, algo, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnConvolutionForward(cudnnHandle_t handle, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnConvolutionDescriptor_t convDesc, + cudnnConvolutionFwdAlgo_t algo, + void *workSpace, + size_t workSpaceSizeInBytes, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, cudnnConvolutionFwdAlgo_t, void *, size_t, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnConvolutionForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha, xDesc, x, wDesc, w, convDesc, algo, workSpace, workSpaceSizeInBytes, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI +cudnnConvolutionBiasActivationForward(cudnnHandle_t handle, + const void *alpha1, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnConvolutionDescriptor_t convDesc, + cudnnConvolutionFwdAlgo_t algo, + void *workSpace, + size_t workSpaceSizeInBytes, + const void *alpha2, + const cudnnTensorDescriptor_t zDesc, + const void *z, + const cudnnTensorDescriptor_t biasDesc, + const void *bias, + const cudnnActivationDescriptor_t activationDesc, + const cudnnTensorDescriptor_t yDesc, + void *y) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, cudnnConvolutionFwdAlgo_t, void *, size_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnActivationDescriptor_t, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnConvolutionBiasActivationForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha1, xDesc, x, wDesc, w, convDesc, algo, workSpace, workSpaceSizeInBytes, alpha2, zDesc, z, biasDesc, bias, activationDesc, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI +cudnnConvolutionBackwardBias(cudnnHandle_t handle, + const void *alpha, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const void *beta, + const cudnnTensorDescriptor_t dbDesc, + void *db) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnConvolutionBackwardBias"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha, dyDesc, dy, beta, dbDesc, db); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolutionBackwardFilterAlgorithmMaxCount(cudnnHandle_t handle, int *count) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, int *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardFilterAlgorithmMaxCount"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, count); +} + +cudnnStatus_t CUDNNWINAPI +cudnnFindConvolutionBackwardFilterAlgorithm(cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnFilterDescriptor_t dwDesc, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionBwdFilterAlgoPerf_t *perfResults) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnFilterDescriptor_t, const int, int *, cudnnConvolutionBwdFilterAlgoPerf_t *); + static auto func_ptr = LoadSymbol("cudnnFindConvolutionBackwardFilterAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, dyDesc, convDesc, dwDesc, requestedAlgoCount, returnedAlgoCount, perfResults); +} + +cudnnStatus_t CUDNNWINAPI +cudnnFindConvolutionBackwardFilterAlgorithmEx(cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnTensorDescriptor_t dyDesc, + const void *y, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnFilterDescriptor_t dwDesc, + void *dw, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionBwdFilterAlgoPerf_t *perfResults, + void *workSpace, + size_t workSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, const cudnnFilterDescriptor_t, void *, const int, int *, cudnnConvolutionBwdFilterAlgoPerf_t *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnFindConvolutionBackwardFilterAlgorithmEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, x, dyDesc, y, convDesc, dwDesc, dw, requestedAlgoCount, returnedAlgoCount, perfResults, workSpace, workSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolutionBackwardFilterAlgorithm(cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnFilterDescriptor_t dwDesc, + cudnnConvolutionBwdFilterPreference_t preference, + size_t memoryLimitInBytes, + cudnnConvolutionBwdFilterAlgo_t *algo) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnFilterDescriptor_t, cudnnConvolutionBwdFilterPreference_t, size_t, cudnnConvolutionBwdFilterAlgo_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardFilterAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, dyDesc, convDesc, dwDesc, preference, memoryLimitInBytes, algo); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolutionBackwardFilterAlgorithm_v7(cudnnHandle_t handle, + const cudnnTensorDescriptor_t srcDesc, + const cudnnTensorDescriptor_t diffDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnFilterDescriptor_t gradDesc, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionBwdFilterAlgoPerf_t *perfResults) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnFilterDescriptor_t, const int, int *, cudnnConvolutionBwdFilterAlgoPerf_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardFilterAlgorithm_v7"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, srcDesc, diffDesc, convDesc, gradDesc, requestedAlgoCount, returnedAlgoCount, perfResults); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolutionBackwardFilterWorkspaceSize(cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnFilterDescriptor_t gradDesc, + cudnnConvolutionBwdFilterAlgo_t algo, + size_t *sizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnFilterDescriptor_t, cudnnConvolutionBwdFilterAlgo_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardFilterWorkspaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, dyDesc, convDesc, gradDesc, algo, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnConvolutionBackwardFilter(cudnnHandle_t handle, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnConvolutionDescriptor_t convDesc, + cudnnConvolutionBwdFilterAlgo_t algo, + void *workSpace, + size_t workSpaceSizeInBytes, + const void *beta, + const cudnnFilterDescriptor_t dwDesc, + void *dw) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, cudnnConvolutionBwdFilterAlgo_t, void *, size_t, const void *, const cudnnFilterDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnConvolutionBackwardFilter"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha, xDesc, x, dyDesc, dy, convDesc, algo, workSpace, workSpaceSizeInBytes, beta, dwDesc, dw); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolutionBackwardDataAlgorithmMaxCount(cudnnHandle_t handle, int *count) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, int *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardDataAlgorithmMaxCount"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, count); +} + +cudnnStatus_t CUDNNWINAPI +cudnnFindConvolutionBackwardDataAlgorithm(cudnnHandle_t handle, + const cudnnFilterDescriptor_t wDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t dxDesc, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionBwdDataAlgoPerf_t *perfResults) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnFilterDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, const int, int *, cudnnConvolutionBwdDataAlgoPerf_t *); + static auto func_ptr = LoadSymbol("cudnnFindConvolutionBackwardDataAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, wDesc, dyDesc, convDesc, dxDesc, requestedAlgoCount, returnedAlgoCount, perfResults); +} + +cudnnStatus_t CUDNNWINAPI +cudnnFindConvolutionBackwardDataAlgorithmEx(cudnnHandle_t handle, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t dxDesc, + void *dx, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionBwdDataAlgoPerf_t *perfResults, + void *workSpace, + size_t workSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, void *, const int, int *, cudnnConvolutionBwdDataAlgoPerf_t *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnFindConvolutionBackwardDataAlgorithmEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, wDesc, w, dyDesc, dy, convDesc, dxDesc, dx, requestedAlgoCount, returnedAlgoCount, perfResults, workSpace, workSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolutionBackwardDataAlgorithm(cudnnHandle_t handle, + const cudnnFilterDescriptor_t wDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t dxDesc, + cudnnConvolutionBwdDataPreference_t preference, + size_t memoryLimitInBytes, + cudnnConvolutionBwdDataAlgo_t *algo) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnFilterDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, cudnnConvolutionBwdDataPreference_t, size_t, cudnnConvolutionBwdDataAlgo_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardDataAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, wDesc, dyDesc, convDesc, dxDesc, preference, memoryLimitInBytes, algo); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolutionBackwardDataAlgorithm_v7(cudnnHandle_t handle, + const cudnnFilterDescriptor_t filterDesc, + const cudnnTensorDescriptor_t diffDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t gradDesc, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionBwdDataAlgoPerf_t *perfResults) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnFilterDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, const int, int *, cudnnConvolutionBwdDataAlgoPerf_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardDataAlgorithm_v7"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, filterDesc, diffDesc, convDesc, gradDesc, requestedAlgoCount, returnedAlgoCount, perfResults); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolutionBackwardDataWorkspaceSize(cudnnHandle_t handle, + const cudnnFilterDescriptor_t wDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t dxDesc, + cudnnConvolutionBwdDataAlgo_t algo, + size_t *sizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnFilterDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, cudnnConvolutionBwdDataAlgo_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardDataWorkspaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, wDesc, dyDesc, convDesc, dxDesc, algo, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnConvolutionBackwardData(cudnnHandle_t handle, + const void *alpha, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnConvolutionDescriptor_t convDesc, + cudnnConvolutionBwdDataAlgo_t algo, + void *workSpace, + size_t workSpaceSizeInBytes, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, cudnnConvolutionBwdDataAlgo_t, void *, size_t, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnConvolutionBackwardData"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha, wDesc, w, dyDesc, dy, convDesc, algo, workSpace, workSpaceSizeInBytes, beta, dxDesc, dx); +} + +cudnnStatus_t CUDNNWINAPI +cudnnIm2Col(cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnFilterDescriptor_t wDesc, + const cudnnConvolutionDescriptor_t convDesc, + void *colBuffer) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const cudnnConvolutionDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnIm2Col"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, x, wDesc, convDesc, colBuffer); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSoftmaxForward(cudnnHandle_t handle, + cudnnSoftmaxAlgorithm_t algo, + cudnnSoftmaxMode_t mode, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnSoftmaxAlgorithm_t, cudnnSoftmaxMode_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnSoftmaxForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, algo, mode, alpha, xDesc, x, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSoftmaxBackward(cudnnHandle_t handle, + cudnnSoftmaxAlgorithm_t algo, + cudnnSoftmaxMode_t mode, + const void *alpha, + const cudnnTensorDescriptor_t yDesc, + const void *y, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnSoftmaxAlgorithm_t, cudnnSoftmaxMode_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnSoftmaxBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, algo, mode, alpha, yDesc, y, dyDesc, dy, beta, dxDesc, dx); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreatePoolingDescriptor(cudnnPoolingDescriptor_t *poolingDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnPoolingDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreatePoolingDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetPooling2dDescriptor(cudnnPoolingDescriptor_t poolingDesc, + cudnnPoolingMode_t mode, + cudnnNanPropagation_t maxpoolingNanOpt, + int windowHeight, + int windowWidth, + int verticalPadding, + int horizontalPadding, + int verticalStride, + int horizontalStride) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnPoolingDescriptor_t, cudnnPoolingMode_t, cudnnNanPropagation_t, int, int, int, int, int, int); + static auto func_ptr = LoadSymbol("cudnnSetPooling2dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc, mode, maxpoolingNanOpt, windowHeight, windowWidth, verticalPadding, horizontalPadding, verticalStride, horizontalStride); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetPooling2dDescriptor(const cudnnPoolingDescriptor_t poolingDesc, + cudnnPoolingMode_t *mode, + cudnnNanPropagation_t *maxpoolingNanOpt, + int *windowHeight, + int *windowWidth, + int *verticalPadding, + int *horizontalPadding, + int *verticalStride, + int *horizontalStride) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnPoolingDescriptor_t, cudnnPoolingMode_t *, cudnnNanPropagation_t *, int *, int *, int *, int *, int *, int *); + static auto func_ptr = LoadSymbol("cudnnGetPooling2dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc, mode, maxpoolingNanOpt, windowHeight, windowWidth, verticalPadding, horizontalPadding, verticalStride, horizontalStride); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetPoolingNdDescriptor(cudnnPoolingDescriptor_t poolingDesc, + const cudnnPoolingMode_t mode, + const cudnnNanPropagation_t maxpoolingNanOpt, + int nbDims, + const int windowDimA[], + const int paddingA[], + const int strideA[]) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnPoolingDescriptor_t, const cudnnPoolingMode_t, const cudnnNanPropagation_t, int, const int [], const int [], const int []); + static auto func_ptr = LoadSymbol("cudnnSetPoolingNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc, mode, maxpoolingNanOpt, nbDims, windowDimA, paddingA, strideA); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetPoolingNdDescriptor(const cudnnPoolingDescriptor_t poolingDesc, + int nbDimsRequested, + cudnnPoolingMode_t *mode, + cudnnNanPropagation_t *maxpoolingNanOpt, + int *nbDims, + int windowDimA[], + int paddingA[], + int strideA[]) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnPoolingDescriptor_t, int, cudnnPoolingMode_t *, cudnnNanPropagation_t *, int *, int [], int [], int []); + static auto func_ptr = LoadSymbol("cudnnGetPoolingNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc, nbDimsRequested, mode, maxpoolingNanOpt, nbDims, windowDimA, paddingA, strideA); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetPoolingNdForwardOutputDim(const cudnnPoolingDescriptor_t poolingDesc, + const cudnnTensorDescriptor_t inputTensorDesc, + int nbDims, + int outputTensorDimA[]) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnPoolingDescriptor_t, const cudnnTensorDescriptor_t, int, int []); + static auto func_ptr = LoadSymbol("cudnnGetPoolingNdForwardOutputDim"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc, inputTensorDesc, nbDims, outputTensorDimA); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetPooling2dForwardOutputDim(const cudnnPoolingDescriptor_t poolingDesc, + const cudnnTensorDescriptor_t inputTensorDesc, + int *n, + int *c, + int *h, + int *w) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnPoolingDescriptor_t, const cudnnTensorDescriptor_t, int *, int *, int *, int *); + static auto func_ptr = LoadSymbol("cudnnGetPooling2dForwardOutputDim"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc, inputTensorDesc, n, c, h, w); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroyPoolingDescriptor(cudnnPoolingDescriptor_t poolingDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnPoolingDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyPoolingDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnPoolingForward(cudnnHandle_t handle, + const cudnnPoolingDescriptor_t poolingDesc, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnPoolingDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnPoolingForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, poolingDesc, alpha, xDesc, x, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI +cudnnPoolingBackward(cudnnHandle_t handle, + const cudnnPoolingDescriptor_t poolingDesc, + const void *alpha, + const cudnnTensorDescriptor_t yDesc, + const void *y, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnPoolingDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnPoolingBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, poolingDesc, alpha, yDesc, y, dyDesc, dy, xDesc, x, beta, dxDesc, dx); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreateActivationDescriptor(cudnnActivationDescriptor_t *activationDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnActivationDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateActivationDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(activationDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetActivationDescriptor(cudnnActivationDescriptor_t activationDesc, + cudnnActivationMode_t mode, + cudnnNanPropagation_t reluNanOpt, + double coef) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnActivationDescriptor_t, cudnnActivationMode_t, cudnnNanPropagation_t, double); + static auto func_ptr = LoadSymbol("cudnnSetActivationDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(activationDesc, mode, reluNanOpt, coef); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetActivationDescriptor(const cudnnActivationDescriptor_t activationDesc, + cudnnActivationMode_t *mode, + cudnnNanPropagation_t *reluNanOpt, + double *coef) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnActivationDescriptor_t, cudnnActivationMode_t *, cudnnNanPropagation_t *, double *); + static auto func_ptr = LoadSymbol("cudnnGetActivationDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(activationDesc, mode, reluNanOpt, coef); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroyActivationDescriptor(cudnnActivationDescriptor_t activationDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnActivationDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyActivationDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(activationDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnActivationForward(cudnnHandle_t handle, + cudnnActivationDescriptor_t activationDesc, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnActivationDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnActivationForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, activationDesc, alpha, xDesc, x, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI +cudnnActivationBackward(cudnnHandle_t handle, + cudnnActivationDescriptor_t activationDesc, + const void *alpha, + const cudnnTensorDescriptor_t yDesc, + const void *y, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnActivationDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnActivationBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, activationDesc, alpha, yDesc, y, dyDesc, dy, xDesc, x, beta, dxDesc, dx); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreateLRNDescriptor(cudnnLRNDescriptor_t *normDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnLRNDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateLRNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(normDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetLRNDescriptor(cudnnLRNDescriptor_t normDesc, unsigned lrnN, double lrnAlpha, double lrnBeta, double lrnK) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnLRNDescriptor_t, unsigned int, double, double, double); + static auto func_ptr = LoadSymbol("cudnnSetLRNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(normDesc, lrnN, lrnAlpha, lrnBeta, lrnK); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetLRNDescriptor(cudnnLRNDescriptor_t normDesc, unsigned *lrnN, double *lrnAlpha, double *lrnBeta, double *lrnK) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnLRNDescriptor_t, unsigned int *, double *, double *, double *); + static auto func_ptr = LoadSymbol("cudnnGetLRNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(normDesc, lrnN, lrnAlpha, lrnBeta, lrnK); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroyLRNDescriptor(cudnnLRNDescriptor_t lrnDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnLRNDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyLRNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(lrnDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnLRNCrossChannelForward(cudnnHandle_t handle, + cudnnLRNDescriptor_t normDesc, + cudnnLRNMode_t lrnMode, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnLRNDescriptor_t, cudnnLRNMode_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnLRNCrossChannelForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, normDesc, lrnMode, alpha, xDesc, x, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI +cudnnLRNCrossChannelBackward(cudnnHandle_t handle, + cudnnLRNDescriptor_t normDesc, + cudnnLRNMode_t lrnMode, + const void *alpha, + const cudnnTensorDescriptor_t yDesc, + const void *y, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnLRNDescriptor_t, cudnnLRNMode_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnLRNCrossChannelBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, normDesc, lrnMode, alpha, yDesc, y, dyDesc, dy, xDesc, x, beta, dxDesc, dx); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDivisiveNormalizationForward(cudnnHandle_t handle, + cudnnLRNDescriptor_t normDesc, + cudnnDivNormMode_t mode, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, /* same desc for means, temp, temp2 */ + const void *x, + const void *means, /* if NULL, means are assumed to be zero */ + void *temp, + void *temp2, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnLRNDescriptor_t, cudnnDivNormMode_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, void *, void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnDivisiveNormalizationForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, normDesc, mode, alpha, xDesc, x, means, temp, temp2, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDivisiveNormalizationBackward(cudnnHandle_t handle, + cudnnLRNDescriptor_t normDesc, + cudnnDivNormMode_t mode, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, /* same desc for x, means, dy, temp, temp2 */ + const void *x, + const void *means, /* if NULL, means are assumed to be zero */ + const void *dy, + void *temp, + void *temp2, + const void *beta, + const cudnnTensorDescriptor_t dXdMeansDesc, /* same desc for dx, dMeans */ + void *dx, /* output x differential */ + void *dMeans) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnLRNDescriptor_t, cudnnDivNormMode_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const void *, void *, void *, const void *, const cudnnTensorDescriptor_t, void *, void *); + static auto func_ptr = LoadSymbol("cudnnDivisiveNormalizationBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, normDesc, mode, alpha, xDesc, x, means, dy, temp, temp2, beta, dXdMeansDesc, dx, dMeans); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDeriveBNTensorDescriptor(cudnnTensorDescriptor_t derivedBnDesc, + const cudnnTensorDescriptor_t xDesc, + cudnnBatchNormMode_t mode) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, cudnnBatchNormMode_t); + static auto func_ptr = LoadSymbol("cudnnDeriveBNTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(derivedBnDesc, xDesc, mode); +} + +cudnnStatus_t CUDNNWINAPI +cudnnBatchNormalizationForwardTraining( + cudnnHandle_t handle, + cudnnBatchNormMode_t mode, + + const void *alpha, /* alpha[0] = result blend factor */ + const void *beta, /* beta[0] = dest layer blend factor */ + + const cudnnTensorDescriptor_t xDesc, + const void *x, /* NxCxHxW */ + const cudnnTensorDescriptor_t yDesc, + void *y, /* NxCxHxW */ + + /* Shared desc for the next 6 tensors in the argument list. + Data type to be set as follows: + type = (typeOf(x) == double) ? double : float + Dimensions for this descriptor depend on normalization mode + - Spatial Normalization : tensors are expected to have dims 1xCx1x1 + (normalization is performed across NxHxW) + - Per-Activation Normalization : tensors are expected to have dims of 1xCxHxW + (normalization is performed across N) */ + const cudnnTensorDescriptor_t bnScaleBiasMeanVarDesc, + + /* 'Gamma' and 'Beta' respectively in Ioffe and Szegedy's paper's notation */ + const void *bnScale, + const void *bnBias, + + /* MUST use factor=1 in the very first call of a complete training cycle. + Use a factor=1/(1+n) at N-th call to the function to get + Cumulative Moving Average (CMA) behavior + CMA[n] = (x[1]+...+x[n])/n + Since CMA[n+1] = (n*CMA[n]+x[n+1])/(n+1) = + ((n+1)*CMA[n]-CMA[n])/(n+1) + x[n+1]/(n+1) = + CMA[n]*(1-1/(n+1)) + x[n+1]*1/(n+1) */ + double exponentialAverageFactor, + + /* Used in Training phase only. + runningMean = newMean*factor + runningMean*(1-factor) */ + void *resultRunningMean, + /* Output in training mode, input in inference. Is the moving average + of variance[x] (factor is applied in the same way as for runningMean) */ + void *resultRunningVariance, + + /* Has to be >= CUDNN_BN_MIN_EPSILON. Should be the same in forward and backward functions. */ + double epsilon, + + /* Optionally save intermediate results from the forward pass here + - can be reused to speed up backward pass. NULL if unused */ + void *resultSaveMean, + void *resultSaveInvVariance) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnBatchNormMode_t, const void *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, const void *, const void *, double, void *, void *, double, void *, void *); + static auto func_ptr = LoadSymbol("cudnnBatchNormalizationForwardTraining"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode, alpha, beta, xDesc, x, yDesc, y, bnScaleBiasMeanVarDesc, bnScale, bnBias, exponentialAverageFactor, resultRunningMean, resultRunningVariance, epsilon, resultSaveMean, resultSaveInvVariance); +} + +cudnnStatus_t CUDNNWINAPI +cudnnBatchNormalizationForwardInference(cudnnHandle_t handle, + cudnnBatchNormMode_t mode, + const void *alpha, /* alpha[0] = result blend factor */ + const void *beta, /* beta[0] = dest layer blend factor */ + const cudnnTensorDescriptor_t xDesc, + const void *x, /* NxCxHxW */ + const cudnnTensorDescriptor_t yDesc, + void *y, /* NxCxHxW */ + const cudnnTensorDescriptor_t bnScaleBiasMeanVarDesc, + const void *bnScale, + const void *bnBias, + const void *estimatedMean, + const void *estimatedVariance, + double epsilon) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnBatchNormMode_t, const void *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, const void *, const void *, const void *, const void *, double); + static auto func_ptr = LoadSymbol("cudnnBatchNormalizationForwardInference"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode, alpha, beta, xDesc, x, yDesc, y, bnScaleBiasMeanVarDesc, bnScale, bnBias, estimatedMean, estimatedVariance, epsilon); +} + +cudnnStatus_t CUDNNWINAPI +cudnnBatchNormalizationBackward(cudnnHandle_t handle, + cudnnBatchNormMode_t mode, + const void *alphaDataDiff, + const void *betaDataDiff, + const void *alphaParamDiff, + const void *betaParamDiff, + const cudnnTensorDescriptor_t xDesc, /* same desc for x, dx, dy */ + const void *x, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnTensorDescriptor_t dxDesc, + void *dx, + /* Shared tensor desc for the 4 tensors below */ + const cudnnTensorDescriptor_t dBnScaleBiasDesc, + const void *bnScale, /* bnBias doesn't affect backpropagation */ + /* scale and bias diff are not backpropagated below this layer */ + void *dBnScaleResult, + void *dBnBiasResult, + /* Same epsilon as forward pass */ + double epsilon, + + /* Optionally cached intermediate results from + forward pass */ + const void *savedMean, + const void *savedInvVariance) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnBatchNormMode_t, const void *, const void *, const void *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, const void *, void *, void *, double, const void *, const void *); + static auto func_ptr = LoadSymbol("cudnnBatchNormalizationBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode, alphaDataDiff, betaDataDiff, alphaParamDiff, betaParamDiff, xDesc, x, dyDesc, dy, dxDesc, dx, dBnScaleBiasDesc, bnScale, dBnScaleResult, dBnBiasResult, epsilon, savedMean, savedInvVariance); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreateSpatialTransformerDescriptor(cudnnSpatialTransformerDescriptor_t *stDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnSpatialTransformerDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateSpatialTransformerDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(stDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetSpatialTransformerNdDescriptor(cudnnSpatialTransformerDescriptor_t stDesc, + cudnnSamplerType_t samplerType, + cudnnDataType_t dataType, + const int nbDims, + const int dimA[]) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnSpatialTransformerDescriptor_t, cudnnSamplerType_t, cudnnDataType_t, const int, const int []); + static auto func_ptr = LoadSymbol("cudnnSetSpatialTransformerNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(stDesc, samplerType, dataType, nbDims, dimA); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroySpatialTransformerDescriptor(cudnnSpatialTransformerDescriptor_t stDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnSpatialTransformerDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroySpatialTransformerDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(stDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSpatialTfGridGeneratorForward(cudnnHandle_t handle, + const cudnnSpatialTransformerDescriptor_t stDesc, + const void *theta, + void *grid) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnSpatialTransformerDescriptor_t, const void *, void *); + static auto func_ptr = LoadSymbol("cudnnSpatialTfGridGeneratorForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, stDesc, theta, grid); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSpatialTfGridGeneratorBackward(cudnnHandle_t handle, + const cudnnSpatialTransformerDescriptor_t stDesc, + const void *dgrid, + void *dtheta) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnSpatialTransformerDescriptor_t, const void *, void *); + static auto func_ptr = LoadSymbol("cudnnSpatialTfGridGeneratorBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, stDesc, dgrid, dtheta); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSpatialTfSamplerForward(cudnnHandle_t handle, + cudnnSpatialTransformerDescriptor_t stDesc, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *grid, + const void *beta, + cudnnTensorDescriptor_t yDesc, + void *y) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnSpatialTransformerDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const void *, cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnSpatialTfSamplerForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, stDesc, alpha, xDesc, x, grid, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSpatialTfSamplerBackward(cudnnHandle_t handle, + cudnnSpatialTransformerDescriptor_t stDesc, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx, + const void *alphaDgrid, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const void *grid, + const void *betaDgrid, + void *dgrid) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnSpatialTransformerDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const void *, void *); + static auto func_ptr = LoadSymbol("cudnnSpatialTfSamplerBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, stDesc, alpha, xDesc, x, beta, dxDesc, dx, alphaDgrid, dyDesc, dy, grid, betaDgrid, dgrid); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreateDropoutDescriptor(cudnnDropoutDescriptor_t *dropoutDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnDropoutDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateDropoutDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dropoutDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroyDropoutDescriptor(cudnnDropoutDescriptor_t dropoutDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnDropoutDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyDropoutDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dropoutDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDropoutGetStatesSize(cudnnHandle_t handle, size_t *sizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnDropoutGetStatesSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDropoutGetReserveSpaceSize(cudnnTensorDescriptor_t xdesc, size_t *sizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnDropoutGetReserveSpaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(xdesc, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetDropoutDescriptor(cudnnDropoutDescriptor_t dropoutDesc, + cudnnHandle_t handle, + float dropout, + void *states, + size_t stateSizeInBytes, + unsigned long long seed) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnDropoutDescriptor_t, cudnnHandle_t, float, void *, size_t, unsigned long long); + static auto func_ptr = LoadSymbol("cudnnSetDropoutDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dropoutDesc, handle, dropout, states, stateSizeInBytes, seed); +} + +cudnnStatus_t CUDNNWINAPI +cudnnRestoreDropoutDescriptor(cudnnDropoutDescriptor_t dropoutDesc, + cudnnHandle_t handle, + float dropout, + void *states, + size_t stateSizeInBytes, + unsigned long long seed) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnDropoutDescriptor_t, cudnnHandle_t, float, void *, size_t, unsigned long long); + static auto func_ptr = LoadSymbol("cudnnRestoreDropoutDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dropoutDesc, handle, dropout, states, stateSizeInBytes, seed); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetDropoutDescriptor(cudnnDropoutDescriptor_t dropoutDesc, + cudnnHandle_t handle, + float *dropout, + void **states, + unsigned long long *seed) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnDropoutDescriptor_t, cudnnHandle_t, float *, void **, unsigned long long *); + static auto func_ptr = LoadSymbol("cudnnGetDropoutDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dropoutDesc, handle, dropout, states, seed); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDropoutForward(cudnnHandle_t handle, + const cudnnDropoutDescriptor_t dropoutDesc, + const cudnnTensorDescriptor_t xdesc, + const void *x, + const cudnnTensorDescriptor_t ydesc, + void *y, + void *reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnDropoutDescriptor_t, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, void *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnDropoutForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, dropoutDesc, xdesc, x, ydesc, y, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDropoutBackward(cudnnHandle_t handle, + const cudnnDropoutDescriptor_t dropoutDesc, + const cudnnTensorDescriptor_t dydesc, + const void *dy, + const cudnnTensorDescriptor_t dxdesc, + void *dx, + void *reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnDropoutDescriptor_t, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, void *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnDropoutBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, dropoutDesc, dydesc, dy, dxdesc, dx, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreateRNNDescriptor(cudnnRNNDescriptor_t *rnnDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateRNNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroyRNNDescriptor(cudnnRNNDescriptor_t rnnDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyRNNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetRNNForwardInferenceAlgorithmMaxCount(cudnnHandle_t handle, const cudnnRNNDescriptor_t rnnDesc, int *count) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, int *); + static auto func_ptr = LoadSymbol("cudnnGetRNNForwardInferenceAlgorithmMaxCount"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, count); +} + +cudnnStatus_t CUDNNWINAPI +cudnnFindRNNForwardInferenceAlgorithmEx(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t *xDesc, + const void *x, + const cudnnTensorDescriptor_t hxDesc, + const void *hx, + const cudnnTensorDescriptor_t cxDesc, + const void *cx, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnTensorDescriptor_t *yDesc, + void *y, + const cudnnTensorDescriptor_t hyDesc, + void *hy, + const cudnnTensorDescriptor_t cyDesc, + void *cy, + const float findIntensity, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnAlgorithmPerformance_t *perfResults, + void *workspace, + size_t workSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t *, void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, const float, const int, int *, cudnnAlgorithmPerformance_t *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnFindRNNForwardInferenceAlgorithmEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, x, hxDesc, hx, cxDesc, cx, wDesc, w, yDesc, y, hyDesc, hy, cyDesc, cy, findIntensity, requestedAlgoCount, returnedAlgoCount, perfResults, workspace, workSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetRNNForwardTrainingAlgorithmMaxCount(cudnnHandle_t handle, const cudnnRNNDescriptor_t rnnDesc, int *count) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, int *); + static auto func_ptr = LoadSymbol("cudnnGetRNNForwardTrainingAlgorithmMaxCount"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, count); +} + +cudnnStatus_t CUDNNWINAPI +cudnnFindRNNForwardTrainingAlgorithmEx(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t *xDesc, + const void *x, + const cudnnTensorDescriptor_t hxDesc, + const void *hx, + const cudnnTensorDescriptor_t cxDesc, + const void *cx, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnTensorDescriptor_t *yDesc, + void *y, + const cudnnTensorDescriptor_t hyDesc, + void *hy, + const cudnnTensorDescriptor_t cyDesc, + void *cy, + const float findIntensity, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnAlgorithmPerformance_t *perfResults, + void *workspace, + size_t workSpaceSizeInBytes, + void *reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t *, void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, const float, const int, int *, cudnnAlgorithmPerformance_t *, void *, size_t, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnFindRNNForwardTrainingAlgorithmEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, x, hxDesc, hx, cxDesc, cx, wDesc, w, yDesc, y, hyDesc, hy, cyDesc, cy, findIntensity, requestedAlgoCount, returnedAlgoCount, perfResults, workspace, workSpaceSizeInBytes, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetRNNBackwardDataAlgorithmMaxCount(cudnnHandle_t handle, const cudnnRNNDescriptor_t rnnDesc, int *count) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, int *); + static auto func_ptr = LoadSymbol("cudnnGetRNNBackwardDataAlgorithmMaxCount"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, count); +} + +cudnnStatus_t CUDNNWINAPI +cudnnFindRNNBackwardDataAlgorithmEx(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t *yDesc, + const void *y, + const cudnnTensorDescriptor_t *dyDesc, + const void *dy, + const cudnnTensorDescriptor_t dhyDesc, + const void *dhy, + const cudnnTensorDescriptor_t dcyDesc, + const void *dcy, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnTensorDescriptor_t hxDesc, + const void *hx, + const cudnnTensorDescriptor_t cxDesc, + const void *cx, + const cudnnTensorDescriptor_t *dxDesc, + void *dx, + const cudnnTensorDescriptor_t dhxDesc, + void *dhx, + const cudnnTensorDescriptor_t dcxDesc, + void *dcx, + const float findIntensity, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnAlgorithmPerformance_t *perfResults, + void *workspace, + size_t workSpaceSizeInBytes, + void *reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t *, void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, const float, const int, int *, cudnnAlgorithmPerformance_t *, void *, size_t, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnFindRNNBackwardDataAlgorithmEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, yDesc, y, dyDesc, dy, dhyDesc, dhy, dcyDesc, dcy, wDesc, w, hxDesc, hx, cxDesc, cx, dxDesc, dx, dhxDesc, dhx, dcxDesc, dcx, findIntensity, requestedAlgoCount, returnedAlgoCount, perfResults, workspace, workSpaceSizeInBytes, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetRNNBackwardWeightsAlgorithmMaxCount(cudnnHandle_t handle, const cudnnRNNDescriptor_t rnnDesc, int *count) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, int *); + static auto func_ptr = LoadSymbol("cudnnGetRNNBackwardWeightsAlgorithmMaxCount"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, count); +} + +cudnnStatus_t CUDNNWINAPI +cudnnFindRNNBackwardWeightsAlgorithmEx(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t *xDesc, + const void *x, + const cudnnTensorDescriptor_t hxDesc, + const void *hx, + const cudnnTensorDescriptor_t *yDesc, + const void *y, + const float findIntensity, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnAlgorithmPerformance_t *perfResults, + const void *workspace, + size_t workSpaceSizeInBytes, + const cudnnFilterDescriptor_t dwDesc, + void *dw, + const void *reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t *, const void *, const float, const int, int *, cudnnAlgorithmPerformance_t *, const void *, size_t, const cudnnFilterDescriptor_t, void *, const void *, size_t); + static auto func_ptr = LoadSymbol("cudnnFindRNNBackwardWeightsAlgorithmEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, x, hxDesc, hx, yDesc, y, findIntensity, requestedAlgoCount, returnedAlgoCount, perfResults, workspace, workSpaceSizeInBytes, dwDesc, dw, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreatePersistentRNNPlan(cudnnRNNDescriptor_t rnnDesc, + const int minibatch, + const cudnnDataType_t dataType, + cudnnPersistentRNNPlan_t *plan) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t, const int, const cudnnDataType_t, cudnnPersistentRNNPlan_t *); + static auto func_ptr = LoadSymbol("cudnnCreatePersistentRNNPlan"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc, minibatch, dataType, plan); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetPersistentRNNPlan(cudnnRNNDescriptor_t rnnDesc, cudnnPersistentRNNPlan_t plan) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t, cudnnPersistentRNNPlan_t); + static auto func_ptr = LoadSymbol("cudnnSetPersistentRNNPlan"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc, plan); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroyPersistentRNNPlan(cudnnPersistentRNNPlan_t plan) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnPersistentRNNPlan_t); + static auto func_ptr = LoadSymbol("cudnnDestroyPersistentRNNPlan"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(plan); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetRNNDescriptor(cudnnHandle_t handle, + cudnnRNNDescriptor_t rnnDesc, + const int hiddenSize, + const int numLayers, + cudnnDropoutDescriptor_t dropoutDesc, /* Between layers, not between recurrent steps. */ + cudnnRNNInputMode_t inputMode, + cudnnDirectionMode_t direction, + cudnnRNNMode_t mode, + cudnnRNNAlgo_t algo, + cudnnDataType_t dataType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnRNNDescriptor_t, const int, const int, cudnnDropoutDescriptor_t, cudnnRNNInputMode_t, cudnnDirectionMode_t, cudnnRNNMode_t, cudnnRNNAlgo_t, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnSetRNNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, hiddenSize, numLayers, dropoutDesc, inputMode, direction, mode, algo, dataType); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetRNNProjectionLayers(cudnnHandle_t handle, + cudnnRNNDescriptor_t rnnDesc, + const int recProjSize, + const int outProjSize) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnRNNDescriptor_t, const int, const int); + static auto func_ptr = LoadSymbol("cudnnSetRNNProjectionLayers"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, recProjSize, outProjSize); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetRNNProjectionLayers(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + int *recProjSize, + int *outProjSize) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, int *, int *); + static auto func_ptr = LoadSymbol("cudnnGetRNNProjectionLayers"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, recProjSize, outProjSize); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetRNNAlgorithmDescriptor(cudnnHandle_t handle, cudnnRNNDescriptor_t rnnDesc, cudnnAlgorithmDescriptor_t algoDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnRNNDescriptor_t, cudnnAlgorithmDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnSetRNNAlgorithmDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, algoDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetRNNDescriptor(cudnnHandle_t handle, + cudnnRNNDescriptor_t rnnDesc, + int *hiddenSize, + int *numLayers, + cudnnDropoutDescriptor_t *dropoutDesc, + cudnnRNNInputMode_t *inputMode, + cudnnDirectionMode_t *direction, + cudnnRNNMode_t *mode, + cudnnRNNAlgo_t *algo, + cudnnDataType_t *dataType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnRNNDescriptor_t, int *, int *, cudnnDropoutDescriptor_t *, cudnnRNNInputMode_t *, cudnnDirectionMode_t *, cudnnRNNMode_t *, cudnnRNNAlgo_t *, cudnnDataType_t *); + static auto func_ptr = LoadSymbol("cudnnGetRNNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, hiddenSize, numLayers, dropoutDesc, inputMode, direction, mode, algo, dataType); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetRNNMatrixMathType(cudnnRNNDescriptor_t rnnDesc, cudnnMathType_t mType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t, cudnnMathType_t); + static auto func_ptr = LoadSymbol("cudnnSetRNNMatrixMathType"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc, mType); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetRNNMatrixMathType(cudnnRNNDescriptor_t rnnDesc, cudnnMathType_t *mType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t, cudnnMathType_t *); + static auto func_ptr = LoadSymbol("cudnnGetRNNMatrixMathType"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc, mType); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetRNNWorkspaceSize(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t *xDesc, + size_t *sizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetRNNWorkspaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetRNNTrainingReserveSize(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t *xDesc, + size_t *sizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetRNNTrainingReserveSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetRNNParamsSize(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const cudnnTensorDescriptor_t xDesc, + size_t *sizeInBytes, + cudnnDataType_t dataType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const cudnnTensorDescriptor_t, size_t *, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnGetRNNParamsSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, xDesc, sizeInBytes, dataType); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetRNNLinLayerMatrixParams(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int pseudoLayer, + const cudnnTensorDescriptor_t xDesc, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const int linLayerID, + cudnnFilterDescriptor_t linLayerMatDesc, + void **linLayerMat) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, const void *, const int, cudnnFilterDescriptor_t, void **); + static auto func_ptr = LoadSymbol("cudnnGetRNNLinLayerMatrixParams"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, pseudoLayer, xDesc, wDesc, w, linLayerID, linLayerMatDesc, linLayerMat); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetRNNLinLayerBiasParams(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int pseudoLayer, + const cudnnTensorDescriptor_t xDesc, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const int linLayerID, + cudnnFilterDescriptor_t linLayerBiasDesc, + void **linLayerBias) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, const void *, const int, cudnnFilterDescriptor_t, void **); + static auto func_ptr = LoadSymbol("cudnnGetRNNLinLayerBiasParams"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, pseudoLayer, xDesc, wDesc, w, linLayerID, linLayerBiasDesc, linLayerBias); +} + +cudnnStatus_t CUDNNWINAPI +cudnnRNNForwardInference(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t *xDesc, + const void *x, + const cudnnTensorDescriptor_t hxDesc, + const void *hx, + const cudnnTensorDescriptor_t cxDesc, + const void *cx, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnTensorDescriptor_t *yDesc, + void *y, + const cudnnTensorDescriptor_t hyDesc, + void *hy, + const cudnnTensorDescriptor_t cyDesc, + void *cy, + void *workspace, + size_t workSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t *, void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnRNNForwardInference"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, x, hxDesc, hx, cxDesc, cx, wDesc, w, yDesc, y, hyDesc, hy, cyDesc, cy, workspace, workSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnRNNForwardTraining(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t *xDesc, + const void *x, + const cudnnTensorDescriptor_t hxDesc, + const void *hx, + const cudnnTensorDescriptor_t cxDesc, + const void *cx, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnTensorDescriptor_t *yDesc, + void *y, + const cudnnTensorDescriptor_t hyDesc, + void *hy, + const cudnnTensorDescriptor_t cyDesc, + void *cy, + void *workspace, + size_t workSpaceSizeInBytes, + void *reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t *, void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, void *, size_t, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnRNNForwardTraining"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, x, hxDesc, hx, cxDesc, cx, wDesc, w, yDesc, y, hyDesc, hy, cyDesc, cy, workspace, workSpaceSizeInBytes, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnRNNBackwardData(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t *yDesc, + const void *y, + const cudnnTensorDescriptor_t *dyDesc, + const void *dy, + const cudnnTensorDescriptor_t dhyDesc, + const void *dhy, + const cudnnTensorDescriptor_t dcyDesc, + const void *dcy, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnTensorDescriptor_t hxDesc, + const void *hx, + const cudnnTensorDescriptor_t cxDesc, + const void *cx, + const cudnnTensorDescriptor_t *dxDesc, + void *dx, + const cudnnTensorDescriptor_t dhxDesc, + void *dhx, + const cudnnTensorDescriptor_t dcxDesc, + void *dcx, + void *workspace, + size_t workSpaceSizeInBytes, + void *reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t *, void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, void *, size_t, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnRNNBackwardData"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, yDesc, y, dyDesc, dy, dhyDesc, dhy, dcyDesc, dcy, wDesc, w, hxDesc, hx, cxDesc, cx, dxDesc, dx, dhxDesc, dhx, dcxDesc, dcx, workspace, workSpaceSizeInBytes, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnRNNBackwardWeights(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t *xDesc, + const void *x, + const cudnnTensorDescriptor_t hxDesc, + const void *hx, + const cudnnTensorDescriptor_t *yDesc, + const void *y, + const void *workspace, + size_t workSpaceSizeInBytes, + const cudnnFilterDescriptor_t dwDesc, + void *dw, + const void *reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t *, const void *, const void *, size_t, const cudnnFilterDescriptor_t, void *, const void *, size_t); + static auto func_ptr = LoadSymbol("cudnnRNNBackwardWeights"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, x, hxDesc, hx, yDesc, y, workspace, workSpaceSizeInBytes, dwDesc, dw, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreateCTCLossDescriptor(cudnnCTCLossDescriptor_t *ctcLossDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnCTCLossDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateCTCLossDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(ctcLossDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetCTCLossDescriptor(cudnnCTCLossDescriptor_t ctcLossDesc, cudnnDataType_t compType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnCTCLossDescriptor_t, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnSetCTCLossDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(ctcLossDesc, compType); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetCTCLossDescriptor(cudnnCTCLossDescriptor_t ctcLossDesc, cudnnDataType_t *compType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnCTCLossDescriptor_t, cudnnDataType_t *); + static auto func_ptr = LoadSymbol("cudnnGetCTCLossDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(ctcLossDesc, compType); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroyCTCLossDescriptor(cudnnCTCLossDescriptor_t ctcLossDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnCTCLossDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyCTCLossDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(ctcLossDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCTCLoss( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t + probsDesc, /* Tensor descriptor for probabilities, the dimensions are T,N,A (T is the timing steps, N is the + mini batch size, A is the alphabet size) */ + const void *probs, /* probabilities after softmax, in GPU memory */ + const int *labels, /* labels, in CPU memory */ + const int *labelLengths, /* the length of each label, in CPU memory */ + const int *inputLengths, /* the lengths of timing steps in each batch, in CPU memory */ + void *costs, /* the returned costs of CTC, in GPU memory */ + const cudnnTensorDescriptor_t gradientsDesc, /* Tensor descriptor for gradients, the dimensions are T,N,A */ + const void *gradients, /* the returned CTC gradients, in GPU memory, to compute costs only, set it to NULL */ + cudnnCTCLossAlgo_t algo, /* algorithm selected, supported now 0 and 1 */ + cudnnCTCLossDescriptor_t ctcLossDesc, + void *workspace, /* pointer to the workspace, in GPU memory */ + size_t workSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const void *, const int *, const int *, const int *, void *, const cudnnTensorDescriptor_t, const void *, cudnnCTCLossAlgo_t, cudnnCTCLossDescriptor_t, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnCTCLoss"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, probsDesc, probs, labels, labelLengths, inputLengths, costs, gradientsDesc, gradients, algo, ctcLossDesc, workspace, workSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetCTCLossWorkspaceSize( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t probsDesc, /* Tensor descriptor for probabilities, the dimensions are T,N,A (T is the + timing steps, N is the mini batch size, A is the alphabet size) */ + const cudnnTensorDescriptor_t gradientsDesc, /* Tensor descriptor for gradients, the + dimensions are T,N,A. To compute costs + only, set it to NULL */ + const int *labels, /* labels, in CPU memory */ + const int *labelLengths, /* the length of each label, in CPU memory */ + const int *inputLengths, /* the lengths of timing steps in each batch, in CPU memory */ + cudnnCTCLossAlgo_t algo, /* algorithm selected, supported now 0 and 1 */ + cudnnCTCLossDescriptor_t ctcLossDesc, + size_t *sizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, const int *, const int *, const int *, cudnnCTCLossAlgo_t, cudnnCTCLossDescriptor_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetCTCLossWorkspaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, probsDesc, gradientsDesc, labels, labelLengths, inputLengths, algo, ctcLossDesc, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreateAlgorithmDescriptor(cudnnAlgorithmDescriptor_t *algoDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnAlgorithmDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateAlgorithmDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(algoDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetAlgorithmDescriptor(cudnnAlgorithmDescriptor_t algoDesc, cudnnAlgorithm_t algorithm) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnAlgorithmDescriptor_t, cudnnAlgorithm_t); + static auto func_ptr = LoadSymbol("cudnnSetAlgorithmDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(algoDesc, algorithm); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetAlgorithmDescriptor(const cudnnAlgorithmDescriptor_t algoDesc, cudnnAlgorithm_t *algorithm) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnAlgorithmDescriptor_t, cudnnAlgorithm_t *); + static auto func_ptr = LoadSymbol("cudnnGetAlgorithmDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(algoDesc, algorithm); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCopyAlgorithmDescriptor(const cudnnAlgorithmDescriptor_t src, cudnnAlgorithmDescriptor_t dest) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnAlgorithmDescriptor_t, cudnnAlgorithmDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnCopyAlgorithmDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(src, dest); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroyAlgorithmDescriptor(cudnnAlgorithmDescriptor_t algoDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnAlgorithmDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyAlgorithmDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(algoDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreateAlgorithmPerformance(cudnnAlgorithmPerformance_t *algoPerf, int numberToCreate) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnAlgorithmPerformance_t *, int); + static auto func_ptr = LoadSymbol("cudnnCreateAlgorithmPerformance"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(algoPerf, numberToCreate); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetAlgorithmPerformance(cudnnAlgorithmPerformance_t algoPerf, + cudnnAlgorithmDescriptor_t algoDesc, + cudnnStatus_t status, + float time, + size_t memory) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnAlgorithmPerformance_t, cudnnAlgorithmDescriptor_t, cudnnStatus_t, float, size_t); + static auto func_ptr = LoadSymbol("cudnnSetAlgorithmPerformance"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(algoPerf, algoDesc, status, time, memory); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetAlgorithmPerformance(const cudnnAlgorithmPerformance_t algoPerf, + cudnnAlgorithmDescriptor_t *algoDesc, + cudnnStatus_t *status, + float *time, + size_t *memory) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnAlgorithmPerformance_t, cudnnAlgorithmDescriptor_t *, cudnnStatus_t *, float *, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetAlgorithmPerformance"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(algoPerf, algoDesc, status, time, memory); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroyAlgorithmPerformance(cudnnAlgorithmPerformance_t *algoPerf, int numberToDestroy) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnAlgorithmPerformance_t *, int); + static auto func_ptr = LoadSymbol("cudnnDestroyAlgorithmPerformance"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(algoPerf, numberToDestroy); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetAlgorithmSpaceSize(cudnnHandle_t handle, cudnnAlgorithmDescriptor_t algoDesc, size_t *algoSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnAlgorithmDescriptor_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetAlgorithmSpaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, algoDesc, algoSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSaveAlgorithm(cudnnHandle_t handle, + cudnnAlgorithmDescriptor_t algoDesc, + void *algoSpace, + size_t algoSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnAlgorithmDescriptor_t, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnSaveAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, algoDesc, algoSpace, algoSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnRestoreAlgorithm(cudnnHandle_t handle, + void *algoSpace, + size_t algoSpaceSizeInBytes, + cudnnAlgorithmDescriptor_t algoDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, void *, size_t, cudnnAlgorithmDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnRestoreAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, algoSpace, algoSpaceSizeInBytes, algoDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnRNNSetClip(cudnnHandle_t handle, + cudnnRNNDescriptor_t rnnDesc, + cudnnRNNClipMode_t clipMode, + cudnnNanPropagation_t clipNanOpt, + double lclip, + double rclip) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnRNNDescriptor_t, cudnnRNNClipMode_t, cudnnNanPropagation_t, double, double); + static auto func_ptr = LoadSymbol("cudnnRNNSetClip"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, clipMode, clipNanOpt, lclip, rclip); +} + +cudnnStatus_t CUDNNWINAPI +cudnnRNNGetClip(cudnnHandle_t handle, + cudnnRNNDescriptor_t rnnDesc, + cudnnRNNClipMode_t *clipMode, + cudnnNanPropagation_t *clipNanOpt, + double *lclip, + double *rclip) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnRNNDescriptor_t, cudnnRNNClipMode_t *, cudnnNanPropagation_t *, double *, double *); + static auto func_ptr = LoadSymbol("cudnnRNNGetClip"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, clipMode, clipNanOpt, lclip, rclip); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetCallback(unsigned mask, void *udata, cudnnCallback_t fptr) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(unsigned int, void *, cudnnCallback_t); + static auto func_ptr = LoadSymbol("cudnnSetCallback"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(mask, udata, fptr); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetCallback(unsigned *mask, void **udata, cudnnCallback_t *fptr) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(unsigned int *, void **, cudnnCallback_t *); + static auto func_ptr = LoadSymbol("cudnnGetCallback"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(mask, udata, fptr); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetRNNPaddingMode(cudnnRNNDescriptor_t rnnDesc, cudnnRNNPaddingMode_t paddingMode) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t, cudnnRNNPaddingMode_t); + static auto func_ptr = LoadSymbol("cudnnSetRNNPaddingMode"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc, paddingMode); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetRNNPaddingMode(cudnnRNNDescriptor_t rnnDesc, cudnnRNNPaddingMode_t *paddingMode) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t, cudnnRNNPaddingMode_t *); + static auto func_ptr = LoadSymbol("cudnnGetRNNPaddingMode"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc, paddingMode); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreateRNNDataDescriptor(cudnnRNNDataDescriptor_t *RNNDataDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDataDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateRNNDataDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(RNNDataDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroyRNNDataDescriptor(cudnnRNNDataDescriptor_t RNNDataDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDataDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyRNNDataDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(RNNDataDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetRNNDataDescriptor(cudnnRNNDataDescriptor_t RNNDataDesc, + cudnnDataType_t dataType, + cudnnRNNDataLayout_t layout, + int maxSeqLength, + int batchSize, + int vectorSize, + const int seqLengthArray[], /* length of each sequence in the batch */ + void *paddingFill) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDataDescriptor_t, cudnnDataType_t, cudnnRNNDataLayout_t, int, int, int, const int [], void *); + static auto func_ptr = LoadSymbol("cudnnSetRNNDataDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(RNNDataDesc, dataType, layout, maxSeqLength, batchSize, vectorSize, seqLengthArray, paddingFill); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetRNNDataDescriptor(cudnnRNNDataDescriptor_t RNNDataDesc, + cudnnDataType_t *dataType, + cudnnRNNDataLayout_t *layout, + int *maxSeqLength, + int *batchSize, + int *vectorSize, + int arrayLengthRequested, + int seqLengthArray[], + void *paddingFill) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDataDescriptor_t, cudnnDataType_t *, cudnnRNNDataLayout_t *, int *, int *, int *, int, int [], void *); + static auto func_ptr = LoadSymbol("cudnnGetRNNDataDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(RNNDataDesc, dataType, layout, maxSeqLength, batchSize, vectorSize, arrayLengthRequested, seqLengthArray, paddingFill); +} + +cudnnStatus_t CUDNNWINAPI +cudnnRNNForwardTrainingEx(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const cudnnRNNDataDescriptor_t xDesc, + const void *x, + const cudnnTensorDescriptor_t hxDesc, + const void *hx, + const cudnnTensorDescriptor_t cxDesc, + const void *cx, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnRNNDataDescriptor_t yDesc, + void *y, + const cudnnTensorDescriptor_t hyDesc, + void *hy, + const cudnnTensorDescriptor_t cyDesc, + void *cy, + const cudnnRNNDataDescriptor_t kDesc, /* reserved, should pass NULL */ + const void *keys, /* reserved, should pass NULL */ + const cudnnRNNDataDescriptor_t cDesc, /* reserved, should pass NULL */ + void *cAttn, /* reserved, should pass NULL */ + const cudnnRNNDataDescriptor_t iDesc, /* reserved, should pass NULL */ + void *iAttn, /* reserved, should pass NULL */ + const cudnnRNNDataDescriptor_t qDesc, /* reserved, should pass NULL */ + void *queries, /* reserved, should pass NULL */ + void *workSpace, + size_t workSpaceSizeInBytes, + void *reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const cudnnRNNDataDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnRNNDataDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, const cudnnRNNDataDescriptor_t, const void *, const cudnnRNNDataDescriptor_t, void *, const cudnnRNNDataDescriptor_t, void *, const cudnnRNNDataDescriptor_t, void *, void *, size_t, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnRNNForwardTrainingEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, xDesc, x, hxDesc, hx, cxDesc, cx, wDesc, w, yDesc, y, hyDesc, hy, cyDesc, cy, kDesc, keys, cDesc, cAttn, iDesc, iAttn, qDesc, queries, workSpace, workSpaceSizeInBytes, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnRNNForwardInferenceEx(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const cudnnRNNDataDescriptor_t xDesc, + const void *x, + const cudnnTensorDescriptor_t hxDesc, + const void *hx, + const cudnnTensorDescriptor_t cxDesc, + const void *cx, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnRNNDataDescriptor_t yDesc, + void *y, + const cudnnTensorDescriptor_t hyDesc, + void *hy, + const cudnnTensorDescriptor_t cyDesc, + void *cy, + const cudnnRNNDataDescriptor_t kDesc, /* reserved, should pass NULL */ + const void *keys, /* reserved, should pass NULL */ + const cudnnRNNDataDescriptor_t cDesc, /* reserved, should pass NULL */ + void *cAttn, /* reserved, should pass NULL */ + const cudnnRNNDataDescriptor_t iDesc, /* reserved, should pass NULL */ + void *iAttn, /* reserved, should pass NULL */ + const cudnnRNNDataDescriptor_t qDesc, /* reserved, should pass NULL */ + void *queries, /* reserved, should pass NULL */ + void *workSpace, + size_t workSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const cudnnRNNDataDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnRNNDataDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, const cudnnRNNDataDescriptor_t, const void *, const cudnnRNNDataDescriptor_t, void *, const cudnnRNNDataDescriptor_t, void *, const cudnnRNNDataDescriptor_t, void *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnRNNForwardInferenceEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, xDesc, x, hxDesc, hx, cxDesc, cx, wDesc, w, yDesc, y, hyDesc, hy, cyDesc, cy, kDesc, keys, cDesc, cAttn, iDesc, iAttn, qDesc, queries, workSpace, workSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnRNNBackwardDataEx(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const cudnnRNNDataDescriptor_t yDesc, + const void *y, + const cudnnRNNDataDescriptor_t dyDesc, + const void *dy, + const cudnnRNNDataDescriptor_t dcDesc, /* reserved, should pass NULL */ + const void *dcAttn, /* reserved, should pass NULL */ + const cudnnTensorDescriptor_t dhyDesc, + const void *dhy, + const cudnnTensorDescriptor_t dcyDesc, + const void *dcy, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnTensorDescriptor_t hxDesc, + const void *hx, + const cudnnTensorDescriptor_t cxDesc, + const void *cx, + const cudnnRNNDataDescriptor_t dxDesc, + void *dx, + const cudnnTensorDescriptor_t dhxDesc, + void *dhx, + const cudnnTensorDescriptor_t dcxDesc, + void *dcx, + const cudnnRNNDataDescriptor_t dkDesc, /* reserved, should pass NULL */ + void *dkeys, /* reserved, should pass NULL */ + void *workSpace, + size_t workSpaceSizeInBytes, + void *reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const cudnnRNNDataDescriptor_t, const void *, const cudnnRNNDataDescriptor_t, const void *, const cudnnRNNDataDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnRNNDataDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, const cudnnRNNDataDescriptor_t, void *, void *, size_t, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnRNNBackwardDataEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, yDesc, y, dyDesc, dy, dcDesc, dcAttn, dhyDesc, dhy, dcyDesc, dcy, wDesc, w, hxDesc, hx, cxDesc, cx, dxDesc, dx, dhxDesc, dhx, dcxDesc, dcx, dkDesc, dkeys, workSpace, workSpaceSizeInBytes, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnRNNBackwardWeightsEx(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const cudnnRNNDataDescriptor_t xDesc, + const void *x, + const cudnnTensorDescriptor_t hxDesc, + const void *hx, + const cudnnRNNDataDescriptor_t yDesc, + const void *y, + void *workSpace, + size_t workSpaceSizeInBytes, + const cudnnFilterDescriptor_t dwDesc, + void *dw, + void *reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const cudnnRNNDataDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnRNNDataDescriptor_t, const void *, void *, size_t, const cudnnFilterDescriptor_t, void *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnRNNBackwardWeightsEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, xDesc, x, hxDesc, hx, yDesc, y, workSpace, workSpaceSizeInBytes, dwDesc, dw, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetRNNDescriptor_v6(cudnnHandle_t handle, + cudnnRNNDescriptor_t rnnDesc, + const int hiddenSize, + const int numLayers, + cudnnDropoutDescriptor_t dropoutDesc, + cudnnRNNInputMode_t inputMode, + cudnnDirectionMode_t direction, + cudnnRNNMode_t mode, + cudnnRNNAlgo_t algo, + cudnnDataType_t dataType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnRNNDescriptor_t, const int, const int, cudnnDropoutDescriptor_t, cudnnRNNInputMode_t, cudnnDirectionMode_t, cudnnRNNMode_t, cudnnRNNAlgo_t, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnSetRNNDescriptor_v6"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, hiddenSize, numLayers, dropoutDesc, inputMode, direction, mode, algo, dataType); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetRNNDescriptor_v5(cudnnRNNDescriptor_t rnnDesc, + int hiddenSize, + int numLayers, + cudnnDropoutDescriptor_t dropoutDesc, + cudnnRNNInputMode_t inputMode, + cudnnDirectionMode_t direction, + cudnnRNNMode_t mode, + cudnnDataType_t dataType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t, int, int, cudnnDropoutDescriptor_t, cudnnRNNInputMode_t, cudnnDirectionMode_t, cudnnRNNMode_t, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnSetRNNDescriptor_v5"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc, hiddenSize, numLayers, dropoutDesc, inputMode, direction, mode, dataType); +} + +} // extern "C" diff --git a/tensorflow/stream_executor/cuda/cudnn_7_4.inc b/tensorflow/stream_executor/cuda/cudnn_7_4.inc new file mode 100644 index 0000000000..cd35c1fbb7 --- /dev/null +++ b/tensorflow/stream_executor/cuda/cudnn_7_4.inc @@ -0,0 +1,2643 @@ +// Auto-generated, do not edit. + +extern "C" { + +size_t CUDNNWINAPI +cudnnGetVersion(void) { + using FuncPtr = size_t (CUDNNWINAPI *)(); + static auto func_ptr = LoadSymbol("cudnnGetVersion"); + if (!func_ptr) return 0; + return func_ptr(); +} + +size_t CUDNNWINAPI +cudnnGetCudartVersion(void) { + using FuncPtr = size_t (CUDNNWINAPI *)(); + static auto func_ptr = LoadSymbol("cudnnGetCudartVersion"); + if (!func_ptr) return 0; + return func_ptr(); +} + +const char *CUDNNWINAPI +cudnnGetErrorString(cudnnStatus_t status) { + using FuncPtr = const char * (CUDNNWINAPI *)(cudnnStatus_t); + static auto func_ptr = LoadSymbol("cudnnGetErrorString"); + if (!func_ptr) return "cudnnGetErrorString symbol not found."; + return func_ptr(status); +} + +cudnnStatus_t CUDNNWINAPI +cudnnQueryRuntimeError(cudnnHandle_t handle, cudnnStatus_t *rstatus, cudnnErrQueryMode_t mode, cudnnRuntimeTag_t *tag) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnStatus_t *, cudnnErrQueryMode_t, cudnnRuntimeTag_t *); + static auto func_ptr = LoadSymbol("cudnnQueryRuntimeError"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rstatus, mode, tag); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetProperty(libraryPropertyType type, int *value) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(libraryPropertyType, int *); + static auto func_ptr = LoadSymbol("cudnnGetProperty"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(type, value); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreate(cudnnHandle_t *handle) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t *); + static auto func_ptr = LoadSymbol("cudnnCreate"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroy(cudnnHandle_t handle) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t); + static auto func_ptr = LoadSymbol("cudnnDestroy"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetStream(cudnnHandle_t handle, cudaStream_t streamId) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudaStream_t); + static auto func_ptr = LoadSymbol("cudnnSetStream"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, streamId); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetStream(cudnnHandle_t handle, cudaStream_t *streamId) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudaStream_t *); + static auto func_ptr = LoadSymbol("cudnnGetStream"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, streamId); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreateTensorDescriptor(cudnnTensorDescriptor_t *tensorDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetTensor4dDescriptor(cudnnTensorDescriptor_t tensorDesc, + cudnnTensorFormat_t format, + cudnnDataType_t dataType, /* image data type */ + int n, /* number of inputs (batch size) */ + int c, /* number of input feature maps */ + int h, /* height of input section */ + int w) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t, cudnnTensorFormat_t, cudnnDataType_t, int, int, int, int); + static auto func_ptr = LoadSymbol("cudnnSetTensor4dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, format, dataType, n, c, h, w); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetTensor4dDescriptorEx(cudnnTensorDescriptor_t tensorDesc, + cudnnDataType_t dataType, /* image data type */ + int n, /* number of inputs (batch size) */ + int c, /* number of input feature maps */ + int h, /* height of input section */ + int w, /* width of input section */ + int nStride, + int cStride, + int hStride, + int wStride) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t, cudnnDataType_t, int, int, int, int, int, int, int, int); + static auto func_ptr = LoadSymbol("cudnnSetTensor4dDescriptorEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, dataType, n, c, h, w, nStride, cStride, hStride, wStride); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetTensor4dDescriptor(const cudnnTensorDescriptor_t tensorDesc, + cudnnDataType_t *dataType, /* image data type */ + int *n, /* number of inputs (batch size) */ + int *c, /* number of input feature maps */ + int *h, /* height of input section */ + int *w, /* width of input section */ + int *nStride, + int *cStride, + int *hStride, + int *wStride) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnTensorDescriptor_t, cudnnDataType_t *, int *, int *, int *, int *, int *, int *, int *, int *); + static auto func_ptr = LoadSymbol("cudnnGetTensor4dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, dataType, n, c, h, w, nStride, cStride, hStride, wStride); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetTensorNdDescriptor(cudnnTensorDescriptor_t tensorDesc, + cudnnDataType_t dataType, + int nbDims, + const int dimA[], + const int strideA[]) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t, cudnnDataType_t, int, const int [], const int []); + static auto func_ptr = LoadSymbol("cudnnSetTensorNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, dataType, nbDims, dimA, strideA); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetTensorNdDescriptorEx(cudnnTensorDescriptor_t tensorDesc, + cudnnTensorFormat_t format, + cudnnDataType_t dataType, + int nbDims, + const int dimA[]) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t, cudnnTensorFormat_t, cudnnDataType_t, int, const int []); + static auto func_ptr = LoadSymbol("cudnnSetTensorNdDescriptorEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, format, dataType, nbDims, dimA); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetTensorNdDescriptor(const cudnnTensorDescriptor_t tensorDesc, + int nbDimsRequested, + cudnnDataType_t *dataType, + int *nbDims, + int dimA[], + int strideA[]) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnTensorDescriptor_t, int, cudnnDataType_t *, int *, int [], int []); + static auto func_ptr = LoadSymbol("cudnnGetTensorNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, nbDimsRequested, dataType, nbDims, dimA, strideA); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetTensorSizeInBytes(const cudnnTensorDescriptor_t tensorDesc, size_t *size) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnTensorDescriptor_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetTensorSizeInBytes"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc, size); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroyTensorDescriptor(cudnnTensorDescriptor_t tensorDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(tensorDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnTransformTensor(cudnnHandle_t handle, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnTransformTensor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha, xDesc, x, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI +cudnnAddTensor(cudnnHandle_t handle, + const void *alpha, + const cudnnTensorDescriptor_t aDesc, + const void *A, + const void *beta, + const cudnnTensorDescriptor_t cDesc, + void *C) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnAddTensor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha, aDesc, A, beta, cDesc, C); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreateOpTensorDescriptor(cudnnOpTensorDescriptor_t *opTensorDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnOpTensorDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateOpTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(opTensorDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetOpTensorDescriptor(cudnnOpTensorDescriptor_t opTensorDesc, + cudnnOpTensorOp_t opTensorOp, + cudnnDataType_t opTensorCompType, + cudnnNanPropagation_t opTensorNanOpt) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnOpTensorDescriptor_t, cudnnOpTensorOp_t, cudnnDataType_t, cudnnNanPropagation_t); + static auto func_ptr = LoadSymbol("cudnnSetOpTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(opTensorDesc, opTensorOp, opTensorCompType, opTensorNanOpt); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetOpTensorDescriptor(const cudnnOpTensorDescriptor_t opTensorDesc, + cudnnOpTensorOp_t *opTensorOp, + cudnnDataType_t *opTensorCompType, + cudnnNanPropagation_t *opTensorNanOpt) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnOpTensorDescriptor_t, cudnnOpTensorOp_t *, cudnnDataType_t *, cudnnNanPropagation_t *); + static auto func_ptr = LoadSymbol("cudnnGetOpTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(opTensorDesc, opTensorOp, opTensorCompType, opTensorNanOpt); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroyOpTensorDescriptor(cudnnOpTensorDescriptor_t opTensorDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnOpTensorDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyOpTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(opTensorDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnOpTensor(cudnnHandle_t handle, + const cudnnOpTensorDescriptor_t opTensorDesc, + const void *alpha1, + const cudnnTensorDescriptor_t aDesc, + const void *A, + const void *alpha2, + const cudnnTensorDescriptor_t bDesc, + const void *B, + const void *beta, + const cudnnTensorDescriptor_t cDesc, + void *C) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnOpTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnOpTensor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, opTensorDesc, alpha1, aDesc, A, alpha2, bDesc, B, beta, cDesc, C); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreateReduceTensorDescriptor(cudnnReduceTensorDescriptor_t *reduceTensorDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnReduceTensorDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateReduceTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(reduceTensorDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetReduceTensorDescriptor(cudnnReduceTensorDescriptor_t reduceTensorDesc, + cudnnReduceTensorOp_t reduceTensorOp, + cudnnDataType_t reduceTensorCompType, + cudnnNanPropagation_t reduceTensorNanOpt, + cudnnReduceTensorIndices_t reduceTensorIndices, + cudnnIndicesType_t reduceTensorIndicesType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnReduceTensorDescriptor_t, cudnnReduceTensorOp_t, cudnnDataType_t, cudnnNanPropagation_t, cudnnReduceTensorIndices_t, cudnnIndicesType_t); + static auto func_ptr = LoadSymbol("cudnnSetReduceTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(reduceTensorDesc, reduceTensorOp, reduceTensorCompType, reduceTensorNanOpt, reduceTensorIndices, reduceTensorIndicesType); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetReduceTensorDescriptor(const cudnnReduceTensorDescriptor_t reduceTensorDesc, + cudnnReduceTensorOp_t *reduceTensorOp, + cudnnDataType_t *reduceTensorCompType, + cudnnNanPropagation_t *reduceTensorNanOpt, + cudnnReduceTensorIndices_t *reduceTensorIndices, + cudnnIndicesType_t *reduceTensorIndicesType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnReduceTensorDescriptor_t, cudnnReduceTensorOp_t *, cudnnDataType_t *, cudnnNanPropagation_t *, cudnnReduceTensorIndices_t *, cudnnIndicesType_t *); + static auto func_ptr = LoadSymbol("cudnnGetReduceTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(reduceTensorDesc, reduceTensorOp, reduceTensorCompType, reduceTensorNanOpt, reduceTensorIndices, reduceTensorIndicesType); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroyReduceTensorDescriptor(cudnnReduceTensorDescriptor_t reduceTensorDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnReduceTensorDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyReduceTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(reduceTensorDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetReductionIndicesSize(cudnnHandle_t handle, + const cudnnReduceTensorDescriptor_t reduceTensorDesc, + const cudnnTensorDescriptor_t aDesc, + const cudnnTensorDescriptor_t cDesc, + size_t *sizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnReduceTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetReductionIndicesSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, reduceTensorDesc, aDesc, cDesc, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetReductionWorkspaceSize(cudnnHandle_t handle, + const cudnnReduceTensorDescriptor_t reduceTensorDesc, + const cudnnTensorDescriptor_t aDesc, + const cudnnTensorDescriptor_t cDesc, + size_t *sizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnReduceTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetReductionWorkspaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, reduceTensorDesc, aDesc, cDesc, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnReduceTensor(cudnnHandle_t handle, + const cudnnReduceTensorDescriptor_t reduceTensorDesc, + void *indices, + size_t indicesSizeInBytes, + void *workspace, + size_t workspaceSizeInBytes, + const void *alpha, + const cudnnTensorDescriptor_t aDesc, + const void *A, + const void *beta, + const cudnnTensorDescriptor_t cDesc, + void *C) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnReduceTensorDescriptor_t, void *, size_t, void *, size_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnReduceTensor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, reduceTensorDesc, indices, indicesSizeInBytes, workspace, workspaceSizeInBytes, alpha, aDesc, A, beta, cDesc, C); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetTensor(cudnnHandle_t handle, const cudnnTensorDescriptor_t yDesc, void *y, const void *valuePtr) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, void *, const void *); + static auto func_ptr = LoadSymbol("cudnnSetTensor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, yDesc, y, valuePtr); +} + +cudnnStatus_t CUDNNWINAPI +cudnnScaleTensor(cudnnHandle_t handle, const cudnnTensorDescriptor_t yDesc, void *y, const void *alpha) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, void *, const void *); + static auto func_ptr = LoadSymbol("cudnnScaleTensor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, yDesc, y, alpha); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreateFilterDescriptor(cudnnFilterDescriptor_t *filterDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnFilterDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateFilterDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(filterDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetFilter4dDescriptor(cudnnFilterDescriptor_t filterDesc, + cudnnDataType_t dataType, /* image data type */ + cudnnTensorFormat_t format, + int k, /* number of output feature maps */ + int c, /* number of input feature maps */ + int h, /* height of each input filter */ + int w) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnFilterDescriptor_t, cudnnDataType_t, cudnnTensorFormat_t, int, int, int, int); + static auto func_ptr = LoadSymbol("cudnnSetFilter4dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(filterDesc, dataType, format, k, c, h, w); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetFilter4dDescriptor(const cudnnFilterDescriptor_t filterDesc, + cudnnDataType_t *dataType, /* image data type */ + cudnnTensorFormat_t *format, + int *k, /* number of output feature maps */ + int *c, /* number of input feature maps */ + int *h, /* height of each input filter */ + int *w) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnFilterDescriptor_t, cudnnDataType_t *, cudnnTensorFormat_t *, int *, int *, int *, int *); + static auto func_ptr = LoadSymbol("cudnnGetFilter4dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(filterDesc, dataType, format, k, c, h, w); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetFilterNdDescriptor(cudnnFilterDescriptor_t filterDesc, + cudnnDataType_t dataType, /* image data type */ + cudnnTensorFormat_t format, + int nbDims, + const int filterDimA[]) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnFilterDescriptor_t, cudnnDataType_t, cudnnTensorFormat_t, int, const int []); + static auto func_ptr = LoadSymbol("cudnnSetFilterNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(filterDesc, dataType, format, nbDims, filterDimA); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetFilterNdDescriptor(const cudnnFilterDescriptor_t filterDesc, + int nbDimsRequested, + cudnnDataType_t *dataType, /* image data type */ + cudnnTensorFormat_t *format, + int *nbDims, + int filterDimA[]) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnFilterDescriptor_t, int, cudnnDataType_t *, cudnnTensorFormat_t *, int *, int []); + static auto func_ptr = LoadSymbol("cudnnGetFilterNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(filterDesc, nbDimsRequested, dataType, format, nbDims, filterDimA); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroyFilterDescriptor(cudnnFilterDescriptor_t filterDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnFilterDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyFilterDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(filterDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreateConvolutionDescriptor(cudnnConvolutionDescriptor_t *convDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateConvolutionDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetConvolutionMathType(cudnnConvolutionDescriptor_t convDesc, cudnnMathType_t mathType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t, cudnnMathType_t); + static auto func_ptr = LoadSymbol("cudnnSetConvolutionMathType"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, mathType); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolutionMathType(cudnnConvolutionDescriptor_t convDesc, cudnnMathType_t *mathType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t, cudnnMathType_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionMathType"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, mathType); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetConvolutionGroupCount(cudnnConvolutionDescriptor_t convDesc, int groupCount) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t, int); + static auto func_ptr = LoadSymbol("cudnnSetConvolutionGroupCount"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, groupCount); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolutionGroupCount(cudnnConvolutionDescriptor_t convDesc, int *groupCount) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t, int *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionGroupCount"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, groupCount); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetConvolution2dDescriptor(cudnnConvolutionDescriptor_t convDesc, + int pad_h, /* zero-padding height */ + int pad_w, /* zero-padding width */ + int u, /* vertical filter stride */ + int v, /* horizontal filter stride */ + int dilation_h, /* filter dilation in the vertical dimension */ + int dilation_w, /* filter dilation in the horizontal dimension */ + cudnnConvolutionMode_t mode, + cudnnDataType_t computeType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t, int, int, int, int, int, int, cudnnConvolutionMode_t, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnSetConvolution2dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, pad_h, pad_w, u, v, dilation_h, dilation_w, mode, computeType); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolution2dDescriptor(const cudnnConvolutionDescriptor_t convDesc, + int *pad_h, /* zero-padding height */ + int *pad_w, /* zero-padding width */ + int *u, /* vertical filter stride */ + int *v, /* horizontal filter stride */ + int *dilation_h, /* filter dilation in the vertical dimension */ + int *dilation_w, /* filter dilation in the horizontal dimension */ + cudnnConvolutionMode_t *mode, + cudnnDataType_t *computeType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnConvolutionDescriptor_t, int *, int *, int *, int *, int *, int *, cudnnConvolutionMode_t *, cudnnDataType_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolution2dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, pad_h, pad_w, u, v, dilation_h, dilation_w, mode, computeType); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolution2dForwardOutputDim(const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t inputTensorDesc, + const cudnnFilterDescriptor_t filterDesc, + int *n, + int *c, + int *h, + int *w) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, int *, int *, int *, int *); + static auto func_ptr = LoadSymbol("cudnnGetConvolution2dForwardOutputDim"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, inputTensorDesc, filterDesc, n, c, h, w); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetConvolutionNdDescriptor(cudnnConvolutionDescriptor_t convDesc, + int arrayLength, /* nbDims-2 size */ + const int padA[], + const int filterStrideA[], + const int dilationA[], + cudnnConvolutionMode_t mode, + cudnnDataType_t computeType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t, int, const int [], const int [], const int [], cudnnConvolutionMode_t, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnSetConvolutionNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, arrayLength, padA, filterStrideA, dilationA, mode, computeType); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolutionNdDescriptor(const cudnnConvolutionDescriptor_t convDesc, + int arrayLengthRequested, + int *arrayLength, + int padA[], + int strideA[], + int dilationA[], + cudnnConvolutionMode_t *mode, + cudnnDataType_t *computeType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnConvolutionDescriptor_t, int, int *, int [], int [], int [], cudnnConvolutionMode_t *, cudnnDataType_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, arrayLengthRequested, arrayLength, padA, strideA, dilationA, mode, computeType); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolutionNdForwardOutputDim(const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t inputTensorDesc, + const cudnnFilterDescriptor_t filterDesc, + int nbDims, + int tensorOuputDimA[]) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, int, int []); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionNdForwardOutputDim"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc, inputTensorDesc, filterDesc, nbDims, tensorOuputDimA); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroyConvolutionDescriptor(cudnnConvolutionDescriptor_t convDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnConvolutionDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyConvolutionDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(convDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolutionForwardAlgorithmMaxCount(cudnnHandle_t handle, int *count) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, int *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionForwardAlgorithmMaxCount"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, count); +} + +cudnnStatus_t CUDNNWINAPI +cudnnFindConvolutionForwardAlgorithm(cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnFilterDescriptor_t wDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t yDesc, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionFwdAlgoPerf_t *perfResults) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, const int, int *, cudnnConvolutionFwdAlgoPerf_t *); + static auto func_ptr = LoadSymbol("cudnnFindConvolutionForwardAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, wDesc, convDesc, yDesc, requestedAlgoCount, returnedAlgoCount, perfResults); +} + +cudnnStatus_t CUDNNWINAPI +cudnnFindConvolutionForwardAlgorithmEx(cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t yDesc, + void *y, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionFwdAlgoPerf_t *perfResults, + void *workSpace, + size_t workSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, void *, const int, int *, cudnnConvolutionFwdAlgoPerf_t *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnFindConvolutionForwardAlgorithmEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, x, wDesc, w, convDesc, yDesc, y, requestedAlgoCount, returnedAlgoCount, perfResults, workSpace, workSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolutionForwardAlgorithm(cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnFilterDescriptor_t wDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t yDesc, + cudnnConvolutionFwdPreference_t preference, + size_t memoryLimitInBytes, + cudnnConvolutionFwdAlgo_t *algo) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, cudnnConvolutionFwdPreference_t, size_t, cudnnConvolutionFwdAlgo_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionForwardAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, wDesc, convDesc, yDesc, preference, memoryLimitInBytes, algo); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolutionForwardAlgorithm_v7(cudnnHandle_t handle, + const cudnnTensorDescriptor_t srcDesc, + const cudnnFilterDescriptor_t filterDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t destDesc, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionFwdAlgoPerf_t *perfResults) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, const int, int *, cudnnConvolutionFwdAlgoPerf_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionForwardAlgorithm_v7"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, srcDesc, filterDesc, convDesc, destDesc, requestedAlgoCount, returnedAlgoCount, perfResults); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolutionForwardWorkspaceSize(cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnFilterDescriptor_t wDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t yDesc, + cudnnConvolutionFwdAlgo_t algo, + size_t *sizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, cudnnConvolutionFwdAlgo_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionForwardWorkspaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, wDesc, convDesc, yDesc, algo, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnConvolutionForward(cudnnHandle_t handle, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnConvolutionDescriptor_t convDesc, + cudnnConvolutionFwdAlgo_t algo, + void *workSpace, + size_t workSpaceSizeInBytes, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, cudnnConvolutionFwdAlgo_t, void *, size_t, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnConvolutionForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha, xDesc, x, wDesc, w, convDesc, algo, workSpace, workSpaceSizeInBytes, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI +cudnnConvolutionBiasActivationForward(cudnnHandle_t handle, + const void *alpha1, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnConvolutionDescriptor_t convDesc, + cudnnConvolutionFwdAlgo_t algo, + void *workSpace, + size_t workSpaceSizeInBytes, + const void *alpha2, + const cudnnTensorDescriptor_t zDesc, + const void *z, + const cudnnTensorDescriptor_t biasDesc, + const void *bias, + const cudnnActivationDescriptor_t activationDesc, + const cudnnTensorDescriptor_t yDesc, + void *y) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, cudnnConvolutionFwdAlgo_t, void *, size_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnActivationDescriptor_t, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnConvolutionBiasActivationForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha1, xDesc, x, wDesc, w, convDesc, algo, workSpace, workSpaceSizeInBytes, alpha2, zDesc, z, biasDesc, bias, activationDesc, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI +cudnnConvolutionBackwardBias(cudnnHandle_t handle, + const void *alpha, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const void *beta, + const cudnnTensorDescriptor_t dbDesc, + void *db) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnConvolutionBackwardBias"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha, dyDesc, dy, beta, dbDesc, db); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolutionBackwardFilterAlgorithmMaxCount(cudnnHandle_t handle, int *count) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, int *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardFilterAlgorithmMaxCount"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, count); +} + +cudnnStatus_t CUDNNWINAPI +cudnnFindConvolutionBackwardFilterAlgorithm(cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnFilterDescriptor_t dwDesc, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionBwdFilterAlgoPerf_t *perfResults) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnFilterDescriptor_t, const int, int *, cudnnConvolutionBwdFilterAlgoPerf_t *); + static auto func_ptr = LoadSymbol("cudnnFindConvolutionBackwardFilterAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, dyDesc, convDesc, dwDesc, requestedAlgoCount, returnedAlgoCount, perfResults); +} + +cudnnStatus_t CUDNNWINAPI +cudnnFindConvolutionBackwardFilterAlgorithmEx(cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnTensorDescriptor_t dyDesc, + const void *y, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnFilterDescriptor_t dwDesc, + void *dw, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionBwdFilterAlgoPerf_t *perfResults, + void *workSpace, + size_t workSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, const cudnnFilterDescriptor_t, void *, const int, int *, cudnnConvolutionBwdFilterAlgoPerf_t *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnFindConvolutionBackwardFilterAlgorithmEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, x, dyDesc, y, convDesc, dwDesc, dw, requestedAlgoCount, returnedAlgoCount, perfResults, workSpace, workSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolutionBackwardFilterAlgorithm(cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnFilterDescriptor_t dwDesc, + cudnnConvolutionBwdFilterPreference_t preference, + size_t memoryLimitInBytes, + cudnnConvolutionBwdFilterAlgo_t *algo) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnFilterDescriptor_t, cudnnConvolutionBwdFilterPreference_t, size_t, cudnnConvolutionBwdFilterAlgo_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardFilterAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, dyDesc, convDesc, dwDesc, preference, memoryLimitInBytes, algo); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolutionBackwardFilterAlgorithm_v7(cudnnHandle_t handle, + const cudnnTensorDescriptor_t srcDesc, + const cudnnTensorDescriptor_t diffDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnFilterDescriptor_t gradDesc, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionBwdFilterAlgoPerf_t *perfResults) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnFilterDescriptor_t, const int, int *, cudnnConvolutionBwdFilterAlgoPerf_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardFilterAlgorithm_v7"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, srcDesc, diffDesc, convDesc, gradDesc, requestedAlgoCount, returnedAlgoCount, perfResults); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolutionBackwardFilterWorkspaceSize(cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnFilterDescriptor_t gradDesc, + cudnnConvolutionBwdFilterAlgo_t algo, + size_t *sizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnFilterDescriptor_t, cudnnConvolutionBwdFilterAlgo_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardFilterWorkspaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, dyDesc, convDesc, gradDesc, algo, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnConvolutionBackwardFilter(cudnnHandle_t handle, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnConvolutionDescriptor_t convDesc, + cudnnConvolutionBwdFilterAlgo_t algo, + void *workSpace, + size_t workSpaceSizeInBytes, + const void *beta, + const cudnnFilterDescriptor_t dwDesc, + void *dw) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, cudnnConvolutionBwdFilterAlgo_t, void *, size_t, const void *, const cudnnFilterDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnConvolutionBackwardFilter"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha, xDesc, x, dyDesc, dy, convDesc, algo, workSpace, workSpaceSizeInBytes, beta, dwDesc, dw); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolutionBackwardDataAlgorithmMaxCount(cudnnHandle_t handle, int *count) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, int *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardDataAlgorithmMaxCount"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, count); +} + +cudnnStatus_t CUDNNWINAPI +cudnnFindConvolutionBackwardDataAlgorithm(cudnnHandle_t handle, + const cudnnFilterDescriptor_t wDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t dxDesc, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionBwdDataAlgoPerf_t *perfResults) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnFilterDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, const int, int *, cudnnConvolutionBwdDataAlgoPerf_t *); + static auto func_ptr = LoadSymbol("cudnnFindConvolutionBackwardDataAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, wDesc, dyDesc, convDesc, dxDesc, requestedAlgoCount, returnedAlgoCount, perfResults); +} + +cudnnStatus_t CUDNNWINAPI +cudnnFindConvolutionBackwardDataAlgorithmEx(cudnnHandle_t handle, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t dxDesc, + void *dx, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionBwdDataAlgoPerf_t *perfResults, + void *workSpace, + size_t workSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, void *, const int, int *, cudnnConvolutionBwdDataAlgoPerf_t *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnFindConvolutionBackwardDataAlgorithmEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, wDesc, w, dyDesc, dy, convDesc, dxDesc, dx, requestedAlgoCount, returnedAlgoCount, perfResults, workSpace, workSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolutionBackwardDataAlgorithm(cudnnHandle_t handle, + const cudnnFilterDescriptor_t wDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t dxDesc, + cudnnConvolutionBwdDataPreference_t preference, + size_t memoryLimitInBytes, + cudnnConvolutionBwdDataAlgo_t *algo) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnFilterDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, cudnnConvolutionBwdDataPreference_t, size_t, cudnnConvolutionBwdDataAlgo_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardDataAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, wDesc, dyDesc, convDesc, dxDesc, preference, memoryLimitInBytes, algo); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolutionBackwardDataAlgorithm_v7(cudnnHandle_t handle, + const cudnnFilterDescriptor_t filterDesc, + const cudnnTensorDescriptor_t diffDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t gradDesc, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionBwdDataAlgoPerf_t *perfResults) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnFilterDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, const int, int *, cudnnConvolutionBwdDataAlgoPerf_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardDataAlgorithm_v7"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, filterDesc, diffDesc, convDesc, gradDesc, requestedAlgoCount, returnedAlgoCount, perfResults); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetConvolutionBackwardDataWorkspaceSize(cudnnHandle_t handle, + const cudnnFilterDescriptor_t wDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t dxDesc, + cudnnConvolutionBwdDataAlgo_t algo, + size_t *sizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnFilterDescriptor_t, const cudnnTensorDescriptor_t, const cudnnConvolutionDescriptor_t, const cudnnTensorDescriptor_t, cudnnConvolutionBwdDataAlgo_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetConvolutionBackwardDataWorkspaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, wDesc, dyDesc, convDesc, dxDesc, algo, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnConvolutionBackwardData(cudnnHandle_t handle, + const void *alpha, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnConvolutionDescriptor_t convDesc, + cudnnConvolutionBwdDataAlgo_t algo, + void *workSpace, + size_t workSpaceSizeInBytes, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnConvolutionDescriptor_t, cudnnConvolutionBwdDataAlgo_t, void *, size_t, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnConvolutionBackwardData"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, alpha, wDesc, w, dyDesc, dy, convDesc, algo, workSpace, workSpaceSizeInBytes, beta, dxDesc, dx); +} + +cudnnStatus_t CUDNNWINAPI +cudnnIm2Col(cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnFilterDescriptor_t wDesc, + const cudnnConvolutionDescriptor_t convDesc, + void *colBuffer) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const cudnnConvolutionDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnIm2Col"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, xDesc, x, wDesc, convDesc, colBuffer); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSoftmaxForward(cudnnHandle_t handle, + cudnnSoftmaxAlgorithm_t algo, + cudnnSoftmaxMode_t mode, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnSoftmaxAlgorithm_t, cudnnSoftmaxMode_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnSoftmaxForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, algo, mode, alpha, xDesc, x, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSoftmaxBackward(cudnnHandle_t handle, + cudnnSoftmaxAlgorithm_t algo, + cudnnSoftmaxMode_t mode, + const void *alpha, + const cudnnTensorDescriptor_t yDesc, + const void *y, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnSoftmaxAlgorithm_t, cudnnSoftmaxMode_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnSoftmaxBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, algo, mode, alpha, yDesc, y, dyDesc, dy, beta, dxDesc, dx); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreatePoolingDescriptor(cudnnPoolingDescriptor_t *poolingDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnPoolingDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreatePoolingDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetPooling2dDescriptor(cudnnPoolingDescriptor_t poolingDesc, + cudnnPoolingMode_t mode, + cudnnNanPropagation_t maxpoolingNanOpt, + int windowHeight, + int windowWidth, + int verticalPadding, + int horizontalPadding, + int verticalStride, + int horizontalStride) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnPoolingDescriptor_t, cudnnPoolingMode_t, cudnnNanPropagation_t, int, int, int, int, int, int); + static auto func_ptr = LoadSymbol("cudnnSetPooling2dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc, mode, maxpoolingNanOpt, windowHeight, windowWidth, verticalPadding, horizontalPadding, verticalStride, horizontalStride); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetPooling2dDescriptor(const cudnnPoolingDescriptor_t poolingDesc, + cudnnPoolingMode_t *mode, + cudnnNanPropagation_t *maxpoolingNanOpt, + int *windowHeight, + int *windowWidth, + int *verticalPadding, + int *horizontalPadding, + int *verticalStride, + int *horizontalStride) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnPoolingDescriptor_t, cudnnPoolingMode_t *, cudnnNanPropagation_t *, int *, int *, int *, int *, int *, int *); + static auto func_ptr = LoadSymbol("cudnnGetPooling2dDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc, mode, maxpoolingNanOpt, windowHeight, windowWidth, verticalPadding, horizontalPadding, verticalStride, horizontalStride); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetPoolingNdDescriptor(cudnnPoolingDescriptor_t poolingDesc, + const cudnnPoolingMode_t mode, + const cudnnNanPropagation_t maxpoolingNanOpt, + int nbDims, + const int windowDimA[], + const int paddingA[], + const int strideA[]) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnPoolingDescriptor_t, const cudnnPoolingMode_t, const cudnnNanPropagation_t, int, const int [], const int [], const int []); + static auto func_ptr = LoadSymbol("cudnnSetPoolingNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc, mode, maxpoolingNanOpt, nbDims, windowDimA, paddingA, strideA); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetPoolingNdDescriptor(const cudnnPoolingDescriptor_t poolingDesc, + int nbDimsRequested, + cudnnPoolingMode_t *mode, + cudnnNanPropagation_t *maxpoolingNanOpt, + int *nbDims, + int windowDimA[], + int paddingA[], + int strideA[]) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnPoolingDescriptor_t, int, cudnnPoolingMode_t *, cudnnNanPropagation_t *, int *, int [], int [], int []); + static auto func_ptr = LoadSymbol("cudnnGetPoolingNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc, nbDimsRequested, mode, maxpoolingNanOpt, nbDims, windowDimA, paddingA, strideA); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetPoolingNdForwardOutputDim(const cudnnPoolingDescriptor_t poolingDesc, + const cudnnTensorDescriptor_t inputTensorDesc, + int nbDims, + int outputTensorDimA[]) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnPoolingDescriptor_t, const cudnnTensorDescriptor_t, int, int []); + static auto func_ptr = LoadSymbol("cudnnGetPoolingNdForwardOutputDim"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc, inputTensorDesc, nbDims, outputTensorDimA); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetPooling2dForwardOutputDim(const cudnnPoolingDescriptor_t poolingDesc, + const cudnnTensorDescriptor_t inputTensorDesc, + int *n, + int *c, + int *h, + int *w) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnPoolingDescriptor_t, const cudnnTensorDescriptor_t, int *, int *, int *, int *); + static auto func_ptr = LoadSymbol("cudnnGetPooling2dForwardOutputDim"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc, inputTensorDesc, n, c, h, w); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroyPoolingDescriptor(cudnnPoolingDescriptor_t poolingDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnPoolingDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyPoolingDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(poolingDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnPoolingForward(cudnnHandle_t handle, + const cudnnPoolingDescriptor_t poolingDesc, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnPoolingDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnPoolingForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, poolingDesc, alpha, xDesc, x, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI +cudnnPoolingBackward(cudnnHandle_t handle, + const cudnnPoolingDescriptor_t poolingDesc, + const void *alpha, + const cudnnTensorDescriptor_t yDesc, + const void *y, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnPoolingDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnPoolingBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, poolingDesc, alpha, yDesc, y, dyDesc, dy, xDesc, x, beta, dxDesc, dx); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreateActivationDescriptor(cudnnActivationDescriptor_t *activationDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnActivationDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateActivationDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(activationDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetActivationDescriptor(cudnnActivationDescriptor_t activationDesc, + cudnnActivationMode_t mode, + cudnnNanPropagation_t reluNanOpt, + double coef) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnActivationDescriptor_t, cudnnActivationMode_t, cudnnNanPropagation_t, double); + static auto func_ptr = LoadSymbol("cudnnSetActivationDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(activationDesc, mode, reluNanOpt, coef); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetActivationDescriptor(const cudnnActivationDescriptor_t activationDesc, + cudnnActivationMode_t *mode, + cudnnNanPropagation_t *reluNanOpt, + double *coef) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnActivationDescriptor_t, cudnnActivationMode_t *, cudnnNanPropagation_t *, double *); + static auto func_ptr = LoadSymbol("cudnnGetActivationDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(activationDesc, mode, reluNanOpt, coef); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroyActivationDescriptor(cudnnActivationDescriptor_t activationDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnActivationDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyActivationDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(activationDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnActivationForward(cudnnHandle_t handle, + cudnnActivationDescriptor_t activationDesc, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnActivationDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnActivationForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, activationDesc, alpha, xDesc, x, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI +cudnnActivationBackward(cudnnHandle_t handle, + cudnnActivationDescriptor_t activationDesc, + const void *alpha, + const cudnnTensorDescriptor_t yDesc, + const void *y, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnActivationDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnActivationBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, activationDesc, alpha, yDesc, y, dyDesc, dy, xDesc, x, beta, dxDesc, dx); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreateLRNDescriptor(cudnnLRNDescriptor_t *normDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnLRNDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateLRNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(normDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetLRNDescriptor(cudnnLRNDescriptor_t normDesc, unsigned lrnN, double lrnAlpha, double lrnBeta, double lrnK) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnLRNDescriptor_t, unsigned int, double, double, double); + static auto func_ptr = LoadSymbol("cudnnSetLRNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(normDesc, lrnN, lrnAlpha, lrnBeta, lrnK); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetLRNDescriptor(cudnnLRNDescriptor_t normDesc, unsigned *lrnN, double *lrnAlpha, double *lrnBeta, double *lrnK) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnLRNDescriptor_t, unsigned int *, double *, double *, double *); + static auto func_ptr = LoadSymbol("cudnnGetLRNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(normDesc, lrnN, lrnAlpha, lrnBeta, lrnK); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroyLRNDescriptor(cudnnLRNDescriptor_t lrnDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnLRNDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyLRNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(lrnDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnLRNCrossChannelForward(cudnnHandle_t handle, + cudnnLRNDescriptor_t normDesc, + cudnnLRNMode_t lrnMode, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnLRNDescriptor_t, cudnnLRNMode_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnLRNCrossChannelForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, normDesc, lrnMode, alpha, xDesc, x, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI +cudnnLRNCrossChannelBackward(cudnnHandle_t handle, + cudnnLRNDescriptor_t normDesc, + cudnnLRNMode_t lrnMode, + const void *alpha, + const cudnnTensorDescriptor_t yDesc, + const void *y, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnLRNDescriptor_t, cudnnLRNMode_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnLRNCrossChannelBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, normDesc, lrnMode, alpha, yDesc, y, dyDesc, dy, xDesc, x, beta, dxDesc, dx); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDivisiveNormalizationForward(cudnnHandle_t handle, + cudnnLRNDescriptor_t normDesc, + cudnnDivNormMode_t mode, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, /* same desc for means, temp, temp2 */ + const void *x, + const void *means, /* if NULL, means are assumed to be zero */ + void *temp, + void *temp2, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnLRNDescriptor_t, cudnnDivNormMode_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, void *, void *, const void *, const cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnDivisiveNormalizationForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, normDesc, mode, alpha, xDesc, x, means, temp, temp2, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDivisiveNormalizationBackward(cudnnHandle_t handle, + cudnnLRNDescriptor_t normDesc, + cudnnDivNormMode_t mode, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, /* same desc for x, means, dy, temp, temp2 */ + const void *x, + const void *means, /* if NULL, means are assumed to be zero */ + const void *dy, + void *temp, + void *temp2, + const void *beta, + const cudnnTensorDescriptor_t dXdMeansDesc, /* same desc for dx, dMeans */ + void *dx, /* output x differential */ + void *dMeans) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnLRNDescriptor_t, cudnnDivNormMode_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const void *, void *, void *, const void *, const cudnnTensorDescriptor_t, void *, void *); + static auto func_ptr = LoadSymbol("cudnnDivisiveNormalizationBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, normDesc, mode, alpha, xDesc, x, means, dy, temp, temp2, beta, dXdMeansDesc, dx, dMeans); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDeriveBNTensorDescriptor(cudnnTensorDescriptor_t derivedBnDesc, + const cudnnTensorDescriptor_t xDesc, + cudnnBatchNormMode_t mode) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, cudnnBatchNormMode_t); + static auto func_ptr = LoadSymbol("cudnnDeriveBNTensorDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(derivedBnDesc, xDesc, mode); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetBatchNormalizationForwardTrainingExWorkspaceSize(cudnnHandle_t handle, + cudnnBatchNormMode_t mode, + cudnnBatchNormOps_t bnOps, + const cudnnTensorDescriptor_t xDesc, + const cudnnTensorDescriptor_t zDesc, + const cudnnTensorDescriptor_t yDesc, + const cudnnTensorDescriptor_t bnScaleBiasMeanVarDesc, + const cudnnActivationDescriptor_t activationDesc, + size_t *sizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnBatchNormMode_t, cudnnBatchNormOps_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnActivationDescriptor_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetBatchNormalizationForwardTrainingExWorkspaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode, bnOps, xDesc, zDesc, yDesc, bnScaleBiasMeanVarDesc, activationDesc, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetBatchNormalizationBackwardExWorkspaceSize(cudnnHandle_t handle, + cudnnBatchNormMode_t mode, + cudnnBatchNormOps_t bnOps, + const cudnnTensorDescriptor_t xDesc, + const cudnnTensorDescriptor_t yDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnTensorDescriptor_t dzDesc, + const cudnnTensorDescriptor_t dxDesc, + const cudnnTensorDescriptor_t dBnScaleBiasDesc, + const cudnnActivationDescriptor_t activationDesc, + size_t *sizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnBatchNormMode_t, cudnnBatchNormOps_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, const cudnnActivationDescriptor_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetBatchNormalizationBackwardExWorkspaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode, bnOps, xDesc, yDesc, dyDesc, dzDesc, dxDesc, dBnScaleBiasDesc, activationDesc, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetBatchNormalizationTrainingExReserveSpaceSize(cudnnHandle_t handle, + cudnnBatchNormMode_t mode, + cudnnBatchNormOps_t bnOps, + const cudnnActivationDescriptor_t activationDesc, + const cudnnTensorDescriptor_t xDesc, + size_t *sizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnBatchNormMode_t, cudnnBatchNormOps_t, const cudnnActivationDescriptor_t, const cudnnTensorDescriptor_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetBatchNormalizationTrainingExReserveSpaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode, bnOps, activationDesc, xDesc, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnBatchNormalizationForwardTraining( + cudnnHandle_t handle, + cudnnBatchNormMode_t mode, + + const void *alpha, /* alpha[0] = result blend factor */ + const void *beta, /* beta[0] = dest layer blend factor */ + + const cudnnTensorDescriptor_t xDesc, + const void *x, /* NxCxHxW */ + const cudnnTensorDescriptor_t yDesc, + void *y, /* NxCxHxW */ + + /* Shared desc for the next 6 tensors in the argument list. + Data type to be set as follows: + type = (typeOf(x) == double) ? double : float + Dimensions for this descriptor depend on normalization mode + - Spatial Normalization : tensors are expected to have dims 1xCx1x1 + (normalization is performed across NxHxW) + - Per-Activation Normalization : tensors are expected to have dims of 1xCxHxW + (normalization is performed across N) */ + const cudnnTensorDescriptor_t bnScaleBiasMeanVarDesc, + + /* 'Gamma' and 'Beta' respectively in Ioffe and Szegedy's paper's notation */ + const void *bnScale, + const void *bnBias, + + /* MUST use factor=1 in the very first call of a complete training cycle. + Use a factor=1/(1+n) at N-th call to the function to get + Cumulative Moving Average (CMA) behavior + CMA[n] = (x[1]+...+x[n])/n + Since CMA[n+1] = (n*CMA[n]+x[n+1])/(n+1) = + ((n+1)*CMA[n]-CMA[n])/(n+1) + x[n+1]/(n+1) = + CMA[n]*(1-1/(n+1)) + x[n+1]*1/(n+1) */ + double exponentialAverageFactor, + + /* Used in Training phase only. + runningMean = newMean*factor + runningMean*(1-factor) */ + void *resultRunningMean, + /* Output in training mode, input in inference. Is the moving average + of variance[x] (factor is applied in the same way as for runningMean) */ + void *resultRunningVariance, + + /* Has to be >= CUDNN_BN_MIN_EPSILON. Should be the same in forward and backward functions. */ + double epsilon, + + /* Optionally save intermediate results from the forward pass here + - can be reused to speed up backward pass. NULL if unused */ + void *resultSaveMean, + void *resultSaveInvVariance) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnBatchNormMode_t, const void *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, const void *, const void *, double, void *, void *, double, void *, void *); + static auto func_ptr = LoadSymbol("cudnnBatchNormalizationForwardTraining"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode, alpha, beta, xDesc, x, yDesc, y, bnScaleBiasMeanVarDesc, bnScale, bnBias, exponentialAverageFactor, resultRunningMean, resultRunningVariance, epsilon, resultSaveMean, resultSaveInvVariance); +} + +cudnnStatus_t CUDNNWINAPI +cudnnBatchNormalizationForwardTrainingEx( + cudnnHandle_t handle, + cudnnBatchNormMode_t mode, + cudnnBatchNormOps_t bnOps, + + const void *alpha, /* alpha[0] = result blend factor */ + const void *beta, /* beta[0] = dest layer blend factor */ + + const cudnnTensorDescriptor_t xDesc, + const void *xData, + const cudnnTensorDescriptor_t zDesc, + const void *zData, + const cudnnTensorDescriptor_t yDesc, + void *yData, + + const cudnnTensorDescriptor_t bnScaleBiasMeanVarDesc, + const void *bnScale, + const void *bnBias, + + double exponentialAverageFactor, + void *resultRunningMean, + void *resultRunningVariance, + + /* Has to be >= CUDNN_BN_MIN_EPSILON. Should be the same in forward and backward functions. */ + double epsilon, + + /* Optionally save intermediate results from the forward pass here + - can be reused to speed up backward pass. NULL if unused */ + void *resultSaveMean, + void *resultSaveInvVariance, + + cudnnActivationDescriptor_t activationDesc, + void *workspace, + size_t workSpaceSizeInBytes, + void *reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnBatchNormMode_t, cudnnBatchNormOps_t, const void *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, const void *, const void *, double, void *, void *, double, void *, void *, cudnnActivationDescriptor_t, void *, size_t, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnBatchNormalizationForwardTrainingEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode, bnOps, alpha, beta, xDesc, xData, zDesc, zData, yDesc, yData, bnScaleBiasMeanVarDesc, bnScale, bnBias, exponentialAverageFactor, resultRunningMean, resultRunningVariance, epsilon, resultSaveMean, resultSaveInvVariance, activationDesc, workspace, workSpaceSizeInBytes, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnBatchNormalizationForwardInference(cudnnHandle_t handle, + cudnnBatchNormMode_t mode, + const void *alpha, /* alpha[0] = result blend factor */ + const void *beta, /* beta[0] = dest layer blend factor */ + const cudnnTensorDescriptor_t xDesc, + const void *x, /* NxCxHxW */ + const cudnnTensorDescriptor_t yDesc, + void *y, /* NxCxHxW */ + const cudnnTensorDescriptor_t bnScaleBiasMeanVarDesc, + const void *bnScale, + const void *bnBias, + const void *estimatedMean, + const void *estimatedVariance, + double epsilon) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnBatchNormMode_t, const void *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, const void *, const void *, const void *, const void *, double); + static auto func_ptr = LoadSymbol("cudnnBatchNormalizationForwardInference"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode, alpha, beta, xDesc, x, yDesc, y, bnScaleBiasMeanVarDesc, bnScale, bnBias, estimatedMean, estimatedVariance, epsilon); +} + +cudnnStatus_t CUDNNWINAPI +cudnnBatchNormalizationBackward(cudnnHandle_t handle, + cudnnBatchNormMode_t mode, + const void *alphaDataDiff, + const void *betaDataDiff, + const void *alphaParamDiff, + const void *betaParamDiff, + const cudnnTensorDescriptor_t xDesc, /* same desc for x, dx, dy */ + const void *x, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnTensorDescriptor_t dxDesc, + void *dx, + /* Shared tensor desc for the 4 tensors below */ + const cudnnTensorDescriptor_t dBnScaleBiasDesc, + const void *bnScale, /* bnBias doesn't affect backpropagation */ + /* scale and bias diff are not backpropagated below this layer */ + void *dBnScaleResult, + void *dBnBiasResult, + /* Same epsilon as forward pass */ + double epsilon, + + /* Optionally cached intermediate results from + forward pass */ + const void *savedMean, + const void *savedInvVariance) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnBatchNormMode_t, const void *, const void *, const void *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, const void *, void *, void *, double, const void *, const void *); + static auto func_ptr = LoadSymbol("cudnnBatchNormalizationBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode, alphaDataDiff, betaDataDiff, alphaParamDiff, betaParamDiff, xDesc, x, dyDesc, dy, dxDesc, dx, dBnScaleBiasDesc, bnScale, dBnScaleResult, dBnBiasResult, epsilon, savedMean, savedInvVariance); +} + +cudnnStatus_t CUDNNWINAPI +cudnnBatchNormalizationBackwardEx(cudnnHandle_t handle, + cudnnBatchNormMode_t mode, + cudnnBatchNormOps_t bnOps, + + const void *alphaDataDiff, + const void *betaDataDiff, + const void *alphaParamDiff, + const void *betaParamDiff, + const cudnnTensorDescriptor_t xDesc, + const void *xData, + const cudnnTensorDescriptor_t yDesc, + const void *yData, + const cudnnTensorDescriptor_t dyDesc, + const void *dyData, + const cudnnTensorDescriptor_t dzDesc, + void *dzData, + const cudnnTensorDescriptor_t dxDesc, + void *dxData, + + /* Shared tensor desc for the 4 tensors below */ + const cudnnTensorDescriptor_t dBnScaleBiasDesc, + const void *bnScaleData, + const void *bnBiasData, /* needed if there is activation */ + void *dBnScaleData, + void *dBnBiasData, + double epsilon, /* Same epsilon as forward pass */ + + /* Optionally cached intermediate results from + forward pass */ + const void *savedMean, + const void *savedInvVariance, + cudnnActivationDescriptor_t activationDesc, + void *workSpace, + size_t workSpaceSizeInBytes, + void *reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnBatchNormMode_t, cudnnBatchNormOps_t, const void *, const void *, const void *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, const void *, const void *, void *, void *, double, const void *, const void *, cudnnActivationDescriptor_t, void *, size_t, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnBatchNormalizationBackwardEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, mode, bnOps, alphaDataDiff, betaDataDiff, alphaParamDiff, betaParamDiff, xDesc, xData, yDesc, yData, dyDesc, dyData, dzDesc, dzData, dxDesc, dxData, dBnScaleBiasDesc, bnScaleData, bnBiasData, dBnScaleData, dBnBiasData, epsilon, savedMean, savedInvVariance, activationDesc, workSpace, workSpaceSizeInBytes, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreateSpatialTransformerDescriptor(cudnnSpatialTransformerDescriptor_t *stDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnSpatialTransformerDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateSpatialTransformerDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(stDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetSpatialTransformerNdDescriptor(cudnnSpatialTransformerDescriptor_t stDesc, + cudnnSamplerType_t samplerType, + cudnnDataType_t dataType, + const int nbDims, + const int dimA[]) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnSpatialTransformerDescriptor_t, cudnnSamplerType_t, cudnnDataType_t, const int, const int []); + static auto func_ptr = LoadSymbol("cudnnSetSpatialTransformerNdDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(stDesc, samplerType, dataType, nbDims, dimA); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroySpatialTransformerDescriptor(cudnnSpatialTransformerDescriptor_t stDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnSpatialTransformerDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroySpatialTransformerDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(stDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSpatialTfGridGeneratorForward(cudnnHandle_t handle, + const cudnnSpatialTransformerDescriptor_t stDesc, + const void *theta, + void *grid) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnSpatialTransformerDescriptor_t, const void *, void *); + static auto func_ptr = LoadSymbol("cudnnSpatialTfGridGeneratorForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, stDesc, theta, grid); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSpatialTfGridGeneratorBackward(cudnnHandle_t handle, + const cudnnSpatialTransformerDescriptor_t stDesc, + const void *dgrid, + void *dtheta) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnSpatialTransformerDescriptor_t, const void *, void *); + static auto func_ptr = LoadSymbol("cudnnSpatialTfGridGeneratorBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, stDesc, dgrid, dtheta); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSpatialTfSamplerForward(cudnnHandle_t handle, + cudnnSpatialTransformerDescriptor_t stDesc, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *grid, + const void *beta, + cudnnTensorDescriptor_t yDesc, + void *y) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnSpatialTransformerDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const void *, cudnnTensorDescriptor_t, void *); + static auto func_ptr = LoadSymbol("cudnnSpatialTfSamplerForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, stDesc, alpha, xDesc, x, grid, beta, yDesc, y); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSpatialTfSamplerBackward(cudnnHandle_t handle, + cudnnSpatialTransformerDescriptor_t stDesc, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx, + const void *alphaDgrid, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const void *grid, + const void *betaDgrid, + void *dgrid) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnSpatialTransformerDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const cudnnTensorDescriptor_t, void *, const void *, const cudnnTensorDescriptor_t, const void *, const void *, const void *, void *); + static auto func_ptr = LoadSymbol("cudnnSpatialTfSamplerBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, stDesc, alpha, xDesc, x, beta, dxDesc, dx, alphaDgrid, dyDesc, dy, grid, betaDgrid, dgrid); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreateDropoutDescriptor(cudnnDropoutDescriptor_t *dropoutDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnDropoutDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateDropoutDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dropoutDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroyDropoutDescriptor(cudnnDropoutDescriptor_t dropoutDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnDropoutDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyDropoutDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dropoutDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDropoutGetStatesSize(cudnnHandle_t handle, size_t *sizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnDropoutGetStatesSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDropoutGetReserveSpaceSize(cudnnTensorDescriptor_t xdesc, size_t *sizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnTensorDescriptor_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnDropoutGetReserveSpaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(xdesc, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetDropoutDescriptor(cudnnDropoutDescriptor_t dropoutDesc, + cudnnHandle_t handle, + float dropout, + void *states, + size_t stateSizeInBytes, + unsigned long long seed) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnDropoutDescriptor_t, cudnnHandle_t, float, void *, size_t, unsigned long long); + static auto func_ptr = LoadSymbol("cudnnSetDropoutDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dropoutDesc, handle, dropout, states, stateSizeInBytes, seed); +} + +cudnnStatus_t CUDNNWINAPI +cudnnRestoreDropoutDescriptor(cudnnDropoutDescriptor_t dropoutDesc, + cudnnHandle_t handle, + float dropout, + void *states, + size_t stateSizeInBytes, + unsigned long long seed) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnDropoutDescriptor_t, cudnnHandle_t, float, void *, size_t, unsigned long long); + static auto func_ptr = LoadSymbol("cudnnRestoreDropoutDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dropoutDesc, handle, dropout, states, stateSizeInBytes, seed); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetDropoutDescriptor(cudnnDropoutDescriptor_t dropoutDesc, + cudnnHandle_t handle, + float *dropout, + void **states, + unsigned long long *seed) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnDropoutDescriptor_t, cudnnHandle_t, float *, void **, unsigned long long *); + static auto func_ptr = LoadSymbol("cudnnGetDropoutDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(dropoutDesc, handle, dropout, states, seed); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDropoutForward(cudnnHandle_t handle, + const cudnnDropoutDescriptor_t dropoutDesc, + const cudnnTensorDescriptor_t xdesc, + const void *x, + const cudnnTensorDescriptor_t ydesc, + void *y, + void *reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnDropoutDescriptor_t, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, void *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnDropoutForward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, dropoutDesc, xdesc, x, ydesc, y, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDropoutBackward(cudnnHandle_t handle, + const cudnnDropoutDescriptor_t dropoutDesc, + const cudnnTensorDescriptor_t dydesc, + const void *dy, + const cudnnTensorDescriptor_t dxdesc, + void *dx, + void *reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnDropoutDescriptor_t, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, void *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnDropoutBackward"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, dropoutDesc, dydesc, dy, dxdesc, dx, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreateRNNDescriptor(cudnnRNNDescriptor_t *rnnDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateRNNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroyRNNDescriptor(cudnnRNNDescriptor_t rnnDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyRNNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetRNNForwardInferenceAlgorithmMaxCount(cudnnHandle_t handle, const cudnnRNNDescriptor_t rnnDesc, int *count) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, int *); + static auto func_ptr = LoadSymbol("cudnnGetRNNForwardInferenceAlgorithmMaxCount"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, count); +} + +cudnnStatus_t CUDNNWINAPI +cudnnFindRNNForwardInferenceAlgorithmEx(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t *xDesc, + const void *x, + const cudnnTensorDescriptor_t hxDesc, + const void *hx, + const cudnnTensorDescriptor_t cxDesc, + const void *cx, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnTensorDescriptor_t *yDesc, + void *y, + const cudnnTensorDescriptor_t hyDesc, + void *hy, + const cudnnTensorDescriptor_t cyDesc, + void *cy, + const float findIntensity, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnAlgorithmPerformance_t *perfResults, + void *workspace, + size_t workSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t *, void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, const float, const int, int *, cudnnAlgorithmPerformance_t *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnFindRNNForwardInferenceAlgorithmEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, x, hxDesc, hx, cxDesc, cx, wDesc, w, yDesc, y, hyDesc, hy, cyDesc, cy, findIntensity, requestedAlgoCount, returnedAlgoCount, perfResults, workspace, workSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetRNNForwardTrainingAlgorithmMaxCount(cudnnHandle_t handle, const cudnnRNNDescriptor_t rnnDesc, int *count) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, int *); + static auto func_ptr = LoadSymbol("cudnnGetRNNForwardTrainingAlgorithmMaxCount"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, count); +} + +cudnnStatus_t CUDNNWINAPI +cudnnFindRNNForwardTrainingAlgorithmEx(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t *xDesc, + const void *x, + const cudnnTensorDescriptor_t hxDesc, + const void *hx, + const cudnnTensorDescriptor_t cxDesc, + const void *cx, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnTensorDescriptor_t *yDesc, + void *y, + const cudnnTensorDescriptor_t hyDesc, + void *hy, + const cudnnTensorDescriptor_t cyDesc, + void *cy, + const float findIntensity, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnAlgorithmPerformance_t *perfResults, + void *workspace, + size_t workSpaceSizeInBytes, + void *reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t *, void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, const float, const int, int *, cudnnAlgorithmPerformance_t *, void *, size_t, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnFindRNNForwardTrainingAlgorithmEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, x, hxDesc, hx, cxDesc, cx, wDesc, w, yDesc, y, hyDesc, hy, cyDesc, cy, findIntensity, requestedAlgoCount, returnedAlgoCount, perfResults, workspace, workSpaceSizeInBytes, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetRNNBackwardDataAlgorithmMaxCount(cudnnHandle_t handle, const cudnnRNNDescriptor_t rnnDesc, int *count) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, int *); + static auto func_ptr = LoadSymbol("cudnnGetRNNBackwardDataAlgorithmMaxCount"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, count); +} + +cudnnStatus_t CUDNNWINAPI +cudnnFindRNNBackwardDataAlgorithmEx(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t *yDesc, + const void *y, + const cudnnTensorDescriptor_t *dyDesc, + const void *dy, + const cudnnTensorDescriptor_t dhyDesc, + const void *dhy, + const cudnnTensorDescriptor_t dcyDesc, + const void *dcy, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnTensorDescriptor_t hxDesc, + const void *hx, + const cudnnTensorDescriptor_t cxDesc, + const void *cx, + const cudnnTensorDescriptor_t *dxDesc, + void *dx, + const cudnnTensorDescriptor_t dhxDesc, + void *dhx, + const cudnnTensorDescriptor_t dcxDesc, + void *dcx, + const float findIntensity, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnAlgorithmPerformance_t *perfResults, + void *workspace, + size_t workSpaceSizeInBytes, + void *reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t *, void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, const float, const int, int *, cudnnAlgorithmPerformance_t *, void *, size_t, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnFindRNNBackwardDataAlgorithmEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, yDesc, y, dyDesc, dy, dhyDesc, dhy, dcyDesc, dcy, wDesc, w, hxDesc, hx, cxDesc, cx, dxDesc, dx, dhxDesc, dhx, dcxDesc, dcx, findIntensity, requestedAlgoCount, returnedAlgoCount, perfResults, workspace, workSpaceSizeInBytes, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetRNNBackwardWeightsAlgorithmMaxCount(cudnnHandle_t handle, const cudnnRNNDescriptor_t rnnDesc, int *count) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, int *); + static auto func_ptr = LoadSymbol("cudnnGetRNNBackwardWeightsAlgorithmMaxCount"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, count); +} + +cudnnStatus_t CUDNNWINAPI +cudnnFindRNNBackwardWeightsAlgorithmEx(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t *xDesc, + const void *x, + const cudnnTensorDescriptor_t hxDesc, + const void *hx, + const cudnnTensorDescriptor_t *yDesc, + const void *y, + const float findIntensity, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnAlgorithmPerformance_t *perfResults, + const void *workspace, + size_t workSpaceSizeInBytes, + const cudnnFilterDescriptor_t dwDesc, + void *dw, + const void *reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t *, const void *, const float, const int, int *, cudnnAlgorithmPerformance_t *, const void *, size_t, const cudnnFilterDescriptor_t, void *, const void *, size_t); + static auto func_ptr = LoadSymbol("cudnnFindRNNBackwardWeightsAlgorithmEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, x, hxDesc, hx, yDesc, y, findIntensity, requestedAlgoCount, returnedAlgoCount, perfResults, workspace, workSpaceSizeInBytes, dwDesc, dw, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreatePersistentRNNPlan(cudnnRNNDescriptor_t rnnDesc, + const int minibatch, + const cudnnDataType_t dataType, + cudnnPersistentRNNPlan_t *plan) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t, const int, const cudnnDataType_t, cudnnPersistentRNNPlan_t *); + static auto func_ptr = LoadSymbol("cudnnCreatePersistentRNNPlan"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc, minibatch, dataType, plan); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetPersistentRNNPlan(cudnnRNNDescriptor_t rnnDesc, cudnnPersistentRNNPlan_t plan) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t, cudnnPersistentRNNPlan_t); + static auto func_ptr = LoadSymbol("cudnnSetPersistentRNNPlan"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc, plan); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroyPersistentRNNPlan(cudnnPersistentRNNPlan_t plan) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnPersistentRNNPlan_t); + static auto func_ptr = LoadSymbol("cudnnDestroyPersistentRNNPlan"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(plan); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetRNNDescriptor(cudnnHandle_t handle, + cudnnRNNDescriptor_t rnnDesc, + const int hiddenSize, + const int numLayers, + cudnnDropoutDescriptor_t dropoutDesc, /* Between layers, not between recurrent steps. */ + cudnnRNNInputMode_t inputMode, + cudnnDirectionMode_t direction, + cudnnRNNMode_t mode, + cudnnRNNAlgo_t algo, + cudnnDataType_t dataType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnRNNDescriptor_t, const int, const int, cudnnDropoutDescriptor_t, cudnnRNNInputMode_t, cudnnDirectionMode_t, cudnnRNNMode_t, cudnnRNNAlgo_t, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnSetRNNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, hiddenSize, numLayers, dropoutDesc, inputMode, direction, mode, algo, dataType); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetRNNProjectionLayers(cudnnHandle_t handle, + cudnnRNNDescriptor_t rnnDesc, + const int recProjSize, + const int outProjSize) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnRNNDescriptor_t, const int, const int); + static auto func_ptr = LoadSymbol("cudnnSetRNNProjectionLayers"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, recProjSize, outProjSize); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetRNNProjectionLayers(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + int *recProjSize, + int *outProjSize) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, int *, int *); + static auto func_ptr = LoadSymbol("cudnnGetRNNProjectionLayers"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, recProjSize, outProjSize); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetRNNAlgorithmDescriptor(cudnnHandle_t handle, cudnnRNNDescriptor_t rnnDesc, cudnnAlgorithmDescriptor_t algoDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnRNNDescriptor_t, cudnnAlgorithmDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnSetRNNAlgorithmDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, algoDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetRNNDescriptor(cudnnHandle_t handle, + cudnnRNNDescriptor_t rnnDesc, + int *hiddenSize, + int *numLayers, + cudnnDropoutDescriptor_t *dropoutDesc, + cudnnRNNInputMode_t *inputMode, + cudnnDirectionMode_t *direction, + cudnnRNNMode_t *mode, + cudnnRNNAlgo_t *algo, + cudnnDataType_t *dataType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnRNNDescriptor_t, int *, int *, cudnnDropoutDescriptor_t *, cudnnRNNInputMode_t *, cudnnDirectionMode_t *, cudnnRNNMode_t *, cudnnRNNAlgo_t *, cudnnDataType_t *); + static auto func_ptr = LoadSymbol("cudnnGetRNNDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, hiddenSize, numLayers, dropoutDesc, inputMode, direction, mode, algo, dataType); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetRNNMatrixMathType(cudnnRNNDescriptor_t rnnDesc, cudnnMathType_t mType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t, cudnnMathType_t); + static auto func_ptr = LoadSymbol("cudnnSetRNNMatrixMathType"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc, mType); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetRNNMatrixMathType(cudnnRNNDescriptor_t rnnDesc, cudnnMathType_t *mType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t, cudnnMathType_t *); + static auto func_ptr = LoadSymbol("cudnnGetRNNMatrixMathType"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc, mType); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetRNNWorkspaceSize(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t *xDesc, + size_t *sizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetRNNWorkspaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetRNNTrainingReserveSize(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t *xDesc, + size_t *sizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetRNNTrainingReserveSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetRNNParamsSize(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const cudnnTensorDescriptor_t xDesc, + size_t *sizeInBytes, + cudnnDataType_t dataType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const cudnnTensorDescriptor_t, size_t *, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnGetRNNParamsSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, xDesc, sizeInBytes, dataType); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetRNNLinLayerMatrixParams(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int pseudoLayer, + const cudnnTensorDescriptor_t xDesc, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const int linLayerID, + cudnnFilterDescriptor_t linLayerMatDesc, + void **linLayerMat) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, const void *, const int, cudnnFilterDescriptor_t, void **); + static auto func_ptr = LoadSymbol("cudnnGetRNNLinLayerMatrixParams"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, pseudoLayer, xDesc, wDesc, w, linLayerID, linLayerMatDesc, linLayerMat); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetRNNLinLayerBiasParams(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int pseudoLayer, + const cudnnTensorDescriptor_t xDesc, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const int linLayerID, + cudnnFilterDescriptor_t linLayerBiasDesc, + void **linLayerBias) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t, const cudnnFilterDescriptor_t, const void *, const int, cudnnFilterDescriptor_t, void **); + static auto func_ptr = LoadSymbol("cudnnGetRNNLinLayerBiasParams"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, pseudoLayer, xDesc, wDesc, w, linLayerID, linLayerBiasDesc, linLayerBias); +} + +cudnnStatus_t CUDNNWINAPI +cudnnRNNForwardInference(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t *xDesc, + const void *x, + const cudnnTensorDescriptor_t hxDesc, + const void *hx, + const cudnnTensorDescriptor_t cxDesc, + const void *cx, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnTensorDescriptor_t *yDesc, + void *y, + const cudnnTensorDescriptor_t hyDesc, + void *hy, + const cudnnTensorDescriptor_t cyDesc, + void *cy, + void *workspace, + size_t workSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t *, void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnRNNForwardInference"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, x, hxDesc, hx, cxDesc, cx, wDesc, w, yDesc, y, hyDesc, hy, cyDesc, cy, workspace, workSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnRNNForwardTraining(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t *xDesc, + const void *x, + const cudnnTensorDescriptor_t hxDesc, + const void *hx, + const cudnnTensorDescriptor_t cxDesc, + const void *cx, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnTensorDescriptor_t *yDesc, + void *y, + const cudnnTensorDescriptor_t hyDesc, + void *hy, + const cudnnTensorDescriptor_t cyDesc, + void *cy, + void *workspace, + size_t workSpaceSizeInBytes, + void *reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t *, void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, void *, size_t, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnRNNForwardTraining"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, x, hxDesc, hx, cxDesc, cx, wDesc, w, yDesc, y, hyDesc, hy, cyDesc, cy, workspace, workSpaceSizeInBytes, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnRNNBackwardData(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t *yDesc, + const void *y, + const cudnnTensorDescriptor_t *dyDesc, + const void *dy, + const cudnnTensorDescriptor_t dhyDesc, + const void *dhy, + const cudnnTensorDescriptor_t dcyDesc, + const void *dcy, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnTensorDescriptor_t hxDesc, + const void *hx, + const cudnnTensorDescriptor_t cxDesc, + const void *cx, + const cudnnTensorDescriptor_t *dxDesc, + void *dx, + const cudnnTensorDescriptor_t dhxDesc, + void *dhx, + const cudnnTensorDescriptor_t dcxDesc, + void *dcx, + void *workspace, + size_t workSpaceSizeInBytes, + void *reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t *, void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, void *, size_t, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnRNNBackwardData"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, yDesc, y, dyDesc, dy, dhyDesc, dhy, dcyDesc, dcy, wDesc, w, hxDesc, hx, cxDesc, cx, dxDesc, dx, dhxDesc, dhx, dcxDesc, dcx, workspace, workSpaceSizeInBytes, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnRNNBackwardWeights(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const int seqLength, + const cudnnTensorDescriptor_t *xDesc, + const void *x, + const cudnnTensorDescriptor_t hxDesc, + const void *hx, + const cudnnTensorDescriptor_t *yDesc, + const void *y, + const void *workspace, + size_t workSpaceSizeInBytes, + const cudnnFilterDescriptor_t dwDesc, + void *dw, + const void *reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const int, const cudnnTensorDescriptor_t *, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t *, const void *, const void *, size_t, const cudnnFilterDescriptor_t, void *, const void *, size_t); + static auto func_ptr = LoadSymbol("cudnnRNNBackwardWeights"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, seqLength, xDesc, x, hxDesc, hx, yDesc, y, workspace, workSpaceSizeInBytes, dwDesc, dw, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreateCTCLossDescriptor(cudnnCTCLossDescriptor_t *ctcLossDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnCTCLossDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateCTCLossDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(ctcLossDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetCTCLossDescriptor(cudnnCTCLossDescriptor_t ctcLossDesc, cudnnDataType_t compType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnCTCLossDescriptor_t, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnSetCTCLossDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(ctcLossDesc, compType); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetCTCLossDescriptor(cudnnCTCLossDescriptor_t ctcLossDesc, cudnnDataType_t *compType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnCTCLossDescriptor_t, cudnnDataType_t *); + static auto func_ptr = LoadSymbol("cudnnGetCTCLossDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(ctcLossDesc, compType); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroyCTCLossDescriptor(cudnnCTCLossDescriptor_t ctcLossDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnCTCLossDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyCTCLossDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(ctcLossDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCTCLoss( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t + probsDesc, /* Tensor descriptor for probabilities, the dimensions are T,N,A (T is the timing steps, N is the + mini batch size, A is the alphabet size) */ + const void *probs, /* probabilities after softmax, in GPU memory */ + const int *labels, /* labels, in CPU memory */ + const int *labelLengths, /* the length of each label, in CPU memory */ + const int *inputLengths, /* the lengths of timing steps in each batch, in CPU memory */ + void *costs, /* the returned costs of CTC, in GPU memory */ + const cudnnTensorDescriptor_t gradientsDesc, /* Tensor descriptor for gradients, the dimensions are T,N,A */ + const void *gradients, /* the returned CTC gradients, in GPU memory, to compute costs only, set it to NULL */ + cudnnCTCLossAlgo_t algo, /* algorithm selected, supported now 0 and 1 */ + cudnnCTCLossDescriptor_t ctcLossDesc, + void *workspace, /* pointer to the workspace, in GPU memory */ + size_t workSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const void *, const int *, const int *, const int *, void *, const cudnnTensorDescriptor_t, const void *, cudnnCTCLossAlgo_t, cudnnCTCLossDescriptor_t, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnCTCLoss"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, probsDesc, probs, labels, labelLengths, inputLengths, costs, gradientsDesc, gradients, algo, ctcLossDesc, workspace, workSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetCTCLossWorkspaceSize( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t probsDesc, /* Tensor descriptor for probabilities, the dimensions are T,N,A (T is the + timing steps, N is the mini batch size, A is the alphabet size) */ + const cudnnTensorDescriptor_t gradientsDesc, /* Tensor descriptor for gradients, the + dimensions are T,N,A. To compute costs + only, set it to NULL */ + const int *labels, /* labels, in CPU memory */ + const int *labelLengths, /* the length of each label, in CPU memory */ + const int *inputLengths, /* the lengths of timing steps in each batch, in CPU memory */ + cudnnCTCLossAlgo_t algo, /* algorithm selected, supported now 0 and 1 */ + cudnnCTCLossDescriptor_t ctcLossDesc, + size_t *sizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnTensorDescriptor_t, const cudnnTensorDescriptor_t, const int *, const int *, const int *, cudnnCTCLossAlgo_t, cudnnCTCLossDescriptor_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetCTCLossWorkspaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, probsDesc, gradientsDesc, labels, labelLengths, inputLengths, algo, ctcLossDesc, sizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreateAlgorithmDescriptor(cudnnAlgorithmDescriptor_t *algoDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnAlgorithmDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateAlgorithmDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(algoDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetAlgorithmDescriptor(cudnnAlgorithmDescriptor_t algoDesc, cudnnAlgorithm_t algorithm) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnAlgorithmDescriptor_t, cudnnAlgorithm_t); + static auto func_ptr = LoadSymbol("cudnnSetAlgorithmDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(algoDesc, algorithm); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetAlgorithmDescriptor(const cudnnAlgorithmDescriptor_t algoDesc, cudnnAlgorithm_t *algorithm) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnAlgorithmDescriptor_t, cudnnAlgorithm_t *); + static auto func_ptr = LoadSymbol("cudnnGetAlgorithmDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(algoDesc, algorithm); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCopyAlgorithmDescriptor(const cudnnAlgorithmDescriptor_t src, cudnnAlgorithmDescriptor_t dest) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnAlgorithmDescriptor_t, cudnnAlgorithmDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnCopyAlgorithmDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(src, dest); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroyAlgorithmDescriptor(cudnnAlgorithmDescriptor_t algoDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnAlgorithmDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyAlgorithmDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(algoDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreateAlgorithmPerformance(cudnnAlgorithmPerformance_t *algoPerf, int numberToCreate) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnAlgorithmPerformance_t *, int); + static auto func_ptr = LoadSymbol("cudnnCreateAlgorithmPerformance"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(algoPerf, numberToCreate); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetAlgorithmPerformance(cudnnAlgorithmPerformance_t algoPerf, + cudnnAlgorithmDescriptor_t algoDesc, + cudnnStatus_t status, + float time, + size_t memory) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnAlgorithmPerformance_t, cudnnAlgorithmDescriptor_t, cudnnStatus_t, float, size_t); + static auto func_ptr = LoadSymbol("cudnnSetAlgorithmPerformance"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(algoPerf, algoDesc, status, time, memory); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetAlgorithmPerformance(const cudnnAlgorithmPerformance_t algoPerf, + cudnnAlgorithmDescriptor_t *algoDesc, + cudnnStatus_t *status, + float *time, + size_t *memory) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(const cudnnAlgorithmPerformance_t, cudnnAlgorithmDescriptor_t *, cudnnStatus_t *, float *, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetAlgorithmPerformance"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(algoPerf, algoDesc, status, time, memory); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroyAlgorithmPerformance(cudnnAlgorithmPerformance_t *algoPerf, int numberToDestroy) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnAlgorithmPerformance_t *, int); + static auto func_ptr = LoadSymbol("cudnnDestroyAlgorithmPerformance"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(algoPerf, numberToDestroy); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetAlgorithmSpaceSize(cudnnHandle_t handle, cudnnAlgorithmDescriptor_t algoDesc, size_t *algoSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnAlgorithmDescriptor_t, size_t *); + static auto func_ptr = LoadSymbol("cudnnGetAlgorithmSpaceSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, algoDesc, algoSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSaveAlgorithm(cudnnHandle_t handle, + cudnnAlgorithmDescriptor_t algoDesc, + void *algoSpace, + size_t algoSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnAlgorithmDescriptor_t, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnSaveAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, algoDesc, algoSpace, algoSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnRestoreAlgorithm(cudnnHandle_t handle, + void *algoSpace, + size_t algoSpaceSizeInBytes, + cudnnAlgorithmDescriptor_t algoDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, void *, size_t, cudnnAlgorithmDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnRestoreAlgorithm"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, algoSpace, algoSpaceSizeInBytes, algoDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnRNNSetClip(cudnnHandle_t handle, + cudnnRNNDescriptor_t rnnDesc, + cudnnRNNClipMode_t clipMode, + cudnnNanPropagation_t clipNanOpt, + double lclip, + double rclip) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnRNNDescriptor_t, cudnnRNNClipMode_t, cudnnNanPropagation_t, double, double); + static auto func_ptr = LoadSymbol("cudnnRNNSetClip"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, clipMode, clipNanOpt, lclip, rclip); +} + +cudnnStatus_t CUDNNWINAPI +cudnnRNNGetClip(cudnnHandle_t handle, + cudnnRNNDescriptor_t rnnDesc, + cudnnRNNClipMode_t *clipMode, + cudnnNanPropagation_t *clipNanOpt, + double *lclip, + double *rclip) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnRNNDescriptor_t, cudnnRNNClipMode_t *, cudnnNanPropagation_t *, double *, double *); + static auto func_ptr = LoadSymbol("cudnnRNNGetClip"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, clipMode, clipNanOpt, lclip, rclip); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetCallback(unsigned mask, void *udata, cudnnCallback_t fptr) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(unsigned int, void *, cudnnCallback_t); + static auto func_ptr = LoadSymbol("cudnnSetCallback"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(mask, udata, fptr); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetCallback(unsigned *mask, void **udata, cudnnCallback_t *fptr) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(unsigned int *, void **, cudnnCallback_t *); + static auto func_ptr = LoadSymbol("cudnnGetCallback"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(mask, udata, fptr); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetRNNPaddingMode(cudnnRNNDescriptor_t rnnDesc, cudnnRNNPaddingMode_t paddingMode) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t, cudnnRNNPaddingMode_t); + static auto func_ptr = LoadSymbol("cudnnSetRNNPaddingMode"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc, paddingMode); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetRNNPaddingMode(cudnnRNNDescriptor_t rnnDesc, cudnnRNNPaddingMode_t *paddingMode) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t, cudnnRNNPaddingMode_t *); + static auto func_ptr = LoadSymbol("cudnnGetRNNPaddingMode"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc, paddingMode); +} + +cudnnStatus_t CUDNNWINAPI +cudnnCreateRNNDataDescriptor(cudnnRNNDataDescriptor_t *RNNDataDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDataDescriptor_t *); + static auto func_ptr = LoadSymbol("cudnnCreateRNNDataDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(RNNDataDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnDestroyRNNDataDescriptor(cudnnRNNDataDescriptor_t RNNDataDesc) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDataDescriptor_t); + static auto func_ptr = LoadSymbol("cudnnDestroyRNNDataDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(RNNDataDesc); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetRNNDataDescriptor(cudnnRNNDataDescriptor_t RNNDataDesc, + cudnnDataType_t dataType, + cudnnRNNDataLayout_t layout, + int maxSeqLength, + int batchSize, + int vectorSize, + const int seqLengthArray[], /* length of each sequence in the batch */ + void *paddingFill) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDataDescriptor_t, cudnnDataType_t, cudnnRNNDataLayout_t, int, int, int, const int [], void *); + static auto func_ptr = LoadSymbol("cudnnSetRNNDataDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(RNNDataDesc, dataType, layout, maxSeqLength, batchSize, vectorSize, seqLengthArray, paddingFill); +} + +cudnnStatus_t CUDNNWINAPI +cudnnGetRNNDataDescriptor(cudnnRNNDataDescriptor_t RNNDataDesc, + cudnnDataType_t *dataType, + cudnnRNNDataLayout_t *layout, + int *maxSeqLength, + int *batchSize, + int *vectorSize, + int arrayLengthRequested, + int seqLengthArray[], + void *paddingFill) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDataDescriptor_t, cudnnDataType_t *, cudnnRNNDataLayout_t *, int *, int *, int *, int, int [], void *); + static auto func_ptr = LoadSymbol("cudnnGetRNNDataDescriptor"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(RNNDataDesc, dataType, layout, maxSeqLength, batchSize, vectorSize, arrayLengthRequested, seqLengthArray, paddingFill); +} + +cudnnStatus_t CUDNNWINAPI +cudnnRNNForwardTrainingEx(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const cudnnRNNDataDescriptor_t xDesc, + const void *x, + const cudnnTensorDescriptor_t hxDesc, + const void *hx, + const cudnnTensorDescriptor_t cxDesc, + const void *cx, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnRNNDataDescriptor_t yDesc, + void *y, + const cudnnTensorDescriptor_t hyDesc, + void *hy, + const cudnnTensorDescriptor_t cyDesc, + void *cy, + const cudnnRNNDataDescriptor_t kDesc, /* reserved, should pass NULL */ + const void *keys, /* reserved, should pass NULL */ + const cudnnRNNDataDescriptor_t cDesc, /* reserved, should pass NULL */ + void *cAttn, /* reserved, should pass NULL */ + const cudnnRNNDataDescriptor_t iDesc, /* reserved, should pass NULL */ + void *iAttn, /* reserved, should pass NULL */ + const cudnnRNNDataDescriptor_t qDesc, /* reserved, should pass NULL */ + void *queries, /* reserved, should pass NULL */ + void *workSpace, + size_t workSpaceSizeInBytes, + void *reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const cudnnRNNDataDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnRNNDataDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, const cudnnRNNDataDescriptor_t, const void *, const cudnnRNNDataDescriptor_t, void *, const cudnnRNNDataDescriptor_t, void *, const cudnnRNNDataDescriptor_t, void *, void *, size_t, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnRNNForwardTrainingEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, xDesc, x, hxDesc, hx, cxDesc, cx, wDesc, w, yDesc, y, hyDesc, hy, cyDesc, cy, kDesc, keys, cDesc, cAttn, iDesc, iAttn, qDesc, queries, workSpace, workSpaceSizeInBytes, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnRNNForwardInferenceEx(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const cudnnRNNDataDescriptor_t xDesc, + const void *x, + const cudnnTensorDescriptor_t hxDesc, + const void *hx, + const cudnnTensorDescriptor_t cxDesc, + const void *cx, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnRNNDataDescriptor_t yDesc, + void *y, + const cudnnTensorDescriptor_t hyDesc, + void *hy, + const cudnnTensorDescriptor_t cyDesc, + void *cy, + const cudnnRNNDataDescriptor_t kDesc, /* reserved, should pass NULL */ + const void *keys, /* reserved, should pass NULL */ + const cudnnRNNDataDescriptor_t cDesc, /* reserved, should pass NULL */ + void *cAttn, /* reserved, should pass NULL */ + const cudnnRNNDataDescriptor_t iDesc, /* reserved, should pass NULL */ + void *iAttn, /* reserved, should pass NULL */ + const cudnnRNNDataDescriptor_t qDesc, /* reserved, should pass NULL */ + void *queries, /* reserved, should pass NULL */ + void *workSpace, + size_t workSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const cudnnRNNDataDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnRNNDataDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, const cudnnRNNDataDescriptor_t, const void *, const cudnnRNNDataDescriptor_t, void *, const cudnnRNNDataDescriptor_t, void *, const cudnnRNNDataDescriptor_t, void *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnRNNForwardInferenceEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, xDesc, x, hxDesc, hx, cxDesc, cx, wDesc, w, yDesc, y, hyDesc, hy, cyDesc, cy, kDesc, keys, cDesc, cAttn, iDesc, iAttn, qDesc, queries, workSpace, workSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnRNNBackwardDataEx(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const cudnnRNNDataDescriptor_t yDesc, + const void *y, + const cudnnRNNDataDescriptor_t dyDesc, + const void *dy, + const cudnnRNNDataDescriptor_t dcDesc, /* reserved, should pass NULL */ + const void *dcAttn, /* reserved, should pass NULL */ + const cudnnTensorDescriptor_t dhyDesc, + const void *dhy, + const cudnnTensorDescriptor_t dcyDesc, + const void *dcy, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnTensorDescriptor_t hxDesc, + const void *hx, + const cudnnTensorDescriptor_t cxDesc, + const void *cx, + const cudnnRNNDataDescriptor_t dxDesc, + void *dx, + const cudnnTensorDescriptor_t dhxDesc, + void *dhx, + const cudnnTensorDescriptor_t dcxDesc, + void *dcx, + const cudnnRNNDataDescriptor_t dkDesc, /* reserved, should pass NULL */ + void *dkeys, /* reserved, should pass NULL */ + void *workSpace, + size_t workSpaceSizeInBytes, + void *reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const cudnnRNNDataDescriptor_t, const void *, const cudnnRNNDataDescriptor_t, const void *, const cudnnRNNDataDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnFilterDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnRNNDataDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, const cudnnTensorDescriptor_t, void *, const cudnnRNNDataDescriptor_t, void *, void *, size_t, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnRNNBackwardDataEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, yDesc, y, dyDesc, dy, dcDesc, dcAttn, dhyDesc, dhy, dcyDesc, dcy, wDesc, w, hxDesc, hx, cxDesc, cx, dxDesc, dx, dhxDesc, dhx, dcxDesc, dcx, dkDesc, dkeys, workSpace, workSpaceSizeInBytes, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnRNNBackwardWeightsEx(cudnnHandle_t handle, + const cudnnRNNDescriptor_t rnnDesc, + const cudnnRNNDataDescriptor_t xDesc, + const void *x, + const cudnnTensorDescriptor_t hxDesc, + const void *hx, + const cudnnRNNDataDescriptor_t yDesc, + const void *y, + void *workSpace, + size_t workSpaceSizeInBytes, + const cudnnFilterDescriptor_t dwDesc, + void *dw, + void *reserveSpace, + size_t reserveSpaceSizeInBytes) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, const cudnnRNNDescriptor_t, const cudnnRNNDataDescriptor_t, const void *, const cudnnTensorDescriptor_t, const void *, const cudnnRNNDataDescriptor_t, const void *, void *, size_t, const cudnnFilterDescriptor_t, void *, void *, size_t); + static auto func_ptr = LoadSymbol("cudnnRNNBackwardWeightsEx"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, xDesc, x, hxDesc, hx, yDesc, y, workSpace, workSpaceSizeInBytes, dwDesc, dw, reserveSpace, reserveSpaceSizeInBytes); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetRNNDescriptor_v6(cudnnHandle_t handle, + cudnnRNNDescriptor_t rnnDesc, + const int hiddenSize, + const int numLayers, + cudnnDropoutDescriptor_t dropoutDesc, + cudnnRNNInputMode_t inputMode, + cudnnDirectionMode_t direction, + cudnnRNNMode_t mode, + cudnnRNNAlgo_t algo, + cudnnDataType_t dataType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnHandle_t, cudnnRNNDescriptor_t, const int, const int, cudnnDropoutDescriptor_t, cudnnRNNInputMode_t, cudnnDirectionMode_t, cudnnRNNMode_t, cudnnRNNAlgo_t, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnSetRNNDescriptor_v6"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rnnDesc, hiddenSize, numLayers, dropoutDesc, inputMode, direction, mode, algo, dataType); +} + +cudnnStatus_t CUDNNWINAPI +cudnnSetRNNDescriptor_v5(cudnnRNNDescriptor_t rnnDesc, + int hiddenSize, + int numLayers, + cudnnDropoutDescriptor_t dropoutDesc, + cudnnRNNInputMode_t inputMode, + cudnnDirectionMode_t direction, + cudnnRNNMode_t mode, + cudnnDataType_t dataType) { + using FuncPtr = cudnnStatus_t (CUDNNWINAPI *)(cudnnRNNDescriptor_t, int, int, cudnnDropoutDescriptor_t, cudnnRNNInputMode_t, cudnnDirectionMode_t, cudnnRNNMode_t, cudnnDataType_t); + static auto func_ptr = LoadSymbol("cudnnSetRNNDescriptor_v5"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rnnDesc, hiddenSize, numLayers, dropoutDesc, inputMode, direction, mode, dataType); +} + +} // extern "C" diff --git a/tensorflow/stream_executor/cuda/cudnn_stub.cc b/tensorflow/stream_executor/cuda/cudnn_stub.cc new file mode 100644 index 0000000000..2727c215e8 --- /dev/null +++ b/tensorflow/stream_executor/cuda/cudnn_stub.cc @@ -0,0 +1,62 @@ +/* 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 "cuda/include/cudnn.h" +#include "tensorflow/stream_executor/lib/env.h" +#include "tensorflow/stream_executor/platform/dso_loader.h" + +// Implements the cuDNN API by forwarding to cuDNN loaded from the DSO. + +namespace { +// Returns DSO handle or null if loading the DSO fails. +void* GetDsoHandle() { +#ifdef PLATFORM_GOOGLE + return nullptr; +#else + static auto handle = []() -> void* { + auto handle_or = stream_executor::internal::DsoLoader::GetCudnnDsoHandle(); + if (!handle_or.ok()) return nullptr; + return handle_or.ValueOrDie(); + }(); + return handle; +#endif +} + +template +T LoadSymbol(const char* symbol_name) { + void* symbol = nullptr; + if (auto handle = GetDsoHandle()) { + stream_executor::port::Env::Default() + ->GetSymbolFromLibrary(handle, symbol_name, &symbol) + .IgnoreError(); + } + return reinterpret_cast(symbol); +} + +cudnnStatus_t GetSymbolNotFoundError() { return CUDNN_STATUS_INTERNAL_ERROR; } +} // namespace + +#if CUDNN_MAJOR < 6 +#error cuDNN version earlier than 6 is not supported. +#elif CUDNN_MAJOR < 7 +#include "tensorflow/stream_executor/cuda/cudnn_6_0.inc" +#elif CUDNN_MINOR < 1 +#include "tensorflow/stream_executor/cuda/cudnn_7_0.inc" +#elif CUDNN_MINOR < 3 +#include "tensorflow/stream_executor/cuda/cudnn_7_1.inc" +#elif CUDNN_MINOR < 4 +#include "tensorflow/stream_executor/cuda/cudnn_7_3.inc" +#else +#include "tensorflow/stream_executor/cuda/cudnn_7_4.inc" +#endif diff --git a/tensorflow/stream_executor/cuda/cufft_10_0.inc b/tensorflow/stream_executor/cuda/cufft_10_0.inc new file mode 100644 index 0000000000..19ae08815f --- /dev/null +++ b/tensorflow/stream_executor/cuda/cufft_10_0.inc @@ -0,0 +1,320 @@ +// Auto-generated, do not edit. + +extern "C" { +cufftResult CUFFTAPI cufftPlan1d(cufftHandle *plan, + int nx, + cufftType type, + int batch) { + using FuncPtr = cufftResult (CUFFTAPI *)(cufftHandle *, int, cufftType, int); + static auto func_ptr = LoadSymbol("cufftPlan1d"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(plan, nx, type, batch); +} + +cufftResult CUFFTAPI cufftPlan2d(cufftHandle *plan, + int nx, int ny, + cufftType type) { + using FuncPtr = cufftResult (CUFFTAPI *)(cufftHandle *, int, int, cufftType); + static auto func_ptr = LoadSymbol("cufftPlan2d"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(plan, nx, ny, type); +} + +cufftResult CUFFTAPI cufftPlan3d(cufftHandle *plan, + int nx, int ny, int nz, + cufftType type) { + using FuncPtr = cufftResult (CUFFTAPI *)(cufftHandle *, int, int, int, cufftType); + static auto func_ptr = LoadSymbol("cufftPlan3d"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(plan, nx, ny, nz, type); +} + +cufftResult CUFFTAPI cufftPlanMany(cufftHandle *plan, + int rank, + int *n, + int *inembed, int istride, int idist, + int *onembed, int ostride, int odist, + cufftType type, + int batch) { + using FuncPtr = cufftResult (CUFFTAPI *)(cufftHandle *, int, int *, int *, int, int, int *, int, int, cufftType, int); + static auto func_ptr = LoadSymbol("cufftPlanMany"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(plan, rank, n, inembed, istride, idist, onembed, ostride, odist, type, batch); +} + +cufftResult CUFFTAPI cufftMakePlan1d(cufftHandle plan, + int nx, + cufftType type, + int batch, + size_t *workSize) { + using FuncPtr = cufftResult (CUFFTAPI *)(cufftHandle, int, cufftType, int, size_t *); + static auto func_ptr = LoadSymbol("cufftMakePlan1d"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(plan, nx, type, batch, workSize); +} + +cufftResult CUFFTAPI cufftMakePlan2d(cufftHandle plan, + int nx, int ny, + cufftType type, + size_t *workSize) { + using FuncPtr = cufftResult (CUFFTAPI *)(cufftHandle, int, int, cufftType, size_t *); + static auto func_ptr = LoadSymbol("cufftMakePlan2d"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(plan, nx, ny, type, workSize); +} + +cufftResult CUFFTAPI cufftMakePlan3d(cufftHandle plan, + int nx, int ny, int nz, + cufftType type, + size_t *workSize) { + using FuncPtr = cufftResult (CUFFTAPI *)(cufftHandle, int, int, int, cufftType, size_t *); + static auto func_ptr = LoadSymbol("cufftMakePlan3d"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(plan, nx, ny, nz, type, workSize); +} + +cufftResult CUFFTAPI cufftMakePlanMany(cufftHandle plan, + int rank, + int *n, + int *inembed, int istride, int idist, + int *onembed, int ostride, int odist, + cufftType type, + int batch, + size_t *workSize) { + using FuncPtr = cufftResult (CUFFTAPI *)(cufftHandle, int, int *, int *, int, int, int *, int, int, cufftType, int, size_t *); + static auto func_ptr = LoadSymbol("cufftMakePlanMany"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(plan, rank, n, inembed, istride, idist, onembed, ostride, odist, type, batch, workSize); +} + +cufftResult CUFFTAPI cufftMakePlanMany64(cufftHandle plan, + int rank, + long long int *n, + long long int *inembed, + long long int istride, + long long int idist, + long long int *onembed, + long long int ostride, long long int odist, + cufftType type, + long long int batch, + size_t * workSize) { + using FuncPtr = cufftResult (CUFFTAPI *)(cufftHandle, int, long long *, long long *, long long, long long, long long *, long long, long long, cufftType, long long, size_t *); + static auto func_ptr = LoadSymbol("cufftMakePlanMany64"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(plan, rank, n, inembed, istride, idist, onembed, ostride, odist, type, batch, workSize); +} + +cufftResult CUFFTAPI cufftGetSizeMany64(cufftHandle plan, + int rank, + long long int *n, + long long int *inembed, + long long int istride, long long int idist, + long long int *onembed, + long long int ostride, long long int odist, + cufftType type, + long long int batch, + size_t *workSize) { + using FuncPtr = cufftResult (CUFFTAPI *)(cufftHandle, int, long long *, long long *, long long, long long, long long *, long long, long long, cufftType, long long, size_t *); + static auto func_ptr = LoadSymbol("cufftGetSizeMany64"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(plan, rank, n, inembed, istride, idist, onembed, ostride, odist, type, batch, workSize); +} + +cufftResult CUFFTAPI cufftEstimate1d(int nx, + cufftType type, + int batch, + size_t *workSize) { + using FuncPtr = cufftResult (CUFFTAPI *)(int, cufftType, int, size_t *); + static auto func_ptr = LoadSymbol("cufftEstimate1d"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(nx, type, batch, workSize); +} + +cufftResult CUFFTAPI cufftEstimate2d(int nx, int ny, + cufftType type, + size_t *workSize) { + using FuncPtr = cufftResult (CUFFTAPI *)(int, int, cufftType, size_t *); + static auto func_ptr = LoadSymbol("cufftEstimate2d"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(nx, ny, type, workSize); +} + +cufftResult CUFFTAPI cufftEstimate3d(int nx, int ny, int nz, + cufftType type, + size_t *workSize) { + using FuncPtr = cufftResult (CUFFTAPI *)(int, int, int, cufftType, size_t *); + static auto func_ptr = LoadSymbol("cufftEstimate3d"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(nx, ny, nz, type, workSize); +} + +cufftResult CUFFTAPI cufftEstimateMany(int rank, + int *n, + int *inembed, int istride, int idist, + int *onembed, int ostride, int odist, + cufftType type, + int batch, + size_t *workSize) { + using FuncPtr = cufftResult (CUFFTAPI *)(int, int *, int *, int, int, int *, int, int, cufftType, int, size_t *); + static auto func_ptr = LoadSymbol("cufftEstimateMany"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(rank, n, inembed, istride, idist, onembed, ostride, odist, type, batch, workSize); +} + +cufftResult CUFFTAPI cufftCreate(cufftHandle * handle) { + using FuncPtr = cufftResult (CUFFTAPI *)(cufftHandle *); + static auto func_ptr = LoadSymbol("cufftCreate"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle); +} + +cufftResult CUFFTAPI cufftGetSize1d(cufftHandle handle, + int nx, + cufftType type, + int batch, + size_t *workSize ) { + using FuncPtr = cufftResult (CUFFTAPI *)(cufftHandle, int, cufftType, int, size_t *); + static auto func_ptr = LoadSymbol("cufftGetSize1d"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, nx, type, batch, workSize); +} + +cufftResult CUFFTAPI cufftGetSize2d(cufftHandle handle, + int nx, int ny, + cufftType type, + size_t *workSize) { + using FuncPtr = cufftResult (CUFFTAPI *)(cufftHandle, int, int, cufftType, size_t *); + static auto func_ptr = LoadSymbol("cufftGetSize2d"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, nx, ny, type, workSize); +} + +cufftResult CUFFTAPI cufftGetSize3d(cufftHandle handle, + int nx, int ny, int nz, + cufftType type, + size_t *workSize) { + using FuncPtr = cufftResult (CUFFTAPI *)(cufftHandle, int, int, int, cufftType, size_t *); + static auto func_ptr = LoadSymbol("cufftGetSize3d"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, nx, ny, nz, type, workSize); +} + +cufftResult CUFFTAPI cufftGetSizeMany(cufftHandle handle, + int rank, int *n, + int *inembed, int istride, int idist, + int *onembed, int ostride, int odist, + cufftType type, int batch, size_t *workArea) { + using FuncPtr = cufftResult (CUFFTAPI *)(cufftHandle, int, int *, int *, int, int, int *, int, int, cufftType, int, size_t *); + static auto func_ptr = LoadSymbol("cufftGetSizeMany"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, rank, n, inembed, istride, idist, onembed, ostride, odist, type, batch, workArea); +} + +cufftResult CUFFTAPI cufftGetSize(cufftHandle handle, size_t *workSize) { + using FuncPtr = cufftResult (CUFFTAPI *)(cufftHandle, size_t *); + static auto func_ptr = LoadSymbol("cufftGetSize"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(handle, workSize); +} + +cufftResult CUFFTAPI cufftSetWorkArea(cufftHandle plan, void *workArea) { + using FuncPtr = cufftResult (CUFFTAPI *)(cufftHandle, void *); + static auto func_ptr = LoadSymbol("cufftSetWorkArea"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(plan, workArea); +} + +cufftResult CUFFTAPI cufftSetAutoAllocation(cufftHandle plan, int autoAllocate) { + using FuncPtr = cufftResult (CUFFTAPI *)(cufftHandle, int); + static auto func_ptr = LoadSymbol("cufftSetAutoAllocation"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(plan, autoAllocate); +} + +cufftResult CUFFTAPI cufftExecC2C(cufftHandle plan, + cufftComplex *idata, + cufftComplex *odata, + int direction) { + using FuncPtr = cufftResult (CUFFTAPI *)(cufftHandle, cufftComplex *, cufftComplex *, int); + static auto func_ptr = LoadSymbol("cufftExecC2C"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(plan, idata, odata, direction); +} + +cufftResult CUFFTAPI cufftExecR2C(cufftHandle plan, + cufftReal *idata, + cufftComplex *odata) { + using FuncPtr = cufftResult (CUFFTAPI *)(cufftHandle, cufftReal *, cufftComplex *); + static auto func_ptr = LoadSymbol("cufftExecR2C"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(plan, idata, odata); +} + +cufftResult CUFFTAPI cufftExecC2R(cufftHandle plan, + cufftComplex *idata, + cufftReal *odata) { + using FuncPtr = cufftResult (CUFFTAPI *)(cufftHandle, cufftComplex *, cufftReal *); + static auto func_ptr = LoadSymbol("cufftExecC2R"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(plan, idata, odata); +} + +cufftResult CUFFTAPI cufftExecZ2Z(cufftHandle plan, + cufftDoubleComplex *idata, + cufftDoubleComplex *odata, + int direction) { + using FuncPtr = cufftResult (CUFFTAPI *)(cufftHandle, cufftDoubleComplex *, cufftDoubleComplex *, int); + static auto func_ptr = LoadSymbol("cufftExecZ2Z"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(plan, idata, odata, direction); +} + +cufftResult CUFFTAPI cufftExecD2Z(cufftHandle plan, + cufftDoubleReal *idata, + cufftDoubleComplex *odata) { + using FuncPtr = cufftResult (CUFFTAPI *)(cufftHandle, cufftDoubleReal *, cufftDoubleComplex *); + static auto func_ptr = LoadSymbol("cufftExecD2Z"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(plan, idata, odata); +} + +cufftResult CUFFTAPI cufftExecZ2D(cufftHandle plan, + cufftDoubleComplex *idata, + cufftDoubleReal *odata) { + using FuncPtr = cufftResult (CUFFTAPI *)(cufftHandle, cufftDoubleComplex *, cufftDoubleReal *); + static auto func_ptr = LoadSymbol("cufftExecZ2D"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(plan, idata, odata); +} + +cufftResult CUFFTAPI cufftSetStream(cufftHandle plan, + cudaStream_t stream) { + using FuncPtr = cufftResult (CUFFTAPI *)(cufftHandle, cudaStream_t); + static auto func_ptr = LoadSymbol("cufftSetStream"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(plan, stream); +} + +cufftResult CUFFTAPI cufftDestroy(cufftHandle plan) { + using FuncPtr = cufftResult (CUFFTAPI *)(cufftHandle); + static auto func_ptr = LoadSymbol("cufftDestroy"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(plan); +} + +cufftResult CUFFTAPI cufftGetVersion(int *version) { + using FuncPtr = cufftResult (CUFFTAPI *)(int *); + static auto func_ptr = LoadSymbol("cufftGetVersion"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(version); +} + +cufftResult CUFFTAPI cufftGetProperty(libraryPropertyType type, + int *value) { + using FuncPtr = cufftResult (CUFFTAPI *)(libraryPropertyType, int *); + static auto func_ptr = LoadSymbol("cufftGetProperty"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(type, value); +} + +} // extern "C" diff --git a/tensorflow/stream_executor/cuda/cufft_stub.cc b/tensorflow/stream_executor/cuda/cufft_stub.cc new file mode 100644 index 0000000000..c15d98730e --- /dev/null +++ b/tensorflow/stream_executor/cuda/cufft_stub.cc @@ -0,0 +1,50 @@ +/* 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 "cuda/include/cufft.h" +#include "tensorflow/stream_executor/lib/env.h" +#include "tensorflow/stream_executor/platform/dso_loader.h" + +// Implements the cuFFT API by forwarding to cuFFT loaded from the DSO. + +namespace { +// Returns DSO handle or null if loading the DSO fails. +void* GetDsoHandle() { +#ifdef PLATFORM_GOOGLE + return nullptr; +#else + static auto handle = []() -> void* { + auto handle_or = stream_executor::internal::DsoLoader::GetCufftDsoHandle(); + if (!handle_or.ok()) return nullptr; + return handle_or.ValueOrDie(); + }(); + return handle; +#endif +} + +template +T LoadSymbol(const char* symbol_name) { + void* symbol = nullptr; + if (auto handle = GetDsoHandle()) { + stream_executor::port::Env::Default() + ->GetSymbolFromLibrary(handle, symbol_name, &symbol) + .IgnoreError(); + } + return reinterpret_cast(symbol); +} + +cufftResult GetSymbolNotFoundError() { return CUFFT_INTERNAL_ERROR; } +} // namespace + +#include "tensorflow/stream_executor/cuda/cufft_10_0.inc" diff --git a/tensorflow/stream_executor/cuda/curand_10_0.inc b/tensorflow/stream_executor/cuda/curand_10_0.inc new file mode 100644 index 0000000000..e6024e2bb3 --- /dev/null +++ b/tensorflow/stream_executor/cuda/curand_10_0.inc @@ -0,0 +1,246 @@ +// Auto-generated, do not edit. + +extern "C" { +curandStatus_t CURANDAPI +curandCreateGenerator(curandGenerator_t *generator, curandRngType_t rng_type) { + using FuncPtr = curandStatus_t (CURANDAPI *)(curandGenerator_t *, curandRngType_t); + static auto func_ptr = LoadSymbol("curandCreateGenerator"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(generator, rng_type); +} + +curandStatus_t CURANDAPI +curandCreateGeneratorHost(curandGenerator_t *generator, curandRngType_t rng_type) { + using FuncPtr = curandStatus_t (CURANDAPI *)(curandGenerator_t *, curandRngType_t); + static auto func_ptr = LoadSymbol("curandCreateGeneratorHost"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(generator, rng_type); +} + +curandStatus_t CURANDAPI +curandDestroyGenerator(curandGenerator_t generator) { + using FuncPtr = curandStatus_t (CURANDAPI *)(curandGenerator_t); + static auto func_ptr = LoadSymbol("curandDestroyGenerator"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(generator); +} + +curandStatus_t CURANDAPI +curandGetVersion(int *version) { + using FuncPtr = curandStatus_t (CURANDAPI *)(int *); + static auto func_ptr = LoadSymbol("curandGetVersion"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(version); +} + +curandStatus_t CURANDAPI +curandGetProperty(libraryPropertyType type, int *value) { + using FuncPtr = curandStatus_t (CURANDAPI *)(libraryPropertyType, int *); + static auto func_ptr = LoadSymbol("curandGetProperty"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(type, value); +} + +curandStatus_t CURANDAPI +curandSetStream(curandGenerator_t generator, cudaStream_t stream) { + using FuncPtr = curandStatus_t (CURANDAPI *)(curandGenerator_t, cudaStream_t); + static auto func_ptr = LoadSymbol("curandSetStream"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(generator, stream); +} + +curandStatus_t CURANDAPI +curandSetPseudoRandomGeneratorSeed(curandGenerator_t generator, unsigned long long seed) { + using FuncPtr = curandStatus_t (CURANDAPI *)(curandGenerator_t, unsigned long long); + static auto func_ptr = LoadSymbol("curandSetPseudoRandomGeneratorSeed"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(generator, seed); +} + +curandStatus_t CURANDAPI +curandSetGeneratorOffset(curandGenerator_t generator, unsigned long long offset) { + using FuncPtr = curandStatus_t (CURANDAPI *)(curandGenerator_t, unsigned long long); + static auto func_ptr = LoadSymbol("curandSetGeneratorOffset"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(generator, offset); +} + +curandStatus_t CURANDAPI +curandSetGeneratorOrdering(curandGenerator_t generator, curandOrdering_t order) { + using FuncPtr = curandStatus_t (CURANDAPI *)(curandGenerator_t, curandOrdering_t); + static auto func_ptr = LoadSymbol("curandSetGeneratorOrdering"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(generator, order); +} + +curandStatus_t CURANDAPI +curandSetQuasiRandomGeneratorDimensions(curandGenerator_t generator, unsigned int num_dimensions) { + using FuncPtr = curandStatus_t (CURANDAPI *)(curandGenerator_t, unsigned int); + static auto func_ptr = LoadSymbol("curandSetQuasiRandomGeneratorDimensions"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(generator, num_dimensions); +} + +curandStatus_t CURANDAPI +curandGenerate(curandGenerator_t generator, unsigned int *outputPtr, size_t num) { + using FuncPtr = curandStatus_t (CURANDAPI *)(curandGenerator_t, unsigned int *, size_t); + static auto func_ptr = LoadSymbol("curandGenerate"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(generator, outputPtr, num); +} + +curandStatus_t CURANDAPI +curandGenerateLongLong(curandGenerator_t generator, unsigned long long *outputPtr, size_t num) { + using FuncPtr = curandStatus_t (CURANDAPI *)(curandGenerator_t, unsigned long long *, size_t); + static auto func_ptr = LoadSymbol("curandGenerateLongLong"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(generator, outputPtr, num); +} + +curandStatus_t CURANDAPI +curandGenerateUniform(curandGenerator_t generator, float *outputPtr, size_t num) { + using FuncPtr = curandStatus_t (CURANDAPI *)(curandGenerator_t, float *, size_t); + static auto func_ptr = LoadSymbol("curandGenerateUniform"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(generator, outputPtr, num); +} + +curandStatus_t CURANDAPI +curandGenerateUniformDouble(curandGenerator_t generator, double *outputPtr, size_t num) { + using FuncPtr = curandStatus_t (CURANDAPI *)(curandGenerator_t, double *, size_t); + static auto func_ptr = LoadSymbol("curandGenerateUniformDouble"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(generator, outputPtr, num); +} + +curandStatus_t CURANDAPI +curandGenerateNormal(curandGenerator_t generator, float *outputPtr, + size_t n, float mean, float stddev) { + using FuncPtr = curandStatus_t (CURANDAPI *)(curandGenerator_t, float *, size_t, float, float); + static auto func_ptr = LoadSymbol("curandGenerateNormal"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(generator, outputPtr, n, mean, stddev); +} + +curandStatus_t CURANDAPI +curandGenerateNormalDouble(curandGenerator_t generator, double *outputPtr, + size_t n, double mean, double stddev) { + using FuncPtr = curandStatus_t (CURANDAPI *)(curandGenerator_t, double *, size_t, double, double); + static auto func_ptr = LoadSymbol("curandGenerateNormalDouble"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(generator, outputPtr, n, mean, stddev); +} + +curandStatus_t CURANDAPI +curandGenerateLogNormal(curandGenerator_t generator, float *outputPtr, + size_t n, float mean, float stddev) { + using FuncPtr = curandStatus_t (CURANDAPI *)(curandGenerator_t, float *, size_t, float, float); + static auto func_ptr = LoadSymbol("curandGenerateLogNormal"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(generator, outputPtr, n, mean, stddev); +} + +curandStatus_t CURANDAPI +curandGenerateLogNormalDouble(curandGenerator_t generator, double *outputPtr, + size_t n, double mean, double stddev) { + using FuncPtr = curandStatus_t (CURANDAPI *)(curandGenerator_t, double *, size_t, double, double); + static auto func_ptr = LoadSymbol("curandGenerateLogNormalDouble"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(generator, outputPtr, n, mean, stddev); +} + +curandStatus_t CURANDAPI +curandCreatePoissonDistribution(double lambda, curandDiscreteDistribution_t *discrete_distribution) { + using FuncPtr = curandStatus_t (CURANDAPI *)(double, curandDiscreteDistribution_t *); + static auto func_ptr = LoadSymbol("curandCreatePoissonDistribution"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(lambda, discrete_distribution); +} + +curandStatus_t CURANDAPI +curandDestroyDistribution(curandDiscreteDistribution_t discrete_distribution) { + using FuncPtr = curandStatus_t (CURANDAPI *)(curandDiscreteDistribution_t); + static auto func_ptr = LoadSymbol("curandDestroyDistribution"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(discrete_distribution); +} + +curandStatus_t CURANDAPI +curandGeneratePoisson(curandGenerator_t generator, unsigned int *outputPtr, + size_t n, double lambda) { + using FuncPtr = curandStatus_t (CURANDAPI *)(curandGenerator_t, unsigned int *, size_t, double); + static auto func_ptr = LoadSymbol("curandGeneratePoisson"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(generator, outputPtr, n, lambda); +} + +curandStatus_t CURANDAPI +curandGeneratePoissonMethod(curandGenerator_t generator, unsigned int *outputPtr, + size_t n, double lambda, curandMethod_t method) { + using FuncPtr = curandStatus_t (CURANDAPI *)(curandGenerator_t, unsigned int *, size_t, double, curandMethod_t); + static auto func_ptr = LoadSymbol("curandGeneratePoissonMethod"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(generator, outputPtr, n, lambda, method); +} + +curandStatus_t CURANDAPI +curandGenerateBinomial(curandGenerator_t generator, unsigned int *outputPtr, + size_t num, unsigned int n, double p) { + using FuncPtr = curandStatus_t (CURANDAPI *)(curandGenerator_t, unsigned int *, size_t, unsigned int, double); + static auto func_ptr = LoadSymbol("curandGenerateBinomial"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(generator, outputPtr, num, n, p); +} + +curandStatus_t CURANDAPI +curandGenerateBinomialMethod(curandGenerator_t generator, + unsigned int *outputPtr, + size_t num, unsigned int n, double p, + curandMethod_t method) { + using FuncPtr = curandStatus_t (CURANDAPI *)(curandGenerator_t, unsigned int *, size_t, unsigned int, double, curandMethod_t); + static auto func_ptr = LoadSymbol("curandGenerateBinomialMethod"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(generator, outputPtr, num, n, p, method); +} + +curandStatus_t CURANDAPI +curandGenerateSeeds(curandGenerator_t generator) { + using FuncPtr = curandStatus_t (CURANDAPI *)(curandGenerator_t); + static auto func_ptr = LoadSymbol("curandGenerateSeeds"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(generator); +} + +curandStatus_t CURANDAPI +curandGetDirectionVectors32(curandDirectionVectors32_t *vectors[], curandDirectionVectorSet_t set) { + using FuncPtr = curandStatus_t (CURANDAPI *)(curandDirectionVectors32_t *[], curandDirectionVectorSet_t); + static auto func_ptr = LoadSymbol("curandGetDirectionVectors32"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(vectors, set); +} + +curandStatus_t CURANDAPI +curandGetScrambleConstants32(unsigned int * * constants) { + using FuncPtr = curandStatus_t (CURANDAPI *)(unsigned int **); + static auto func_ptr = LoadSymbol("curandGetScrambleConstants32"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(constants); +} + +curandStatus_t CURANDAPI +curandGetDirectionVectors64(curandDirectionVectors64_t *vectors[], curandDirectionVectorSet_t set) { + using FuncPtr = curandStatus_t (CURANDAPI *)(curandDirectionVectors64_t *[], curandDirectionVectorSet_t); + static auto func_ptr = LoadSymbol("curandGetDirectionVectors64"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(vectors, set); +} + +curandStatus_t CURANDAPI +curandGetScrambleConstants64(unsigned long long * * constants) { + using FuncPtr = curandStatus_t (CURANDAPI *)(unsigned long long **); + static auto func_ptr = LoadSymbol("curandGetScrambleConstants64"); + if (!func_ptr) return GetSymbolNotFoundError(); + return func_ptr(constants); +} + +} // extern "C" diff --git a/tensorflow/stream_executor/cuda/curand_stub.cc b/tensorflow/stream_executor/cuda/curand_stub.cc new file mode 100644 index 0000000000..dd70384d64 --- /dev/null +++ b/tensorflow/stream_executor/cuda/curand_stub.cc @@ -0,0 +1,50 @@ +/* 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 "cuda/include/curand.h" +#include "tensorflow/stream_executor/lib/env.h" +#include "tensorflow/stream_executor/platform/dso_loader.h" + +// Implements the cuRAND API by forwarding to cuRAND loaded from the DSO. + +namespace { +// Returns DSO handle or null if loading the DSO fails. +void* GetDsoHandle() { +#ifdef PLATFORM_GOOGLE + return nullptr; +#else + static auto handle = []() -> void* { + auto handle_or = stream_executor::internal::DsoLoader::GetCurandDsoHandle(); + if (!handle_or.ok()) return nullptr; + return handle_or.ValueOrDie(); + }(); + return handle; +#endif +} + +template +T LoadSymbol(const char* symbol_name) { + void* symbol = nullptr; + if (auto handle = GetDsoHandle()) { + stream_executor::port::Env::Default() + ->GetSymbolFromLibrary(handle, symbol_name, &symbol) + .IgnoreError(); + } + return reinterpret_cast(symbol); +} + +curandStatus_t GetSymbolNotFoundError() { return CURAND_STATUS_INTERNAL_ERROR; } +} // namespace + +#include "tensorflow/stream_executor/cuda/curand_10_0.inc" -- GitLab From 057117afa53fe48cc877d509b835d67c1d465b07 Mon Sep 17 00:00:00 2001 From: Nick Felt Date: Thu, 21 Feb 2019 22:46:25 -0800 Subject: [PATCH 325/766] Ensure that tf.summary.write() keeps metadata tensor on CPU Previously, when in a GPU device context, the logic attempted to create the default empty metadata tensor on the GPU, which fails since it's a string tensor. PiperOrigin-RevId: 235135723 --- tensorflow/python/kernel_tests/BUILD | 3 ++- .../python/kernel_tests/summary_ops_test.py | 16 ++++++++++++++++ tensorflow/python/ops/summary_ops_v2.py | 4 ++-- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/tensorflow/python/kernel_tests/BUILD b/tensorflow/python/kernel_tests/BUILD index 6ed8f69afa..ea45896970 100644 --- a/tensorflow/python/kernel_tests/BUILD +++ b/tensorflow/python/kernel_tests/BUILD @@ -1084,7 +1084,7 @@ tf_py_test( ], ) -tf_py_test( +cuda_py_test( name = "summary_ops_test", size = "small", srcs = ["summary_ops_test.py"], @@ -1107,6 +1107,7 @@ tf_py_test( "//tensorflow/python/keras:engine", "//tensorflow/python/keras:layers", ], + xla_enable_strict_auto_jit = True, ) tf_py_test( diff --git a/tensorflow/python/kernel_tests/summary_ops_test.py b/tensorflow/python/kernel_tests/summary_ops_test.py index f8cc3299fe..b7a05136c4 100644 --- a/tensorflow/python/kernel_tests/summary_ops_test.py +++ b/tensorflow/python/kernel_tests/summary_ops_test.py @@ -144,6 +144,22 @@ class SummaryOpsCoreTest(test_util.TensorFlowTestCase): value = events[1].summary.value[0] self.assertAllEqual([b'foo', b'bar'], to_numpy(value)) + @test_util.run_gpu_only + def testWrite_gpuDeviceContext(self): + logdir = self.get_temp_dir() + with context.eager_mode(): + with summary_ops.create_file_writer(logdir).as_default(): + with ops.device('/GPU:0'): + value = constant_op.constant(42.0) + step = constant_op.constant(12, dtype=dtypes.int64) + summary_ops.write('tag', value, step=step).numpy() + empty_metadata = summary_pb2.SummaryMetadata() + events = events_from_logdir(logdir) + self.assertEqual(2, len(events)) + self.assertEqual(12, events[1].step) + self.assertEqual(42, to_numpy(events[1].summary.value[0])) + self.assertEqual(empty_metadata, events[1].summary.value[0].metadata) + @test_util.also_run_as_tf_function def testWrite_noDefaultWriter(self): with context.eager_mode(): diff --git a/tensorflow/python/ops/summary_ops_v2.py b/tensorflow/python/ops/summary_ops_v2.py index 0f7e8cf12c..cde00c766e 100644 --- a/tensorflow/python/ops/summary_ops_v2.py +++ b/tensorflow/python/ops/summary_ops_v2.py @@ -448,9 +448,9 @@ def write(tag, tensor, step, metadata=None, name=None): if context.context().summary_writer_resource is None: return constant_op.constant(False) if metadata is None: - serialized_metadata = constant_op.constant(b"") + serialized_metadata = b"" elif hasattr(metadata, "SerializeToString"): - serialized_metadata = constant_op.constant(metadata.SerializeToString()) + serialized_metadata = metadata.SerializeToString() else: serialized_metadata = metadata -- GitLab From 27810c92b711afa119311ed63a66a14babe871c4 Mon Sep 17 00:00:00 2001 From: Jiankang Date: Fri, 22 Feb 2019 15:05:55 +0800 Subject: [PATCH 326/766] FIx CudnnCompatibleLSTM document error Fix the CudnnCompatibleLSTM document error, it should be tf.contrib.cudnn_rnn.CudnnCompatibleLSTMCell instead of tf.contrib.cudnn_rnn.CudnnCompatibleLSTM. --- tensorflow/contrib/cudnn_rnn/python/layers/cudnn_rnn.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/contrib/cudnn_rnn/python/layers/cudnn_rnn.py b/tensorflow/contrib/cudnn_rnn/python/layers/cudnn_rnn.py index 1cb477716d..c4e37b41c8 100644 --- a/tensorflow/contrib/cudnn_rnn/python/layers/cudnn_rnn.py +++ b/tensorflow/contrib/cudnn_rnn/python/layers/cudnn_rnn.py @@ -104,7 +104,7 @@ class _CudnnRNN(base_layer.Layer): # Inference subgraph for unidirectional RNN on, e.g., CPU or mobile. with tf.Graph().as_default(): - single_cell = lambda: tf.contrib.cudnn_rnn.CudnnCompatibleLSTM(num_units) + single_cell = lambda: tf.contrib.cudnn_rnn.CudnnCompatibleLSTMCell(num_units) # NOTE: Even if there's only one layer, the cell needs to be wrapped in # MultiRNNCell. @@ -124,7 +124,7 @@ class _CudnnRNN(base_layer.Layer): # Inference subgraph for bidirectional RNN with tf.Graph().as_default(): - single_cell = lambda: tf.contrib.cudnn_rnn.CudnnCompatibleLSTM(num_units) + single_cell = lambda: tf.contrib.cudnn_rnn.CudnnCompatibleLSTMCell(num_units) cells_fw = [single_cell() for _ in range(num_layers)] cells_bw = [single_cell() for _ in range(num_layers)] -- GitLab From 4bf9ea4295a0eff68ab737ab41399ab8bb3464ef Mon Sep 17 00:00:00 2001 From: Blake Hechtman Date: Thu, 21 Feb 2019 23:23:36 -0800 Subject: [PATCH 327/766] [XLA] Just use ShapeUtil::AlignLayouts for reshape layout selection. PiperOrigin-RevId: 235138912 --- .../compiler/xla/service/layout_assignment.cc | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/tensorflow/compiler/xla/service/layout_assignment.cc b/tensorflow/compiler/xla/service/layout_assignment.cc index aa791ea195..bf1d1c233f 100644 --- a/tensorflow/compiler/xla/service/layout_assignment.cc +++ b/tensorflow/compiler/xla/service/layout_assignment.cc @@ -1019,16 +1019,6 @@ std::unique_ptr LayoutAssignment::ChooseOperandLayoutFromOutputLayout( Shape operand_shape = operand->shape(); *operand_shape.mutable_layout() = LayoutUtil::GetDefaultLayoutForShape(operand_shape); - if (ShapeUtil::ReshapeIsBitcast(operand_shape, output_shape_with_layout)) { - return absl::make_unique(operand_shape.layout()); - } - if (operand_shape.rank() == output_shape.rank()) { - *operand_shape.mutable_layout() = output_layout; - if (ShapeUtil::ReshapeIsBitcast(operand_shape, - output_shape_with_layout)) { - return absl::make_unique(output_layout); - } - } auto aligned_operand_shape = ShapeUtil::AlignLayouts(output_shape_with_layout, operand_shape); if (aligned_operand_shape) { @@ -1090,16 +1080,6 @@ std::unique_ptr LayoutAssignment::ChooseOutputLayoutFromOperandLayout( Shape output_shape = user->shape(); *output_shape.mutable_layout() = LayoutUtil::GetDefaultLayoutForShape(output_shape); - if (ShapeUtil::ReshapeIsBitcast(output_shape, operand_shape_with_layout)) { - return absl::make_unique(output_shape.layout()); - } - if (operand->shape().rank() == output_shape.rank()) { - *output_shape.mutable_layout() = operand_layout; - if (ShapeUtil::ReshapeIsBitcast(output_shape, - operand_shape_with_layout)) { - return absl::make_unique(operand_layout); - } - } auto aligned_user_shape = ShapeUtil::AlignLayouts(operand_shape_with_layout, output_shape); if (aligned_user_shape) { -- GitLab From 8853e47acc6423d5274bc42e25001f97bba104fe Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 22 Feb 2019 00:15:09 -0800 Subject: [PATCH 328/766] Automated rollback of commit a17a64d0b76e74a0d2f79a39f3f329e0826f2d8c PiperOrigin-RevId: 235144234 --- .../compiler/jit/create_xla_launch_op.cc | 5 +- .../jit/encapsulate_subgraphs_pass.cc | 5 +- .../compiler/jit/mark_for_compilation_pass.cc | 2 +- .../compiler/jit/partially_decluster_pass.cc | 32 +-- .../jit/partially_decluster_pass_test.cc | 8 - tensorflow/compiler/tests/while_test.py | 107 +-------- tensorflow/compiler/tf2xla/const_analysis.cc | 101 +-------- tensorflow/compiler/tf2xla/const_analysis.h | 12 +- .../compiler/tf2xla/const_analysis_test.cc | 16 +- tensorflow/compiler/tf2xla/graph_compiler.cc | 7 +- tensorflow/compiler/tf2xla/kernels/BUILD | 1 - .../compiler/tf2xla/kernels/while_op.cc | 210 ++---------------- tensorflow/compiler/tf2xla/xla_compiler.h | 2 +- tensorflow/python/keras/BUILD | 15 -- .../time_distributed_learning_phase_test.py | 43 ---- .../python/keras/layers/wrappers_test.py | 11 + .../kernel_tests/control_flow_ops_py_test.py | 22 +- .../python/kernel_tests/while_v2_test.py | 24 +- tensorflow/python/ops/while_v2.py | 143 +++++++----- .../ops/while_v2_indexed_slices_rewriter.py | 4 +- 20 files changed, 173 insertions(+), 597 deletions(-) delete mode 100644 tensorflow/python/keras/layers/time_distributed_learning_phase_test.py diff --git a/tensorflow/compiler/jit/create_xla_launch_op.cc b/tensorflow/compiler/jit/create_xla_launch_op.cc index 7e4c8466d8..6f1ff85f24 100644 --- a/tensorflow/compiler/jit/create_xla_launch_op.cc +++ b/tensorflow/compiler/jit/create_xla_launch_op.cc @@ -126,9 +126,8 @@ Status GetBodyAndConstantsAndResources(FunctionLibraryRuntime* flr, const DataTypeVector& arg_types = (*fbody)->arg_types; std::vector const_args(arg_types.size()); // If we can't analyze the const args. Bail out. - TF_RETURN_IF_ERROR( - BackwardsConstAnalysis(*((*fbody)->graph), &const_args, - /*compile_time_const_nodes=*/nullptr, flr)); + TF_RETURN_IF_ERROR(BackwardsConstAnalysis( + *((*fbody)->graph), &const_args, /*compile_time_const_nodes=*/nullptr)); for (int i = 0; i < const_args.size(); ++i) { if (const_args[i]) { diff --git a/tensorflow/compiler/jit/encapsulate_subgraphs_pass.cc b/tensorflow/compiler/jit/encapsulate_subgraphs_pass.cc index 1e69e61a17..d0d7a3f378 100644 --- a/tensorflow/compiler/jit/encapsulate_subgraphs_pass.cc +++ b/tensorflow/compiler/jit/encapsulate_subgraphs_pass.cc @@ -2575,9 +2575,8 @@ Status EncapsulateSubgraphsPass::Run( const int num_args = input_permutation->size(); std::vector const_args(num_args); - TF_RETURN_IF_ERROR( - BackwardsConstAnalysis(**subgraph, &const_args, - /*compile_time_const_nodes=*/nullptr, flr)); + TF_RETURN_IF_ERROR(BackwardsConstAnalysis( + **subgraph, &const_args, /*compile_time_const_nodes=*/nullptr)); DataTypeVector arg_types(num_args); TF_RETURN_IF_ERROR(GetArgTypes(**subgraph, &arg_types)); diff --git a/tensorflow/compiler/jit/mark_for_compilation_pass.cc b/tensorflow/compiler/jit/mark_for_compilation_pass.cc index fd778490c3..d9a83049d6 100644 --- a/tensorflow/compiler/jit/mark_for_compilation_pass.cc +++ b/tensorflow/compiler/jit/mark_for_compilation_pass.cc @@ -441,7 +441,7 @@ Status FindCompilationCandidates( std::vector compile_time_const_nodes(graph.num_node_ids(), false); TF_RETURN_IF_ERROR( BackwardsConstAnalysis(graph, /*compile_time_const_arg_indices=*/nullptr, - &compile_time_const_nodes, lib_runtime)); + &compile_time_const_nodes)); int64& fuel = GetMarkForCompilationPassFlags()->tf_xla_clustering_fuel; diff --git a/tensorflow/compiler/jit/partially_decluster_pass.cc b/tensorflow/compiler/jit/partially_decluster_pass.cc index 202e0323bf..e1fd2aaee2 100644 --- a/tensorflow/compiler/jit/partially_decluster_pass.cc +++ b/tensorflow/compiler/jit/partially_decluster_pass.cc @@ -20,13 +20,9 @@ limitations under the License. #include "tensorflow/compiler/jit/xla_cluster_util.h" #include "tensorflow/compiler/tf2xla/const_analysis.h" #include "tensorflow/compiler/tf2xla/xla_op_registry.h" -#include "tensorflow/core/common_runtime/function.h" -#include "tensorflow/core/framework/function.h" #include "tensorflow/core/framework/memory_types.h" #include "tensorflow/core/framework/node_def.pb.h" #include "tensorflow/core/framework/op_kernel.h" -#include "tensorflow/core/lib/core/errors.h" -#include "tensorflow/core/public/version.h" namespace tensorflow { namespace { @@ -278,18 +274,10 @@ Status MustCompileNode(const Node* n, bool* must_compile) { // regress performance in any significant manner. We will have to revisit this // algorith with a more complex cost model if this assumption turns out to be // incorrect. -Status PartiallyDeclusterGraph(Graph* graph, - const FunctionLibraryDefinition* flib_def, - Env* env) { +Status PartiallyDeclusterGraph(Graph* graph) { std::vector compile_time_const_nodes(graph->num_node_ids()); - OptimizerOptions opts; - auto pflr = absl::make_unique( - nullptr, env, TF_GRAPH_DEF_VERSION, flib_def, opts); - FunctionLibraryRuntime* lib_runtime = - pflr->GetFLR(ProcessFunctionLibraryRuntime::kDefaultFLRDevice); - TF_RETURN_IF_ERROR(BackwardsConstAnalysis(*graph, nullptr, - &compile_time_const_nodes, - lib_runtime, IsIntraClusterEdge)); + TF_RETURN_IF_ERROR(BackwardsConstAnalysis( + *graph, nullptr, &compile_time_const_nodes, IsIntraClusterEdge)); std::vector rpo; GetReversePostOrder(*graph, &rpo, /*stable_comparator=*/NodeComparatorName(), @@ -353,19 +341,7 @@ Status PartiallyDeclusterPass::Run( TF_RETURN_IF_ERROR( reduce_device_to_host_copies::PartiallyDeclusterGraph(graph)); - if (options.flib_def == nullptr) { - return errors::InvalidArgument( - "GraphOptimizationPassOptions::flib_def must be set for " - "PartiallyDeclusterPass."); - } - if (options.session_options == nullptr || - options.session_options->env == nullptr) { - return errors::InvalidArgument( - "GraphOptimizationPassOptions::session_options::env must be set for " - "PartiallyDeclusterPass."); - } - TF_RETURN_IF_ERROR(reduce_recompilation::PartiallyDeclusterGraph( - graph, options.flib_def, options.session_options->env)); + TF_RETURN_IF_ERROR(reduce_recompilation::PartiallyDeclusterGraph(graph)); return Status::OK(); } diff --git a/tensorflow/compiler/jit/partially_decluster_pass_test.cc b/tensorflow/compiler/jit/partially_decluster_pass_test.cc index 3494d0ee7e..1d81a8f4fc 100644 --- a/tensorflow/compiler/jit/partially_decluster_pass_test.cc +++ b/tensorflow/compiler/jit/partially_decluster_pass_test.cc @@ -27,8 +27,6 @@ limitations under the License. #include "tensorflow/compiler/tf2xla/cc/ops/xla_ops.h" #include "tensorflow/compiler/tf2xla/xla_op_kernel.h" #include "tensorflow/compiler/tf2xla/xla_op_registry.h" -#include "tensorflow/core/framework/function.h" -#include "tensorflow/core/framework/function.pb.h" #include "tensorflow/core/framework/node_def_util.h" #include "tensorflow/core/framework/op.h" #include "tensorflow/core/graph/algorithm.h" @@ -92,12 +90,6 @@ Status PartiallyDecluster(std::unique_ptr* graph) { GraphOptimizationPassOptions opt_options; opt_options.graph = graph; - FunctionDefLibrary fdef_lib; - FunctionLibraryDefinition flib_def(OpRegistry::Global(), fdef_lib); - opt_options.flib_def = &flib_def; - SessionOptions session_options; - session_options.env = Env::Default(); - opt_options.session_options = &session_options; PartiallyDeclusterPass pass; return pass.Run(opt_options); } diff --git a/tensorflow/compiler/tests/while_test.py b/tensorflow/compiler/tests/while_test.py index 55d1f85370..4ee144beb7 100644 --- a/tensorflow/compiler/tests/while_test.py +++ b/tensorflow/compiler/tests/while_test.py @@ -18,7 +18,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -import os import numpy as np from tensorflow.compiler.tests import xla_test @@ -26,10 +25,7 @@ from tensorflow.compiler.tf2xla.python import xla from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes from tensorflow.python.framework import function -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 gradients_impl from tensorflow.python.platform import test @@ -129,105 +125,6 @@ class WhileTest(xla_test.XLATestCase): result = sess.run(loop_outputs, {init_index: 0}) self.assertAllClose(result, [10, 7], rtol=1e-3) - def _testMaxItersSimple(self): - if is_compile_on_demand(): - self.skipTest("list_ops are not supported in cpu_ondemand") - with self.cached_session() as sess, self.test_scope(): - xla_context = control_flow_ops.XLAControlFlowContext() - xla_context.Enter() - v = constant_op.constant(1.0) - p = array_ops.placeholder(dtype=dtypes.int32) - - def create_while_loop(): - iterations = array_ops.size(p, name="iterations") - r = control_flow_ops.while_loop( - lambda *_: True, - lambda i, x: (i + 1, v * x), (0, 1.0), - maximum_iterations=iterations, - name="outer") - return array_ops.identity(r[1]) - - output = create_while_loop() - output = gradients_impl.gradients(output, v)[0] - - result = sess.run(output, feed_dict={p: [0, 0, 0]}) - print(result) - xla_context.Exit() - - def testMaxItersSimple(self): - self.skipTest("Fails with v1 control flow") - # This fails with old control. - # self._testMaxItersSimple() - - @test_util.enable_control_flow_v2 - def testMaxItersSimpleV2(self): - self._testMaxItersSimple() - - def _testNestedWhileLoopWithMaxItersFromOuterContext(self): - if is_compile_on_demand(): - self.skipTest("list_ops are not supported in cpu_ondemand") - with self.cached_session() as sess, self.test_scope(): - xla_context = control_flow_ops.XLAControlFlowContext() - xla_context.Enter() - v = constant_op.constant(1.0) - p = array_ops.placeholder(dtype=dtypes.int32) - - def mid_body_builder(iterations): - - def mid_body(i, x): - r = control_flow_ops.while_loop( - lambda *_: True, - lambda i, x: (i + 1, v * x), (0, x), - maximum_iterations=iterations, - name="inner") - return (i + 1, gradients_impl.gradients(x + r[1], v)[0]) - - return mid_body - - def outer_body(i, x): - iterations = array_ops.size(p, name="iterations") - return (i + 1, x + control_flow_ops.while_loop( - lambda *_: True, - mid_body_builder(iterations), (0, x), - maximum_iterations=iterations, - name="mid")[1]) - - def create_while_loop(): - r = control_flow_ops.while_loop( - lambda *_: True, - outer_body, (0, 1.0), - maximum_iterations=5, - name="outer") - return array_ops.identity(r[1]) - - # p:placeholder - # j = 0 - # i, x = 0, 1. - # while j++ < 5: - # i1, x1 = 0, x - # while i1++ < len(p): - # i2, x2 = 0, x1 - # while i2++ < len(p): - # x2 = v * x2 - # x1 = grad(x1 + x2, v) - # x = x1 - # output = x - output = create_while_loop() - sess.run(output, feed_dict={p: [0, 0, 0]}) - xla_context.Exit() - - def testNestedWhileLoopWithMaxItersFromOuterContext(self): - self._testNestedWhileLoopWithMaxItersFromOuterContext() - - @test_util.enable_control_flow_v2 - def testNestedWhileLoopWithMaxItersFromOuterContextV2(self): - self._testNestedWhileLoopWithMaxItersFromOuterContext() - - -def is_compile_on_demand(): - return ("TF_XLA_FLAGS" in os.environ and - "tf_xla_compile_on_demand" in os.environ["TF_XLA_FLAGS"]) - - -if __name__ == "__main__": + +if __name__ == '__main__': test.main() diff --git a/tensorflow/compiler/tf2xla/const_analysis.cc b/tensorflow/compiler/tf2xla/const_analysis.cc index 6aff436da4..a57095f91e 100644 --- a/tensorflow/compiler/tf2xla/const_analysis.cc +++ b/tensorflow/compiler/tf2xla/const_analysis.cc @@ -20,26 +20,15 @@ limitations under the License. #include "absl/algorithm/container.h" #include "tensorflow/compiler/tf2xla/xla_op_registry.h" -#include "tensorflow/compiler/xla/status_macros.h" -#include "tensorflow/core/common_runtime/function.h" -#include "tensorflow/core/framework/attr_value.pb.h" -#include "tensorflow/core/framework/function.h" #include "tensorflow/core/framework/node_def_util.h" #include "tensorflow/core/graph/algorithm.h" -#include "tensorflow/core/lib/core/errors.h" namespace tensorflow { - -Status GetCompileTimeConstInputs(const Node* node, - std::vector* const_input_idxs, - FunctionLibraryRuntime* flib_runtime); - // Backwards dataflow analysis that finds arguments to a graph that must be // compile-time constants. Status BackwardsConstAnalysis(const Graph& g, std::vector* compile_time_const_arg_indices, std::vector* compile_time_const_nodes, - FunctionLibraryRuntime* flib_runtime, std::function edge_filter) { std::vector compile_time_const_nodes_impl; if (compile_time_const_nodes) { @@ -72,18 +61,7 @@ Status BackwardsConstAnalysis(const Graph& g, } for (const Edge* pred : node->in_edges()) { if (!pred->IsControlEdge() && edge_filter(*pred)) { - // If the src node of the `pred` is an IdentityN do not mark it as a - // compile-time const. Only mark the corresponding input to the - // IdentityN node as a const. - // Note: XLA IdentityN op simply forwards its inputs so this is safe. - while (edge_filter(*pred) && - pred->src()->type_string() == "IdentityN") { - status = pred->src()->input_edge(pred->src_output(), &pred); - if (!status.ok()) return; - } - if (edge_filter(*pred)) { - (*compile_time_const_nodes)[pred->src()->id()] = true; - } + (*compile_time_const_nodes)[pred->src()->id()] = true; } } return; @@ -91,29 +69,17 @@ Status BackwardsConstAnalysis(const Graph& g, // Mark any compile-time constant operator arguments as const. std::vector const_input_idxs; - status = GetCompileTimeConstInputs(node, &const_input_idxs, flib_runtime); + status = XlaOpRegistry::CompileTimeConstantInputs( + node->def(), node->op_def(), &const_input_idxs); if (!status.ok()) { return; } for (Edge const* edge : node->in_edges()) { - if (!edge->IsControlEdge() && - absl::c_binary_search(const_input_idxs, edge->dst_input()) && + if (absl::c_binary_search(const_input_idxs, edge->dst_input()) && edge_filter(*edge)) { - // Do not mark IdentityN nodes as compile-time const. - // If the src node of the `pred` is an IdentityN do not mark it as a - // compile-time const. Only mark the corresponding input to the - // IdentityN node as a const. - // Note: XLA IdentityN op simply forwards its inputs so this is safe. - while (edge_filter(*edge) && - edge->src()->type_string() == "IdentityN") { - status = edge->src()->input_edge(edge->src_output(), &edge); - if (!status.ok()) return; - } - if (edge_filter(*edge)) { - (*compile_time_const_nodes)[edge->src()->id()] = true; - } + (*compile_time_const_nodes)[edge->src()->id()] = true; } } }; @@ -125,61 +91,4 @@ Status BackwardsConstAnalysis(const Graph& g, return status; } -Status GetCompileTimeConstInputs(const Node* node, - std::vector* const_input_idxs, - FunctionLibraryRuntime* flib_runtime) { - if (node->type_string() != "While") { - return XlaOpRegistry::CompileTimeConstantInputs(node->def(), node->op_def(), - const_input_idxs); - } - // For While nodes, recurse into the body and cond graphs. - // TODO(b/124403063): Implement similar functionality for cond nodes and other - // functional ops. - NameAttrList cond_function; - TF_RETURN_IF_ERROR(GetNodeAttr(node->def(), "cond", &cond_function)); - NameAttrList body_function; - TF_RETURN_IF_ERROR(GetNodeAttr(node->def(), "body", &body_function)); - FunctionLibraryRuntime::Handle cond_handle; - FunctionLibraryRuntime::Handle body_handle; - TF_RETURN_IF_ERROR(flib_runtime->Instantiate( - cond_function.name(), AttrSlice(&cond_function.attr()), &cond_handle)); - TF_RETURN_IF_ERROR(flib_runtime->Instantiate( - body_function.name(), AttrSlice(&body_function.attr()), &body_handle)); - const FunctionBody* fcond = flib_runtime->GetFunctionBody(cond_handle); - const FunctionBody* fbody = flib_runtime->GetFunctionBody(body_handle); - TF_RET_CHECK(fcond); - TF_RET_CHECK(fbody); - int num_inputs = fbody->fdef.signature().input_arg_size(); - - // Stores which of the loop inputs are expected to be compile time constants. - std::vector compile_time_const_arg_indices(num_inputs); - TF_RETURN_IF_ERROR(BackwardsConstAnalysis( - *(fcond->graph), &compile_time_const_arg_indices, - /*compile_time_const_nodes=*/nullptr, flib_runtime)); - TF_RETURN_IF_ERROR(BackwardsConstAnalysis( - *(fbody->graph), &compile_time_const_arg_indices, - /*compile_time_const_nodes=*/nullptr, flib_runtime)); - for (int i = 0; i < num_inputs; i++) { - if (compile_time_const_arg_indices[i]) { - // Check that this input is actually a loop invariant. - // NOTE(srbs): Ideally this should raise an error if the loop body - // requires the input at this index to be a compile time const but it is - // not a loop invariant. However, that causes problems because const - // analysis is performed for the entire graph (in the - // MarkForCompilationPass for example) and not just for the ops - // that will actually be run using XLA kernels. So we silently return here - // and let the error be raised during the actual compilation of the - // XLA graph. - Node* arg_i = fbody->arg_nodes[i]; - Node* ret_i = fbody->ret_nodes[i]; - const Node* ret_i_input_0; - TF_RETURN_IF_ERROR(ret_i->input_node(0, &ret_i_input_0)); - if (ret_i_input_0->id() == arg_i->id()) { - const_input_idxs->push_back(i); - } - } - } - return Status::OK(); -} - } // namespace tensorflow diff --git a/tensorflow/compiler/tf2xla/const_analysis.h b/tensorflow/compiler/tf2xla/const_analysis.h index 1663cbff41..49b3c6d413 100644 --- a/tensorflow/compiler/tf2xla/const_analysis.h +++ b/tensorflow/compiler/tf2xla/const_analysis.h @@ -34,13 +34,11 @@ namespace tensorflow { // `compile_time_const_nodes`, if `compile_time_const_nodes` is not null. // // Only propagate const-ness along edges for which `edge_filter` returns true. -Status BackwardsConstAnalysis( - const Graph& g, std::vector* compile_time_const_arg_indices, - std::vector* compile_time_const_nodes, - FunctionLibraryRuntime* flib_runtime, - std::function edge_filter = [](const Edge& e) { - return true; - }); +Status BackwardsConstAnalysis(const Graph& g, + std::vector* compile_time_const_arg_indices, + std::vector* compile_time_const_nodes, + std::function edge_filter = + [](const Edge& e) { return true; }); } // namespace tensorflow diff --git a/tensorflow/compiler/tf2xla/const_analysis_test.cc b/tensorflow/compiler/tf2xla/const_analysis_test.cc index ed5f004550..40c6d0e017 100644 --- a/tensorflow/compiler/tf2xla/const_analysis_test.cc +++ b/tensorflow/compiler/tf2xla/const_analysis_test.cc @@ -44,8 +44,8 @@ TEST(ConstAnalysisTest, Basics) { std::vector const_args(4, false); std::vector const_nodes(root.graph()->num_node_ids(), false); - TF_ASSERT_OK(BackwardsConstAnalysis(*root.graph(), &const_args, &const_nodes, - /*flib_runtime=*/nullptr)); + TF_ASSERT_OK( + BackwardsConstAnalysis(*root.graph(), &const_args, &const_nodes)); // Arg 0 doesn't need to be constant since the graph only uses its shape. // Arg 1 must be constant because it flows to the shape argument of a Reshape. @@ -82,8 +82,7 @@ TEST(ConstAnalysisTest, TopologicalOrder) { std::vector const_args(3, false); TF_ASSERT_OK(BackwardsConstAnalysis(graph, &const_args, - /*compile_time_const_nodes=*/nullptr, - /*flib_runtime=*/nullptr)); + /*compile_time_const_nodes=*/nullptr)); EXPECT_EQ(const_args, std::vector({true, true, false})); } @@ -104,8 +103,7 @@ TEST(ConstAnalysisTest, DontFollowControlDependencies) { std::vector const_args(2, false); TF_ASSERT_OK(BackwardsConstAnalysis(graph, &const_args, - /*compile_time_const_nodes=*/nullptr, - /*flib_runtime=*/nullptr)); + /*compile_time_const_nodes=*/nullptr)); EXPECT_EQ(const_args, std::vector({false, true})); } @@ -130,8 +128,7 @@ TEST(ConstAnalysisTest, RespectExplicitAttr_0) { std::vector const_args(2, false); TF_ASSERT_OK(BackwardsConstAnalysis(graph, &const_args, - /*compile_time_const_nodes=*/nullptr, - /*flib_runtime=*/nullptr)); + /*compile_time_const_nodes=*/nullptr)); EXPECT_EQ(const_args, std::vector({false, false})); } @@ -155,8 +152,7 @@ TEST(ConstAnalysisTest, RespectExplicitAttr_1) { std::vector const_args(1, false); TF_ASSERT_OK(BackwardsConstAnalysis(graph, &const_args, - /*compile_time_const_nodes=*/nullptr, - /*flib_runtime=*/nullptr)); + /*compile_time_const_nodes=*/nullptr)); EXPECT_EQ(const_args, std::vector({true})); } diff --git a/tensorflow/compiler/tf2xla/graph_compiler.cc b/tensorflow/compiler/tf2xla/graph_compiler.cc index b3cb23003e..5e4699bbb6 100644 --- a/tensorflow/compiler/tf2xla/graph_compiler.cc +++ b/tensorflow/compiler/tf2xla/graph_compiler.cc @@ -56,9 +56,9 @@ Status PrepareArguments(XlaOpKernelContext* ctx, Graph* graph, auto client = ctx->compiler()->client(); std::vector arg_must_be_compile_time_constant(expressions.size()); - TF_RETURN_IF_ERROR(BackwardsConstAnalysis( - *graph, &arg_must_be_compile_time_constant, - /*compile_time_const_nodes=*/nullptr, ctx->function_library())); + TF_RETURN_IF_ERROR( + BackwardsConstAnalysis(*graph, &arg_must_be_compile_time_constant, + /*compile_time_const_nodes=*/nullptr)); args->resize(expressions.size()); for (int i = 0; i < args->size(); ++i) { @@ -284,7 +284,6 @@ void GraphCompiler::PartiallySetupParams(OpKernelContext::Params* params) { params->inputs = &tensor_inputs_; params->step_container = step_container_; params->resource_manager = device_->resource_manager(); - params->function_library = flib_; } } // namespace tensorflow diff --git a/tensorflow/compiler/tf2xla/kernels/BUILD b/tensorflow/compiler/tf2xla/kernels/BUILD index 316a662751..343568b239 100644 --- a/tensorflow/compiler/tf2xla/kernels/BUILD +++ b/tensorflow/compiler/tf2xla/kernels/BUILD @@ -251,7 +251,6 @@ tf_kernel_library( "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core:protos_all_cc", - "@com_google_absl//absl/strings", ], ) diff --git a/tensorflow/compiler/tf2xla/kernels/while_op.cc b/tensorflow/compiler/tf2xla/kernels/while_op.cc index 1943793688..f49da9683b 100644 --- a/tensorflow/compiler/tf2xla/kernels/while_op.cc +++ b/tensorflow/compiler/tf2xla/kernels/while_op.cc @@ -15,7 +15,6 @@ limitations under the License. #include "tensorflow/compiler/tf2xla/kernels/while_op.h" -#include "absl/strings/str_split.h" #include "tensorflow/compiler/tf2xla/shape_util.h" #include "tensorflow/compiler/tf2xla/side_effect_util.h" #include "tensorflow/compiler/tf2xla/type_util.h" @@ -26,7 +25,6 @@ limitations under the License. #include "tensorflow/compiler/xla/client/xla_builder.h" #include "tensorflow/compiler/xla/client/xla_computation.h" #include "tensorflow/compiler/xla/literal.h" -#include "tensorflow/core/framework/attr_value.pb.h" #include "tensorflow/core/framework/function.h" #include "tensorflow/core/framework/op_kernel.h" @@ -91,151 +89,6 @@ Status MakeXlaCompilerArgumentsFromInputs( return Status::OK(); } -// Populates loop invariant indices to true in `loop_invariants`. -void GetLoopInvariants(XlaOpKernelContext* ctx, - const NameAttrList& body_name_attr, - std::vector* const loop_invariants) { - const FunctionBody* body; - OP_REQUIRES_OK(ctx, ctx->compiler()->FindFunctionBody(body_name_attr, &body)); - for (int i = 0; i < body->ret_nodes.size(); i++) { - const Node* arg = body->arg_nodes[i]; - const Node* ret = body->ret_nodes[i]; - const Node* ret_input_0; - OP_REQUIRES_OK(ctx, ret->input_node(0, &ret_input_0)); - (*loop_invariants)[i] = ret_input_0->id() == arg->id(); - } -} - -// Converts entries in `args` which are loop invariants and have compile -// time constant inputs to constants so that they can be propagated in the loop -// body. -Status ConvertLoopInvariantsToConst( - XlaOpKernelContext* ctx, const NameAttrList& body_name_attr, - std::vector* args, - std::vector* compile_time_const_arg_indices, - int* num_compile_time_const_args, xla::Client* client) { - std::vector loop_invariants(ctx->num_inputs()); - GetLoopInvariants(ctx, body_name_attr, &loop_invariants); - for (int i = 0; i < ctx->num_inputs(); i++) { - XlaCompiler::Argument& arg = (*args)[i]; - const XlaExpression& expression = ctx->InputExpression(i); - // If this is a loop invariant and the input tensor is a compile time - // constant build a kConstant type argument. - if (arg.kind != XlaCompiler::Argument::kResource && loop_invariants[i]) { - // NOTE: We can not simple check that this is Kind::kConstant because - // this could be the output of a MetadataOnly op e.g. Size. - xla::StatusOr> maybe_constant = - expression.ResolveConstant(client); - if (maybe_constant.ok() && maybe_constant.ValueOrDie().has_value()) { - arg.kind = XlaCompiler::Argument::kConstant; - arg.type = expression.dtype(); - arg.constant_value = std::move(maybe_constant.ValueOrDie().value()); - arg.shape = expression.GetShape().ValueOrDie(); - compile_time_const_arg_indices->at(i) = true; - (*num_compile_time_const_args)++; - } - } - } - return Status::OK(); -} - -Status VerifyBodyInputAndOutputShapeMatch( - XlaOpKernelContext* ctx, - const std::vector& compile_time_const_arg_indices, - const XlaCompiler::CompilationResult& body, bool has_token_input_output) { - xla::Shape body_input_shape = body.xla_input_shapes[0]; - xla::Shape body_output_shape; - body_output_shape.set_element_type(xla::TUPLE); - for (int i = 0; i < ctx->num_outputs(); i++) { - if (!compile_time_const_arg_indices[i]) { - *(body_output_shape.add_tuple_shapes()) = - body.xla_output_shape.tuple_shapes(i); - } - } - // If `body` has a token output, append its shape to `body_output_shape`. - if (has_token_input_output) { - *(body_output_shape.add_tuple_shapes()) = - body.xla_output_shape.tuple_shapes(ctx->num_inputs()); - } - if (!xla::ShapeUtil::Compatible(body_input_shape, body_output_shape)) { - return errors::InvalidArgument( - "Input and output shapes of loop body do not match: ", - xla::ShapeUtil::HumanString(body_input_shape), " vs. ", - xla::ShapeUtil::HumanString(body_output_shape)); - } - return Status::OK(); -} - -xla::StatusOr BuildWrappedCond( - XlaOpKernelContext* ctx, const XlaCompiler::CompilationResult& cond) { - xla::Shape cond_input_shape = cond.xla_input_shapes[0]; - std::unique_ptr cb = - ctx->builder()->CreateSubBuilder("cond_wrapper"); - auto inputs = xla::Parameter(cb.get(), 0, cond_input_shape, "inputs"); - auto outputs = xla::Call(cb.get(), *cond.computation, {inputs}); - xla::GetTupleElement(outputs, 0); - return cb->Build(); -} - -xla::StatusOr BuildWrappedBody( - XlaOpKernelContext* ctx, const XlaCompiler::CompilationResult& body, - const std::vector& compile_time_const_arg_indices, - bool has_token_input_output) { - xla::XlaComputation body_wrapper; - std::unique_ptr cb = - ctx->builder()->CreateSubBuilder("body_wrapper"); - xla::Shape body_input_shape = body.xla_input_shapes[0]; - auto inputs = xla::Parameter(cb.get(), 0, body_input_shape, "inputs"); - // Call the original body function which has mismatched inputs and outputs - // and strip the compile time consts from the list of outputs. While requires - // the inputs and outputs of its body function to match. - auto outputs = xla::Call(cb.get(), *body.computation, {inputs}); - std::vector non_compile_time_const_outputs; - for (int i = 0; i < compile_time_const_arg_indices.size(); i++) { - if (!compile_time_const_arg_indices[i]) { - non_compile_time_const_outputs.push_back( - xla::GetTupleElement(outputs, i)); - } - } - // If `body` has a token output, append it to - // `non_compile_time_const_outputs`. - if (has_token_input_output) { - non_compile_time_const_outputs.push_back( - xla::GetTupleElement(outputs, ctx->num_outputs())); - } - xla::Tuple(cb.get(), non_compile_time_const_outputs); - return cb->Build(); -} - -xla::XlaOp BuildWhile(XlaOpKernelContext* ctx, - const xla::XlaComputation& wrapped_cond, - const xla::XlaComputation& wrapped_body, - const xla::XlaOp& initial_values, - const std::vector& input_mapping, - const std::vector& compile_time_const_arg_indices, - int num_compile_time_const_args, - bool has_token_input_output) { - xla::XlaOp while_result = - xla::While(wrapped_cond, wrapped_body, initial_values); - std::vector padded_while_outputs(ctx->num_outputs()); - int while_result_index = 0; - for (int i = 0; i < ctx->num_inputs(); i++) { - if (!compile_time_const_arg_indices[i]) { - padded_while_outputs[input_mapping[while_result_index]] = - xla::GetTupleElement(while_result, while_result_index); - while_result_index++; - } else { - padded_while_outputs[i] = ctx->Input(i); - } - } - // If `body` has a token output, append it to `padded_while_outputs`. - if (has_token_input_output) { - padded_while_outputs.push_back(xla::GetTupleElement( - while_result, ctx->num_inputs() - num_compile_time_const_args)); - } - return xla::Tuple(ctx->builder(), padded_while_outputs); -} - } // anonymous namespace XlaWhileOp::XlaWhileOp(OpKernelConstruction* ctx) : XlaOpKernel(ctx) { @@ -264,23 +117,6 @@ void XlaWhileOp::Compile(XlaOpKernelContext* ctx) { xla::XlaBuilder* builder = ctx->builder(); XlaCompiler* compiler = ctx->compiler(); - // Indices of loop vars which satisfy the following conditions: - // 1. They are loop invariants. - // 2. The op inputs at these indices are compile time constants. - // - // These compile time consts do not appear as _Args in the cond/body functions - // and are replaced by kConstant nodes instead. As as result, the compiled - // body function does not have matching input and output shape. We fix this - // by rewriting the body computation (see body_wrapper below) to output - // just the non compile-time-const values and later pad up the while output - // with the const args. - std::vector compile_time_const_arg_indices(ctx->num_inputs()); - int num_compile_time_const_args = 0; - OP_REQUIRES_OK(ctx, ConvertLoopInvariantsToConst( - ctx, body_name_attr_, &arguments, - &compile_time_const_arg_indices, - &num_compile_time_const_args, compiler->client())); - VLOG(1) << "Compiling body"; // All resource that are inputs to the loop's body must also be @@ -396,13 +232,12 @@ void XlaWhileOp::Compile(XlaOpKernelContext* ctx) { "Input shapes of loop body and condition do not match: ", xla::ShapeUtil::HumanString(body_input_shape), " vs. ", xla::ShapeUtil::HumanString(cond_input_shape))); - - // Check that the shape of the body outputs excluding the compile time const - // args (which are pruned from the body outputs in body_wapper) matches the - // shape of the inputs. - OP_REQUIRES_OK(ctx, VerifyBodyInputAndOutputShapeMatch( - ctx, compile_time_const_arg_indices, body, - has_token_input_output_)); + OP_REQUIRES( + ctx, xla::ShapeUtil::Compatible(body_input_shape, body.xla_output_shape), + errors::InvalidArgument( + "Input and output shapes of loop body do not match: ", + xla::ShapeUtil::HumanString(body_input_shape), " vs. ", + xla::ShapeUtil::HumanString(body.xla_output_shape))); xla::Shape expected_cond_output_shape_without_side_effect = xla::ShapeUtil::MakeTupleShape( @@ -440,7 +275,7 @@ void XlaWhileOp::Compile(XlaOpKernelContext* ctx) { OP_REQUIRES_OK(ctx, ctx->GetResourceInput(input_num, &resource)); OP_REQUIRES_OK(ctx, resource->Pack(&inputs[i], builder)); } else { - inputs[i] = ctx->Input(input_num); + inputs[i] = ctx->Input(i); } } @@ -449,27 +284,26 @@ void XlaWhileOp::Compile(XlaOpKernelContext* ctx) { VLOG(1) << "Building while loop"; // Wraps the condition in a computation that unpacks the output tuple. - xla::StatusOr cond_result = BuildWrappedCond(ctx, cond); - OP_REQUIRES_OK(ctx, cond_result.status()); - xla::XlaComputation wrapped_cond = std::move(cond_result.ValueOrDie()); - - // Remove compile time const args from the list of body outputs. - xla::StatusOr body_result = BuildWrappedBody( - ctx, body, compile_time_const_arg_indices, has_token_input_output_); - OP_REQUIRES_OK(ctx, body_result.status()); - xla::XlaComputation wrapped_body = std::move(body_result.ValueOrDie()); - - // Builds the While op and pads its output with the compile time const args. - xla::XlaOp while_result = - BuildWhile(ctx, wrapped_cond, wrapped_body, init, body.input_mapping, - compile_time_const_arg_indices, num_compile_time_const_args, - has_token_input_output_); + xla::XlaComputation cond_wrapper; + { + std::unique_ptr cb = + builder->CreateSubBuilder("cond_wrapper"); + auto inputs = xla::Parameter(cb.get(), 0, cond_input_shape, "inputs"); + auto outputs = xla::Call(cb.get(), *cond.computation, {inputs}); + xla::GetTupleElement(outputs, 0); + xla::StatusOr result = cb->Build(); + OP_REQUIRES_OK(ctx, result.status()); + cond_wrapper = std::move(result.ValueOrDie()); + } + + xla::XlaOp while_result = xla::While(cond_wrapper, *body.computation, init); // Sets non-variable outputs and determine when resource variables start. int resource_index = 0; for (int i = 0; i < ctx->num_outputs(); ++i) { if (ctx->input_type(i) != DT_RESOURCE) { - ctx->SetOutput(i, xla::GetTupleElement(while_result, i)); + ctx->SetOutput(body.input_mapping[i], + xla::GetTupleElement(while_result, i)); ++resource_index; } else { break; diff --git a/tensorflow/compiler/tf2xla/xla_compiler.h b/tensorflow/compiler/tf2xla/xla_compiler.h index f6fd877d9c..ad3144b41b 100644 --- a/tensorflow/compiler/tf2xla/xla_compiler.h +++ b/tensorflow/compiler/tf2xla/xla_compiler.h @@ -416,11 +416,11 @@ class XlaCompiler { Status SetNodeToken(const string& node_name, const xla::XlaOp& op); xla::StatusOr GetNodeToken(const string& node_name); + private: // Sets the function body `fbody` to the one registered as `function`. Status FindFunctionBody(const NameAttrList& function, const FunctionBody** fbody); - private: // Returns the optimized graph object in this function body. std::unique_ptr GetGraph(const FunctionBody* fbody); diff --git a/tensorflow/python/keras/BUILD b/tensorflow/python/keras/BUILD index 917acf1561..8743dbaa13 100755 --- a/tensorflow/python/keras/BUILD +++ b/tensorflow/python/keras/BUILD @@ -858,21 +858,6 @@ tf_py_test( ], ) -tf_py_test( - name = "time_distributed_learning_phase_test", - size = "small", - srcs = ["layers/time_distributed_learning_phase_test.py"], - additional_deps = [ - ":keras", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], - tags = [ - "noasan", # http://b/78599823 - "notsan", - ], -) - tf_py_test( name = "scikit_learn_test", size = "small", diff --git a/tensorflow/python/keras/layers/time_distributed_learning_phase_test.py b/tensorflow/python/keras/layers/time_distributed_learning_phase_test.py deleted file mode 100644 index 2c38f25d33..0000000000 --- a/tensorflow/python/keras/layers/time_distributed_learning_phase_test.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright 2019 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 layer wrappers.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import numpy as np - -from tensorflow.python import keras -from tensorflow.python.platform import test - - -# TODO(b/125513261): Move this back into wrappers_test.py. -class TimeDistributedLearningPhaseTest(test.TestCase): - - def test_TimeDistributed_learning_phase(self): - with self.cached_session(): - # test layers that need learning_phase to be set - np.random.seed(1234) - x = keras.layers.Input(shape=(3, 2)) - y = keras.layers.TimeDistributed(keras.layers.Dropout(.999))( - x, training=True) - model = keras.models.Model(x, y) - y = model.predict(np.random.random((10, 3, 2))) - self.assertAllClose(np.mean(y), 0., atol=1e-1, rtol=1e-1) - - -if __name__ == '__main__': - test.main() diff --git a/tensorflow/python/keras/layers/wrappers_test.py b/tensorflow/python/keras/layers/wrappers_test.py index 8fa0fd7de2..8fa0e7bdac 100644 --- a/tensorflow/python/keras/layers/wrappers_test.py +++ b/tensorflow/python/keras/layers/wrappers_test.py @@ -155,6 +155,17 @@ class TimeDistributedTest(test.TestCase): model.compile(optimizer='rmsprop', loss='mse') self.assertEqual(len(model.losses), 1) + def test_TimeDistributed_learning_phase(self): + with self.cached_session(): + # test layers that need learning_phase to be set + np.random.seed(1234) + x = keras.layers.Input(shape=(3, 2)) + y = keras.layers.TimeDistributed(keras.layers.Dropout(.999))( + x, training=True) + model = keras.models.Model(x, y) + y = model.predict(np.random.random((10, 3, 2))) + self.assertAllClose(np.mean(y), 0., atol=1e-1, rtol=1e-1) + def test_TimeDistributed_batchnorm(self): with self.cached_session(): # test that wrapped BN updates still work. diff --git a/tensorflow/python/kernel_tests/control_flow_ops_py_test.py b/tensorflow/python/kernel_tests/control_flow_ops_py_test.py index 1b13546b03..982ead7e94 100644 --- a/tensorflow/python/kernel_tests/control_flow_ops_py_test.py +++ b/tensorflow/python/kernel_tests/control_flow_ops_py_test.py @@ -1411,6 +1411,7 @@ class ControlFlowTest(test.TestCase): r"while loop context '' \(currently defined in 'cond/.+'\)"): _ = gradients_impl.gradients(loop, v) + @test_util.disable_control_flow_v2("b/123601232") @test_util.run_v1_only("b/120545219") def testNestedWhileLoopWithMaxItersFromOuterContextInXLAContext(self): v = constant_op.constant(1.0) @@ -1455,14 +1456,12 @@ class ControlFlowTest(test.TestCase): with self.session(use_gpu=False) as sess: opts = config_pb2.RunOptions(trace_level=config_pb2.RunOptions.FULL_TRACE) - run_metadata_without_xla_context = config_pb2.RunMetadata() run_metadata = config_pb2.RunMetadata() final_value_without_xla_context = sess.run( - final_without_xla_context, - feed_dict={p: [0, 0, 0]}, - options=opts, - run_metadata=run_metadata_without_xla_context) + final_without_xla_context, feed_dict={ + p: [0, 0, 0] + }) final_value_with_xla_context = sess.run( final_with_xla_context, @@ -1470,18 +1469,9 @@ class ControlFlowTest(test.TestCase): options=opts, run_metadata=run_metadata) - if control_flow_util.ENABLE_CONTROL_FLOW_V2: - # With while_v2 on xla, run_metadata only contains the unlowered While - # op so node_stats does not have statistics for the pushes. So as a - # loose check we check the pushes in the lowered version. - node_stats = run_metadata_without_xla_context.step_stats.dev_stats[ - 0].node_stats - stack_push_op = "TensorListPushBack" - else: - node_stats = run_metadata.step_stats.dev_stats[0].node_stats - stack_push_op = "StackPushV2" + node_stats = run_metadata.step_stats.dev_stats[0].node_stats stack_push_count = len( - [x for x in node_stats if x.node_name.endswith(stack_push_op)]) + [x for x in node_stats if x.node_name.endswith("StackPushV2")]) # Pushes to the stack = product of maximum_iterations values; # the last two "3"s comes from size(p), when p == [0, 0, 0]. self.assertEqual(stack_push_count, 5 * 3 * 3) diff --git a/tensorflow/python/kernel_tests/while_v2_test.py b/tensorflow/python/kernel_tests/while_v2_test.py index f406624fc6..2c6d275900 100644 --- a/tensorflow/python/kernel_tests/while_v2_test.py +++ b/tensorflow/python/kernel_tests/while_v2_test.py @@ -283,8 +283,8 @@ class WhileV2Test(test.TestCase, parameterized.TestCase): while_op = op body_graph = while_v2._get_graph(while_op, "body") - x_input_index = [i for i, inp in enumerate(while_op.inputs) if inp == x][0] - x_input_t = body_graph.inputs[x_input_index] + # body_graph.inputs: [counter_arg, x_arg, tl_arg, *accumulators] + x_input_t = body_graph.inputs[1] accumulator_count = len( [c for c in x_input_t.consumers() if c.type == "TensorListPushBack"]) self.assertEqual(accumulator_count, 1) @@ -331,13 +331,12 @@ class WhileV2Test(test.TestCase, parameterized.TestCase): while_op = ret[0].op.inputs[0].op # Gradient pass. grad = gradients_impl.gradients(ret[0], x) - # Note: There is an Identity b/w grad[0] and the While op. grad_while_op = grad[0].op.inputs[0].op # Get the TensorList output of While op containing the accumulated values # of y. - x_input_index = [i for i, inp in enumerate(while_op.inputs) if x == inp][0] - output = GetAccumulatorForInputAtIndex(while_op, x_input_index) + # while_op.inputs: [counter_arg, x_arg, y_arg, *accumulators] + output = GetAccumulatorForInputAtIndex(while_op, 2) _, val = list_ops.tensor_list_pop_back(output, element_dtype=dtypes.float32) MatchShape(val.shape) @@ -348,9 +347,8 @@ class WhileV2Test(test.TestCase, parameterized.TestCase): # Get the TensorList output of gradient While op containing the accumulated # values of grad_x (note that grad_x is needed by the second derivative). # grad_while_op.inputs: - grad_output_index = grad_while_op.outputs.index(grad[0].op.inputs[0]) - grad_output = GetAccumulatorForInputAtIndex(grad_while_op, - grad_output_index) + # [counter_arg, total_iters_arg, grad_x_arg, grad_y_arg, *other_args] + grad_output = GetAccumulatorForInputAtIndex(grad_while_op, 2) _, val = list_ops.tensor_list_pop_back(grad_output, element_dtype=dtypes.float32) MatchShape(val.shape) @@ -449,17 +447,17 @@ class WhileV2Test(test.TestCase, parameterized.TestCase): [x])[0] while_op = output.op.inputs[0].op self.assertEqual(while_op.type, "While") - # outputs = [loop_counter, max_iters, x] - self.assertLen(while_op.outputs, 3) + # outputs = [loop_counter, x] + self.assertLen(while_op.outputs, 2) gradients_impl.gradients(output, x) # while_op should have been rewritten to output 2.0 intermediate. - # outputs = [loop_counter, max_iters, x, 2.0_accumulator, x_accumulator] - self.assertLen(while_op.outputs, 5) + # outputs = [loop_counter, x, 2.0_accumulator, x_accumulator] + self.assertLen(while_op.outputs, 4) gradients_impl.gradients(output, x) # Computing the gradient again shouldn't rewrite while_op again. - self.assertLen(while_op.outputs, 5) + self.assertLen(while_op.outputs, 4) def ScalarShape(): diff --git a/tensorflow/python/ops/while_v2.py b/tensorflow/python/ops/while_v2.py index 4a42e94748..68f1cbae0a 100644 --- a/tensorflow/python/ops/while_v2.py +++ b/tensorflow/python/ops/while_v2.py @@ -23,6 +23,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +from tensorflow.core.framework import attr_value_pb2 from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes from tensorflow.python.framework import func_graph as func_graph_module @@ -30,6 +31,7 @@ from tensorflow.python.framework import function_def_to_graph from tensorflow.python.framework import ops from tensorflow.python.framework import tensor_shape from tensorflow.python.framework import tensor_spec +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 control_flow_util @@ -62,6 +64,7 @@ def while_loop(cond, name=None, return_same_structure=True): """Like tf.while_loop, except emits a single While op.""" + maximum_iterations = _validate_and_convert_to_tensor(maximum_iterations) # Keep the original loop_vars around to know which args were TensorArrays. orig_loop_vars = loop_vars # Cache its length since we use it at multiple places below. @@ -85,25 +88,24 @@ def while_loop(cond, with ops.name_scope(None): cond_name = util.unique_fn_name(scope, "cond") body_name = util.unique_fn_name(scope, "body") - maximum_iterations_loop_var = _build_maximum_iterations_loop_var( - maximum_iterations) + loop_counter = constant_op.constant( 0, - dtype=maximum_iterations_loop_var.dtype + dtype=maximum_iterations.dtype if maximum_iterations is not None else None, name="loop_counter") # Add loop counter needed for computing gradients. - loop_vars = [loop_counter, maximum_iterations_loop_var] + loop_vars + loop_vars = [loop_counter] + loop_vars - shape_invariants = type(shape_invariants)( - [tensor_shape.scalar(), tensor_shape.scalar()]) + shape_invariants + shape_invariants = type(shape_invariants)([tensor_shape.scalar() + ]) + shape_invariants # Automatic control dependencies are added in defuns, but not in v1 # graphs. Propagate that behavior here. add_control_dependencies = ops.get_default_graph()._add_control_dependencies # Build a `cond` wrapper that can handle the extra counter loop_var. - def wrapped_cond(loop_counter, maximum_iterations_arg, *args): + def wrapped_cond(loop_counter, *args): # Convert the flow variables in `args` to TensorArrays. `args` should # already have the same structure as `orig_loop_vars` but currently there # is no nest.zip so we call `_pack_sequence_as` which flattens both @@ -113,7 +115,7 @@ def while_loop(cond, return cond(*_pack_sequence_as(orig_loop_vars, args)) else: return math_ops.logical_and( - loop_counter < maximum_iterations_arg, + loop_counter < maximum_iterations, cond(*_pack_sequence_as(orig_loop_vars, args))) # NOTE(skyewm): we set collections to the outer graph's collections for @@ -128,12 +130,11 @@ def while_loop(cond, cond_name, collections=ops.get_default_graph()._collections), # pylint: disable=protected-access add_control_dependencies=add_control_dependencies) - def wrapped_body(loop_counter, maximum_iterations_arg, *args): + def wrapped_body(loop_counter, *args): """Loop body augmented with counter update. Args: loop_counter: Loop counter which needs to be incremented in the body. - maximum_iterations_arg: Maximum iterations of the loop. *args: List of args Returns: @@ -160,7 +161,7 @@ def while_loop(cond, # TODO(srbs): Update lowering code to create _Enter nodes with # is_constant=True for inputs that are directly passed to outputs. - return [loop_counter + 1, maximum_iterations_arg] + list(outputs) + return [loop_counter + 1] + list(outputs) body_graph = func_graph_module.func_graph_from_py_func( body_name, @@ -194,16 +195,10 @@ def while_loop(cond, # shape invariants, or the shapes of the loop vars if the invariants are not # specified. num_flattened_outputs = len(nest.flatten(orig_loop_vars)) - # First var is loop counter and second var is maximum_iterations. - first_loop_var_index = 2 _check_shapes_compat( - body_graph.outputs[first_loop_var_index:first_loop_var_index + - num_flattened_outputs], - nest.flatten( - shape_invariants[first_loop_var_index:first_loop_var_index + - len_orig_loop_vars]), - nest.flatten(loop_vars[first_loop_var_index:first_loop_var_index + - len_orig_loop_vars])) + body_graph.outputs[1:1 + num_flattened_outputs], + nest.flatten(shape_invariants[1:1 + len_orig_loop_vars]), + nest.flatten(loop_vars[1:1 + len_orig_loop_vars])) flattened_loop_vars = nest.flatten(loop_vars) _check_num_inputs_outputs(cond_graph, body_graph, len(flattened_loop_vars)) @@ -218,6 +213,7 @@ def while_loop(cond, _copy_handle_data(body_graph.outputs, outputs) util.maybe_set_lowering_attr(outputs[0].op) + _maybe_set_maximum_iterations_attr(outputs[0].op, maximum_iterations) # Return identities for each output of the While op, rather than the output # of the While op directly. This makes pruning work if the output of @@ -227,9 +223,9 @@ def while_loop(cond, # identity op will end up with only the appropriate exit op as input. outputs = tuple(array_ops.identity(t) for t in outputs) - outputs = _pack_sequence_as( - orig_loop_vars, outputs[first_loop_var_index:first_loop_var_index + - num_flattened_outputs]) + # First var is loop counter. + outputs = _pack_sequence_as(orig_loop_vars, + outputs[1:1 + num_flattened_outputs]) if return_same_structure: return outputs @@ -253,8 +249,11 @@ def _WhileGrad(op, *grads): # pylint: disable=invalid-name body_graph = _get_graph(while_op, "body") orig_num_params = len(body_graph.outputs) - maximum_iterations = op.inputs[1] + maximum_iterations = op.get_attr( + "_maximum_iterations") if _is_in_xla_context() else None parallel_iterations = op.get_attr("parallel_iterations") + assert not _is_in_xla_context() or maximum_iterations is not None + maximum_iterations = _validate_and_convert_to_tensor(maximum_iterations) grads = [_preprocess_grad(grad, body_out, while_out) for grad, body_out, while_out @@ -299,9 +298,8 @@ def _WhileGrad(op, *grads): # pylint: disable=invalid-name loop_vars = while_v2_indexed_slices_rewriter.rewrite_grad_indexed_slices( grads, body_grad_graph, loop_vars, while_op.inputs) - def grad_cond(counter, unused_maximum_iterations_arg, forward_loop_iters, - *unused_args): - return counter < forward_loop_iters + def grad_cond(counter, max_iters, *unused_args): + return counter < max_iters grad_cond_name = util.unique_grad_fn_name(op.get_attr("cond").name) cond_grad_graph = func_graph_module.func_graph_from_py_func( @@ -321,6 +319,7 @@ def _WhileGrad(op, *grads): # pylint: disable=invalid-name _copy_handle_data(body_grad_graph.outputs, outputs) util.maybe_set_lowering_attr(grad_op) + _maybe_set_maximum_iterations_attr(grad_op, maximum_iterations) # See comment in while_loop. outputs = [array_ops.identity(t) for t in outputs] @@ -388,6 +387,48 @@ def _is_trainable(tensor): return True +def _validate_and_convert_to_tensor(maximum_iterations): + """Checks that `maximum_iterations` is valid. + + In XLA context, `maximum_iterations` is required and must be statically + inferable, e.g. output tensor of a Const node. + + Args: + maximum_iterations: The maximum_iterations passed to while_loop. + + Returns: + A scalar valued tensor of type int32 or None. + + Raises: + ValueError: If `maximum_iterations` is invalid. + """ + if maximum_iterations is None: + return None + + if _is_in_xla_context() and isinstance(maximum_iterations, ops.Tensor): + # Get the constant value from the `maximum_iterations` tensor to avoid + # capturing a Const tensor from outside this graph. + value = tensor_util.constant_value(maximum_iterations) + if value is None: + # XLA requires maximum_iterations to be statically known (e.g. a + # constant value or known shape dimension) when intermediate values + # from the forward pass are needed in the gradients pass. However, + # maximum_iterations may not be required if the gradient isn't built + # or no intermediates are required, thus we return the tensor as is. + return maximum_iterations + + maximum_iterations = value + + # EmptyTensorList expects `max_num_elements` to be of type int32. + maximum_iterations = ops.convert_to_tensor( + maximum_iterations, dtype=dtypes.int32, name="maximum_iterations") + if maximum_iterations.shape.ndims != 0: + raise ValueError("maximum_iterations must be a scalar, saw shape: %s" % + maximum_iterations.shape) + + return maximum_iterations + + # TODO(srbs): Pull this into common utils for cond_v2 and while_v2. def _get_graph(while_op, func_attr_name): """Returns `FuncGraph` for the given function attribute. @@ -417,7 +458,7 @@ def _get_graph(while_op, func_attr_name): def _create_grad_func(ys, xs, grads, cond_graph, body_graph, name, while_op, - maximum_iterations): + max_iters): """Builds and returns the gradient FuncGraph of `func_graph` and its args. The returned grad_func_graph must be called with the returned @@ -431,7 +472,7 @@ def _create_grad_func(ys, xs, grads, cond_graph, body_graph, name, while_op, body_graph: FuncGraph for the forward body function. name: Name of the returned gradient function. while_op: The forward While op. - maximum_iterations: Tensor. The maximum number of iterations. + max_iters: the maximum number of iterations, or None if no limit. Returns: 2-tuple of (grad_func_graph, args). @@ -442,7 +483,7 @@ def _create_grad_func(ys, xs, grads, cond_graph, body_graph, name, while_op, counter = constant_op.constant( 0, dtype=total_iters.dtype, name="grad_counter") - args = [counter, maximum_iterations, total_iters] + list(grads) + args = [counter, total_iters] + list(grads) # Note: The returned function does not have `args` in the list of # `external_captures`. grad_func_graph = func_graph_module.func_graph_from_py_func( @@ -450,7 +491,7 @@ def _create_grad_func(ys, xs, grads, cond_graph, body_graph, name, while_op, lambda *args: _grad_fn(ys, xs, args, body_graph), args, {}, func_graph=_WhileBodyGradFuncGraph(name, cond_graph, body_graph, - maximum_iterations)) + max_iters)) # Add the popped accumulators to the list of outputs. for internal_capture in grad_func_graph.internal_captures: @@ -480,14 +521,13 @@ def _grad_fn(ys, xs, args, func_graph): args: The input arguments. args[0] - Loop counter args[1] - Total number of iterations. - args[2] - maximum_iterations. - args[3:] - Incoming gradients for `ys`. + args[2:] - Incoming gradients for `ys`. func_graph: function.FuncGraph. The corresponding forward-pass function. Returns: The output gradient Tensors. """ - grad_ys = args[3:] + grad_ys = args[2:] # Build the gradient graph. Note that this builds the gradient computation of # func_graph in the current graph, which requires capturing tensors from @@ -502,9 +542,8 @@ def _grad_fn(ys, xs, args, func_graph): # is a tf.StopGradient in the loop body. assert all(g is not None for g in grad_outs) counter = args[0] - maximum_iterations = args[1] - total_iters = args[2] - return [counter + 1, maximum_iterations, total_iters] + grad_outs + total_iters = args[1] + return [counter + 1, total_iters] + grad_outs def _resolve_grad_captures(body_graph, body_grad_graph, while_op): @@ -561,10 +600,9 @@ def _get_structured_grad_output(outputs, grads, body_grad_graph): """ result = [] # outputs[0] is the loop counter. - # outputs[1] is maximum_iterations. - # outputs[2] is the total number of loop iterations. - outputs_idx = 3 - structured_outputs_idx = 3 + # outputs[1] is the total number of loop iterations. + outputs_idx = 2 + structured_outputs_idx = 2 for g in grads: # Set None as the output gradient for tensors with None input gradient. if g is None: @@ -686,8 +724,7 @@ class _WhileBodyGradFuncGraph(util.WhileBodyFuncGraph): values of this dict need to be added to the list of outputs. """ - def __init__(self, name, forward_cond_graph, forward_body_graph, - maximum_iterations): + def __init__(self, name, forward_cond_graph, forward_body_graph, max_iters): super(_WhileBodyGradFuncGraph, self).__init__(name) self.empty_tensor_lists = [] self.popped_tensor_lists = {} @@ -695,7 +732,7 @@ class _WhileBodyGradFuncGraph(util.WhileBodyFuncGraph): self._forward_graph = forward_body_graph # FuncGraph for the cond of the forward While op. self._forward_cond_graph = forward_cond_graph - self._maximum_iterations = maximum_iterations + self._maximum_iterations = max_iters # Dict from forward intermediate tensor to its indirectly captured tensor # in this graph. Indirect capturing happens in two ways: # 1. For non-resource tensors we capture their accumulators from the forward @@ -928,6 +965,15 @@ def _copy_handle_data(src_tensors, tgt_tensors): custom_gradient.copy_handle_data(src_t, tgt_t) +def _maybe_set_maximum_iterations_attr(op, maximum_iterations): + if maximum_iterations is not None and control_flow_util.IsInXLAContext(op): + # Store the maximum_iterations to use in the gradient pass. + op._set_attr( # pylint: disable=protected-access + "_maximum_iterations", + attr_value_pb2.AttrValue( + i=tensor_util.constant_value(maximum_iterations))) + + # TODO(srbs): This method should be in control_flow_util but that introduces # a circular dependency ops -> control_flow_util -> ops. def _is_in_xla_context(): @@ -978,13 +1024,4 @@ def _build_signature(loop_vars, shape_invariants): ]) -def _build_maximum_iterations_loop_var(maximum_iterations): - if maximum_iterations is None: - # Default value for max_num_elements to EmptyTensorList meaning that the - # list size is unbounded. - maximum_iterations = -1 - # EmptyTensorList expects `max_num_elements` to be of type int32. - return ops.convert_to_tensor( - maximum_iterations, dtype=dtypes.int32, name="maximum_iterations") - # pylint: enable=protected-access diff --git a/tensorflow/python/ops/while_v2_indexed_slices_rewriter.py b/tensorflow/python/ops/while_v2_indexed_slices_rewriter.py index 9951cf74cc..30e9709d70 100644 --- a/tensorflow/python/ops/while_v2_indexed_slices_rewriter.py +++ b/tensorflow/python/ops/while_v2_indexed_slices_rewriter.py @@ -63,8 +63,8 @@ def rewrite_grad_indexed_slices(grads, body_grad_graph, loop_vars, # body_grad_graph.outputs, which contains flattened composite tensors. inputs_with_grads = [t for g, t in zip(grads, forward_inputs) if g is not None] - # Skip loop counter, maximum_iterations and total number of loop iterations. - structured_outputs = body_grad_graph.structured_outputs[3:] + # Skip loop counter and total number of loop iterations. + structured_outputs = body_grad_graph.structured_outputs[2:] for forward_input, output in zip(inputs_with_grads, structured_outputs): if not isinstance(output, ops.IndexedSlices): continue -- GitLab From 418cb1acbc0dd49295fe37a50a2bc9ffb7ba6b0f Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Mon, 11 Feb 2019 11:58:18 -0800 Subject: [PATCH 329/766] Add CreateTensorSliceDataset[Kernel] functions to the test base class --- .../core/kernels/data/dataset_test_base.cc | 48 +++++++++++++++++++ .../core/kernels/data/dataset_test_base.h | 11 +++++ 2 files changed, 59 insertions(+) diff --git a/tensorflow/core/kernels/data/dataset_test_base.cc b/tensorflow/core/kernels/data/dataset_test_base.cc index e163dbaa3d..99ed844f5b 100644 --- a/tensorflow/core/kernels/data/dataset_test_base.cc +++ b/tensorflow/core/kernels/data/dataset_test_base.cc @@ -35,6 +35,54 @@ Status DatasetOpsTestBase::ExpectEqual(const Tensor& a, const Tensor& b) { return Status::OK(); } +Status DatasetOpsTestBase::CreateTensorSliceDatasetKernel( + StringPiece node_name, DataTypeVector dtypes, + std::vector shapes, + std::unique_ptr* tensor_dataset_kernel) { + std::vector components; + components.reserve(dtypes.size()); + for (int i = 0; i < dtypes.size(); i++) { + components.emplace_back(strings::StrCat("component_", i)); + } + NodeDef node_def = test::function::NDef( + node_name, "TensorSliceDataset", components, + {{"Toutput_types", dtypes}, {"output_shapes", shapes}}); + TF_RETURN_IF_ERROR(CreateOpKernel(node_def, tensor_dataset_kernel)); + return Status::OK(); +} + +Status DatasetOpsTestBase::CreateTensorSliceDataset( + StringPiece node_name, std::vector components, + DatasetBase** tensor_slice_data) { + std::unique_ptr tensor_slice_dataset_kernel; + DataTypeVector dtypes; + std::vector shapes; + for (auto& t : components) { + dtypes.push_back(t.dtype()); + gtl::InlinedVector partial_dim_sizes; + for (int i = 1; i < t.dims(); ++i) { + partial_dim_sizes.push_back(t.dim_size(i)); + } + shapes.emplace_back(std::move(partial_dim_sizes)); + } + TF_RETURN_IF_ERROR(CreateTensorSliceDatasetKernel( + node_name, dtypes, shapes, &tensor_slice_dataset_kernel)); + gtl::InlinedVector inputs; + for (auto& tensor : components) { + inputs.emplace_back(&tensor); + } + TF_RETURN_IF_ERROR(CheckOpKernelInput(*tensor_slice_dataset_kernel, inputs)); + std::unique_ptr context; + TF_RETURN_IF_ERROR(CreateOpKernelContext(tensor_slice_dataset_kernel.get(), + &inputs, &context)); + TF_RETURN_IF_ERROR( + RunOpKernel(tensor_slice_dataset_kernel.get(), context.get())); + TF_RETURN_IF_ERROR( + GetDatasetFromContext(context.get(), 0, tensor_slice_data)); + return Status::OK(); +} + +>>>>>>> Add CreateTensorSliceDataset[Kernel] functions to the test base class Status DatasetOpsTestBase::CreateOpKernel( const NodeDef& node_def, std::unique_ptr* op_kernel) { Status status; diff --git a/tensorflow/core/kernels/data/dataset_test_base.h b/tensorflow/core/kernels/data/dataset_test_base.h index 9c3a62c693..90550aae3e 100644 --- a/tensorflow/core/kernels/data/dataset_test_base.h +++ b/tensorflow/core/kernels/data/dataset_test_base.h @@ -108,6 +108,17 @@ class DatasetOpsTestBase : public ::testing::Test { return Status::OK(); } + // Creates a new TensorSliceDataset op kernel. + Status CreateTensorSliceDatasetKernel( + StringPiece node_name, DataTypeVector dtypes, + std::vector shapes, + std::unique_ptr* tensor_dataset_kernel); + + // Creates a new TensorSliceDataset. + Status CreateTensorSliceDataset(StringPiece node_name, + std::vector components, + DatasetBase** tensor_slice_data); + // Fetches the dataset from the operation context. Status GetDatasetFromContext(OpKernelContext* context, int output_index, DatasetBase** const dataset); -- GitLab From 76955ea92cdc82f3368e2eae9410cba6aca97d15 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Mon, 11 Feb 2019 11:59:43 -0800 Subject: [PATCH 330/766] Add tests for ZipDataset --- tensorflow/core/kernels/data/BUILD | 40 ++ .../core/kernels/data/zip_dataset_op_test.cc | 539 ++++++++++++++++++ 2 files changed, 579 insertions(+) create mode 100644 tensorflow/core/kernels/data/zip_dataset_op_test.cc diff --git a/tensorflow/core/kernels/data/BUILD b/tensorflow/core/kernels/data/BUILD index d22bf0dc57..251c2582bc 100644 --- a/tensorflow/core/kernels/data/BUILD +++ b/tensorflow/core/kernels/data/BUILD @@ -510,6 +510,26 @@ tf_kernel_library( ], ) +tf_cc_test( + name = "zip_dataset_op_test", + size = "small", + srcs = ["zip_dataset_op_test.cc"], + deps = [ + ":dataset_test_base", + ":dataset_utils", + ":iterator_ops", + ":range_dataset_op", + ":zip_dataset_op", + "//tensorflow/core:core_cpu_internal", + "//tensorflow/core:dataset_ops_op_lib", + "//tensorflow/core:framework", + "//tensorflow/core:lib_internal", + "//tensorflow/core:test", + "//tensorflow/core:test_main", + "//tensorflow/core:testlib", + ], +) + tf_kernel_library( name = "concatenate_dataset_op", srcs = ["concatenate_dataset_op.cc"], @@ -521,6 +541,26 @@ tf_kernel_library( ], ) +tf_cc_test( + name = "concatenate_dataset_op_test", + size = "small", + srcs = ["concatenate_dataset_op_test.cc"], + deps = [ + ":concatenate_dataset_op", + ":dataset_test_base", + ":dataset_utils", + ":iterator_ops", + ":tensor_slice_dataset_op", + "//tensorflow/core:core_cpu_internal", + "//tensorflow/core:dataset_ops_op_lib", + "//tensorflow/core:framework", + "//tensorflow/core:lib_internal", + "//tensorflow/core:test", + "//tensorflow/core:test_main", + "//tensorflow/core:testlib", + ], +) + tf_kernel_library( name = "reader_dataset_ops", srcs = ["reader_dataset_ops.cc"], diff --git a/tensorflow/core/kernels/data/zip_dataset_op_test.cc b/tensorflow/core/kernels/data/zip_dataset_op_test.cc new file mode 100644 index 0000000000..2c44b2aca1 --- /dev/null +++ b/tensorflow/core/kernels/data/zip_dataset_op_test.cc @@ -0,0 +1,539 @@ +/* Copyright 2019 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/dataset_test_base.h" + +namespace tensorflow { +namespace data { +namespace { + +constexpr char kNodeName[] = "zip_dataset"; +constexpr char kOpName[] = "ZipDataset"; + +struct RangeDatasetParam { + int64 start; + int64 end; + int64 step; +}; + +class ZipDatasetOpTest : public DatasetOpsTestBase { + protected: + // Creates RangeDatasets and save them as the variant tensors. + Status CreateRangeDatasetTensors(std::vector params, + std::vector *dataset_tensors) { + for (int i = 0; i < params.size(); ++i) { + DatasetBase *range_dataset; + TF_RETURN_IF_ERROR(CreateRangeDataset( + params[i].start, params[i].end, params[i].step, + strings::StrCat("range_", i), &range_dataset)); + Tensor dataset_tensor(DT_VARIANT, TensorShape({})); + TF_RETURN_IF_ERROR( + StoreDatasetInVariantTensor(range_dataset, &dataset_tensor)); + dataset_tensors->emplace_back(std::move(dataset_tensor)); + } + } + + // Creates a new ZipDataset op kernel. + Status CreateZipDatasetKernel(DataTypeVector dtypes, + std::vector output_shapes, + int n, std::unique_ptr *op_kernel) { + std::vector input_datasets; + input_datasets.reserve(n); + for (int i = 0; i < n; ++i) { + input_datasets.emplace_back(strings::StrCat("input_dataset_", i)); + } + node_def_ = test::function::NDef( + kNodeName, kOpName, input_datasets, + {{"output_types", dtypes}, {"output_shapes", output_shapes}, {"N", n}}); + TF_RETURN_IF_ERROR(CreateOpKernel(node_def_, op_kernel)); + return Status::OK(); + } + + // Creates a new ZipDataset op kernel context. + Status CreateZipDatasetContext(OpKernel *const op_kernel, + gtl::InlinedVector *inputs, + std::unique_ptr *context) { + TF_RETURN_IF_ERROR(CheckOpKernelInput(*op_kernel, *inputs)); + TF_RETURN_IF_ERROR(CreateOpKernelContext(op_kernel, inputs, context)); + return Status::OK(); + } + + private: + NodeDef node_def_; +}; + +struct TestParam { + std::vector testcases; + std::vector expected_outputs; + std::vector breakpoints; +} TestCases[] = { + // Test the input datasets with same number of outputs. + {{RangeDatasetParam{0, 3, 1}, RangeDatasetParam{10, 13, 1}}, + {DatasetOpsTestBase::CreateTensor(TensorShape{}, {0}), + DatasetOpsTestBase::CreateTensor(TensorShape{}, {10}), + DatasetOpsTestBase::CreateTensor(TensorShape{}, {1}), + DatasetOpsTestBase::CreateTensor(TensorShape{}, {11}), + DatasetOpsTestBase::CreateTensor(TensorShape{}, {2}), + DatasetOpsTestBase::CreateTensor(TensorShape{}, {12})}, + {0, 1, 4}}, + // Test the input datasets with different number of outputs. + {{RangeDatasetParam{0, 3, 1}, RangeDatasetParam{10, 15, 1}}, + {DatasetOpsTestBase::CreateTensor(TensorShape{}, {0}), + DatasetOpsTestBase::CreateTensor(TensorShape{}, {10}), + DatasetOpsTestBase::CreateTensor(TensorShape{}, {1}), + DatasetOpsTestBase::CreateTensor(TensorShape{}, {11}), + DatasetOpsTestBase::CreateTensor(TensorShape{}, {2}), + DatasetOpsTestBase::CreateTensor(TensorShape{}, {12})}, + {0, 1, 4}}}; + +struct DatasetGetNextTest : ZipDatasetOpTest, + ::testing::WithParamInterface {}; + +TEST_P(DatasetGetNextTest, GetNext) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + std::vector testcases = GetParam().testcases; + std::vector expected_outputs = GetParam().expected_outputs; + auto expected_outputs_it = expected_outputs.begin(); + + std::vector range_dataset_tensors; + TF_ASSERT_OK(CreateRangeDatasetTensors(testcases, &range_dataset_tensors)); + gtl::InlinedVector inputs; + for (auto &tensor : range_dataset_tensors) { + inputs.emplace_back(&tensor); + } + int num_tensors_per_slice = testcases.size(); + std::unique_ptr dataset_kernel; + TF_ASSERT_OK(CreateZipDatasetKernel({DT_INT64}, {{num_tensors_per_slice}}, + inputs.size(), &dataset_kernel)); + std::unique_ptr dataset_kernel_ctx; + TF_ASSERT_OK(CreateZipDatasetContext(dataset_kernel.get(), &inputs, + &dataset_kernel_ctx)); + DatasetBase *dataset; + TF_ASSERT_OK( + CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); + core::ScopedUnref scoped_unref(dataset); + + std::unique_ptr iterator_ctx; + TF_ASSERT_OK(CreateIteratorContext(dataset_kernel_ctx.get(), &iterator_ctx)); + std::unique_ptr iterator; + TF_ASSERT_OK( + dataset->MakeIterator(iterator_ctx.get(), "Iterator", &iterator)); + + bool end_of_sequence = false; + std::vector out_tensors; + while (!end_of_sequence) { + TF_EXPECT_OK( + iterator->GetNext(iterator_ctx.get(), &out_tensors, &end_of_sequence)); + if (!end_of_sequence) { + EXPECT_NE(expected_outputs_it, expected_outputs.end()); + for (int i = 0; i < num_tensors_per_slice; ++i) { + TF_EXPECT_OK(ExpectEqual(out_tensors[i], *expected_outputs_it)); + expected_outputs_it++; + } + } + } + EXPECT_EQ(expected_outputs_it, expected_outputs.end()); +} + +INSTANTIATE_TEST_CASE_P(ZipDatasetOpTest, DatasetGetNextTest, + ::testing::ValuesIn(TestCases)); + +TEST_F(ZipDatasetOpTest, DatasetName) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + std::vector testcases = TestCases[0].testcases; + std::vector range_dataset_tensors; + TF_ASSERT_OK(CreateRangeDatasetTensors(testcases, &range_dataset_tensors)); + gtl::InlinedVector inputs; + for (auto &tensor : range_dataset_tensors) { + inputs.emplace_back(&tensor); + } + + std::unique_ptr dataset_kernel; + TF_ASSERT_OK(CreateZipDatasetKernel({DT_INT64}, {{2}}, inputs.size(), + &dataset_kernel)); + std::unique_ptr dataset_kernel_ctx; + TF_ASSERT_OK(CreateZipDatasetContext(dataset_kernel.get(), &inputs, + &dataset_kernel_ctx)); + DatasetBase *dataset; + TF_ASSERT_OK( + CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); + core::ScopedUnref scoped_unref(dataset); + + EXPECT_EQ(dataset->name(), kOpName); +} + +struct DatasetOutputDtypesTest : ZipDatasetOpTest, + ::testing::WithParamInterface {}; + +TEST_P(DatasetOutputDtypesTest, DatasetOutputDtypes) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + std::vector testcases = GetParam().testcases; + std::vector expected_outputs = GetParam().expected_outputs; + + std::vector range_dataset_tensors; + TF_ASSERT_OK(CreateRangeDatasetTensors(testcases, &range_dataset_tensors)); + gtl::InlinedVector inputs; + for (auto &tensor : range_dataset_tensors) { + inputs.emplace_back(&tensor); + } + int num_tensors_per_slice = testcases.size(); + std::unique_ptr dataset_kernel; + TF_ASSERT_OK(CreateZipDatasetKernel({DT_INT64}, {{num_tensors_per_slice}}, + inputs.size(), &dataset_kernel)); + std::unique_ptr dataset_kernel_ctx; + TF_ASSERT_OK(CreateZipDatasetContext(dataset_kernel.get(), &inputs, + &dataset_kernel_ctx)); + DatasetBase *dataset; + TF_ASSERT_OK( + CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); + core::ScopedUnref scoped_unref(dataset); + + DataTypeVector expected_output_dtypes; + for (int i = 0; i < num_tensors_per_slice; ++i) { + expected_output_dtypes.emplace_back(expected_outputs[i].dtype()); + } + + TF_EXPECT_OK( + VerifyTypesMatch(dataset->output_dtypes(), expected_output_dtypes)); +} + +INSTANTIATE_TEST_CASE_P(ZipDatasetOpTest, DatasetOutputDtypesTest, + ::testing::ValuesIn(TestCases)); + +struct DatasetOutputShapesTest : ZipDatasetOpTest, + ::testing::WithParamInterface {}; + +TEST_P(DatasetOutputShapesTest, DatasetOutputShapes) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + std::vector testcases = GetParam().testcases; + std::vector expected_outputs = GetParam().expected_outputs; + + std::vector range_dataset_tensors; + TF_ASSERT_OK(CreateRangeDatasetTensors(testcases, &range_dataset_tensors)); + gtl::InlinedVector inputs; + for (auto &tensor : range_dataset_tensors) { + inputs.emplace_back(&tensor); + } + int num_tensors_per_slice = testcases.size(); + std::unique_ptr dataset_kernel; + TF_ASSERT_OK(CreateZipDatasetKernel({DT_INT64}, {{num_tensors_per_slice}}, + inputs.size(), &dataset_kernel)); + std::unique_ptr dataset_kernel_ctx; + TF_ASSERT_OK(CreateZipDatasetContext(dataset_kernel.get(), &inputs, + &dataset_kernel_ctx)); + DatasetBase *dataset; + TF_ASSERT_OK( + CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); + core::ScopedUnref scoped_unref(dataset); + + std::vector expected_output_shapes; + for (int i = 0; i < num_tensors_per_slice; ++i) { + expected_output_shapes.emplace_back(expected_outputs[i].shape()); + } + + TF_EXPECT_OK( + VerifyShapesCompatible(dataset->output_shapes(), expected_output_shapes)); +} + +INSTANTIATE_TEST_CASE_P(ZipDatasetOpTest, DatasetOutputShapesTest, + ::testing::ValuesIn(TestCases)); + +struct DatasetCardinalityTest : ZipDatasetOpTest, + ::testing::WithParamInterface {}; + +TEST_P(DatasetCardinalityTest, Cardinality) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + std::vector testcases = GetParam().testcases; + std::vector expected_outputs = GetParam().expected_outputs; + + std::vector range_dataset_tensors; + TF_ASSERT_OK(CreateRangeDatasetTensors(testcases, &range_dataset_tensors)); + gtl::InlinedVector inputs; + for (auto &tensor : range_dataset_tensors) { + inputs.emplace_back(&tensor); + } + int num_tensors_per_slice = testcases.size(); + std::unique_ptr dataset_kernel; + TF_ASSERT_OK(CreateZipDatasetKernel({DT_INT64}, {{num_tensors_per_slice}}, + inputs.size(), &dataset_kernel)); + std::unique_ptr dataset_kernel_ctx; + TF_ASSERT_OK(CreateZipDatasetContext(dataset_kernel.get(), &inputs, + &dataset_kernel_ctx)); + DatasetBase *dataset; + TF_ASSERT_OK( + CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); + core::ScopedUnref scoped_unref(dataset); + + EXPECT_EQ(dataset->Cardinality(), + expected_outputs.size() / num_tensors_per_slice); +} + +INSTANTIATE_TEST_CASE_P(ZipDatasetOpTest, DatasetCardinalityTest, + ::testing::ValuesIn(TestCases)); + +TEST_F(ZipDatasetOpTest, DatasetSave) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + std::vector testcases = TestCases[0].testcases; + std::vector range_dataset_tensors; + TF_ASSERT_OK(CreateRangeDatasetTensors(testcases, &range_dataset_tensors)); + gtl::InlinedVector inputs; + for (auto &tensor : range_dataset_tensors) { + inputs.emplace_back(&tensor); + } + + std::unique_ptr dataset_kernel; + TF_ASSERT_OK(CreateZipDatasetKernel({DT_INT64}, {{2}}, inputs.size(), + &dataset_kernel)); + std::unique_ptr dataset_kernel_ctx; + TF_ASSERT_OK(CreateZipDatasetContext(dataset_kernel.get(), &inputs, + &dataset_kernel_ctx)); + DatasetBase *dataset; + TF_ASSERT_OK( + CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); + core::ScopedUnref scoped_unref(dataset); + + std::unique_ptr serialization_ctx; + TF_ASSERT_OK(CreateSerializationContext(&serialization_ctx)); + VariantTensorData data; + VariantTensorDataWriter writer(&data); + TF_ASSERT_OK(dataset->Save(serialization_ctx.get(), &writer)); + TF_ASSERT_OK(writer.Flush()); +} + +struct IteratorOutputDtypesTest : ZipDatasetOpTest, + ::testing::WithParamInterface {}; + +TEST_P(IteratorOutputDtypesTest, IteratorOutputDtypes) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + std::vector testcases = GetParam().testcases; + std::vector expected_outputs = GetParam().expected_outputs; + + std::vector range_dataset_tensors; + TF_ASSERT_OK(CreateRangeDatasetTensors(testcases, &range_dataset_tensors)); + gtl::InlinedVector inputs; + for (auto &tensor : range_dataset_tensors) { + inputs.emplace_back(&tensor); + } + int num_tensors_per_slice = testcases.size(); + std::unique_ptr dataset_kernel; + TF_ASSERT_OK(CreateZipDatasetKernel({DT_INT64}, {{num_tensors_per_slice}}, + inputs.size(), &dataset_kernel)); + std::unique_ptr dataset_kernel_ctx; + TF_ASSERT_OK(CreateZipDatasetContext(dataset_kernel.get(), &inputs, + &dataset_kernel_ctx)); + DatasetBase *dataset; + TF_ASSERT_OK( + CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); + core::ScopedUnref scoped_unref(dataset); + + std::unique_ptr iterator_ctx; + TF_ASSERT_OK(CreateIteratorContext(dataset_kernel_ctx.get(), &iterator_ctx)); + std::unique_ptr iterator; + TF_ASSERT_OK( + dataset->MakeIterator(iterator_ctx.get(), "Iterator", &iterator)); + + DataTypeVector expected_output_dtypes; + for (int i = 0; i < num_tensors_per_slice; ++i) { + expected_output_dtypes.emplace_back(expected_outputs[i].dtype()); + } + + TF_EXPECT_OK( + VerifyTypesMatch(iterator->output_dtypes(), expected_output_dtypes)); +} + +INSTANTIATE_TEST_CASE_P(ZipDatasetOpTest, IteratorOutputDtypesTest, + ::testing::ValuesIn(TestCases)); + +struct IteratorOutputShapesTest : ZipDatasetOpTest, + ::testing::WithParamInterface {}; + +TEST_P(IteratorOutputShapesTest, IteratorOutputShapes) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + std::vector testcases = GetParam().testcases; + std::vector expected_outputs = GetParam().expected_outputs; + + std::vector range_dataset_tensors; + TF_ASSERT_OK(CreateRangeDatasetTensors(testcases, &range_dataset_tensors)); + gtl::InlinedVector inputs; + for (auto &tensor : range_dataset_tensors) { + inputs.emplace_back(&tensor); + } + int num_tensors_per_slice = testcases.size(); + std::unique_ptr dataset_kernel; + TF_ASSERT_OK(CreateZipDatasetKernel({DT_INT64}, {{num_tensors_per_slice}}, + inputs.size(), &dataset_kernel)); + std::unique_ptr dataset_kernel_ctx; + TF_ASSERT_OK(CreateZipDatasetContext(dataset_kernel.get(), &inputs, + &dataset_kernel_ctx)); + DatasetBase *dataset; + TF_ASSERT_OK( + CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); + core::ScopedUnref scoped_unref(dataset); + + std::unique_ptr iterator_ctx; + TF_ASSERT_OK(CreateIteratorContext(dataset_kernel_ctx.get(), &iterator_ctx)); + std::unique_ptr iterator; + TF_ASSERT_OK( + dataset->MakeIterator(iterator_ctx.get(), "Iterator", &iterator)); + + std::vector expected_output_shapes; + expected_output_shapes.reserve(num_tensors_per_slice); + for (int i = 0; i < num_tensors_per_slice; ++i) { + expected_output_shapes.emplace_back(expected_outputs[i].shape()); + } + + TF_EXPECT_OK(VerifyShapesCompatible(iterator->output_shapes(), + expected_output_shapes)); +} + +INSTANTIATE_TEST_CASE_P(ZipDatasetOpTest, IteratorOutputShapesTest, + ::testing::ValuesIn(TestCases)); + +TEST_F(ZipDatasetOpTest, IteratorOutputPrefix) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + std::vector testcases( + {RangeDatasetParam{0, 10, 1}, RangeDatasetParam{10, 20, 1}}); + std::vector range_dataset_tensors; + TF_ASSERT_OK(CreateRangeDatasetTensors(testcases, &range_dataset_tensors)); + gtl::InlinedVector inputs; + for (auto &tensor : range_dataset_tensors) { + inputs.emplace_back(&tensor); + } + + std::unique_ptr dataset_kernel; + TF_ASSERT_OK(CreateZipDatasetKernel({DT_INT64}, {{2}}, inputs.size(), + &dataset_kernel)); + std::unique_ptr dataset_kernel_ctx; + TF_ASSERT_OK(CreateZipDatasetContext(dataset_kernel.get(), &inputs, + &dataset_kernel_ctx)); + DatasetBase *dataset; + TF_ASSERT_OK( + CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); + core::ScopedUnref scoped_unref(dataset); + + std::unique_ptr iterator_ctx; + TF_ASSERT_OK(CreateIteratorContext(dataset_kernel_ctx.get(), &iterator_ctx)); + std::unique_ptr iterator; + TF_ASSERT_OK( + dataset->MakeIterator(iterator_ctx.get(), "Iterator", &iterator)); + EXPECT_EQ(iterator->prefix(), "Iterator::Zip"); +} + +struct IteratorRoundtripTest : ZipDatasetOpTest, + ::testing::WithParamInterface {}; + +TEST_P(IteratorRoundtripTest, Roundtrip) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + std::vector testcases = GetParam().testcases; + std::vector expected_outputs = GetParam().expected_outputs; + auto expected_outputs_it = expected_outputs.begin(); + std::vector breakpoints = GetParam().breakpoints; + + std::vector range_dataset_tensors; + TF_ASSERT_OK(CreateRangeDatasetTensors(testcases, &range_dataset_tensors)); + gtl::InlinedVector inputs; + for (auto &tensor : range_dataset_tensors) { + inputs.emplace_back(&tensor); + } + int num_tensors_per_slice = testcases.size(); + std::unique_ptr dataset_kernel; + TF_ASSERT_OK(CreateZipDatasetKernel({DT_INT64}, {{num_tensors_per_slice}}, + inputs.size(), &dataset_kernel)); + std::unique_ptr dataset_kernel_ctx; + TF_ASSERT_OK(CreateZipDatasetContext(dataset_kernel.get(), &inputs, + &dataset_kernel_ctx)); + DatasetBase *dataset; + TF_ASSERT_OK( + CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); + core::ScopedUnref scoped_unref(dataset); + + std::unique_ptr iterator_ctx; + TF_ASSERT_OK(CreateIteratorContext(dataset_kernel_ctx.get(), &iterator_ctx)); + std::unique_ptr iterator; + TF_ASSERT_OK( + dataset->MakeIterator(iterator_ctx.get(), "Iterator", &iterator)); + + std::unique_ptr serialization_ctx; + TF_ASSERT_OK(CreateSerializationContext(&serialization_ctx)); + + bool end_of_sequence = false; + std::vector out_tensors; + int cur_iteration = 0; + for (int breakpoint : breakpoints) { + VariantTensorData data; + VariantTensorDataWriter writer(&data); + TF_EXPECT_OK(iterator->Save(serialization_ctx.get(), &writer)); + TF_EXPECT_OK(writer.Flush()); + VariantTensorDataReader reader(&data); + TF_EXPECT_OK(iterator->Restore(iterator_ctx.get(), &reader)); + + while (cur_iteration < breakpoint) { + TF_EXPECT_OK(iterator->GetNext(iterator_ctx.get(), &out_tensors, + &end_of_sequence)); + if (!end_of_sequence) { + for (auto &tensor : out_tensors) { + EXPECT_NE(expected_outputs_it, expected_outputs.end()); + TF_EXPECT_OK(ExpectEqual(tensor, *expected_outputs_it)); + expected_outputs_it++; + } + } + cur_iteration++; + } + + if (breakpoint >= dataset->Cardinality()) { + EXPECT_TRUE(end_of_sequence); + EXPECT_EQ(expected_outputs_it, expected_outputs.end()); + } else { + EXPECT_FALSE(end_of_sequence); + } + } +} + +INSTANTIATE_TEST_CASE_P(ZipDatasetOpTest, IteratorRoundtripTest, + ::testing::ValuesIn(TestCases)); + +} // namespace +} // namespace data +} // namespace tensorflow -- GitLab From ae903296c8e3ea89ce06690eb351bf89a3fadb7f Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Mon, 11 Feb 2019 12:00:14 -0800 Subject: [PATCH 331/766] Add tests for ConcatenateDataset --- .../data/concatenate_dataset_op_test.cc | 594 ++++++++++++++++++ 1 file changed, 594 insertions(+) create mode 100644 tensorflow/core/kernels/data/concatenate_dataset_op_test.cc diff --git a/tensorflow/core/kernels/data/concatenate_dataset_op_test.cc b/tensorflow/core/kernels/data/concatenate_dataset_op_test.cc new file mode 100644 index 0000000000..7a8e8ea037 --- /dev/null +++ b/tensorflow/core/kernels/data/concatenate_dataset_op_test.cc @@ -0,0 +1,594 @@ +/* Copyright 2019 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/dataset_test_base.h" + +namespace tensorflow { +namespace data { +namespace { + +constexpr char kNodeName[] = "concatenate_dataset"; +constexpr char kOpName[] = "ConcatenateDataset"; + +class ConcatenateDatasetOpTest : public DatasetOpsTestBase { + protected: + // Creates TensorSliceDatasets and save them as the variant tensors. + Status CreateTensorSliceDatasetTensors( + std::vector> tensor_vectors, + std::vector *dataset_tensors) { + for (int i = 0; i < tensor_vectors.size(); ++i) { + std::vector tensors = tensor_vectors[i]; + DatasetBase *tensor_slice_dataset; + TF_RETURN_IF_ERROR( + CreateTensorSliceDataset(strings::StrCat("tensor_slice_node_", i), + tensors, &tensor_slice_dataset)); + Tensor dataset_tensor(DT_VARIANT, TensorShape({})); + TF_RETURN_IF_ERROR( + StoreDatasetInVariantTensor(tensor_slice_dataset, &dataset_tensor)); + dataset_tensors->emplace_back(std::move(dataset_tensor)); + } + return Status::OK(); + } + + // Creates a new ConcatenateDataset op kernel. + Status CreateConcatenateDatasetKernel( + DataTypeVector output_tyeps, + std::vector output_shapes, + std::unique_ptr *op_kernel) { + node_def_ = test::function::NDef( + kNodeName, kOpName, {"input_dataset", "another_dataset"}, + {{"output_types", output_tyeps}, {"output_shapes", output_shapes}}); + TF_CHECK_OK(CreateOpKernel(node_def_, op_kernel)); + return Status::OK(); + } + + // Creates a new ConcatenateDataset op kernel context. + Status CreateConcatenateDatasetContext( + OpKernel *const op_kernel, gtl::InlinedVector *inputs, + std::unique_ptr *context) { + TF_RETURN_IF_ERROR(CheckOpKernelInput(*op_kernel, *inputs)); + TF_RETURN_IF_ERROR(CreateOpKernelContext(op_kernel, inputs, context)); + return Status::OK(); + } + + private: + NodeDef node_def_; +}; + +struct TestParam { + std::vector> input_tensors; + std::vector expected_outputs; + DataTypeVector expected_output_dtypes; + std::vector expected_output_shapes; + int64 expected_cardinality; + std::vector breakpoints; +} TestCases[] = { + // Same shape. + {{{DatasetOpsTestBase::CreateTensor(TensorShape{2, 2}, {1, 2, 3, 4}), + DatasetOpsTestBase::CreateTensor(TensorShape{2, 2}, + {5, 6, 7, 8})}, + {DatasetOpsTestBase::CreateTensor(TensorShape{2, 2}, + {11, 12, 13, 14}), + DatasetOpsTestBase::CreateTensor(TensorShape{2, 2}, + {15, 16, 17, 18})}}, + {{DatasetOpsTestBase::CreateTensor(TensorShape{2}, {1, 2}), + DatasetOpsTestBase::CreateTensor(TensorShape{2}, {5, 6}), + DatasetOpsTestBase::CreateTensor(TensorShape{2}, {3, 4}), + DatasetOpsTestBase::CreateTensor(TensorShape{2}, {7, 8}), + DatasetOpsTestBase::CreateTensor(TensorShape{2}, {11, 12}), + DatasetOpsTestBase::CreateTensor(TensorShape{2}, {15, 16}), + DatasetOpsTestBase::CreateTensor(TensorShape{2}, {13, 14}), + DatasetOpsTestBase::CreateTensor(TensorShape{2}, {17, 18})}}, + {DT_INT64, DT_INT64}, + {PartialTensorShape({2}), PartialTensorShape({2})}, + 4, + {0, 2, 5}}, + // Different shape. + {{{DatasetOpsTestBase::CreateTensor(TensorShape{2, 3}, + {1, 2, 3, 4, 5, 6}), + DatasetOpsTestBase::CreateTensor(TensorShape{2, 2}, + {7, 8, 9, 10})}, + {DatasetOpsTestBase::CreateTensor(TensorShape{2, 2}, + {11, 12, 13, 14}), + DatasetOpsTestBase::CreateTensor(TensorShape{2, 1}, {15, 16})}}, + {DatasetOpsTestBase::CreateTensor(TensorShape{3}, {1, 2, 3}), + DatasetOpsTestBase::CreateTensor(TensorShape{2}, {7, 8}), + DatasetOpsTestBase::CreateTensor(TensorShape{3}, {4, 5, 6}), + DatasetOpsTestBase::CreateTensor(TensorShape{2}, {9, 10}), + DatasetOpsTestBase::CreateTensor(TensorShape{2}, {11, 12}), + DatasetOpsTestBase::CreateTensor(TensorShape{1}, {15}), + DatasetOpsTestBase::CreateTensor(TensorShape{2}, {13, 14}), + DatasetOpsTestBase::CreateTensor(TensorShape{1}, {16})}, + {DT_INT64, DT_INT64}, + {PartialTensorShape({-1}), PartialTensorShape({-1})}, + 4, + {0, 2, 5}}}; + +struct DatasetGetNextTest : ConcatenateDatasetOpTest, + ::testing::WithParamInterface {}; + +TEST_P(DatasetGetNextTest, GetNext) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + std::vector> tensor_vectors = GetParam().input_tensors; + std::vector expected_outputs = GetParam().expected_outputs; + auto expected_outputs_it = expected_outputs.begin(); + std::vector expected_output_shapes = + GetParam().expected_output_shapes; + DataTypeVector expected_output_dtypes = GetParam().expected_output_dtypes; + std::vector tensor_slice_dataset_tensors; + TF_ASSERT_OK(CreateTensorSliceDatasetTensors(tensor_vectors, + &tensor_slice_dataset_tensors)); + gtl::InlinedVector inputs; + for (auto &tensor : tensor_slice_dataset_tensors) { + inputs.emplace_back(&tensor); + } + + std::unique_ptr dataset_kernel; + TF_ASSERT_OK(CreateConcatenateDatasetKernel( + expected_output_dtypes, expected_output_shapes, &dataset_kernel)); + std::unique_ptr dataset_kernel_ctx; + TF_ASSERT_OK(CreateConcatenateDatasetContext(dataset_kernel.get(), &inputs, + &dataset_kernel_ctx)); + DatasetBase *dataset; + TF_ASSERT_OK( + CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); + core::ScopedUnref scoped_unref(dataset); + + std::unique_ptr iterator_ctx; + TF_ASSERT_OK(CreateIteratorContext(dataset_kernel_ctx.get(), &iterator_ctx)); + std::unique_ptr iterator; + TF_ASSERT_OK( + dataset->MakeIterator(iterator_ctx.get(), "Iterator", &iterator)); + + bool end_of_sequence = false; + std::vector out_tensors; + while (!end_of_sequence) { + TF_EXPECT_OK( + iterator->GetNext(iterator_ctx.get(), &out_tensors, &end_of_sequence)); + if (!end_of_sequence) { + for (auto &tensor : out_tensors) { + EXPECT_NE(expected_outputs_it, expected_outputs.end()); + TF_EXPECT_OK(ExpectEqual(tensor, *expected_outputs_it)); + expected_outputs_it++; + } + } + } + EXPECT_EQ(expected_outputs_it, expected_outputs.end()); +} + +INSTANTIATE_TEST_CASE_P(ConcatenateDatasetOpTest, DatasetGetNextTest, + ::testing::ValuesIn(TestCases)); + +TEST_F(ConcatenateDatasetOpTest, DifferentDtypes) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + std::vector> tensor_vectors = { + {CreateTensor(TensorShape({2, 2}), {1, 2, 3, 4})}, + {CreateTensor(TensorShape({2, 2}), {1.0, 2.0, 3.0, 4.0})}}; + std::vector tensor_slice_dataset_tensors; + TF_ASSERT_OK(CreateTensorSliceDatasetTensors(tensor_vectors, + &tensor_slice_dataset_tensors)); + gtl::InlinedVector inputs; + for (auto &tensor : tensor_slice_dataset_tensors) { + inputs.emplace_back(&tensor); + } + std::vector output_shapes = + TestCases[0].expected_output_shapes; + DataTypeVector output_dtypes = TestCases[0].expected_output_dtypes; + std::unique_ptr dataset_kernel; + TF_ASSERT_OK(CreateConcatenateDatasetKernel(output_dtypes, output_shapes, + &dataset_kernel)); + std::unique_ptr dataset_kernel_ctx; + TF_ASSERT_OK(CreateConcatenateDatasetContext(dataset_kernel.get(), &inputs, + &dataset_kernel_ctx)); + DatasetBase *dataset; + EXPECT_EQ( + CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset) + .code(), + tensorflow::error::INVALID_ARGUMENT); +} + +TEST_F(ConcatenateDatasetOpTest, DatasetName) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + std::vector> tensor_vectors = TestCases[0].input_tensors; + std::vector tensor_slice_dataset_tensors; + TF_ASSERT_OK(CreateTensorSliceDatasetTensors(tensor_vectors, + &tensor_slice_dataset_tensors)); + gtl::InlinedVector inputs; + for (auto &tensor : tensor_slice_dataset_tensors) { + inputs.emplace_back(&tensor); + } + std::vector output_shapes = + TestCases[0].expected_output_shapes; + DataTypeVector output_dtypes = TestCases[0].expected_output_dtypes; + std::unique_ptr dataset_kernel; + TF_ASSERT_OK(CreateConcatenateDatasetKernel(output_dtypes, output_shapes, + &dataset_kernel)); + std::unique_ptr dataset_kernel_ctx; + TF_ASSERT_OK(CreateConcatenateDatasetContext(dataset_kernel.get(), &inputs, + &dataset_kernel_ctx)); + DatasetBase *dataset; + TF_ASSERT_OK( + CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); + core::ScopedUnref scoped_unref(dataset); + + EXPECT_EQ(dataset->name(), kOpName); +} + +struct DatasetOutputDtypesTest : ConcatenateDatasetOpTest, + ::testing::WithParamInterface {}; + +TEST_P(DatasetOutputDtypesTest, DatasetOutputDtypes) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + std::vector> tensor_vectors = GetParam().input_tensors; + std::vector expected_outputs = GetParam().expected_outputs; + std::vector expected_output_shapes = + GetParam().expected_output_shapes; + DataTypeVector expected_output_dtypes = GetParam().expected_output_dtypes; + std::vector tensor_slice_dataset_tensors; + TF_ASSERT_OK(CreateTensorSliceDatasetTensors(tensor_vectors, + &tensor_slice_dataset_tensors)); + gtl::InlinedVector inputs; + for (auto &tensor : tensor_slice_dataset_tensors) { + inputs.emplace_back(&tensor); + } + + std::unique_ptr dataset_kernel; + TF_ASSERT_OK(CreateConcatenateDatasetKernel( + expected_output_dtypes, expected_output_shapes, &dataset_kernel)); + std::unique_ptr dataset_kernel_ctx; + TF_ASSERT_OK(CreateConcatenateDatasetContext(dataset_kernel.get(), &inputs, + &dataset_kernel_ctx)); + DatasetBase *dataset; + TF_ASSERT_OK( + CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); + core::ScopedUnref scoped_unref(dataset); + + EXPECT_OK(VerifyTypesMatch(dataset->output_dtypes(), expected_output_dtypes)); +} + +INSTANTIATE_TEST_CASE_P(ConcatenateDatasetOpTest, DatasetOutputDtypesTest, + ::testing::ValuesIn(TestCases)); + +struct DatasetOutputShapesTest : ConcatenateDatasetOpTest, + ::testing::WithParamInterface {}; + +TEST_P(DatasetOutputShapesTest, DatasetOutputShapes) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + std::vector> tensor_vectors = GetParam().input_tensors; + std::vector expected_outputs = GetParam().expected_outputs; + std::vector expected_output_shapes = + GetParam().expected_output_shapes; + DataTypeVector expected_output_dtypes = GetParam().expected_output_dtypes; + std::vector tensor_slice_dataset_tensors; + TF_ASSERT_OK(CreateTensorSliceDatasetTensors(tensor_vectors, + &tensor_slice_dataset_tensors)); + gtl::InlinedVector inputs; + for (auto &tensor : tensor_slice_dataset_tensors) { + inputs.emplace_back(&tensor); + } + + std::unique_ptr dataset_kernel; + TF_ASSERT_OK(CreateConcatenateDatasetKernel( + expected_output_dtypes, expected_output_shapes, &dataset_kernel)); + std::unique_ptr dataset_kernel_ctx; + TF_ASSERT_OK(CreateConcatenateDatasetContext(dataset_kernel.get(), &inputs, + &dataset_kernel_ctx)); + DatasetBase *dataset; + TF_ASSERT_OK( + CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); + core::ScopedUnref scoped_unref(dataset); + + EXPECT_OK( + VerifyShapesCompatible(dataset->output_shapes(), expected_output_shapes)); +} + +INSTANTIATE_TEST_CASE_P(ConcatenateDatasetOpTest, DatasetOutputShapesTest, + ::testing::ValuesIn(TestCases)); + +struct DatasetCardinalityTest : ConcatenateDatasetOpTest, + ::testing::WithParamInterface {}; + +TEST_P(DatasetCardinalityTest, Cardinality) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + std::vector> tensor_vectors = GetParam().input_tensors; + std::vector expected_outputs = GetParam().expected_outputs; + std::vector expected_output_shapes = + GetParam().expected_output_shapes; + DataTypeVector expected_output_dtypes = GetParam().expected_output_dtypes; + int64 expected_cardinality = GetParam().expected_cardinality; + std::vector tensor_slice_dataset_tensors; + TF_ASSERT_OK(CreateTensorSliceDatasetTensors(tensor_vectors, + &tensor_slice_dataset_tensors)); + gtl::InlinedVector inputs; + for (auto &tensor : tensor_slice_dataset_tensors) { + inputs.emplace_back(&tensor); + } + + std::unique_ptr dataset_kernel; + TF_ASSERT_OK(CreateConcatenateDatasetKernel( + expected_output_dtypes, expected_output_shapes, &dataset_kernel)); + std::unique_ptr dataset_kernel_ctx; + TF_ASSERT_OK(CreateConcatenateDatasetContext(dataset_kernel.get(), &inputs, + &dataset_kernel_ctx)); + DatasetBase *dataset; + TF_ASSERT_OK( + CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); + core::ScopedUnref scoped_unref(dataset); + + EXPECT_EQ(dataset->Cardinality(), expected_cardinality); +} + +INSTANTIATE_TEST_CASE_P(ConcatenateDatasetOpTest, DatasetCardinalityTest, + ::testing::ValuesIn(TestCases)); + +TEST_F(ConcatenateDatasetOpTest, DatasetSave) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + std::vector> tensor_vectors = TestCases[0].input_tensors; + std::vector tensor_slice_dataset_tensors; + TF_ASSERT_OK(CreateTensorSliceDatasetTensors(tensor_vectors, + &tensor_slice_dataset_tensors)); + gtl::InlinedVector inputs; + for (auto &tensor : tensor_slice_dataset_tensors) { + inputs.emplace_back(&tensor); + } + std::vector output_shapes = + TestCases[0].expected_output_shapes; + DataTypeVector output_dtypes = TestCases[0].expected_output_dtypes; + std::unique_ptr dataset_kernel; + TF_ASSERT_OK(CreateConcatenateDatasetKernel(output_dtypes, output_shapes, + &dataset_kernel)); + std::unique_ptr dataset_kernel_ctx; + TF_ASSERT_OK(CreateConcatenateDatasetContext(dataset_kernel.get(), &inputs, + &dataset_kernel_ctx)); + DatasetBase *dataset; + TF_ASSERT_OK( + CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); + core::ScopedUnref scoped_unref(dataset); + + std::unique_ptr serialization_ctx; + TF_ASSERT_OK(CreateSerializationContext(&serialization_ctx)); + VariantTensorData data; + VariantTensorDataWriter writer(&data); + TF_ASSERT_OK(dataset->Save(serialization_ctx.get(), &writer)); + TF_ASSERT_OK(writer.Flush()); +} + +struct IteratorOutputDtypesTest : ConcatenateDatasetOpTest, + ::testing::WithParamInterface {}; + +TEST_P(IteratorOutputDtypesTest, IteratorOutputDtypes) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + std::vector> tensor_vectors = GetParam().input_tensors; + std::vector expected_outputs = GetParam().expected_outputs; + std::vector expected_output_shapes = + GetParam().expected_output_shapes; + DataTypeVector expected_output_dtypes = GetParam().expected_output_dtypes; + std::vector tensor_slice_dataset_tensors; + TF_ASSERT_OK(CreateTensorSliceDatasetTensors(tensor_vectors, + &tensor_slice_dataset_tensors)); + gtl::InlinedVector inputs; + for (auto &tensor : tensor_slice_dataset_tensors) { + inputs.emplace_back(&tensor); + } + + std::unique_ptr dataset_kernel; + TF_ASSERT_OK(CreateConcatenateDatasetKernel( + expected_output_dtypes, expected_output_shapes, &dataset_kernel)); + std::unique_ptr dataset_kernel_ctx; + TF_ASSERT_OK(CreateConcatenateDatasetContext(dataset_kernel.get(), &inputs, + &dataset_kernel_ctx)); + DatasetBase *dataset; + TF_ASSERT_OK( + CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); + core::ScopedUnref scoped_unref(dataset); + + std::unique_ptr iterator_ctx; + TF_ASSERT_OK(CreateIteratorContext(dataset_kernel_ctx.get(), &iterator_ctx)); + std::unique_ptr iterator; + TF_ASSERT_OK( + dataset->MakeIterator(iterator_ctx.get(), "Iterator", &iterator)); + + TF_EXPECT_OK( + VerifyTypesMatch(iterator->output_dtypes(), expected_output_dtypes)); +} + +INSTANTIATE_TEST_CASE_P(ConcatenateDatasetOpTest, IteratorOutputDtypesTest, + ::testing::ValuesIn(TestCases)); + +struct IteratorOutputShapesTest : ConcatenateDatasetOpTest, + ::testing::WithParamInterface {}; + +TEST_P(IteratorOutputShapesTest, IteratorOutputShapes) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + std::vector> tensor_vectors = GetParam().input_tensors; + std::vector expected_outputs = GetParam().expected_outputs; + std::vector expected_output_shapes = + GetParam().expected_output_shapes; + DataTypeVector expected_output_dtypes = GetParam().expected_output_dtypes; + std::vector tensor_slice_dataset_tensors; + TF_ASSERT_OK(CreateTensorSliceDatasetTensors(tensor_vectors, + &tensor_slice_dataset_tensors)); + gtl::InlinedVector inputs; + for (auto &tensor : tensor_slice_dataset_tensors) { + inputs.emplace_back(&tensor); + } + + std::unique_ptr dataset_kernel; + TF_ASSERT_OK(CreateConcatenateDatasetKernel( + expected_output_dtypes, expected_output_shapes, &dataset_kernel)); + std::unique_ptr dataset_kernel_ctx; + TF_ASSERT_OK(CreateConcatenateDatasetContext(dataset_kernel.get(), &inputs, + &dataset_kernel_ctx)); + DatasetBase *dataset; + TF_ASSERT_OK( + CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); + core::ScopedUnref scoped_unref(dataset); + + std::unique_ptr iterator_ctx; + TF_ASSERT_OK(CreateIteratorContext(dataset_kernel_ctx.get(), &iterator_ctx)); + std::unique_ptr iterator; + TF_ASSERT_OK( + dataset->MakeIterator(iterator_ctx.get(), "Iterator", &iterator)); + + TF_EXPECT_OK(VerifyShapesCompatible(iterator->output_shapes(), + expected_output_shapes)); +} + +INSTANTIATE_TEST_CASE_P(ConcatenateDatasetOpTest, IteratorOutputShapesTest, + ::testing::ValuesIn(TestCases)); + +TEST_F(ConcatenateDatasetOpTest, IteratorOutputPrefix) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + std::vector> tensor_vectors = TestCases[0].input_tensors; + std::vector tensor_slice_dataset_tensors; + TF_ASSERT_OK(CreateTensorSliceDatasetTensors(tensor_vectors, + &tensor_slice_dataset_tensors)); + gtl::InlinedVector inputs; + for (auto &tensor : tensor_slice_dataset_tensors) { + inputs.emplace_back(&tensor); + } + std::vector output_shapes = + TestCases[0].expected_output_shapes; + DataTypeVector output_dtypes = TestCases[0].expected_output_dtypes; + std::unique_ptr dataset_kernel; + TF_ASSERT_OK(CreateConcatenateDatasetKernel(output_dtypes, output_shapes, + &dataset_kernel)); + std::unique_ptr dataset_kernel_ctx; + TF_ASSERT_OK(CreateConcatenateDatasetContext(dataset_kernel.get(), &inputs, + &dataset_kernel_ctx)); + DatasetBase *dataset; + TF_ASSERT_OK( + CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); + core::ScopedUnref scoped_unref(dataset); + + std::unique_ptr iterator_ctx; + TF_ASSERT_OK(CreateIteratorContext(dataset_kernel_ctx.get(), &iterator_ctx)); + std::unique_ptr iterator; + TF_ASSERT_OK( + dataset->MakeIterator(iterator_ctx.get(), "Iterator", &iterator)); + EXPECT_EQ(iterator->prefix(), "Iterator::Concatenate"); +} + +struct IteratorRoundtripTest : ConcatenateDatasetOpTest, + ::testing::WithParamInterface {}; + +TEST_P(IteratorRoundtripTest, Roundtrip) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + + std::vector> tensor_vectors = GetParam().input_tensors; + std::vector expected_outputs = GetParam().expected_outputs; + auto expected_outputs_it = expected_outputs.begin(); + std::vector expected_output_shapes = + GetParam().expected_output_shapes; + DataTypeVector expected_output_dtypes = GetParam().expected_output_dtypes; + std::vector tensor_slice_dataset_tensors; + TF_ASSERT_OK(CreateTensorSliceDatasetTensors(tensor_vectors, + &tensor_slice_dataset_tensors)); + gtl::InlinedVector inputs; + for (auto &tensor : tensor_slice_dataset_tensors) { + inputs.emplace_back(&tensor); + } + + std::unique_ptr dataset_kernel; + TF_ASSERT_OK(CreateConcatenateDatasetKernel( + expected_output_dtypes, expected_output_shapes, &dataset_kernel)); + std::unique_ptr dataset_kernel_ctx; + TF_ASSERT_OK(CreateConcatenateDatasetContext(dataset_kernel.get(), &inputs, + &dataset_kernel_ctx)); + DatasetBase *dataset; + TF_ASSERT_OK( + CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); + core::ScopedUnref scoped_unref(dataset); + + std::unique_ptr iterator_ctx; + TF_ASSERT_OK(CreateIteratorContext(dataset_kernel_ctx.get(), &iterator_ctx)); + std::unique_ptr iterator; + TF_ASSERT_OK( + dataset->MakeIterator(iterator_ctx.get(), "Iterator", &iterator)); + + std::unique_ptr serialization_ctx; + TF_ASSERT_OK(CreateSerializationContext(&serialization_ctx)); + + bool end_of_sequence = false; + std::vector out_tensors; + int cur_iteration = 0; + std::vector breakpoints = GetParam().breakpoints; + for (int breakpoint : breakpoints) { + VariantTensorData data; + VariantTensorDataWriter writer(&data); + TF_EXPECT_OK(iterator->Save(serialization_ctx.get(), &writer)); + TF_EXPECT_OK(writer.Flush()); + VariantTensorDataReader reader(&data); + TF_EXPECT_OK(iterator->Restore(iterator_ctx.get(), &reader)); + + while (cur_iteration < breakpoint) { + TF_EXPECT_OK(iterator->GetNext(iterator_ctx.get(), &out_tensors, + &end_of_sequence)); + if (!end_of_sequence) { + for (auto &tensor : out_tensors) { + EXPECT_NE(expected_outputs_it, expected_outputs.end()); + TF_EXPECT_OK(ExpectEqual(tensor, *expected_outputs_it)); + expected_outputs_it++; + } + } + cur_iteration++; + } + + if (breakpoint >= dataset->Cardinality()) { + EXPECT_TRUE(end_of_sequence); + EXPECT_EQ(expected_outputs_it, expected_outputs.end()); + } else { + EXPECT_FALSE(end_of_sequence); + } + } +} + +INSTANTIATE_TEST_CASE_P(ConcatenateDatasetOpTest, IteratorRoundtripTest, + ::testing::ValuesIn(TestCases)); +} // namespace +} // namespace data +} // namespace tensorflow -- GitLab From 099c4badf358ceeb25dc2625447cce6ea62da00c Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Mon, 11 Feb 2019 12:00:54 -0800 Subject: [PATCH 332/766] Fix the typo issue --- .../core/kernels/data/map_dataset_op_test.cc | 38 +++++++++---------- .../kernels/data/range_dataset_op_test.cc | 20 +++++----- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/tensorflow/core/kernels/data/map_dataset_op_test.cc b/tensorflow/core/kernels/data/map_dataset_op_test.cc index f9c1cf4936..68803dcf12 100644 --- a/tensorflow/core/kernels/data/map_dataset_op_test.cc +++ b/tensorflow/core/kernels/data/map_dataset_op_test.cc @@ -130,7 +130,7 @@ TEST_P(DatasetGetNextTest, GetNext) { DatasetBase* map_dataset; TF_ASSERT_OK( CreateDataset(map_kernel.get(), map_context.get(), &map_dataset)); - core::ScopedUnref scored_unref_map_dataset(map_dataset); + core::ScopedUnref scoped_unref_map_dataset(map_dataset); std::unique_ptr iterator_context; TF_ASSERT_OK(CreateIteratorContext(map_context.get(), &iterator_context)); @@ -176,7 +176,7 @@ TEST_F(MapDatasetOpTest, DatasetName) { DatasetBase* range_dataset; TF_ASSERT_OK( CreateRangeDataset(start, end, step, "range", &range_dataset)); - core::ScopedUnref scored_unref_range_dataset(range_dataset); + core::ScopedUnref scoped_unref_range_dataset(range_dataset); std::unique_ptr map_kernel; TF_ASSERT_OK(CreateMapDatasetOpKernel( @@ -187,7 +187,7 @@ TEST_F(MapDatasetOpTest, DatasetName) { DatasetBase* map_dataset; TF_ASSERT_OK( CreateDataset(map_kernel.get(), map_context.get(), &map_dataset)); - core::ScopedUnref scored_unref_map_dataset(map_dataset); + core::ScopedUnref scoped_unref_map_dataset(map_dataset); EXPECT_EQ(map_dataset->name(), kOpName); } @@ -203,7 +203,7 @@ TEST_F(MapDatasetOpTest, DatasetOutputDtypes) { DatasetBase* range_dataset; TF_ASSERT_OK( CreateRangeDataset(start, end, step, "range", &range_dataset)); - core::ScopedUnref scored_unref_range_dataset(range_dataset); + core::ScopedUnref scoped_unref_range_dataset(range_dataset); std::unique_ptr map_kernel; TF_ASSERT_OK(CreateMapDatasetOpKernel( @@ -214,7 +214,7 @@ TEST_F(MapDatasetOpTest, DatasetOutputDtypes) { DatasetBase* map_dataset; TF_ASSERT_OK( CreateDataset(map_kernel.get(), map_context.get(), &map_dataset)); - core::ScopedUnref scored_unref_map_dataset(map_dataset); + core::ScopedUnref scoped_unref_map_dataset(map_dataset); DataTypeVector expected_dtypes({DT_INT64}); EXPECT_EQ(map_dataset->output_dtypes(), expected_dtypes); @@ -231,7 +231,7 @@ TEST_F(MapDatasetOpTest, DatasetOutputShapes) { DatasetBase* range_dataset; TF_ASSERT_OK( CreateRangeDataset(start, end, step, "range", &range_dataset)); - core::ScopedUnref scored_unref_range_dataset(range_dataset); + core::ScopedUnref scoped_unref_range_dataset(range_dataset); std::unique_ptr map_kernel; TF_ASSERT_OK(CreateMapDatasetOpKernel( @@ -242,7 +242,7 @@ TEST_F(MapDatasetOpTest, DatasetOutputShapes) { DatasetBase* map_dataset; TF_ASSERT_OK( CreateDataset(map_kernel.get(), map_context.get(), &map_dataset)); - core::ScopedUnref scored_unref_map_dataset(map_dataset); + core::ScopedUnref scoped_unref_map_dataset(map_dataset); std::vector expected_shapes({PartialTensorShape({})}); EXPECT_EQ(map_dataset->output_shapes().size(), expected_shapes.size()); @@ -283,7 +283,7 @@ TEST_P(DatasetCardinalityTest, Cardinality) { TF_ASSERT_OK(CreateRangeDataset(test_params.start, test_params.end, test_params.step, "range", &range_dataset)); - core::ScopedUnref scored_unref_range_dataset(range_dataset); + core::ScopedUnref scoped_unref_range_dataset(range_dataset); std::unique_ptr map_kernel; TF_ASSERT_OK(CreateMapDatasetOpKernel( @@ -294,7 +294,7 @@ TEST_P(DatasetCardinalityTest, Cardinality) { DatasetBase* map_dataset; TF_ASSERT_OK( CreateDataset(map_kernel.get(), map_context.get(), &map_dataset)); - core::ScopedUnref scored_unref_map_dataset(map_dataset); + core::ScopedUnref scoped_unref_map_dataset(map_dataset); EXPECT_EQ(map_dataset->Cardinality(), test_params.expected_cardinality); } @@ -315,7 +315,7 @@ TEST_F(MapDatasetOpTest, DatasetSave) { DatasetBase* range_dataset; TF_ASSERT_OK( CreateRangeDataset(start, end, step, "range", &range_dataset)); - core::ScopedUnref scored_unref_range_dataset(range_dataset); + core::ScopedUnref scoped_unref_range_dataset(range_dataset); std::unique_ptr map_kernel; TF_ASSERT_OK(CreateMapDatasetOpKernel( @@ -326,7 +326,7 @@ TEST_F(MapDatasetOpTest, DatasetSave) { DatasetBase* map_dataset; TF_ASSERT_OK( CreateDataset(map_kernel.get(), map_context.get(), &map_dataset)); - core::ScopedUnref scored_unref_map_dataset(map_dataset); + core::ScopedUnref scoped_unref_map_dataset(map_dataset); std::unique_ptr serialization_context; TF_ASSERT_OK(CreateSerializationContext(&serialization_context)); @@ -347,7 +347,7 @@ TEST_F(MapDatasetOpTest, IteratorOutputDtypes) { DatasetBase* range_dataset; TF_ASSERT_OK( CreateRangeDataset(start, end, step, "range", &range_dataset)); - core::ScopedUnref scored_unref_range_dataset(range_dataset); + core::ScopedUnref scoped_unref_range_dataset(range_dataset); std::unique_ptr map_kernel; TF_ASSERT_OK(CreateMapDatasetOpKernel( @@ -358,7 +358,7 @@ TEST_F(MapDatasetOpTest, IteratorOutputDtypes) { DatasetBase* map_dataset; TF_ASSERT_OK( CreateDataset(map_kernel.get(), map_context.get(), &map_dataset)); - core::ScopedUnref scored_unref_map_dataset(map_dataset); + core::ScopedUnref scoped_unref_map_dataset(map_dataset); std::unique_ptr iterator_context; TF_ASSERT_OK(CreateIteratorContext(map_context.get(), &iterator_context)); @@ -380,7 +380,7 @@ TEST_F(MapDatasetOpTest, IteratorOutputShapes) { DatasetBase* range_dataset; TF_ASSERT_OK( CreateRangeDataset(start, end, step, "range", &range_dataset)); - core::ScopedUnref scored_unref_range_dataset(range_dataset); + core::ScopedUnref scoped_unref_range_dataset(range_dataset); std::unique_ptr map_kernel; TF_ASSERT_OK(CreateMapDatasetOpKernel( @@ -391,7 +391,7 @@ TEST_F(MapDatasetOpTest, IteratorOutputShapes) { DatasetBase* map_dataset; TF_ASSERT_OK( CreateDataset(map_kernel.get(), map_context.get(), &map_dataset)); - core::ScopedUnref scored_unref_map_dataset(map_dataset); + core::ScopedUnref scoped_unref_map_dataset(map_dataset); std::unique_ptr iterator_context; TF_ASSERT_OK(CreateIteratorContext(map_context.get(), &iterator_context)); @@ -417,7 +417,7 @@ TEST_F(MapDatasetOpTest, IteratorOutputPrefix) { DatasetBase* range_dataset; TF_ASSERT_OK( CreateRangeDataset(start, end, step, "range", &range_dataset)); - core::ScopedUnref scored_unref_range_dataset(range_dataset); + core::ScopedUnref scoped_unref_range_dataset(range_dataset); std::unique_ptr map_kernel; TF_ASSERT_OK(CreateMapDatasetOpKernel( @@ -428,7 +428,7 @@ TEST_F(MapDatasetOpTest, IteratorOutputPrefix) { DatasetBase* map_dataset; TF_ASSERT_OK( CreateDataset(map_kernel.get(), map_context.get(), &map_dataset)); - core::ScopedUnref scored_unref_map_dataset(map_dataset); + core::ScopedUnref scoped_unref_map_dataset(map_dataset); std::unique_ptr iterator_context; TF_ASSERT_OK(CreateIteratorContext(map_context.get(), &iterator_context)); @@ -477,7 +477,7 @@ TEST_P(IteratorRoundtripTest, Roundtrip) { TF_ASSERT_OK(CreateRangeDataset(test_params.start, test_params.end, test_params.step, "range", &range_dataset)); - core::ScopedUnref scored_unref_range_dataset(range_dataset); + core::ScopedUnref scoped_unref_range_dataset(range_dataset); std::unique_ptr map_kernel; TF_ASSERT_OK(CreateMapDatasetOpKernel( @@ -488,7 +488,7 @@ TEST_P(IteratorRoundtripTest, Roundtrip) { DatasetBase* map_dataset; TF_ASSERT_OK( CreateDataset(map_kernel.get(), map_context.get(), &map_dataset)); - core::ScopedUnref scored_unref_map_dataset(map_dataset); + core::ScopedUnref scoped_unref_map_dataset(map_dataset); std::unique_ptr iterator_context; TF_ASSERT_OK(CreateIteratorContext(map_context.get(), &iterator_context)); diff --git a/tensorflow/core/kernels/data/range_dataset_op_test.cc b/tensorflow/core/kernels/data/range_dataset_op_test.cc index 0bbc09a212..9cc3ce54c7 100644 --- a/tensorflow/core/kernels/data/range_dataset_op_test.cc +++ b/tensorflow/core/kernels/data/range_dataset_op_test.cc @@ -85,7 +85,7 @@ TEST_P(DatasetGetNextTest, GetNext) { DatasetBase* range_dataset; TF_ASSERT_OK( CreateDataset(range_kernel.get(), range_context.get(), &range_dataset)); - core::ScopedUnref scored_unref(range_dataset); + core::ScopedUnref scoped_unref(range_dataset); std::unique_ptr iterator_context; TF_ASSERT_OK(CreateIteratorContext(range_context.get(), &iterator_context)); @@ -134,7 +134,7 @@ TEST_F(RangeDatasetOpTest, DatasetName) { DatasetBase* range_dataset; TF_ASSERT_OK( CreateDataset(range_kernel.get(), range_context.get(), &range_dataset)); - core::ScopedUnref scored_unref(range_dataset); + core::ScopedUnref scoped_unref(range_dataset); EXPECT_EQ(range_dataset->name(), kOpName); } @@ -154,7 +154,7 @@ TEST_F(RangeDatasetOpTest, DatasetOutputDtypes) { DatasetBase* range_dataset; TF_ASSERT_OK( CreateDataset(range_kernel.get(), range_context.get(), &range_dataset)); - core::ScopedUnref scored_unref(range_dataset); + core::ScopedUnref scoped_unref(range_dataset); DataTypeVector expected_dtypes({DT_INT64}); EXPECT_EQ(range_dataset->output_dtypes(), expected_dtypes); @@ -175,7 +175,7 @@ TEST_F(RangeDatasetOpTest, DatasetOutputShapes) { DatasetBase* range_dataset; TF_ASSERT_OK( CreateDataset(range_kernel.get(), range_context.get(), &range_dataset)); - core::ScopedUnref scored_unref(range_dataset); + core::ScopedUnref scoped_unref(range_dataset); std::vector expected_shapes({PartialTensorShape({})}); EXPECT_EQ(range_dataset->output_shapes().size(), expected_shapes.size()); @@ -219,7 +219,7 @@ TEST_P(DatasetCardinalityTest, Cardinality) { DatasetBase* range_dataset; TF_ASSERT_OK( CreateDataset(range_kernel.get(), range_context.get(), &range_dataset)); - core::ScopedUnref scored_unref(range_dataset); + core::ScopedUnref scoped_unref(range_dataset); EXPECT_EQ(range_dataset->Cardinality(), params.expected_cardinality); } @@ -244,7 +244,7 @@ TEST_F(RangeDatasetOpTest, DatasetSave) { DatasetBase* range_dataset; TF_ASSERT_OK( CreateDataset(range_kernel.get(), range_context.get(), &range_dataset)); - core::ScopedUnref scored_unref(range_dataset); + core::ScopedUnref scoped_unref(range_dataset); std::unique_ptr serialization_context; TF_ASSERT_OK(CreateSerializationContext(&serialization_context)); @@ -270,7 +270,7 @@ TEST_F(RangeDatasetOpTest, IteratorOutputDtypes) { DatasetBase* range_dataset; TF_ASSERT_OK( CreateDataset(range_kernel.get(), range_context.get(), &range_dataset)); - core::ScopedUnref scored_unref(range_dataset); + core::ScopedUnref scoped_unref(range_dataset); std::unique_ptr iterator_context; TF_ASSERT_OK(CreateIteratorContext(range_context.get(), &iterator_context)); @@ -297,7 +297,7 @@ TEST_F(RangeDatasetOpTest, IteratorOutputShapes) { DatasetBase* range_dataset; TF_ASSERT_OK( CreateDataset(range_kernel.get(), range_context.get(), &range_dataset)); - core::ScopedUnref scored_unref(range_dataset); + core::ScopedUnref scoped_unref(range_dataset); std::unique_ptr iterator_context; TF_ASSERT_OK(CreateIteratorContext(range_context.get(), &iterator_context)); @@ -327,7 +327,7 @@ TEST_F(RangeDatasetOpTest, IteratorOutputPrefix) { DatasetBase* range_dataset; TF_ASSERT_OK( CreateDataset(range_kernel.get(), range_context.get(), &range_dataset)); - core::ScopedUnref scored_unref(range_dataset); + core::ScopedUnref scoped_unref(range_dataset); std::unique_ptr iterator_context; TF_ASSERT_OK(CreateIteratorContext(range_context.get(), &iterator_context)); @@ -371,7 +371,7 @@ TEST_P(IteratorRoundtripTest, Roundtrip) { DatasetBase* range_dataset; TF_ASSERT_OK( CreateDataset(range_kernel.get(), range_context.get(), &range_dataset)); - core::ScopedUnref scored_unref(range_dataset); + core::ScopedUnref scoped_unref(range_dataset); std::unique_ptr iterator_context; TF_ASSERT_OK(CreateIteratorContext(range_context.get(), &iterator_context)); -- GitLab From b43aa32a6b1362452a1a134afedd19b260ace942 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Wed, 13 Feb 2019 15:05:09 -0800 Subject: [PATCH 333/766] Change some function params to be const reference --- tensorflow/core/kernels/data/dataset_test_base.cc | 10 +++++----- tensorflow/core/kernels/data/dataset_test_base.h | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tensorflow/core/kernels/data/dataset_test_base.cc b/tensorflow/core/kernels/data/dataset_test_base.cc index 99ed844f5b..0aecbd0c89 100644 --- a/tensorflow/core/kernels/data/dataset_test_base.cc +++ b/tensorflow/core/kernels/data/dataset_test_base.cc @@ -36,12 +36,12 @@ Status DatasetOpsTestBase::ExpectEqual(const Tensor& a, const Tensor& b) { } Status DatasetOpsTestBase::CreateTensorSliceDatasetKernel( - StringPiece node_name, DataTypeVector dtypes, - std::vector shapes, + const StringPiece& node_name, const DataTypeVector& dtypes, + const std::vector& shapes, std::unique_ptr* tensor_dataset_kernel) { std::vector components; components.reserve(dtypes.size()); - for (int i = 0; i < dtypes.size(); i++) { + for (int i = 0; i < dtypes.size(); ++i) { components.emplace_back(strings::StrCat("component_", i)); } NodeDef node_def = test::function::NDef( @@ -52,12 +52,12 @@ Status DatasetOpsTestBase::CreateTensorSliceDatasetKernel( } Status DatasetOpsTestBase::CreateTensorSliceDataset( - StringPiece node_name, std::vector components, + const StringPiece& node_name, std::vector& components, DatasetBase** tensor_slice_data) { std::unique_ptr tensor_slice_dataset_kernel; DataTypeVector dtypes; std::vector shapes; - for (auto& t : components) { + for (const auto& t : components) { dtypes.push_back(t.dtype()); gtl::InlinedVector partial_dim_sizes; for (int i = 1; i < t.dims(); ++i) { diff --git a/tensorflow/core/kernels/data/dataset_test_base.h b/tensorflow/core/kernels/data/dataset_test_base.h index 90550aae3e..fb299ea56c 100644 --- a/tensorflow/core/kernels/data/dataset_test_base.h +++ b/tensorflow/core/kernels/data/dataset_test_base.h @@ -110,13 +110,13 @@ class DatasetOpsTestBase : public ::testing::Test { // Creates a new TensorSliceDataset op kernel. Status CreateTensorSliceDatasetKernel( - StringPiece node_name, DataTypeVector dtypes, - std::vector shapes, + const StringPiece& node_name, const DataTypeVector& dtypes, + const std::vector& shapes, std::unique_ptr* tensor_dataset_kernel); // Creates a new TensorSliceDataset. - Status CreateTensorSliceDataset(StringPiece node_name, - std::vector components, + Status CreateTensorSliceDataset(const StringPiece& node_name, + std::vector& components, DatasetBase** tensor_slice_data); // Fetches the dataset from the operation context. -- GitLab From 9e44a1db279183beaef3ac152d9f75f73b183294 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Wed, 13 Feb 2019 21:54:38 -0800 Subject: [PATCH 334/766] Change some function param data type and variable names --- .../data/concatenate_dataset_op_test.cc | 16 ++++++++------- .../core/kernels/data/dataset_test_base.cc | 18 +++++++++-------- .../core/kernels/data/dataset_test_base.h | 10 +++++----- .../core/kernels/data/zip_dataset_op_test.cc | 20 ++++++++++--------- 4 files changed, 35 insertions(+), 29 deletions(-) diff --git a/tensorflow/core/kernels/data/concatenate_dataset_op_test.cc b/tensorflow/core/kernels/data/concatenate_dataset_op_test.cc index 7a8e8ea037..6d4dcea178 100644 --- a/tensorflow/core/kernels/data/concatenate_dataset_op_test.cc +++ b/tensorflow/core/kernels/data/concatenate_dataset_op_test.cc @@ -24,16 +24,17 @@ constexpr char kOpName[] = "ConcatenateDataset"; class ConcatenateDatasetOpTest : public DatasetOpsTestBase { protected: - // Creates TensorSliceDatasets and save them as the variant tensors. + // Creates `TensorSliceDataset` variant tensors from the input vector of + // tensor vectors. Status CreateTensorSliceDatasetTensors( - std::vector> tensor_vectors, - std::vector *dataset_tensors) { + const std::vector> &tensor_vectors, + std::vector *const dataset_tensors) { for (int i = 0; i < tensor_vectors.size(); ++i) { std::vector tensors = tensor_vectors[i]; DatasetBase *tensor_slice_dataset; TF_RETURN_IF_ERROR( CreateTensorSliceDataset(strings::StrCat("tensor_slice_node_", i), - tensors, &tensor_slice_dataset)); + &tensors, &tensor_slice_dataset)); Tensor dataset_tensor(DT_VARIANT, TensorShape({})); TF_RETURN_IF_ERROR( StoreDatasetInVariantTensor(tensor_slice_dataset, &dataset_tensor)); @@ -44,8 +45,8 @@ class ConcatenateDatasetOpTest : public DatasetOpsTestBase { // Creates a new ConcatenateDataset op kernel. Status CreateConcatenateDatasetKernel( - DataTypeVector output_tyeps, - std::vector output_shapes, + const DataTypeVector &output_tyeps, + const std::vector &output_shapes, std::unique_ptr *op_kernel) { node_def_ = test::function::NDef( kNodeName, kOpName, {"input_dataset", "another_dataset"}, @@ -56,7 +57,8 @@ class ConcatenateDatasetOpTest : public DatasetOpsTestBase { // Creates a new ConcatenateDataset op kernel context. Status CreateConcatenateDatasetContext( - OpKernel *const op_kernel, gtl::InlinedVector *inputs, + OpKernel *const op_kernel, + gtl::InlinedVector *const inputs, std::unique_ptr *context) { TF_RETURN_IF_ERROR(CheckOpKernelInput(*op_kernel, *inputs)); TF_RETURN_IF_ERROR(CreateOpKernelContext(op_kernel, inputs, context)); diff --git a/tensorflow/core/kernels/data/dataset_test_base.cc b/tensorflow/core/kernels/data/dataset_test_base.cc index 0aecbd0c89..590d7c7a41 100644 --- a/tensorflow/core/kernels/data/dataset_test_base.cc +++ b/tensorflow/core/kernels/data/dataset_test_base.cc @@ -36,28 +36,30 @@ Status DatasetOpsTestBase::ExpectEqual(const Tensor& a, const Tensor& b) { } Status DatasetOpsTestBase::CreateTensorSliceDatasetKernel( - const StringPiece& node_name, const DataTypeVector& dtypes, + StringPiece node_name, const DataTypeVector& dtypes, const std::vector& shapes, - std::unique_ptr* tensor_dataset_kernel) { + std::unique_ptr* tensor_slice_dataset_kernel) { std::vector components; components.reserve(dtypes.size()); for (int i = 0; i < dtypes.size(); ++i) { + // Create the placeholder names for the input components of + // `TensorSliceDataset`. components.emplace_back(strings::StrCat("component_", i)); } NodeDef node_def = test::function::NDef( node_name, "TensorSliceDataset", components, {{"Toutput_types", dtypes}, {"output_shapes", shapes}}); - TF_RETURN_IF_ERROR(CreateOpKernel(node_def, tensor_dataset_kernel)); + TF_RETURN_IF_ERROR(CreateOpKernel(node_def, tensor_slice_dataset_kernel)); return Status::OK(); } Status DatasetOpsTestBase::CreateTensorSliceDataset( - const StringPiece& node_name, std::vector& components, - DatasetBase** tensor_slice_data) { + StringPiece node_name, std::vector* const components, + DatasetBase** tensor_slice_dataset) { std::unique_ptr tensor_slice_dataset_kernel; DataTypeVector dtypes; std::vector shapes; - for (const auto& t : components) { + for (const auto& t : *components) { dtypes.push_back(t.dtype()); gtl::InlinedVector partial_dim_sizes; for (int i = 1; i < t.dims(); ++i) { @@ -68,7 +70,7 @@ Status DatasetOpsTestBase::CreateTensorSliceDataset( TF_RETURN_IF_ERROR(CreateTensorSliceDatasetKernel( node_name, dtypes, shapes, &tensor_slice_dataset_kernel)); gtl::InlinedVector inputs; - for (auto& tensor : components) { + for (auto& tensor : *components) { inputs.emplace_back(&tensor); } TF_RETURN_IF_ERROR(CheckOpKernelInput(*tensor_slice_dataset_kernel, inputs)); @@ -78,7 +80,7 @@ Status DatasetOpsTestBase::CreateTensorSliceDataset( TF_RETURN_IF_ERROR( RunOpKernel(tensor_slice_dataset_kernel.get(), context.get())); TF_RETURN_IF_ERROR( - GetDatasetFromContext(context.get(), 0, tensor_slice_data)); + GetDatasetFromContext(context.get(), 0, tensor_slice_dataset)); return Status::OK(); } diff --git a/tensorflow/core/kernels/data/dataset_test_base.h b/tensorflow/core/kernels/data/dataset_test_base.h index fb299ea56c..1d14608e34 100644 --- a/tensorflow/core/kernels/data/dataset_test_base.h +++ b/tensorflow/core/kernels/data/dataset_test_base.h @@ -110,14 +110,14 @@ class DatasetOpsTestBase : public ::testing::Test { // Creates a new TensorSliceDataset op kernel. Status CreateTensorSliceDatasetKernel( - const StringPiece& node_name, const DataTypeVector& dtypes, + StringPiece node_name, const DataTypeVector& dtypes, const std::vector& shapes, - std::unique_ptr* tensor_dataset_kernel); + std::unique_ptr* tensor_slice_dataset_kernel); // Creates a new TensorSliceDataset. - Status CreateTensorSliceDataset(const StringPiece& node_name, - std::vector& components, - DatasetBase** tensor_slice_data); + Status CreateTensorSliceDataset(StringPiece node_name, + std::vector* const components, + DatasetBase** tensor_slice_dataset); // Fetches the dataset from the operation context. Status GetDatasetFromContext(OpKernelContext* context, int output_index, diff --git a/tensorflow/core/kernels/data/zip_dataset_op_test.cc b/tensorflow/core/kernels/data/zip_dataset_op_test.cc index 2c44b2aca1..87ebd00035 100644 --- a/tensorflow/core/kernels/data/zip_dataset_op_test.cc +++ b/tensorflow/core/kernels/data/zip_dataset_op_test.cc @@ -30,9 +30,9 @@ struct RangeDatasetParam { class ZipDatasetOpTest : public DatasetOpsTestBase { protected: - // Creates RangeDatasets and save them as the variant tensors. - Status CreateRangeDatasetTensors(std::vector params, - std::vector *dataset_tensors) { + // Creates `RangeDataset` variant tensors + Status CreateRangeDatasetTensors(const std::vector ¶ms, + std::vector *const dataset_tensors) { for (int i = 0; i < params.size(); ++i) { DatasetBase *range_dataset; TF_RETURN_IF_ERROR(CreateRangeDataset( @@ -46,9 +46,10 @@ class ZipDatasetOpTest : public DatasetOpsTestBase { } // Creates a new ZipDataset op kernel. - Status CreateZipDatasetKernel(DataTypeVector dtypes, - std::vector output_shapes, - int n, std::unique_ptr *op_kernel) { + Status CreateZipDatasetKernel( + const DataTypeVector &dtypes, + const std::vector &output_shapes, int n, + std::unique_ptr *op_kernel) { std::vector input_datasets; input_datasets.reserve(n); for (int i = 0; i < n; ++i) { @@ -62,9 +63,10 @@ class ZipDatasetOpTest : public DatasetOpsTestBase { } // Creates a new ZipDataset op kernel context. - Status CreateZipDatasetContext(OpKernel *const op_kernel, - gtl::InlinedVector *inputs, - std::unique_ptr *context) { + Status CreateZipDatasetContext( + OpKernel *const op_kernel, + gtl::InlinedVector *const inputs, + std::unique_ptr *context) { TF_RETURN_IF_ERROR(CheckOpKernelInput(*op_kernel, *inputs)); TF_RETURN_IF_ERROR(CreateOpKernelContext(op_kernel, inputs, context)); return Status::OK(); -- GitLab From 28585e585a385c4a0088e52fd128b3c8de740cd2 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Sat, 16 Feb 2019 19:19:48 -0800 Subject: [PATCH 335/766] Reduce the code repetition --- .../data/concatenate_dataset_op_test.cc | 472 +++++------------- .../core/kernels/data/dataset_test_base.cc | 2 + 2 files changed, 129 insertions(+), 345 deletions(-) diff --git a/tensorflow/core/kernels/data/concatenate_dataset_op_test.cc b/tensorflow/core/kernels/data/concatenate_dataset_op_test.cc index 6d4dcea178..38d5acb580 100644 --- a/tensorflow/core/kernels/data/concatenate_dataset_op_test.cc +++ b/tensorflow/core/kernels/data/concatenate_dataset_op_test.cc @@ -77,34 +77,39 @@ struct TestParam { int64 expected_cardinality; std::vector breakpoints; } TestCases[] = { - // Same shape. - {{{DatasetOpsTestBase::CreateTensor(TensorShape{2, 2}, {1, 2, 3, 4}), - DatasetOpsTestBase::CreateTensor(TensorShape{2, 2}, - {5, 6, 7, 8})}, - {DatasetOpsTestBase::CreateTensor(TensorShape{2, 2}, - {11, 12, 13, 14}), - DatasetOpsTestBase::CreateTensor(TensorShape{2, 2}, - {15, 16, 17, 18})}}, - {{DatasetOpsTestBase::CreateTensor(TensorShape{2}, {1, 2}), - DatasetOpsTestBase::CreateTensor(TensorShape{2}, {5, 6}), - DatasetOpsTestBase::CreateTensor(TensorShape{2}, {3, 4}), - DatasetOpsTestBase::CreateTensor(TensorShape{2}, {7, 8}), - DatasetOpsTestBase::CreateTensor(TensorShape{2}, {11, 12}), - DatasetOpsTestBase::CreateTensor(TensorShape{2}, {15, 16}), - DatasetOpsTestBase::CreateTensor(TensorShape{2}, {13, 14}), - DatasetOpsTestBase::CreateTensor(TensorShape{2}, {17, 18})}}, - {DT_INT64, DT_INT64}, + // Test case 1: same shape. + {/*input_tensors*/ {{DatasetOpsTestBase::CreateTensor( + TensorShape{2, 2}, {1, 2, 3, 4}), + DatasetOpsTestBase::CreateTensor( + TensorShape{2, 2}, {5, 6, 7, 8})}, + {DatasetOpsTestBase::CreateTensor( + TensorShape{2, 2}, {11, 12, 13, 14}), + DatasetOpsTestBase::CreateTensor( + TensorShape{2, 2}, {15, 16, 17, 18})}}, + /*expected_outputs*/ + {DatasetOpsTestBase::CreateTensor(TensorShape{2}, {1, 2}), + DatasetOpsTestBase::CreateTensor(TensorShape{2}, {5, 6}), + DatasetOpsTestBase::CreateTensor(TensorShape{2}, {3, 4}), + DatasetOpsTestBase::CreateTensor(TensorShape{2}, {7, 8}), + DatasetOpsTestBase::CreateTensor(TensorShape{2}, {11, 12}), + DatasetOpsTestBase::CreateTensor(TensorShape{2}, {15, 16}), + DatasetOpsTestBase::CreateTensor(TensorShape{2}, {13, 14}), + DatasetOpsTestBase::CreateTensor(TensorShape{2}, {17, 18})}, + /*expected_output_dtypes*/ {DT_INT64, DT_INT64}, + /*expected_output_shapes*/ {PartialTensorShape({2}), PartialTensorShape({2})}, - 4, - {0, 2, 5}}, - // Different shape. - {{{DatasetOpsTestBase::CreateTensor(TensorShape{2, 3}, - {1, 2, 3, 4, 5, 6}), - DatasetOpsTestBase::CreateTensor(TensorShape{2, 2}, - {7, 8, 9, 10})}, - {DatasetOpsTestBase::CreateTensor(TensorShape{2, 2}, - {11, 12, 13, 14}), - DatasetOpsTestBase::CreateTensor(TensorShape{2, 1}, {15, 16})}}, + /*expected_cardinality*/ 4, + /*breakpoints*/ {0, 2, 5}}, + // Test case 2: different shape. + {/*input_tensors*/ {{DatasetOpsTestBase::CreateTensor( + TensorShape{2, 3}, {1, 2, 3, 4, 5, 6}), + DatasetOpsTestBase::CreateTensor( + TensorShape{2, 2}, {7, 8, 9, 10})}, + {DatasetOpsTestBase::CreateTensor( + TensorShape{2, 2}, {11, 12, 13, 14}), + DatasetOpsTestBase::CreateTensor( + TensorShape{2, 1}, {15, 16})}}, + /*expected_outputs*/ {DatasetOpsTestBase::CreateTensor(TensorShape{3}, {1, 2, 3}), DatasetOpsTestBase::CreateTensor(TensorShape{2}, {7, 8}), DatasetOpsTestBase::CreateTensor(TensorShape{3}, {4, 5, 6}), @@ -113,50 +118,62 @@ struct TestParam { DatasetOpsTestBase::CreateTensor(TensorShape{1}, {15}), DatasetOpsTestBase::CreateTensor(TensorShape{2}, {13, 14}), DatasetOpsTestBase::CreateTensor(TensorShape{1}, {16})}, - {DT_INT64, DT_INT64}, + /*expected_output_dtypes*/ {DT_INT64, DT_INT64}, + /*expected_output_shapes*/ {PartialTensorShape({-1}), PartialTensorShape({-1})}, - 4, - {0, 2, 5}}}; + /*expected_cardinality*/ 4, + /*breakpoints*/ {0, 2, 5}}}; -struct DatasetGetNextTest : ConcatenateDatasetOpTest, - ::testing::WithParamInterface {}; +struct ConcatenateDatasetOpTestHelper : ConcatenateDatasetOpTest { + ~ConcatenateDatasetOpTestHelper() { + if (dataset) dataset->Unref(); + } -TEST_P(DatasetGetNextTest, GetNext) { - int thread_num = 2, cpu_num = 2; - TF_ASSERT_OK(InitThreadPool(thread_num)); - TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + Status CreateDatasetFromTestCase(const TestParam &test_case) { + std::vector tensor_slice_dataset_tensors; + TF_RETURN_IF_ERROR(CreateTensorSliceDatasetTensors( + test_case.input_tensors, &tensor_slice_dataset_tensors)); + gtl::InlinedVector inputs; + for (auto &tensor : tensor_slice_dataset_tensors) { + inputs.emplace_back(&tensor); + } + TF_RETURN_IF_ERROR(CreateConcatenateDatasetKernel( + test_case.expected_output_dtypes, test_case.expected_output_shapes, + &dataset_kernel)); + TF_RETURN_IF_ERROR(CreateConcatenateDatasetContext( + dataset_kernel.get(), &inputs, &dataset_kernel_ctx)); + TF_RETURN_IF_ERROR(CreateDataset(dataset_kernel.get(), + dataset_kernel_ctx.get(), &dataset)); + return Status::OK(); + } - std::vector> tensor_vectors = GetParam().input_tensors; - std::vector expected_outputs = GetParam().expected_outputs; - auto expected_outputs_it = expected_outputs.begin(); - std::vector expected_output_shapes = - GetParam().expected_output_shapes; - DataTypeVector expected_output_dtypes = GetParam().expected_output_dtypes; - std::vector tensor_slice_dataset_tensors; - TF_ASSERT_OK(CreateTensorSliceDatasetTensors(tensor_vectors, - &tensor_slice_dataset_tensors)); - gtl::InlinedVector inputs; - for (auto &tensor : tensor_slice_dataset_tensors) { - inputs.emplace_back(&tensor); + Status CreateIteratorFromTestCase(const TestParam &test_case) { + TF_RETURN_IF_ERROR(CreateDatasetFromTestCase(test_case)); + TF_RETURN_IF_ERROR( + CreateIteratorContext(dataset_kernel_ctx.get(), &iterator_ctx)); + TF_RETURN_IF_ERROR( + dataset->MakeIterator(iterator_ctx.get(), "Iterator", &iterator)); + return Status::OK(); } std::unique_ptr dataset_kernel; - TF_ASSERT_OK(CreateConcatenateDatasetKernel( - expected_output_dtypes, expected_output_shapes, &dataset_kernel)); std::unique_ptr dataset_kernel_ctx; - TF_ASSERT_OK(CreateConcatenateDatasetContext(dataset_kernel.get(), &inputs, - &dataset_kernel_ctx)); - DatasetBase *dataset; - TF_ASSERT_OK( - CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); - core::ScopedUnref scoped_unref(dataset); - + DatasetBase *dataset = nullptr; // owned by this class. std::unique_ptr iterator_ctx; - TF_ASSERT_OK(CreateIteratorContext(dataset_kernel_ctx.get(), &iterator_ctx)); std::unique_ptr iterator; - TF_ASSERT_OK( - dataset->MakeIterator(iterator_ctx.get(), "Iterator", &iterator)); +}; + +struct DatasetGetNextTest : public ConcatenateDatasetOpTestHelper, + ::testing::WithParamInterface {}; + +TEST_P(DatasetGetNextTest, GetNext) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + const TestParam &test_case = GetParam(); + TF_ASSERT_OK(CreateIteratorFromTestCase(test_case)); + auto expected_outputs_it = test_case.expected_outputs.begin(); bool end_of_sequence = false; std::vector out_tensors; while (!end_of_sequence) { @@ -164,221 +181,99 @@ TEST_P(DatasetGetNextTest, GetNext) { iterator->GetNext(iterator_ctx.get(), &out_tensors, &end_of_sequence)); if (!end_of_sequence) { for (auto &tensor : out_tensors) { - EXPECT_NE(expected_outputs_it, expected_outputs.end()); + EXPECT_NE(expected_outputs_it, test_case.expected_outputs.end()); TF_EXPECT_OK(ExpectEqual(tensor, *expected_outputs_it)); expected_outputs_it++; } } } - EXPECT_EQ(expected_outputs_it, expected_outputs.end()); + EXPECT_EQ(expected_outputs_it, test_case.expected_outputs.end()); } INSTANTIATE_TEST_CASE_P(ConcatenateDatasetOpTest, DatasetGetNextTest, ::testing::ValuesIn(TestCases)); -TEST_F(ConcatenateDatasetOpTest, DifferentDtypes) { +TEST_F(ConcatenateDatasetOpTestHelper, DifferentDtypes) { int thread_num = 2, cpu_num = 2; TF_ASSERT_OK(InitThreadPool(thread_num)); TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); - std::vector> tensor_vectors = { - {CreateTensor(TensorShape({2, 2}), {1, 2, 3, 4})}, - {CreateTensor(TensorShape({2, 2}), {1.0, 2.0, 3.0, 4.0})}}; - std::vector tensor_slice_dataset_tensors; - TF_ASSERT_OK(CreateTensorSliceDatasetTensors(tensor_vectors, - &tensor_slice_dataset_tensors)); - gtl::InlinedVector inputs; - for (auto &tensor : tensor_slice_dataset_tensors) { - inputs.emplace_back(&tensor); - } - std::vector output_shapes = - TestCases[0].expected_output_shapes; - DataTypeVector output_dtypes = TestCases[0].expected_output_dtypes; - std::unique_ptr dataset_kernel; - TF_ASSERT_OK(CreateConcatenateDatasetKernel(output_dtypes, output_shapes, - &dataset_kernel)); - std::unique_ptr dataset_kernel_ctx; - TF_ASSERT_OK(CreateConcatenateDatasetContext(dataset_kernel.get(), &inputs, - &dataset_kernel_ctx)); - DatasetBase *dataset; - EXPECT_EQ( - CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset) - .code(), - tensorflow::error::INVALID_ARGUMENT); + TestParam test_case_with_different_dtypes = { + /*input_tensors*/ { + {CreateTensor(TensorShape({2, 2}), {1, 2, 3, 4})}, + {CreateTensor(TensorShape({2, 2}), {1.0, 2.0, 3.0, 4.0})}}, + /*expected_outputs*/ {}, + /*expected_output_dtypes*/ {DT_INT64}, + /*expected_output_shapes*/ {PartialTensorShape({2})}, + /*expected_cardinality*/ 0, + /*breakpoints*/ {}}; + + EXPECT_EQ(CreateDatasetFromTestCase(test_case_with_different_dtypes).code(), + tensorflow::error::INVALID_ARGUMENT); } -TEST_F(ConcatenateDatasetOpTest, DatasetName) { +TEST_F(ConcatenateDatasetOpTestHelper, DatasetName) { int thread_num = 2, cpu_num = 2; TF_ASSERT_OK(InitThreadPool(thread_num)); TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); - - std::vector> tensor_vectors = TestCases[0].input_tensors; - std::vector tensor_slice_dataset_tensors; - TF_ASSERT_OK(CreateTensorSliceDatasetTensors(tensor_vectors, - &tensor_slice_dataset_tensors)); - gtl::InlinedVector inputs; - for (auto &tensor : tensor_slice_dataset_tensors) { - inputs.emplace_back(&tensor); - } - std::vector output_shapes = - TestCases[0].expected_output_shapes; - DataTypeVector output_dtypes = TestCases[0].expected_output_dtypes; - std::unique_ptr dataset_kernel; - TF_ASSERT_OK(CreateConcatenateDatasetKernel(output_dtypes, output_shapes, - &dataset_kernel)); - std::unique_ptr dataset_kernel_ctx; - TF_ASSERT_OK(CreateConcatenateDatasetContext(dataset_kernel.get(), &inputs, - &dataset_kernel_ctx)); - DatasetBase *dataset; - TF_ASSERT_OK( - CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); - core::ScopedUnref scoped_unref(dataset); + TF_ASSERT_OK(CreateDatasetFromTestCase(TestCases[0])); EXPECT_EQ(dataset->name(), kOpName); } -struct DatasetOutputDtypesTest : ConcatenateDatasetOpTest, +struct DatasetOutputDtypesTest : ConcatenateDatasetOpTestHelper, ::testing::WithParamInterface {}; TEST_P(DatasetOutputDtypesTest, DatasetOutputDtypes) { int thread_num = 2, cpu_num = 2; TF_ASSERT_OK(InitThreadPool(thread_num)); TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); - - std::vector> tensor_vectors = GetParam().input_tensors; - std::vector expected_outputs = GetParam().expected_outputs; - std::vector expected_output_shapes = - GetParam().expected_output_shapes; - DataTypeVector expected_output_dtypes = GetParam().expected_output_dtypes; - std::vector tensor_slice_dataset_tensors; - TF_ASSERT_OK(CreateTensorSliceDatasetTensors(tensor_vectors, - &tensor_slice_dataset_tensors)); - gtl::InlinedVector inputs; - for (auto &tensor : tensor_slice_dataset_tensors) { - inputs.emplace_back(&tensor); - } - - std::unique_ptr dataset_kernel; - TF_ASSERT_OK(CreateConcatenateDatasetKernel( - expected_output_dtypes, expected_output_shapes, &dataset_kernel)); - std::unique_ptr dataset_kernel_ctx; - TF_ASSERT_OK(CreateConcatenateDatasetContext(dataset_kernel.get(), &inputs, - &dataset_kernel_ctx)); - DatasetBase *dataset; - TF_ASSERT_OK( - CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); - core::ScopedUnref scoped_unref(dataset); - - EXPECT_OK(VerifyTypesMatch(dataset->output_dtypes(), expected_output_dtypes)); + const TestParam &test_case = GetParam(); + TF_ASSERT_OK(CreateDatasetFromTestCase(test_case)); + EXPECT_OK(VerifyTypesMatch(dataset->output_dtypes(), + test_case.expected_output_dtypes)); } INSTANTIATE_TEST_CASE_P(ConcatenateDatasetOpTest, DatasetOutputDtypesTest, ::testing::ValuesIn(TestCases)); -struct DatasetOutputShapesTest : ConcatenateDatasetOpTest, +struct DatasetOutputShapesTest : ConcatenateDatasetOpTestHelper, ::testing::WithParamInterface {}; TEST_P(DatasetOutputShapesTest, DatasetOutputShapes) { int thread_num = 2, cpu_num = 2; TF_ASSERT_OK(InitThreadPool(thread_num)); TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); - - std::vector> tensor_vectors = GetParam().input_tensors; - std::vector expected_outputs = GetParam().expected_outputs; - std::vector expected_output_shapes = - GetParam().expected_output_shapes; - DataTypeVector expected_output_dtypes = GetParam().expected_output_dtypes; - std::vector tensor_slice_dataset_tensors; - TF_ASSERT_OK(CreateTensorSliceDatasetTensors(tensor_vectors, - &tensor_slice_dataset_tensors)); - gtl::InlinedVector inputs; - for (auto &tensor : tensor_slice_dataset_tensors) { - inputs.emplace_back(&tensor); - } - - std::unique_ptr dataset_kernel; - TF_ASSERT_OK(CreateConcatenateDatasetKernel( - expected_output_dtypes, expected_output_shapes, &dataset_kernel)); - std::unique_ptr dataset_kernel_ctx; - TF_ASSERT_OK(CreateConcatenateDatasetContext(dataset_kernel.get(), &inputs, - &dataset_kernel_ctx)); - DatasetBase *dataset; - TF_ASSERT_OK( - CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); - core::ScopedUnref scoped_unref(dataset); - - EXPECT_OK( - VerifyShapesCompatible(dataset->output_shapes(), expected_output_shapes)); + const TestParam &test_case = GetParam(); + TF_ASSERT_OK(CreateDatasetFromTestCase(test_case)); + EXPECT_OK(VerifyShapesCompatible(dataset->output_shapes(), + test_case.expected_output_shapes)); } INSTANTIATE_TEST_CASE_P(ConcatenateDatasetOpTest, DatasetOutputShapesTest, ::testing::ValuesIn(TestCases)); -struct DatasetCardinalityTest : ConcatenateDatasetOpTest, +struct DatasetCardinalityTest : ConcatenateDatasetOpTestHelper, ::testing::WithParamInterface {}; TEST_P(DatasetCardinalityTest, Cardinality) { int thread_num = 2, cpu_num = 2; TF_ASSERT_OK(InitThreadPool(thread_num)); TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + const TestParam &test_case = GetParam(); + TF_ASSERT_OK(CreateDatasetFromTestCase(test_case)); - std::vector> tensor_vectors = GetParam().input_tensors; - std::vector expected_outputs = GetParam().expected_outputs; - std::vector expected_output_shapes = - GetParam().expected_output_shapes; - DataTypeVector expected_output_dtypes = GetParam().expected_output_dtypes; - int64 expected_cardinality = GetParam().expected_cardinality; - std::vector tensor_slice_dataset_tensors; - TF_ASSERT_OK(CreateTensorSliceDatasetTensors(tensor_vectors, - &tensor_slice_dataset_tensors)); - gtl::InlinedVector inputs; - for (auto &tensor : tensor_slice_dataset_tensors) { - inputs.emplace_back(&tensor); - } - - std::unique_ptr dataset_kernel; - TF_ASSERT_OK(CreateConcatenateDatasetKernel( - expected_output_dtypes, expected_output_shapes, &dataset_kernel)); - std::unique_ptr dataset_kernel_ctx; - TF_ASSERT_OK(CreateConcatenateDatasetContext(dataset_kernel.get(), &inputs, - &dataset_kernel_ctx)); - DatasetBase *dataset; - TF_ASSERT_OK( - CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); - core::ScopedUnref scoped_unref(dataset); - - EXPECT_EQ(dataset->Cardinality(), expected_cardinality); + EXPECT_EQ(dataset->Cardinality(), GetParam().expected_cardinality); } INSTANTIATE_TEST_CASE_P(ConcatenateDatasetOpTest, DatasetCardinalityTest, ::testing::ValuesIn(TestCases)); -TEST_F(ConcatenateDatasetOpTest, DatasetSave) { +TEST_F(ConcatenateDatasetOpTestHelper, DatasetSave) { int thread_num = 2, cpu_num = 2; TF_ASSERT_OK(InitThreadPool(thread_num)); TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); - - std::vector> tensor_vectors = TestCases[0].input_tensors; - std::vector tensor_slice_dataset_tensors; - TF_ASSERT_OK(CreateTensorSliceDatasetTensors(tensor_vectors, - &tensor_slice_dataset_tensors)); - gtl::InlinedVector inputs; - for (auto &tensor : tensor_slice_dataset_tensors) { - inputs.emplace_back(&tensor); - } - std::vector output_shapes = - TestCases[0].expected_output_shapes; - DataTypeVector output_dtypes = TestCases[0].expected_output_dtypes; - std::unique_ptr dataset_kernel; - TF_ASSERT_OK(CreateConcatenateDatasetKernel(output_dtypes, output_shapes, - &dataset_kernel)); - std::unique_ptr dataset_kernel_ctx; - TF_ASSERT_OK(CreateConcatenateDatasetContext(dataset_kernel.get(), &inputs, - &dataset_kernel_ctx)); - DatasetBase *dataset; - TF_ASSERT_OK( - CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); - core::ScopedUnref scoped_unref(dataset); + TF_ASSERT_OK(CreateDatasetFromTestCase(TestCases[0])); std::unique_ptr serialization_ctx; TF_ASSERT_OK(CreateSerializationContext(&serialization_ctx)); @@ -388,169 +283,56 @@ TEST_F(ConcatenateDatasetOpTest, DatasetSave) { TF_ASSERT_OK(writer.Flush()); } -struct IteratorOutputDtypesTest : ConcatenateDatasetOpTest, +struct IteratorOutputDtypesTest : ConcatenateDatasetOpTestHelper, ::testing::WithParamInterface {}; TEST_P(IteratorOutputDtypesTest, IteratorOutputDtypes) { int thread_num = 2, cpu_num = 2; TF_ASSERT_OK(InitThreadPool(thread_num)); TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); - - std::vector> tensor_vectors = GetParam().input_tensors; - std::vector expected_outputs = GetParam().expected_outputs; - std::vector expected_output_shapes = - GetParam().expected_output_shapes; - DataTypeVector expected_output_dtypes = GetParam().expected_output_dtypes; - std::vector tensor_slice_dataset_tensors; - TF_ASSERT_OK(CreateTensorSliceDatasetTensors(tensor_vectors, - &tensor_slice_dataset_tensors)); - gtl::InlinedVector inputs; - for (auto &tensor : tensor_slice_dataset_tensors) { - inputs.emplace_back(&tensor); - } - - std::unique_ptr dataset_kernel; - TF_ASSERT_OK(CreateConcatenateDatasetKernel( - expected_output_dtypes, expected_output_shapes, &dataset_kernel)); - std::unique_ptr dataset_kernel_ctx; - TF_ASSERT_OK(CreateConcatenateDatasetContext(dataset_kernel.get(), &inputs, - &dataset_kernel_ctx)); - DatasetBase *dataset; - TF_ASSERT_OK( - CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); - core::ScopedUnref scoped_unref(dataset); - - std::unique_ptr iterator_ctx; - TF_ASSERT_OK(CreateIteratorContext(dataset_kernel_ctx.get(), &iterator_ctx)); - std::unique_ptr iterator; - TF_ASSERT_OK( - dataset->MakeIterator(iterator_ctx.get(), "Iterator", &iterator)); - - TF_EXPECT_OK( - VerifyTypesMatch(iterator->output_dtypes(), expected_output_dtypes)); + const TestParam &test_case = GetParam(); + TF_ASSERT_OK(CreateIteratorFromTestCase(test_case)); + TF_EXPECT_OK(VerifyTypesMatch(iterator->output_dtypes(), + test_case.expected_output_dtypes)); } INSTANTIATE_TEST_CASE_P(ConcatenateDatasetOpTest, IteratorOutputDtypesTest, ::testing::ValuesIn(TestCases)); -struct IteratorOutputShapesTest : ConcatenateDatasetOpTest, +struct IteratorOutputShapesTest : ConcatenateDatasetOpTestHelper, ::testing::WithParamInterface {}; TEST_P(IteratorOutputShapesTest, IteratorOutputShapes) { int thread_num = 2, cpu_num = 2; TF_ASSERT_OK(InitThreadPool(thread_num)); TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); - - std::vector> tensor_vectors = GetParam().input_tensors; - std::vector expected_outputs = GetParam().expected_outputs; - std::vector expected_output_shapes = - GetParam().expected_output_shapes; - DataTypeVector expected_output_dtypes = GetParam().expected_output_dtypes; - std::vector tensor_slice_dataset_tensors; - TF_ASSERT_OK(CreateTensorSliceDatasetTensors(tensor_vectors, - &tensor_slice_dataset_tensors)); - gtl::InlinedVector inputs; - for (auto &tensor : tensor_slice_dataset_tensors) { - inputs.emplace_back(&tensor); - } - - std::unique_ptr dataset_kernel; - TF_ASSERT_OK(CreateConcatenateDatasetKernel( - expected_output_dtypes, expected_output_shapes, &dataset_kernel)); - std::unique_ptr dataset_kernel_ctx; - TF_ASSERT_OK(CreateConcatenateDatasetContext(dataset_kernel.get(), &inputs, - &dataset_kernel_ctx)); - DatasetBase *dataset; - TF_ASSERT_OK( - CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); - core::ScopedUnref scoped_unref(dataset); - - std::unique_ptr iterator_ctx; - TF_ASSERT_OK(CreateIteratorContext(dataset_kernel_ctx.get(), &iterator_ctx)); - std::unique_ptr iterator; - TF_ASSERT_OK( - dataset->MakeIterator(iterator_ctx.get(), "Iterator", &iterator)); - + const TestParam &test_case = GetParam(); + TF_ASSERT_OK(CreateIteratorFromTestCase(test_case)); TF_EXPECT_OK(VerifyShapesCompatible(iterator->output_shapes(), - expected_output_shapes)); + test_case.expected_output_shapes)); } INSTANTIATE_TEST_CASE_P(ConcatenateDatasetOpTest, IteratorOutputShapesTest, ::testing::ValuesIn(TestCases)); -TEST_F(ConcatenateDatasetOpTest, IteratorOutputPrefix) { +TEST_F(ConcatenateDatasetOpTestHelper, IteratorOutputPrefix) { int thread_num = 2, cpu_num = 2; TF_ASSERT_OK(InitThreadPool(thread_num)); TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); - - std::vector> tensor_vectors = TestCases[0].input_tensors; - std::vector tensor_slice_dataset_tensors; - TF_ASSERT_OK(CreateTensorSliceDatasetTensors(tensor_vectors, - &tensor_slice_dataset_tensors)); - gtl::InlinedVector inputs; - for (auto &tensor : tensor_slice_dataset_tensors) { - inputs.emplace_back(&tensor); - } - std::vector output_shapes = - TestCases[0].expected_output_shapes; - DataTypeVector output_dtypes = TestCases[0].expected_output_dtypes; - std::unique_ptr dataset_kernel; - TF_ASSERT_OK(CreateConcatenateDatasetKernel(output_dtypes, output_shapes, - &dataset_kernel)); - std::unique_ptr dataset_kernel_ctx; - TF_ASSERT_OK(CreateConcatenateDatasetContext(dataset_kernel.get(), &inputs, - &dataset_kernel_ctx)); - DatasetBase *dataset; - TF_ASSERT_OK( - CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); - core::ScopedUnref scoped_unref(dataset); - - std::unique_ptr iterator_ctx; - TF_ASSERT_OK(CreateIteratorContext(dataset_kernel_ctx.get(), &iterator_ctx)); - std::unique_ptr iterator; - TF_ASSERT_OK( - dataset->MakeIterator(iterator_ctx.get(), "Iterator", &iterator)); + TF_ASSERT_OK(CreateIteratorFromTestCase(TestCases[0])); EXPECT_EQ(iterator->prefix(), "Iterator::Concatenate"); } -struct IteratorRoundtripTest : ConcatenateDatasetOpTest, +struct IteratorRoundtripTest : ConcatenateDatasetOpTestHelper, ::testing::WithParamInterface {}; TEST_P(IteratorRoundtripTest, Roundtrip) { int thread_num = 2, cpu_num = 2; TF_ASSERT_OK(InitThreadPool(thread_num)); TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); - - std::vector> tensor_vectors = GetParam().input_tensors; - std::vector expected_outputs = GetParam().expected_outputs; - auto expected_outputs_it = expected_outputs.begin(); - std::vector expected_output_shapes = - GetParam().expected_output_shapes; - DataTypeVector expected_output_dtypes = GetParam().expected_output_dtypes; - std::vector tensor_slice_dataset_tensors; - TF_ASSERT_OK(CreateTensorSliceDatasetTensors(tensor_vectors, - &tensor_slice_dataset_tensors)); - gtl::InlinedVector inputs; - for (auto &tensor : tensor_slice_dataset_tensors) { - inputs.emplace_back(&tensor); - } - - std::unique_ptr dataset_kernel; - TF_ASSERT_OK(CreateConcatenateDatasetKernel( - expected_output_dtypes, expected_output_shapes, &dataset_kernel)); - std::unique_ptr dataset_kernel_ctx; - TF_ASSERT_OK(CreateConcatenateDatasetContext(dataset_kernel.get(), &inputs, - &dataset_kernel_ctx)); - DatasetBase *dataset; - TF_ASSERT_OK( - CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); - core::ScopedUnref scoped_unref(dataset); - - std::unique_ptr iterator_ctx; - TF_ASSERT_OK(CreateIteratorContext(dataset_kernel_ctx.get(), &iterator_ctx)); - std::unique_ptr iterator; - TF_ASSERT_OK( - dataset->MakeIterator(iterator_ctx.get(), "Iterator", &iterator)); + const TestParam &test_case = GetParam(); + auto expected_outputs_it = test_case.expected_outputs.begin(); + TF_ASSERT_OK(CreateIteratorFromTestCase(test_case)); std::unique_ptr serialization_ctx; TF_ASSERT_OK(CreateSerializationContext(&serialization_ctx)); @@ -572,7 +354,7 @@ TEST_P(IteratorRoundtripTest, Roundtrip) { &end_of_sequence)); if (!end_of_sequence) { for (auto &tensor : out_tensors) { - EXPECT_NE(expected_outputs_it, expected_outputs.end()); + EXPECT_NE(expected_outputs_it, test_case.expected_outputs.end()); TF_EXPECT_OK(ExpectEqual(tensor, *expected_outputs_it)); expected_outputs_it++; } @@ -582,7 +364,7 @@ TEST_P(IteratorRoundtripTest, Roundtrip) { if (breakpoint >= dataset->Cardinality()) { EXPECT_TRUE(end_of_sequence); - EXPECT_EQ(expected_outputs_it, expected_outputs.end()); + EXPECT_EQ(expected_outputs_it, test_case.expected_outputs.end()); } else { EXPECT_FALSE(end_of_sequence); } diff --git a/tensorflow/core/kernels/data/dataset_test_base.cc b/tensorflow/core/kernels/data/dataset_test_base.cc index 590d7c7a41..f5939deab1 100644 --- a/tensorflow/core/kernels/data/dataset_test_base.cc +++ b/tensorflow/core/kernels/data/dataset_test_base.cc @@ -58,7 +58,9 @@ Status DatasetOpsTestBase::CreateTensorSliceDataset( DatasetBase** tensor_slice_dataset) { std::unique_ptr tensor_slice_dataset_kernel; DataTypeVector dtypes; + dtypes.reserve(components->size()); std::vector shapes; + shapes.reserve(components->size()); for (const auto& t : *components) { dtypes.push_back(t.dtype()); gtl::InlinedVector partial_dim_sizes; -- GitLab From c4cec011deb2da0bc1fb60eef72ddd5e4083cf3f Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 22 Feb 2019 01:03:31 -0800 Subject: [PATCH 336/766] compat: Update forward compatibility horizon to 2019-02-22 PiperOrigin-RevId: 235149900 --- tensorflow/python/compat/compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/compat/compat.py b/tensorflow/python/compat/compat.py index c1df5c4f8b..abb1ab7f75 100644 --- a/tensorflow/python/compat/compat.py +++ b/tensorflow/python/compat/compat.py @@ -27,7 +27,7 @@ import datetime from tensorflow.python.util import tf_contextlib from tensorflow.python.util.tf_export import tf_export -_FORWARD_COMPATIBILITY_HORIZON = datetime.date(2019, 2, 21) +_FORWARD_COMPATIBILITY_HORIZON = datetime.date(2019, 2, 22) @tf_export("compat.forward_compatible") -- GitLab From 9d508106b32eb6518912501d29a80ff9967dfe05 Mon Sep 17 00:00:00 2001 From: James Keeling Date: Fri, 22 Feb 2019 02:15:29 -0800 Subject: [PATCH 337/766] Improve documentation and error message for tf.split This function previously stated that it could accept a rank-0 Tensor as its `num_or_size_splits` argument. This is in fact not correct: TF graphs must have a static number of output tensors for any operation, so the number of splits has to be known statically or we cannot validate the correctness of the graph. This method will now raise a ValueError if called with a rank-0 Tensor. I also make the documentation more explicit. Strangely, the test was testing for the incorrect behaviour, so I also fixed that. PiperOrigin-RevId: 235157900 --- .../python/kernel_tests/split_op_test.py | 18 +++++------ tensorflow/python/ops/array_ops.py | 30 +++++++++++-------- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/tensorflow/python/kernel_tests/split_op_test.py b/tensorflow/python/kernel_tests/split_op_test.py index 42b4d1b778..53423991e4 100644 --- a/tensorflow/python/kernel_tests/split_op_test.py +++ b/tensorflow/python/kernel_tests/split_op_test.py @@ -59,17 +59,17 @@ class SplitOpTest(test.TestCase): with self.assertRaises(errors_impl.InvalidArgumentError): sess.run(array_ops.split(model_input, [4]), {model_input: inp}) - # test that we can pass a scalar Tensor as num_splits + # scalar Tensors are not permitted as num_splits for axis in [0, -2]: with self.cached_session(use_gpu=True) as sess: - result = sess.run( - array_ops.split( - array_ops.ones([4, 4]), - num_or_size_splits=array_ops.ones([2, 2]).get_shape()[1], - axis=axis)) - - self.assertEqual(result[0].shape, (2, 4)) - self.assertEqual(result[1].shape, (2, 4)) + with self.assertRaises(ValueError): + # pylint: disable=expression-not-assigned + sess.run( + array_ops.split( + array_ops.ones([4, 4]), + num_or_size_splits=constant_op.constant(2), + axis=axis)) + # pylint: enable=expression-not-assigned # test that none split dimensions remain, even if we don't know how # the split_dim will be split, but we do know the axis diff --git a/tensorflow/python/ops/array_ops.py b/tensorflow/python/ops/array_ops.py index 8db23c467a..801e41922f 100644 --- a/tensorflow/python/ops/array_ops.py +++ b/tensorflow/python/ops/array_ops.py @@ -1464,14 +1464,14 @@ unique_with_counts.__doc__ = gen_array_ops.unique_with_counts.__doc__ def split(value, num_or_size_splits, axis=0, num=None, name="split"): """Splits a tensor into sub tensors. - If `num_or_size_splits` is an integer type, then `value` is split - along dimension `axis` into `num_split` smaller tensors. - Requires that `num_split` evenly divides `value.shape[axis]`. + If `num_or_size_splits` is an integer, then `value` is split along dimension + `axis` into `num_split` smaller tensors. This requires that `num_split` evenly + divides `value.shape[axis]`. - If `num_or_size_splits` is not an integer type, it is presumed to be a Tensor - `size_splits`, then splits `value` into `len(size_splits)` pieces. The shape - of the `i`-th piece has the same size as the `value` except along dimension - `axis` where the size is `size_splits[i]`. + If `num_or_size_splits` is a 1-D Tensor (or list), we call it `size_splits` + and `value` is split into `len(size_splits)` elements. The shape of the `i`-th + element has the same size as the `value` except along dimension `axis` where + the size is `size_splits[i]`. For example: @@ -1489,13 +1489,13 @@ def split(value, num_or_size_splits, axis=0, num=None, name="split"): Args: value: The `Tensor` to split. - num_or_size_splits: Either a 0-D integer `Tensor` indicating the number of - splits along split_dim or a 1-D integer `Tensor` containing + num_or_size_splits: Either an integer indicating the number of + splits along split_dim or a 1-D integer `Tensor` or Python list containing the sizes of each output tensor along split_dim. If a scalar then it must evenly divide `value.shape[axis]`; otherwise the sum of sizes along the split dimension must match that of the `value`. - axis: A 0-D `int32` `Tensor`. The dimension along which to split. - Must be in the range `[-rank(value), rank(value))`. Defaults to 0. + axis: An integer or scalar `int32` `Tensor`. The dimension along which to + split. Must be in the range `[-rank(value), rank(value))`. Defaults to 0. num: Optional, used to specify the number of outputs when it cannot be inferred from the shape of `size_splits`. name: A name for the operation (optional). @@ -1510,10 +1510,16 @@ def split(value, num_or_size_splits, axis=0, num=None, name="split"): ValueError: If `num` is unspecified and cannot be inferred. """ size_splits = ops.convert_to_tensor(num_or_size_splits) - if size_splits._rank() == 0 and size_splits.dtype.is_integer: + if isinstance(num_or_size_splits, + six.integer_types + (tensor_shape.Dimension,)): return gen_array_ops.split( axis=axis, num_split=num_or_size_splits, value=value, name=name) + if size_splits._rank() == 0: + raise ValueError( + "Rank-0 tensors are not supported as the num_or_size_splits argument " + "to split. Argument provided: %s" % (num_or_size_splits,)) + if num is None: size_splits_shape = size_splits._shape_tuple() if size_splits_shape: -- GitLab From a2bb5db1bf7931b0dc2cd08e53b8798489568198 Mon Sep 17 00:00:00 2001 From: Brian Lee Date: Fri, 22 Feb 2019 06:51:22 -0800 Subject: [PATCH 338/766] Be more consistent in handling bound methods in AutoGraph fallback pathway PiperOrigin-RevId: 235184105 --- tensorflow/python/autograph/impl/api.py | 57 +++++++++++++++---------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/tensorflow/python/autograph/impl/api.py b/tensorflow/python/autograph/impl/api.py index 356cdc0e3b..0b29b0b384 100644 --- a/tensorflow/python/autograph/impl/api.py +++ b/tensorflow/python/autograph/impl/api.py @@ -166,6 +166,35 @@ def do_not_convert(run_as=RunMode.GRAPH, return_dtypes=None): return decorator +def _call_original_func(f, *args, **kwargs): + """Call the original function without converting with AutoGraph. + + Args typically include `self`, as required by the conversion process. + When conversion is skipped, `self` is not necessary, because the + original bound method is being executed. This code removes it. + + Args: + f: original function or bound method + *args: args; may have self as first argument. + **kwargs: kwargs + + Returns: + result of executing original function. + """ + # TODO(mdan): This may be inconsistent in certain situations. + # If the function had already been annotated with @tf.function, it + # may be bound to the incorrect object. It's unclear if those situations + # are possible, but if they happen, we need to check if f is bound + # to a shim like WeakrefSelf and unpack it. + + if tf_inspect.ismethod(f) and args: + f_self = inspect_utils.getmethodself(f) + if args[0] is f_self: + args = args[1:] + + return f(*args, **kwargs) + + def converted_call(f, owner, options, args, kwargs): """Compiles a function call inline. For internal use only.""" logging.log(1, @@ -198,14 +227,14 @@ def converted_call(f, owner, options, args, kwargs): ' by AutoGraph. The function will be called without transformation.' ' You may however apply AutoGraph before the decorator.'.format(f), 1) logging.log(2, 'Permanently whitelisted: %s: wrapt decorated', f) - return f(*args, **kwargs) + return _call_original_func(f, *args, **kwargs) # Constructors are permanently whitelisted. # TODO(mdan): Toggle as experimental feature instead. # TODO(b/124016764): Remove this limitation. if tf_inspect.isclass(f): logging.log(2, 'Permanently whitelisted: %s: constructor', f) - return f(*args, **kwargs) + return _call_original_func(f, *args, **kwargs) # Other built-in modules are permanently whitelisted. # TODO(mdan): Figure out how to do this consistently for all stdlib modules. @@ -213,33 +242,17 @@ def converted_call(f, owner, options, args, kwargs): if any(f in m.__dict__.values() for m in (collections, pdb, copy, tf_inspect._inspect)): # pylint:disable=protected-access logging.log(2, 'Permanently whitelisted: %s: part of builtin module', f) - return f(*args, **kwargs) + return _call_original_func(f, *args, **kwargs) - # TODO(mdan): This needs cleanup. if not options.force_conversion and conversion.is_whitelisted_for_graph(f): - - # TODO(mdan): This may be inconsistent in certain situations. - # If the function had already been annotated with @tf.function, it - # may be bound to the incorrect object. It's unclear if those situations - # are possible, but if they happen, we need to check if f is bound - # to a shim like WeakrefSelf and unpack it. - - # Args typically include `self`, as required by the conversion process. - # When conversion is skipped, `self` is not necessary, because the - # original bound method is being executed. This code removes it. - if tf_inspect.ismethod(f) and args: - f_self = inspect_utils.getmethodself(f) - if args[0] is f_self: - args = args[1:] - - return f(*args, **kwargs) + return _call_original_func(f, *args, **kwargs) # internal_convert_user_code is for example turned off when issuing a dynamic # call conversion from generated code while in nonrecursive mode. In that # case we evidently don't want to recurse, but we still have to convert # things like builtins. if not options.internal_convert_user_code: - return f(*args, **kwargs) + return _call_original_func(f, *args, **kwargs) # TODO(mdan): Move this entire block inside to_graph. try: # Begin of transformation error guards @@ -355,7 +368,7 @@ def converted_call(f, owner, options, args, kwargs): ' variable AUTOGRAPH_VERBOSITY >= 1. Please report this to the' ' AutoGraph team. Cause: %s', target_entity, e) - return f(*args, **kwargs) + return _call_original_func(f, *args, **kwargs) result = converted_f(*effective_args, **kwargs) -- GitLab From 565952cc2f17fdfd995e25171cf07be0f6f06180 Mon Sep 17 00:00:00 2001 From: Dan Moldovan Date: Fri, 22 Feb 2019 08:02:36 -0800 Subject: [PATCH 339/766] Work around the limitation in Dataset which prevents for/Dataset loops with no state. PiperOrigin-RevId: 235192783 --- .../autograph/operators/control_flow.py | 12 +++++++-- .../autograph/operators/control_flow_test.py | 26 +++++++++++++------ 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/tensorflow/python/autograph/operators/control_flow.py b/tensorflow/python/autograph/operators/control_flow.py index adfde4623d..0354c7286c 100644 --- a/tensorflow/python/autograph/operators/control_flow.py +++ b/tensorflow/python/autograph/operators/control_flow.py @@ -21,6 +21,7 @@ from __future__ import print_function from tensorflow.python.autograph.operators import py_builtins from tensorflow.python.autograph.operators import special_values from tensorflow.python.data.ops import dataset_ops +from tensorflow.python.framework import constant_op from tensorflow.python.framework import tensor_util from tensorflow.python.ops import control_flow_ops from tensorflow.python.ops import gen_math_ops @@ -139,9 +140,16 @@ def _dataset_for_stmt(ds, extra_test, body, init_state): new_state = body(iterate, *state) return new_state - results = ds.reduce(init_state, reduce_body) + if init_state: + return ds.reduce(init_state, reduce_body) - return results + # Workaround for Datset.reduce not allowing empty state tensors - create + # a dummy state variable that remains unused. + def reduce_body_with_dummy_state(state, iterate): + reduce_body((), iterate) + return state + ds.reduce((constant_op.constant(0),), reduce_body_with_dummy_state) + return () def while_stmt(test, body, init_state, extra_deps, opts=None): diff --git a/tensorflow/python/autograph/operators/control_flow_test.py b/tensorflow/python/autograph/operators/control_flow_test.py index c06c4bcb97..e5af283611 100644 --- a/tensorflow/python/autograph/operators/control_flow_test.py +++ b/tensorflow/python/autograph/operators/control_flow_test.py @@ -23,7 +23,7 @@ from tensorflow.python.data.ops import dataset_ops 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 math_ops +from tensorflow.python.ops import variables from tensorflow.python.platform import test @@ -47,16 +47,26 @@ class ForLoopTest(test.TestCase): init_state=(0,)) self.assertEqual((10,), s) - @test_util.run_deprecated_v1 def test_dataset(self): - to_int32 = lambda i: math_ops.cast(i, dtypes.int32) s = control_flow.for_stmt( - dataset_ops.Dataset.range(5).map(to_int32), - None, + dataset_ops.Dataset.range(5), + extra_test=None, body=lambda i, s: (s + i,), - init_state=(0,)) - with self.cached_session(): - self.assertEqual((10,), self.evaluate(s)) + init_state=(constant_op.constant(0, dtype=dtypes.int64),)) + self.assertEqual(self.evaluate(s), (10,)) + + @test_util.run_v2_only + def test_dataset_no_state(self): + v = variables.Variable(0, dtype=dtypes.int64) + def stateless_with_side_effects(i): + v.assign(v.read_value() + i) + s = control_flow.for_stmt( + dataset_ops.Dataset.range(5), + extra_test=None, + body=stateless_with_side_effects, + init_state=()) + self.evaluate(s) + self.assertEqual(self.evaluate(v.read_value()), 10) class WhileLoopTest(test.TestCase): -- GitLab From 6f2eaa3b99c241a9c09c345e1029513bc4cd470a Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 22 Feb 2019 08:38:40 -0800 Subject: [PATCH 340/766] Internal Change PiperOrigin-RevId: 235197341 --- tensorflow/python/BUILD | 6 +++++- tensorflow/python/training/tracking/BUILD | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tensorflow/python/BUILD b/tensorflow/python/BUILD index 5a8935a6b9..1f2ea28f38 100644 --- a/tensorflow/python/BUILD +++ b/tensorflow/python/BUILD @@ -279,7 +279,10 @@ tf_py_test( size = "small", srcs = ["platform/app_test.py"], additional_deps = [":platform"], - tags = ["notap"], + tags = [ + "nomac", + "notap", + ], ) cc_library( @@ -6050,6 +6053,7 @@ tf_py_test( "grappler", "no_cuda_on_cpu_tap", "no_pip", + "nomac", ], ) diff --git a/tensorflow/python/training/tracking/BUILD b/tensorflow/python/training/tracking/BUILD index 40a6e93e09..dfb7325c53 100644 --- a/tensorflow/python/training/tracking/BUILD +++ b/tensorflow/python/training/tracking/BUILD @@ -93,6 +93,7 @@ tf_py_test( "//tensorflow/python/keras:engine", "//tensorflow/python/keras:layers", ], + tags = ["nomac"], ) py_library( -- GitLab From 0924cc634ee289a231ba6346bf595cd6dd13034a Mon Sep 17 00:00:00 2001 From: Dan Moldovan Date: Fri, 22 Feb 2019 08:40:41 -0800 Subject: [PATCH 341/766] Avoid mixing other arguments with **kwargs in generic function proxies. PiperOrigin-RevId: 235197577 --- tensorflow/python/autograph/impl/api.py | 24 ++++++++++---------- tensorflow/python/autograph/impl/api_test.py | 14 ++++++++++++ 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/tensorflow/python/autograph/impl/api.py b/tensorflow/python/autograph/impl/api.py index 0b29b0b384..d0f52f52eb 100644 --- a/tensorflow/python/autograph/impl/api.py +++ b/tensorflow/python/autograph/impl/api.py @@ -166,20 +166,20 @@ def do_not_convert(run_as=RunMode.GRAPH, return_dtypes=None): return decorator -def _call_original_func(f, *args, **kwargs): - """Call the original function without converting with AutoGraph. +def _call_unconverted(f, args, kwargs): + """Calls the original function without converting with AutoGraph. Args typically include `self`, as required by the conversion process. When conversion is skipped, `self` is not necessary, because the original bound method is being executed. This code removes it. Args: - f: original function or bound method - *args: args; may have self as first argument. - **kwargs: kwargs + f: the original function for which conversion was requested. + args: positional arguments for f May or may not include self. + kwargs: keyword arguments for f Returns: - result of executing original function. + The return value of f(*args, **kwargs). """ # TODO(mdan): This may be inconsistent in certain situations. # If the function had already been annotated with @tf.function, it @@ -227,14 +227,14 @@ def converted_call(f, owner, options, args, kwargs): ' by AutoGraph. The function will be called without transformation.' ' You may however apply AutoGraph before the decorator.'.format(f), 1) logging.log(2, 'Permanently whitelisted: %s: wrapt decorated', f) - return _call_original_func(f, *args, **kwargs) + return _call_unconverted(f, args, kwargs) # Constructors are permanently whitelisted. # TODO(mdan): Toggle as experimental feature instead. # TODO(b/124016764): Remove this limitation. if tf_inspect.isclass(f): logging.log(2, 'Permanently whitelisted: %s: constructor', f) - return _call_original_func(f, *args, **kwargs) + return _call_unconverted(f, args, kwargs) # Other built-in modules are permanently whitelisted. # TODO(mdan): Figure out how to do this consistently for all stdlib modules. @@ -242,17 +242,17 @@ def converted_call(f, owner, options, args, kwargs): if any(f in m.__dict__.values() for m in (collections, pdb, copy, tf_inspect._inspect)): # pylint:disable=protected-access logging.log(2, 'Permanently whitelisted: %s: part of builtin module', f) - return _call_original_func(f, *args, **kwargs) + return _call_unconverted(f, args, kwargs) if not options.force_conversion and conversion.is_whitelisted_for_graph(f): - return _call_original_func(f, *args, **kwargs) + return _call_unconverted(f, args, kwargs) # internal_convert_user_code is for example turned off when issuing a dynamic # call conversion from generated code while in nonrecursive mode. In that # case we evidently don't want to recurse, but we still have to convert # things like builtins. if not options.internal_convert_user_code: - return _call_original_func(f, *args, **kwargs) + return _call_unconverted(f, args, kwargs) # TODO(mdan): Move this entire block inside to_graph. try: # Begin of transformation error guards @@ -368,7 +368,7 @@ def converted_call(f, owner, options, args, kwargs): ' variable AUTOGRAPH_VERBOSITY >= 1. Please report this to the' ' AutoGraph team. Cause: %s', target_entity, e) - return _call_original_func(f, *args, **kwargs) + return _call_unconverted(f, args, kwargs) result = converted_f(*effective_args, **kwargs) diff --git a/tensorflow/python/autograph/impl/api_test.py b/tensorflow/python/autograph/impl/api_test.py index aa9ad0c882..ab24281924 100644 --- a/tensorflow/python/autograph/impl/api_test.py +++ b/tensorflow/python/autograph/impl/api_test.py @@ -515,6 +515,20 @@ class ApiTest(test.TestCase): self.assertEqual(compiled_fn(), 3) + def test_to_graph_with_kwargs_clashing_unconverted_call(self): + + @api.do_not_convert() + def called_fn(**kwargs): + return kwargs['f'] + kwargs['owner'] + + def test_fn(): + # These arg names intentionally match _call_unconverted's + return called_fn(f=1, owner=2) + + compiled_fn = api.to_graph(test_fn) + + self.assertEqual(compiled_fn(), 3) + def test_to_code_basic(self): def test_fn(x, s): -- GitLab From df139a8aeef22aa3a9dfdc6eea389693f7f80b8b Mon Sep 17 00:00:00 2001 From: Guangda Lai Date: Fri, 22 Feb 2019 09:05:46 -0800 Subject: [PATCH 342/766] Reduce spammy logs. This help to make debugging easier when VLOG(1) is enabled. PiperOrigin-RevId: 235201217 --- tensorflow/compiler/tf2tensorrt/convert/convert_nodes.cc | 2 +- tensorflow/compiler/tf2tensorrt/segment/segment.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.cc b/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.cc index 9a2ac8c3e5..dd28062d3c 100644 --- a/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.cc +++ b/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.cc @@ -3096,7 +3096,7 @@ Status ConvertBinary(OpConverterParams* params) { // If both input are tensors, or one of them is weights but the conversion // above failed, try the conversion using BinaryTensorOpTensor. if ((inputs.at(0).is_tensor() && inputs.at(1).is_tensor()) || !status.ok()) { - if (!status.ok()) VLOG(1) << status; + if (!status.ok()) VLOG(2) << status; status = BinaryTensorOpTensor(params, inputs.at(0), inputs.at(1)); } return status; diff --git a/tensorflow/compiler/tf2tensorrt/segment/segment.cc b/tensorflow/compiler/tf2tensorrt/segment/segment.cc index 3794929b1d..b3031b69ec 100644 --- a/tensorflow/compiler/tf2tensorrt/segment/segment.cc +++ b/tensorflow/compiler/tf2tensorrt/segment/segment.cc @@ -578,7 +578,7 @@ tensorflow::Status SegmentGraph( device_maps[u.ParentValue()->name()].insert( tf_node->requested_device()); } else { - VLOG(1) << "Node " << tf_node->name() + VLOG(2) << "Node " << tf_node->name() << " has no device assigned requested device is: " << tf_node->requested_device(); } -- GitLab From 32187f06dcd54c13ed489796c6ef4e10ed9f310d Mon Sep 17 00:00:00 2001 From: Rohan Jain Date: Fri, 22 Feb 2019 09:40:15 -0800 Subject: [PATCH 343/766] Renames to prepare for the TF2.0 API. HashTable -> StaticHashTable and MutableDenseHashTable -> DenseHashTable. Also insert -> insert_or_assign and remove -> erase for DenseHashTable. PiperOrigin-RevId: 235206233 --- tensorflow/contrib/lookup/lookup_ops.py | 2 +- .../python/kernel_tests/lookup_ops_test.py | 184 +++++++++--------- tensorflow/python/ops/lookup_ops.py | 98 +++++++--- 3 files changed, 162 insertions(+), 122 deletions(-) diff --git a/tensorflow/contrib/lookup/lookup_ops.py b/tensorflow/contrib/lookup/lookup_ops.py index 3d21fb68a1..2a8e674ab3 100644 --- a/tensorflow/contrib/lookup/lookup_ops.py +++ b/tensorflow/contrib/lookup/lookup_ops.py @@ -362,4 +362,4 @@ class HashTable(InitializableLookupTableBase): MutableHashTable = lookup_ops.MutableHashTable -MutableDenseHashTable = lookup_ops.MutableDenseHashTable +MutableDenseHashTable = lookup_ops.DenseHashTable diff --git a/tensorflow/python/kernel_tests/lookup_ops_test.py b/tensorflow/python/kernel_tests/lookup_ops_test.py index baa1550fb4..cc45b93a6d 100644 --- a/tensorflow/python/kernel_tests/lookup_ops_test.py +++ b/tensorflow/python/kernel_tests/lookup_ops_test.py @@ -41,14 +41,14 @@ from tensorflow.python.training import server_lib from tensorflow.python.training.tracking import util as trackable -class HashTableTest(test.TestCase): +class StaticHashTableTest(test.TestCase): - def testHashTable(self): + def testStaticHashTable(self): with self.cached_session(): default_val = -1 keys = constant_op.constant(["brain", "salad", "surgery"]) values = constant_op.constant([0, 1, 2], dtypes.int64) - table = lookup_ops.HashTable( + table = lookup_ops.StaticHashTable( lookup_ops.KeyValueTensorInitializer(keys, values), default_val) self.evaluate(table.initializer) @@ -67,12 +67,12 @@ class HashTableTest(test.TestCase): self.evaluate(exported_keys_tensor)) self.assertItemsEqual([0, 1, 2], self.evaluate(exported_values_tensor)) - def testHashTableFindHighRank(self): + def testStaticHashTableFindHighRank(self): with self.cached_session(): default_val = -1 keys = constant_op.constant(["brain", "salad", "surgery"]) values = constant_op.constant([0, 1, 2], dtypes.int64) - table = lookup_ops.HashTable( + table = lookup_ops.StaticHashTable( lookup_ops.KeyValueTensorInitializer(keys, values), default_val) self.evaluate(table.initializer) @@ -85,12 +85,12 @@ class HashTableTest(test.TestCase): result = self.evaluate(output) self.assertAllEqual([[0, 1], [-1, -1]], result) - def testHashTableInitWithPythonArrays(self): + def testStaticHashTableInitWithPythonArrays(self): with self.cached_session(): default_val = -1 keys = ["brain", "salad", "surgery"] values = [0, 1, 2] - table = lookup_ops.HashTable( + table = lookup_ops.StaticHashTable( lookup_ops.KeyValueTensorInitializer( keys, values, value_dtype=dtypes.int64), default_val) self.evaluate(table.initializer) @@ -103,12 +103,12 @@ class HashTableTest(test.TestCase): result = self.evaluate(output) self.assertAllEqual([0, 1, -1], result) - def testHashTableInitWithNumPyArrays(self): + def testStaticHashTableInitWithNumPyArrays(self): with self.cached_session(): default_val = -1 keys = np.array(["brain", "salad", "surgery"], dtype=np.str) values = np.array([0, 1, 2], dtype=np.int64) - table = lookup_ops.HashTable( + table = lookup_ops.StaticHashTable( lookup_ops.KeyValueTensorInitializer(keys, values), default_val) self.evaluate(table.initializer) @@ -120,18 +120,18 @@ class HashTableTest(test.TestCase): result = self.evaluate(output) self.assertAllEqual([0, 1, -1], result) - def testMultipleHashTables(self): + def testMultipleStaticHashTables(self): with self.cached_session(): default_val = -1 keys = constant_op.constant(["brain", "salad", "surgery"]) values = constant_op.constant([0, 1, 2], dtypes.int64) - table1 = lookup_ops.HashTable( + table1 = lookup_ops.StaticHashTable( lookup_ops.KeyValueTensorInitializer(keys, values), default_val) - table2 = lookup_ops.HashTable( + table2 = lookup_ops.StaticHashTable( lookup_ops.KeyValueTensorInitializer(keys, values), default_val) - table3 = lookup_ops.HashTable( + table3 = lookup_ops.StaticHashTable( lookup_ops.KeyValueTensorInitializer(keys, values), default_val) self.evaluate(table1.initializer) @@ -151,12 +151,12 @@ class HashTableTest(test.TestCase): self.assertAllEqual([0, 1, -1], out2) self.assertAllEqual([0, 1, -1], out3) - def testHashTableWithTensorDefault(self): + def testStaticHashTableWithTensorDefault(self): with self.cached_session(): default_val = constant_op.constant(-1, dtypes.int64) keys = constant_op.constant(["brain", "salad", "surgery"]) values = constant_op.constant([0, 1, 2], dtypes.int64) - table = lookup_ops.HashTable( + table = lookup_ops.StaticHashTable( lookup_ops.KeyValueTensorInitializer(keys, values), default_val) self.evaluate(table.initializer) @@ -166,12 +166,12 @@ class HashTableTest(test.TestCase): result = self.evaluate(output) self.assertAllEqual([0, 1, -1], result) - def testHashTableWithSparseTensorInput(self): + def testStaticHashTableWithSparseTensorInput(self): with self.cached_session(): default_val = constant_op.constant(-1, dtypes.int64) keys = constant_op.constant(["brain", "salad", "surgery"]) values = constant_op.constant([0, 1, 2], dtypes.int64) - table = lookup_ops.HashTable( + table = lookup_ops.StaticHashTable( lookup_ops.KeyValueTensorInitializer(keys, values), default_val) self.evaluate(table.initializer) @@ -194,7 +194,7 @@ class HashTableTest(test.TestCase): default_val = -1 keys = constant_op.constant(["brain", "salad", "surgery"]) values = constant_op.constant([0, 1, 2], dtypes.int64) - table = lookup_ops.HashTable( + table = lookup_ops.StaticHashTable( lookup_ops.KeyValueTensorInitializer(keys, values), default_val) self.evaluate(table.initializer) @@ -208,14 +208,14 @@ class HashTableTest(test.TestCase): table.lookup(input_string) with self.assertRaises(TypeError): - lookup_ops.HashTable( + lookup_ops.StaticHashTable( lookup_ops.KeyValueTensorInitializer(keys, values), "UNK") def testDTypes(self): with self.cached_session(): default_val = -1 with self.assertRaises(TypeError): - lookup_ops.HashTable( + lookup_ops.StaticHashTable( lookup_ops.KeyValueTensorInitializer(["a"], [1], [dtypes.string], dtypes.int64), default_val) @@ -223,7 +223,7 @@ class HashTableTest(test.TestCase): def testNotInitialized(self): with self.cached_session(): default_val = -1 - table = lookup_ops.HashTable( + table = lookup_ops.StaticHashTable( lookup_ops.KeyValueTensorInitializer(["a"], [1], value_dtype=dtypes.int64), default_val) @@ -240,7 +240,7 @@ class HashTableTest(test.TestCase): default_val = -1 keys = constant_op.constant(["brain", "salad", "surgery"]) values = constant_op.constant([0, 1, 2], dtypes.int64) - table = lookup_ops.HashTable( + table = lookup_ops.StaticHashTable( lookup_ops.KeyValueTensorInitializer(keys, values), default_val) self.evaluate(table.initializer) @@ -255,7 +255,7 @@ class HashTableTest(test.TestCase): values = constant_op.constant([0, 1, 2, 3, 4], dtypes.int64) with self.assertRaises(ValueError): - lookup_ops.HashTable( + lookup_ops.StaticHashTable( lookup_ops.KeyValueTensorInitializer(keys, values), default_val) @test_util.run_deprecated_v1 @@ -271,7 +271,7 @@ class HashTableTest(test.TestCase): default_val = -1 keys = constant_op.constant(["brain", "salad", "surgery"]) values = constant_op.constant([0, 1, 2], dtypes.int64) - table = lookup_ops.HashTable( + table = lookup_ops.StaticHashTable( lookup_ops.KeyValueTensorInitializer(keys, values), default_val, name="t1") @@ -287,12 +287,12 @@ class HashTableTest(test.TestCase): table.initializer.run() self.assertAllEqual(3, self.evaluate(table.size())) - def testHashTableInt32String(self): + def testStaticHashTableInt32String(self): with self.cached_session(): default_val = "n/a" keys = constant_op.constant([0, 1, 2], dtypes.int32) values = constant_op.constant(["brain", "salad", "surgery"]) - table = lookup_ops.HashTable( + table = lookup_ops.StaticHashTable( lookup_ops.KeyValueTensorInitializer(keys, values), default_val) self.evaluate(table.initializer) @@ -576,7 +576,7 @@ class KeyValueTensorInitializerTest(test.TestCase): init = lookup_ops.KeyValueTensorInitializer( ("brain", "salad", "surgery"), (0, 1, 2), dtypes.string, dtypes.int64) self.assertEqual("", init._shared_name) - table = lookup_ops.HashTable(init, default_value=-1) + table = lookup_ops.StaticHashTable(init, default_value=-1) table.initializer.run() def test_multiple_tables(self): @@ -586,7 +586,7 @@ class KeyValueTensorInitializerTest(test.TestCase): ("brain", "salad", "surgery"), (0, 1, 2), dtypes.string, dtypes.int64) self.assertEqual("", init1._shared_name) - table1 = lookup_ops.HashTable(init1, default_value=-1) + table1 = lookup_ops.StaticHashTable(init1, default_value=-1) self.assertEqual("hash_table", table1.name) self.assertEqual("table_scope/hash_table", table1.resource_handle.op.name) @@ -594,7 +594,7 @@ class KeyValueTensorInitializerTest(test.TestCase): ("brain", "salad", "surgery"), (0, 1, 2), dtypes.string, dtypes.int64) self.assertEqual("", init2._shared_name) - table2 = lookup_ops.HashTable(init2, default_value=-1) + table2 = lookup_ops.StaticHashTable(init2, default_value=-1) self.assertEqual("hash_table_1", table2.name) self.assertEqual("table_scope/hash_table_1", table2.resource_handle.op.name) @@ -604,7 +604,7 @@ class KeyValueTensorInitializerTest(test.TestCase): init = lookup_ops.KeyValueTensorInitializer((42, 1, -1000), (0, 1, 2), dtypes.int64, dtypes.int64) self.assertEqual("", init._shared_name) - table = lookup_ops.HashTable(init, default_value=-1) + table = lookup_ops.StaticHashTable(init, default_value=-1) table.initializer.run() def test_int32(self): @@ -612,7 +612,7 @@ class KeyValueTensorInitializerTest(test.TestCase): init = lookup_ops.KeyValueTensorInitializer((42, 1, -1000), (0, 1, 2), dtypes.int32, dtypes.int64) self.assertEqual("", init._shared_name) - table = lookup_ops.HashTable(init, default_value=-1) + table = lookup_ops.StaticHashTable(init, default_value=-1) with self.assertRaisesRegexp(errors_impl.OpError, "No OpKernel was registered"): table.initializer.run() @@ -882,7 +882,7 @@ class InitializeTableFromFileOpTest(test.TestCase): vocabulary_file, dtypes.string, lookup_ops.TextFileIndex.WHOLE_LINE, dtypes.int64, lookup_ops.TextFileIndex.LINE_NUMBER) self.assertTrue("one_column_1.txt_-2_-1" in init._shared_name) - table = lookup_ops.HashTable(init, default_value) + table = lookup_ops.StaticHashTable(init, default_value) self.evaluate(table.initializer) output = table.lookup(constant_op.constant(["brain", "salad", "tank"])) @@ -900,7 +900,7 @@ class InitializeTableFromFileOpTest(test.TestCase): vocabulary_file, dtypes.int64, lookup_ops.TextFileIndex.WHOLE_LINE, dtypes.int64, lookup_ops.TextFileIndex.LINE_NUMBER) self.assertTrue("one_column_int64.txt_-2_-1" in init._shared_name) - table = lookup_ops.HashTable(init, default_value) + table = lookup_ops.StaticHashTable(init, default_value) self.evaluate(table.initializer) output = table.lookup( @@ -919,7 +919,7 @@ class InitializeTableFromFileOpTest(test.TestCase): init = lookup_ops.TextFileInitializer( vocabulary_file, dtypes.int64, key_index, dtypes.string, value_index) self.assertTrue("one_column_2.txt_-1_-2" in init._shared_name) - table = lookup_ops.HashTable(init, default_value) + table = lookup_ops.StaticHashTable(init, default_value) self.evaluate(table.initializer) input_values = constant_op.constant([0, 1, 2, 3], dtypes.int64) @@ -941,7 +941,7 @@ class InitializeTableFromFileOpTest(test.TestCase): init = lookup_ops.TextFileInitializer( vocabulary_file, dtypes.string, key_index, dtypes.int64, value_index) self.assertTrue("three_columns.txt_1_2" in init._shared_name) - table = lookup_ops.HashTable(init, default_value) + table = lookup_ops.StaticHashTable(init, default_value) self.evaluate(table.initializer) input_string = constant_op.constant(["brain", "salad", "surgery"]) @@ -963,7 +963,7 @@ class InitializeTableFromFileOpTest(test.TestCase): vocabulary_file, dtypes.string, key_index, dtypes.int64, value_index) self.assertTrue("three_columns.txt_2_1" in init._shared_name) with self.assertRaisesOpError("is not a valid"): - table = lookup_ops.HashTable(init, default_value) + table = lookup_ops.StaticHashTable(init, default_value) self.evaluate(table.initializer) def testInvalidDataType(self): @@ -979,7 +979,7 @@ class InitializeTableFromFileOpTest(test.TestCase): key_index, dtypes.string, value_index) self.assertTrue("one_column_3.txt_-2_-1" in init._shared_name) - lookup_ops.HashTable(init, default_value) + lookup_ops.StaticHashTable(init, default_value) def testInvalidIndex(self): vocabulary_file = self._createVocabFile("one_column_4.txt") @@ -992,7 +992,7 @@ class InitializeTableFromFileOpTest(test.TestCase): self.assertTrue("one_column_4.txt_1_-1" in init._shared_name) with self.assertRaisesOpError("Invalid number of columns"): - table = lookup_ops.HashTable(init, default_value) + table = lookup_ops.StaticHashTable(init, default_value) self.evaluate(table.initializer) def testInitializeSameTableWithMultipleNodes(self): @@ -1004,17 +1004,17 @@ class InitializeTableFromFileOpTest(test.TestCase): vocabulary_file, dtypes.string, lookup_ops.TextFileIndex.WHOLE_LINE, dtypes.int64, lookup_ops.TextFileIndex.LINE_NUMBER) self.assertTrue("one_column_5.txt_-2_-1" in init1._shared_name) - table1 = lookup_ops.HashTable(init1, default_value) + table1 = lookup_ops.StaticHashTable(init1, default_value) init2 = lookup_ops.TextFileInitializer( vocabulary_file, dtypes.string, lookup_ops.TextFileIndex.WHOLE_LINE, dtypes.int64, lookup_ops.TextFileIndex.LINE_NUMBER) self.assertTrue("one_column_5.txt_-2_-1" in init2._shared_name) - table2 = lookup_ops.HashTable(init2, default_value) + table2 = lookup_ops.StaticHashTable(init2, default_value) init3 = lookup_ops.TextFileInitializer( vocabulary_file, dtypes.string, lookup_ops.TextFileIndex.WHOLE_LINE, dtypes.int64, lookup_ops.TextFileIndex.LINE_NUMBER) self.assertTrue("one_column_5.txt_-2_-1" in init3._shared_name) - table3 = lookup_ops.HashTable(init3, default_value) + table3 = lookup_ops.StaticHashTable(init3, default_value) self.evaluate(lookup_ops.tables_initializer()) @@ -1033,7 +1033,7 @@ class InitializeTableFromFileOpTest(test.TestCase): with self.cached_session(): default_value = -1 with self.assertRaises(ValueError): - lookup_ops.HashTable( + lookup_ops.StaticHashTable( lookup_ops.TextFileInitializer( "", dtypes.string, lookup_ops.TextFileIndex.WHOLE_LINE, dtypes.int64, lookup_ops.TextFileIndex.LINE_NUMBER), @@ -1052,7 +1052,7 @@ class InitializeTableFromFileOpTest(test.TestCase): lookup_ops.TextFileIndex.LINE_NUMBER, vocab_size=vocab_size) self.assertTrue("one_column6.txt_3_-2_-1" in init1._shared_name) - table1 = lookup_ops.HashTable(init1, default_value) + table1 = lookup_ops.StaticHashTable(init1, default_value) # Initialize from file. self.evaluate(table1.initializer) @@ -1069,7 +1069,7 @@ class InitializeTableFromFileOpTest(test.TestCase): vocab_size=vocab_size) self.assertTrue("one_column7.txt_5_-2_-1" in init2._shared_name) with self.assertRaisesOpError("Invalid vocab_size"): - table2 = lookup_ops.HashTable(init2, default_value) + table2 = lookup_ops.StaticHashTable(init2, default_value) self.evaluate(table2.initializer) vocab_size = 1 @@ -1082,7 +1082,7 @@ class InitializeTableFromFileOpTest(test.TestCase): lookup_ops.TextFileIndex.LINE_NUMBER, vocab_size=vocab_size) self.assertTrue("one_column3.txt_1_-2_-1" in init3._shared_name) - table3 = lookup_ops.HashTable(init3, default_value) + table3 = lookup_ops.StaticHashTable(init3, default_value) # Smaller vocab size reads only vocab_size records. self.evaluate(table3.initializer) @@ -1098,7 +1098,7 @@ class InitializeTableFromFileOpTest(test.TestCase): "old_file.txt", dtypes.string, lookup_ops.TextFileIndex.WHOLE_LINE, dtypes.int64, lookup_ops.TextFileIndex.LINE_NUMBER) self.assertTrue("old_file.txt_-2_-1" in init._shared_name) - table = lookup_ops.HashTable(init, default_value) + table = lookup_ops.StaticHashTable(init, default_value) # Initialize with non existing file (old_file.txt) should fail. # TODO(yleon): Update message, which might change per FileSystem. @@ -1124,7 +1124,7 @@ class InitializeTableFromFileOpTest(test.TestCase): # Invalid data type other_type = constant_op.constant(1) with self.assertRaises(Exception) as cm: - lookup_ops.HashTable( + lookup_ops.StaticHashTable( lookup_ops.TextFileInitializer( other_type, dtypes.string, lookup_ops.TextFileIndex.WHOLE_LINE, dtypes.int64, lookup_ops.TextFileIndex.LINE_NUMBER), @@ -1135,7 +1135,7 @@ class InitializeTableFromFileOpTest(test.TestCase): filenames = constant_op.constant([vocabulary_file, vocabulary_file]) if not context.executing_eagerly(): with self.assertRaises(Exception) as cm: - lookup_ops.HashTable( + lookup_ops.StaticHashTable( lookup_ops.TextFileInitializer( filenames, dtypes.string, lookup_ops.TextFileIndex.WHOLE_LINE, dtypes.int64, lookup_ops.TextFileIndex.LINE_NUMBER), @@ -1143,7 +1143,7 @@ class InitializeTableFromFileOpTest(test.TestCase): self.assertTrue(isinstance(cm.exception, (ValueError, TypeError))) else: with self.assertRaises(errors_impl.InvalidArgumentError): - lookup_ops.HashTable( + lookup_ops.StaticHashTable( lookup_ops.TextFileInitializer( filenames, dtypes.string, lookup_ops.TextFileIndex.WHOLE_LINE, dtypes.int64, lookup_ops.TextFileIndex.LINE_NUMBER), @@ -1157,7 +1157,7 @@ class InitializeTableFromFileOpTest(test.TestCase): init = lookup_ops.TextFileStringTableInitializer( vocab_file, vocab_size=vocab_size) self.assertTrue("feat_to_id_1.txt_3_-1_-2", init._shared_name) - table = lookup_ops.HashTable(init, default_value) + table = lookup_ops.StaticHashTable(init, default_value) self.evaluate(table.initializer) @@ -1176,7 +1176,7 @@ class InitializeTableFromFileOpTest(test.TestCase): init = lookup_ops.TextFileIdTableInitializer( vocab_file, vocab_size=vocab_size) self.assertTrue("feat_to_id_2.txt_3_-1_-2", init._shared_name) - table = lookup_ops.HashTable(init, default_value) + table = lookup_ops.StaticHashTable(init, default_value) self.evaluate(table.initializer) input_string = constant_op.constant(["brain", "salad", "surgery", "UNK"]) @@ -1194,7 +1194,7 @@ class InitializeTableFromFileOpTest(test.TestCase): init = lookup_ops.TextFileIdTableInitializer( vocab_file, vocab_size=vocab_size, key_dtype=dtypes.int64) self.assertTrue("feat_to_id_3.txt_3_-1_-2", init._shared_name) - table = lookup_ops.HashTable(init, default_value) + table = lookup_ops.StaticHashTable(init, default_value) self.evaluate(table.initializer) out = table.lookup( @@ -1219,7 +1219,7 @@ class IdTableWithHashBucketsTest(test.TestCase): vocab_size = 3 oov_buckets = 1 table = lookup_ops.IdTableWithHashBuckets( - lookup_ops.HashTable( + lookup_ops.StaticHashTable( lookup_ops.TextFileIdTableInitializer( vocab_file, vocab_size=vocab_size), default_value), oov_buckets) @@ -1240,7 +1240,7 @@ class IdTableWithHashBucketsTest(test.TestCase): vocab_size = 3 oov_buckets = 1 table = lookup_ops.IdTableWithHashBuckets( - lookup_ops.HashTable( + lookup_ops.StaticHashTable( lookup_ops.TextFileIdTableInitializer( vocab_file, vocab_size=vocab_size, key_dtype=dtypes.int64), default_value), @@ -1263,7 +1263,7 @@ class IdTableWithHashBucketsTest(test.TestCase): vocab_size = 3 oov_buckets = 1 table = lookup_ops.IdTableWithHashBuckets( - lookup_ops.HashTable( + lookup_ops.StaticHashTable( lookup_ops.TextFileIdTableInitializer( vocab_file, vocab_size=vocab_size, key_dtype=dtypes.int64), default_value), oov_buckets) @@ -1341,7 +1341,7 @@ class IdTableWithHashBucketsTest(test.TestCase): vocab_size = 3 oov_buckets = 3 - vocab_table = lookup_ops.HashTable( + vocab_table = lookup_ops.StaticHashTable( lookup_ops.TextFileIdTableInitializer( vocab_file, vocab_size=vocab_size), default_value) table1 = lookup_ops.IdTableWithHashBuckets( @@ -1382,7 +1382,7 @@ class IdTableWithHashBucketsTest(test.TestCase): vocab_size = 3 oov_buckets = 1 table1 = lookup_ops.IdTableWithHashBuckets( - lookup_ops.HashTable( + lookup_ops.StaticHashTable( lookup_ops.TextFileIdTableInitializer( vocab_file, vocab_size=vocab_size), default_value), oov_buckets) @@ -1405,7 +1405,7 @@ class IdTableWithHashBucketsTest(test.TestCase): # Underlying lookup table already initialized in previous session. # No need to call table2.initializer.run() table2 = lookup_ops.IdTableWithHashBuckets( - lookup_ops.HashTable( + lookup_ops.StaticHashTable( lookup_ops.TextFileIdTableInitializer( vocab_file, vocab_size=vocab_size), default_value), oov_buckets) @@ -1425,14 +1425,14 @@ class IdTableWithHashBucketsTest(test.TestCase): vocab_size = 3 oov_buckets = 0 table1 = lookup_ops.IdTableWithHashBuckets( - lookup_ops.HashTable( + lookup_ops.StaticHashTable( lookup_ops.TextFileIdTableInitializer( vocab_file, vocab_size=vocab_size), default_value1), oov_buckets) default_value2 = -2 table2 = lookup_ops.IdTableWithHashBuckets( - lookup_ops.HashTable( + lookup_ops.StaticHashTable( lookup_ops.TextFileIdTableInitializer( vocab_file, vocab_size=vocab_size), default_value2), oov_buckets) @@ -1465,7 +1465,7 @@ class IdTableWithHashBucketsTest(test.TestCase): constant_op.constant(input_shape, dtypes.int64)) table = lookup_ops.IdTableWithHashBuckets( - lookup_ops.HashTable( + lookup_ops.StaticHashTable( lookup_ops.TextFileIdTableInitializer(vocab_file, vocab_size=3), -1), 1) table.initializer.run() @@ -1492,7 +1492,7 @@ class IdTableWithHashBucketsTest(test.TestCase): constant_op.constant(input_shape, dtypes.int64)) table = lookup_ops.IdTableWithHashBuckets( - lookup_ops.HashTable( + lookup_ops.StaticHashTable( lookup_ops.KeyValueTensorInitializer( (42, 1, -1000), (0, 1, 2), dtypes.int64, dtypes.int64), -1), 1, @@ -1521,7 +1521,7 @@ class IdTableWithHashBucketsTest(test.TestCase): constant_op.constant(input_shape, dtypes.int64)) table = lookup_ops.IdTableWithHashBuckets( - lookup_ops.HashTable( + lookup_ops.StaticHashTable( lookup_ops.KeyValueTensorInitializer( (42, 1, -1000), (0, 1, 2), dtypes.int64, dtypes.int64), -1), 1, @@ -1545,7 +1545,7 @@ class IdTableWithHashBucketsTest(test.TestCase): default_value = -1 vocab_size = 3 oov_buckets = 1 - lookup_table = lookup_ops.HashTable( + lookup_table = lookup_ops.StaticHashTable( lookup_ops.TextFileIdTableInitializer( vocab_file, vocab_size=vocab_size), default_value) @@ -2130,14 +2130,14 @@ class MutableHashTableOpTest(test.TestCase): self.assertAllEqual((b"brain", b"salad", b"n/a"), result) -class MutableDenseHashTableOpTest(test.TestCase): +class DenseHashTableOpTest(test.TestCase): def testBasic(self): with self.cached_session(): keys = constant_op.constant([11, 12, 13, 14], dtypes.int64) values = constant_op.constant([0, 1, 2, 3], dtypes.int64) - table = lookup_ops.MutableDenseHashTable( + table = lookup_ops.DenseHashTable( dtypes.int64, dtypes.int64, default_value=-1, @@ -2164,7 +2164,7 @@ class MutableDenseHashTableOpTest(test.TestCase): keys = constant_op.constant([11, 12, 13, 14], dtypes.int64) values = constant_op.constant([True, True, True, True], dtypes.bool) - table = lookup_ops.MutableDenseHashTable( + table = lookup_ops.DenseHashTable( dtypes.int64, dtypes.bool, default_value=False, @@ -2190,7 +2190,7 @@ class MutableDenseHashTableOpTest(test.TestCase): with self.cached_session(): with self.assertRaisesRegexp(errors_impl.InvalidArgumentError, "Empty and deleted keys"): - table = lookup_ops.MutableDenseHashTable( + table = lookup_ops.DenseHashTable( dtypes.int64, dtypes.int64, default_value=-1, @@ -2203,7 +2203,7 @@ class MutableDenseHashTableOpTest(test.TestCase): with self.cached_session(): keys = constant_op.constant([11, 12, 13], dtypes.int64) values = constant_op.constant([0, 1, 2], dtypes.int64) - table = lookup_ops.MutableDenseHashTable( + table = lookup_ops.DenseHashTable( dtypes.int64, dtypes.int64, default_value=-1, @@ -2225,7 +2225,7 @@ class MutableDenseHashTableOpTest(test.TestCase): keys = constant_op.constant(["a", "b", "c", "d"], dtypes.string) values = constant_op.constant([0.0, 1.1, 2.2, 3.3], dtypes.float32) default_value = constant_op.constant(-1.5, dtypes.float32) - table = lookup_ops.MutableDenseHashTable( + table = lookup_ops.DenseHashTable( dtypes.string, dtypes.float32, default_value=default_value, @@ -2254,7 +2254,7 @@ class MutableDenseHashTableOpTest(test.TestCase): keys = constant_op.constant([11, 12, 13, 14], dtypes.int64) values = constant_op.constant([0.0, 1.1, 2.2, 3.3], float_dtype) default_value = constant_op.constant(-1.5, float_dtype) - table = lookup_ops.MutableDenseHashTable( + table = lookup_ops.DenseHashTable( dtypes.int64, float_dtype, default_value=default_value, @@ -2282,7 +2282,7 @@ class MutableDenseHashTableOpTest(test.TestCase): values = constant_op.constant([[0, 1, 2, 3], [3, 4, 5, 6], [6, 7, 8, 9]], dtypes.int64) default_value = constant_op.constant([-1, -2, -3, -4], dtypes.int64) - table = lookup_ops.MutableDenseHashTable( + table = lookup_ops.DenseHashTable( dtypes.int64, dtypes.int64, default_value=default_value, @@ -2325,7 +2325,7 @@ class MutableDenseHashTableOpTest(test.TestCase): empty_key = constant_op.constant([0, 3], dtypes.int64) deleted_key = constant_op.constant([-1, -1], dtypes.int64) default_value = constant_op.constant(-1, dtypes.int64) - table = lookup_ops.MutableDenseHashTable( + table = lookup_ops.DenseHashTable( dtypes.int64, dtypes.int64, default_value=default_value, @@ -2361,7 +2361,7 @@ class MutableDenseHashTableOpTest(test.TestCase): with self.cached_session(): keys = constant_op.constant([11, 12, 13], dtypes.int64) values = constant_op.constant([0, 1, 2], dtypes.int64) - table = lookup_ops.MutableDenseHashTable( + table = lookup_ops.DenseHashTable( dtypes.int64, dtypes.int64, default_value=-1, @@ -2396,7 +2396,7 @@ class MutableDenseHashTableOpTest(test.TestCase): keys = constant_op.constant([11, 12, 13, 14], dtypes.int64) values = constant_op.constant([1, 2, 3, 4], dtypes.int64) - table = lookup_ops.MutableDenseHashTable( + table = lookup_ops.DenseHashTable( dtypes.int64, dtypes.int64, default_value=-1, @@ -2438,7 +2438,7 @@ class MutableDenseHashTableOpTest(test.TestCase): deleted_key = -1 keys = constant_op.constant([11, 12, 13, 14], dtypes.int64) values = constant_op.constant([0, 1, 2, 3], dtypes.int64) - table = lookup_ops.MutableDenseHashTable( + table = lookup_ops.DenseHashTable( dtypes.int64, dtypes.int64, default_value=default_value, @@ -2465,7 +2465,7 @@ class MutableDenseHashTableOpTest(test.TestCase): self.assertEqual(save_path, val) with self.session(graph=ops.Graph()) as sess: - table = lookup_ops.MutableDenseHashTable( + table = lookup_ops.DenseHashTable( dtypes.int64, dtypes.int64, default_value=default_value, @@ -2502,7 +2502,7 @@ class MutableDenseHashTableOpTest(test.TestCase): deleted_key = -1 keys = constant_op.constant([11, 12, 13], dtypes.int64) values = constant_op.constant([0, 1, 2], dtypes.int64) - save_table = lookup_ops.MutableDenseHashTable( + save_table = lookup_ops.DenseHashTable( dtypes.int64, dtypes.int64, default_value=default_value, @@ -2522,7 +2522,7 @@ class MutableDenseHashTableOpTest(test.TestCase): save_path = save_checkpoint.save(save_prefix) del save_table, save_checkpoint - load_table = lookup_ops.MutableDenseHashTable( + load_table = lookup_ops.DenseHashTable( dtypes.int64, dtypes.int64, default_value=default_value, @@ -2563,7 +2563,7 @@ class MutableDenseHashTableOpTest(test.TestCase): dtypes.int64) values = constant_op.constant([[0, 1], [2, 3], [2, 4], [4, 5]], dtypes.int64) - table = lookup_ops.MutableDenseHashTable( + table = lookup_ops.DenseHashTable( dtypes.int64, dtypes.int64, default_value=default_value, @@ -2593,7 +2593,7 @@ class MutableDenseHashTableOpTest(test.TestCase): empty_key = constant_op.constant([11, 13], dtypes.int64) deleted_key = constant_op.constant([-2, -3], dtypes.int64) default_value = constant_op.constant([-1, -2], dtypes.int64) - table = lookup_ops.MutableDenseHashTable( + table = lookup_ops.DenseHashTable( dtypes.int64, dtypes.int64, default_value=default_value, @@ -2634,7 +2634,7 @@ class MutableDenseHashTableOpTest(test.TestCase): keys = constant_op.constant([[11, 12], [11, 14], [12, 13], [13, 14]], dtypes.int64) values = constant_op.constant([0, 1, 2, 3], dtypes.int64) - table = lookup_ops.MutableDenseHashTable( + table = lookup_ops.DenseHashTable( dtypes.int64, dtypes.int64, default_value=default_value, @@ -2664,7 +2664,7 @@ class MutableDenseHashTableOpTest(test.TestCase): empty_key = constant_op.constant([11, 13], dtypes.int64) deleted_key = constant_op.constant([-1, -1], dtypes.int64) default_value = constant_op.constant(-1, dtypes.int64) - table = lookup_ops.MutableDenseHashTable( + table = lookup_ops.DenseHashTable( dtypes.int64, dtypes.int64, default_value=default_value, @@ -2698,7 +2698,7 @@ class MutableDenseHashTableOpTest(test.TestCase): # The values are chosen to make sure collisions occur when using GCC STL keys = constant_op.constant([11, 12, 13, 19, 20, 21], dtypes.int64) values = constant_op.constant([51, 52, 53, 54, 55, 56], dtypes.int64) - table = lookup_ops.MutableDenseHashTable( + table = lookup_ops.DenseHashTable( dtypes.int64, dtypes.int64, default_value=-1, @@ -2722,7 +2722,7 @@ class MutableDenseHashTableOpTest(test.TestCase): with self.cached_session(): keys = constant_op.constant([11, 0, 13], dtypes.int64) values = constant_op.constant([0, 1, 2], dtypes.int64) - table = lookup_ops.MutableDenseHashTable( + table = lookup_ops.DenseHashTable( dtypes.int64, dtypes.int64, default_value=-1, @@ -2742,7 +2742,7 @@ class MutableDenseHashTableOpTest(test.TestCase): def testErrors(self): with self.cached_session(): - table = lookup_ops.MutableDenseHashTable( + table = lookup_ops.DenseHashTable( dtypes.int64, dtypes.int64, default_value=-1, @@ -2785,7 +2785,7 @@ class MutableDenseHashTableOpTest(test.TestCase): with self.assertRaisesRegexp(errors_impl.InvalidArgumentError, "Number of buckets must be"): - table2 = lookup_ops.MutableDenseHashTable( + table2 = lookup_ops.DenseHashTable( dtypes.int64, dtypes.int64, default_value=-1, @@ -2797,7 +2797,7 @@ class MutableDenseHashTableOpTest(test.TestCase): with self.assertRaisesRegexp( errors_impl.InvalidArgumentError, "Empty and deleted keys must have same shape"): - table3 = lookup_ops.MutableDenseHashTable( + table3 = lookup_ops.DenseHashTable( dtypes.int64, dtypes.int64, default_value=-1, @@ -2807,7 +2807,7 @@ class MutableDenseHashTableOpTest(test.TestCase): with self.assertRaisesRegexp(errors_impl.InvalidArgumentError, "Empty and deleted keys cannot be equal"): - table4 = lookup_ops.MutableDenseHashTable( + table4 = lookup_ops.DenseHashTable( dtypes.int64, dtypes.int64, default_value=-1, @@ -2817,7 +2817,7 @@ class MutableDenseHashTableOpTest(test.TestCase): with self.assertRaisesRegexp(errors_impl.InvalidArgumentError, "Empty and deleted keys cannot be equal"): - table5 = lookup_ops.MutableDenseHashTable( + table5 = lookup_ops.DenseHashTable( dtypes.int64, dtypes.int64, default_value=-1, @@ -2874,10 +2874,10 @@ class MutableHashTableBenchmark(test.Benchmark): assert sess.run(size) >= 1000 * 32 -class MutableDenseHashTableBenchmark(MutableHashTableBenchmark): +class DenseHashTableBenchmark(MutableHashTableBenchmark): def _create_table(self): - return lookup_ops.MutableDenseHashTable( + return lookup_ops.DenseHashTable( dtypes.int64, dtypes.float32, default_value=0.0, diff --git a/tensorflow/python/ops/lookup_ops.py b/tensorflow/python/ops/lookup_ops.py index d991a47b55..a1d29fbf47 100644 --- a/tensorflow/python/ops/lookup_ops.py +++ b/tensorflow/python/ops/lookup_ops.py @@ -237,13 +237,13 @@ class InitializableLookupTableBase(LookupInterface): return values -class HashTable(InitializableLookupTableBase): +class StaticHashTable(InitializableLookupTableBase): """A generic hash table implementation. Example usage: ```python - table = tf.HashTable( + table = tf.lookup.StaticHashTable( tf.KeyValueTensorInitializer(keys, values), -1) out = table.lookup(input_tensor) table.init.run() @@ -273,7 +273,7 @@ class HashTable(InitializableLookupTableBase): self._shared_name = self._initializer._shared_name # pylint: disable=protected-access self._name = name or "hash_table" self._table_name = None - super(HashTable, self).__init__(default_value, initializer) + super(StaticHashTable, self).__init__(default_value, initializer) self._value_shape = self._default_value.get_shape() def create_resource(self): @@ -311,6 +311,10 @@ class HashTable(InitializableLookupTableBase): return exported_keys, exported_values +# For backwards compatibility. This will be removed in TF 2.0. +HashTable = StaticHashTable + + class TableInitializerBase(trackable_base.Trackable): """Base class for lookup table initializers.""" @@ -442,7 +446,7 @@ class TextFileInitializer(TableInitializerBase): * `palmer -> 30` ```python - table = tf.lookup.HashTable(tf.lookup.TextFileInitializer( + table = tf.lookup.StaticHashTable(tf.lookup.TextFileInitializer( "test.txt", tf.string, 0, tf.int64, 1, delimiter=" "), -1) ... table.init.run() @@ -455,7 +459,7 @@ class TextFileInitializer(TableInitializerBase): * `palmer 30 -> 2` ```python - table = tf.lookup.HashTable(tf.lookup.TextFileInitializer( + table = tf.lookup.StaticHashTable(tf.lookup.TextFileInitializer( "test.txt", tf.string, tf.lookup.TextFileIndex.WHOLE_LINE, tf.int64, tf.lookup.TextFileIndex.LINE_NUMBER, delimiter=" "), -1) ... @@ -770,7 +774,8 @@ class IdTableWithHashBuckets(LookupInterface): num_oov_buckets = 3 input_tensor = tf.constant(["emerson", "lake", "palmer", "king", "crimnson"]) table = tf.IdTableWithHashBuckets( - tf.HashTable(tf.TextFileIdTableInitializer(filename), default_value), + tf.StaticHashTable(tf.TextFileIdTableInitializer(filename), + default_value), num_oov_buckets) out = table.lookup(input_tensor). table.init.run() @@ -1239,7 +1244,7 @@ def index_table_from_file(vocabulary_file=None, value_column_index=value_column_index, delimiter=delimiter) - table = HashTable(init, default_value) + table = StaticHashTable(init, default_value) if num_oov_buckets: table = IdTableWithHashBuckets( table, @@ -1336,7 +1341,7 @@ def index_table_from_tensor(vocabulary_list, table_keys.dtype.base_dtype, dtypes.int64, name="table_init") - table = HashTable(init, default_value) + table = StaticHashTable(init, default_value) if num_oov_buckets: table = IdTableWithHashBuckets( table, @@ -1433,7 +1438,7 @@ def index_to_string_table_from_file(vocabulary_file, delimiter=delimiter) # TODO(yleon): Use a more effienct structure. - return HashTable(init, default_value) + return StaticHashTable(init, default_value) def index_to_string_table_from_tensor(vocabulary_list, @@ -1494,7 +1499,7 @@ def index_to_string_table_from_tensor(vocabulary_list, init = KeyValueTensorInitializer( keys, vocabulary_list, dtypes.int64, dtypes.string, name="table_init") # TODO(yleon): Use a more effienct structure. - return HashTable(init, default_value) + return StaticHashTable(init, default_value) class MutableHashTable(LookupInterface): @@ -1728,7 +1733,7 @@ class MutableHashTable(LookupInterface): self.op.resource_handle, restored_tensors[0], restored_tensors[1]) -class MutableDenseHashTable(LookupInterface): +class DenseHashTable(LookupInterface): """A generic mutable hash table implementation using tensors as backing store. Data can be inserted by calling the insert method and removed by calling the @@ -1736,18 +1741,18 @@ class MutableDenseHashTable(LookupInterface): It uses "open addressing" with quadratic reprobing to resolve collisions. Compared to `MutableHashTable` the insert, remove and lookup operations in a - `MutableDenseHashTable` are typically faster, but memory usage can be higher. - However, `MutableDenseHashTable` does not require additional memory for + `DenseHashTable` are typically faster, but memory usage can be higher. + However, `DenseHashTable` does not require additional memory for temporary tensors created during checkpointing and restore operations. Example usage: ```python - table = tf.lookup.MutableDenseHashTable(key_dtype=tf.int64, - value_dtype=tf.int64, - default_value=-1, - empty_key=0, - deleted_key=-1) + table = tf.lookup.DenseHashTable(key_dtype=tf.int64, + value_dtype=tf.int64, + default_value=-1, + empty_key=0, + deleted_key=-1) sess.run(table.insert(keys, values)) out = table.lookup(query_keys) @@ -1766,7 +1771,7 @@ class MutableDenseHashTable(LookupInterface): initial_num_buckets=None, name="MutableDenseHashTable", checkpoint=True): - """Creates an empty `MutableDenseHashTable` object. + """Creates an empty `DenseHashTable` object. Creates a table, the type of its keys and values are specified by key_dtype and value_dtype, respectively. @@ -1787,7 +1792,7 @@ class MutableDenseHashTable(LookupInterface): is shared using the table node name. Returns: - A `MutableDenseHashTable` object. + A `DenseHashTable` object. Raises: ValueError: If checkpoint is True and no name was specified. @@ -1813,11 +1818,11 @@ class MutableDenseHashTable(LookupInterface): # tables in a loop is uncommon). # TODO(rohanj): Use context.shared_name() instead. self._shared_name = "table_%d" % (ops.uid(),) - super(MutableDenseHashTable, self).__init__(key_dtype, value_dtype) + super(DenseHashTable, self).__init__(key_dtype, value_dtype) self._resource_handle = self.create_resource() if checkpoint: - saveable = MutableDenseHashTable._Saveable(self, name) + saveable = DenseHashTable._Saveable(self, name) if not context.executing_eagerly(): ops.add_to_collection(ops.GraphKeys.SAVEABLE_OBJECTS, saveable) @@ -1884,7 +1889,7 @@ class MutableDenseHashTable(LookupInterface): return values - def insert(self, keys, values, name=None): + def insert_or_assign(self, keys, values, name=None): """Associates `keys` with `values`. Args: @@ -1911,7 +1916,26 @@ class MutableDenseHashTable(LookupInterface): values) return op - def remove(self, keys, name=None): + def insert(self, keys, values, name=None): + """Associates `keys` with `values`. + + Args: + keys: Keys to insert. Can be a tensor of any shape. Must match the table's + key type. + values: Values to be associated with keys. Must be a tensor of the same + shape as `keys` and match the table's value type. + name: A name for the operation (optional). + + Returns: + The created Operation. + + Raises: + TypeError: when `keys` or `values` doesn't match the table data + types. + """ + return self.insert_or_assign(keys, values, name) + + def erase(self, keys, name=None): """Removes `keys` and its associated values from the table. If a key is not present in the table, it is silently ignored. @@ -1938,6 +1962,24 @@ class MutableDenseHashTable(LookupInterface): return op + def remove(self, keys, name=None): + """Removes `keys` and its associated values from the table. + + If a key is not present in the table, it is silently ignored. + + Args: + keys: Keys to remove. Can be a tensor of any shape. Must match the table's + key type. + name: A name for the operation (optional). + + Returns: + The created Operation. + + Raises: + TypeError: when `keys` do not match the table data types. + """ + return self.erase(keys, name) + def export(self, name=None): """Returns tensors of all keys and values in the table. @@ -1958,12 +2000,10 @@ class MutableDenseHashTable(LookupInterface): def _gather_saveables_for_checkpoint(self): """For object-based checkpointing.""" - return { - "table": functools.partial(MutableDenseHashTable._Saveable, table=self) - } + return {"table": functools.partial(DenseHashTable._Saveable, table=self)} class _Saveable(BaseSaverBuilder.SaveableObject): - """SaveableObject implementation for MutableDenseHashTable.""" + """SaveableObject implementation for DenseHashTable.""" def __init__(self, table, name): tensors = table.export() @@ -1972,7 +2012,7 @@ class MutableDenseHashTable(LookupInterface): BaseSaverBuilder.SaveSpec(tensors[1], "", name + "-values") ] # pylint: disable=protected-access - super(MutableDenseHashTable._Saveable, self).__init__(table, specs, name) + super(DenseHashTable._Saveable, self).__init__(table, specs, name) def restore(self, restored_tensors, restored_shapes, name=None): del restored_shapes # unused -- GitLab From ab6dee70dd5bbe10b6fc631bf7a594a5317e1572 Mon Sep 17 00:00:00 2001 From: Zhenyu Tan Date: Fri, 22 Feb 2019 09:49:06 -0800 Subject: [PATCH 344/766] Improve Ftrl doc. PiperOrigin-RevId: 235207665 --- tensorflow/python/keras/optimizer_v2/ftrl.py | 22 ++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/keras/optimizer_v2/ftrl.py b/tensorflow/python/keras/optimizer_v2/ftrl.py index a86fd8d89d..09e3e060bb 100644 --- a/tensorflow/python/keras/optimizer_v2/ftrl.py +++ b/tensorflow/python/keras/optimizer_v2/ftrl.py @@ -26,13 +26,31 @@ from tensorflow.python.util.tf_export import keras_export @keras_export('keras.optimizers.Ftrl') class Ftrl(optimizer_v2.OptimizerV2): - """Optimizer that implements the FTRL algorithm. + r"""Optimizer that implements the FTRL algorithm. - See this [paper]( + See Algorithm 1 of this [paper]( https://www.eecs.tufts.edu/~dsculley/papers/ad-click-prediction.pdf). This version has support for both online L2 (the L2 penalty given in the paper above) and shrinkage-type L2 (which is the addition of an L2 penalty to the loss function). + + Initialization: + $t = 0$ + $n_{0} = 0$ + $\sigma_{0} = 0$ + $z_{0} = 0$ + + Update ($i$ is variable index): + $t = t + 1$ + $n_{t,i} = n_{t-1,i} + g_{t,i}^{2}$ + $\sigma_{t,i} = (\sqrt{n_{t,i}} - \sqrt{n_{t-1,i}}) / \alpha$ + $z_{t,i} = z_{t-1,i} + g_{t,i} - \sigma_{t,i} * w_{t,i}$ + $w_{t,i} = - ((\beta+\sqrt{n+{t}}) / \alpha + \lambda_{2})^{-1} * (z_{i} - + sgn(z_{i}) * \lambda_{1}) if \abs{z_{i}} > \lambda_{i} else 0$ + + Check the documentation for the l2_shrinkage_regularization_strength + parameter for more details when shrinkage is enabled, where gradient is + replaced with gradient_with_shrinkage. """ def __init__(self, -- GitLab From e17c55f2de83ee32e8fc372a1133ebb03a0637b3 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 22 Feb 2019 09:50:29 -0800 Subject: [PATCH 345/766] Internal build visibility change. PiperOrigin-RevId: 235207900 --- tensorflow/python/tpu/BUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/python/tpu/BUILD b/tensorflow/python/tpu/BUILD index a76c620564..56cc8e3dbe 100644 --- a/tensorflow/python/tpu/BUILD +++ b/tensorflow/python/tpu/BUILD @@ -18,6 +18,7 @@ package( "//learning/brain:__subpackages__", "//learning/deepmind:__subpackages__", "//medical/pathology:__subpackages__", + "//research/graph:__subpackages__", "//tensorflow:__subpackages__", "//vr/perception:__subpackages__", ], -- GitLab From 85538ad4b6b0dfc43d3c4e950b3bcc96e69832da Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 22 Feb 2019 10:04:13 -0800 Subject: [PATCH 346/766] [xla] plumb ReplicaId into local Python client PiperOrigin-RevId: 235210508 --- .../compiler/xla/python/local_computation_builder.cc | 2 ++ .../compiler/xla/python/local_computation_builder.h | 2 ++ .../compiler/xla/python/local_computation_builder.i | 1 + tensorflow/compiler/xla/python/xla_client.py | 8 ++++++++ tensorflow/compiler/xla/python/xla_client_test.py | 6 ++++++ 5 files changed, 19 insertions(+) diff --git a/tensorflow/compiler/xla/python/local_computation_builder.cc b/tensorflow/compiler/xla/python/local_computation_builder.cc index c14a01a858..9104b27888 100644 --- a/tensorflow/compiler/xla/python/local_computation_builder.cc +++ b/tensorflow/compiler/xla/python/local_computation_builder.cc @@ -443,6 +443,8 @@ StatusOr ComputationBuilder::GetReturnValueShape() { return program_shape.result(); } +LocalOp ComputationBuilder::ReplicaId() { return xla::ReplicaId(&builder_); } + LocalOp ComputationBuilder::Infeed(const Shape& shape) { return xla::Infeed(&builder_, shape); } diff --git a/tensorflow/compiler/xla/python/local_computation_builder.h b/tensorflow/compiler/xla/python/local_computation_builder.h index 66b1cce7fb..9b35f4f2a3 100644 --- a/tensorflow/compiler/xla/python/local_computation_builder.h +++ b/tensorflow/compiler/xla/python/local_computation_builder.h @@ -224,6 +224,8 @@ class ComputationBuilder { // Returns the shape of the current return value for the computation. StatusOr GetReturnValueShape(); + LocalOp ReplicaId(); + LocalOp Infeed(const Shape& shape); void Outfeed(const LocalOp& operand, const Shape& shape, diff --git a/tensorflow/compiler/xla/python/local_computation_builder.i b/tensorflow/compiler/xla/python/local_computation_builder.i index 7d7a860baa..2f60387e7b 100644 --- a/tensorflow/compiler/xla/python/local_computation_builder.i +++ b/tensorflow/compiler/xla/python/local_computation_builder.i @@ -379,6 +379,7 @@ tensorflow::ImportNumpy(); %unignore xla::swig::ComputationBuilder::Parameter; %unignore xla::swig::ComputationBuilder::GetShape; %unignore xla::swig::ComputationBuilder::GetReturnValueShape; +%unignore xla::swig::ComputationBuilder::ReplicaId; %unignore xla::swig::ComputationBuilder::Infeed; %unignore xla::swig::ComputationBuilder::Outfeed; %unignore xla::swig::ComputationBuilder::ConstantLiteral; diff --git a/tensorflow/compiler/xla/python/xla_client.py b/tensorflow/compiler/xla/python/xla_client.py index 9019a979a6..5ae7fddbc6 100644 --- a/tensorflow/compiler/xla/python/xla_client.py +++ b/tensorflow/compiler/xla/python/xla_client.py @@ -1136,6 +1136,14 @@ class ComputationBuilder(object): def GetComputationStats(self): raise NotImplementedError() + def ReplicaId(self): + """Enqueues a ReplicaId operation onto the computation. + + Returns: + A LocalOp representing the replica id. + """ + return self._client.ReplicaId() + def Pad(self, operand, padding_value, padding_config): """Enqueues a Pad operation onto the computation. diff --git a/tensorflow/compiler/xla/python/xla_client_test.py b/tensorflow/compiler/xla/python/xla_client_test.py index 51ef7d7f3a..745f7b2093 100644 --- a/tensorflow/compiler/xla/python/xla_client_test.py +++ b/tensorflow/compiler/xla/python/xla_client_test.py @@ -603,6 +603,12 @@ class SingleOpTest(ComputationTest): c.CrossReplicaSum(c.Constant(lhs)) self._ExecuteAndCompareExact(c, expected=lhs) + def DISABLED_testReplicaId(self): + # TODO(b/125740859): enable this test + c = self._NewComputation() + _ = c.ReplicaId() + self._ExecuteAndCompareExact(c, expected=0) + def testCrossReplicaSumOneReplicaWithSingletonGroup(self): samples = [ NumpyArrayF32(42.0), -- GitLab From 848e56c93cc48140f8720eebd078ac8fe022b0f4 Mon Sep 17 00:00:00 2001 From: Bruce Fontaine Date: Fri, 22 Feb 2019 10:06:15 -0800 Subject: [PATCH 347/766] Restrict tpu_embedding hosts to just worker jobs. Pass ClusterDef to TpuEmbedding. PiperOrigin-RevId: 235210894 --- .../python/tpu/_tpu_estimator_embedding.py | 12 ++++-- tensorflow/python/tpu/tpu_context.py | 25 ++--------- tensorflow/python/tpu/tpu_embedding.py | 16 +++++-- tensorflow/python/tpu/tpu_system_metadata.py | 43 +++++++++++++++++++ 4 files changed, 68 insertions(+), 28 deletions(-) diff --git a/tensorflow/python/tpu/_tpu_estimator_embedding.py b/tensorflow/python/tpu/_tpu_estimator_embedding.py index 08e0e968a6..99d1c97620 100644 --- a/tensorflow/python/tpu/_tpu_estimator_embedding.py +++ b/tensorflow/python/tpu/_tpu_estimator_embedding.py @@ -272,13 +272,13 @@ class EmbeddingConfig(object): """ def __init__(self, embedding_config_spec, train_batch_size, eval_batch_size, - num_hosts, num_cores, master): + num_hosts, num_cores, run_config): self._embedding_config_spec = embedding_config_spec self._train_batch_size = train_batch_size self._eval_batch_size = eval_batch_size self._num_hosts = num_hosts self._num_cores = num_cores - self._master = master + self._run_config = run_config self._table_to_config_dict, self._feature_to_table_dict = ( get_tpu_embedding_config_from_feature_columns( @@ -306,13 +306,19 @@ class EmbeddingConfig(object): else: raise ValueError('Mode {} is not supported.'.format(mode)) + master = ( + self._run_config.evaluation_master + if mode == model_fn_lib.ModeKeys.EVAL else self._run_config.master) + cluster_def = (self._run_config.session_config.cluster_def + if self._run_config.session_config else None) tpu_embedding_ = tpu_embedding.TPUEmbedding( self._table_to_config_dict, self._feature_to_table_dict, batch_size, tpu_embedding_mode, - self._master, + master, self._optimization_parameters, + cluster_def, ) return tpu_embedding_ diff --git a/tensorflow/python/tpu/tpu_context.py b/tensorflow/python/tpu/tpu_context.py index 2511e427a2..9febcf03ab 100644 --- a/tensorflow/python/tpu/tpu_context.py +++ b/tensorflow/python/tpu/tpu_context.py @@ -313,7 +313,7 @@ class _InternalTPUContext(object): if self._use_tpu and self._embedding_config_spec: embedding_config = _tpu_estimator_embedding.EmbeddingConfig( self._embedding_config_spec, self._train_batch_size, - self._eval_batch_size, self.num_hosts, self.num_cores, master) + self._eval_batch_size, self.num_hosts, self.num_cores, self.config) if not embedding_config.has_embedding_tables(): embedding_config = None self._lazy_embedding_config_dict[master] = embedding_config @@ -510,27 +510,10 @@ class _InternalTPUContext(object): master = ( run_config.evaluation_master if mode == model_fn_lib.ModeKeys.EVAL else run_config.master) - if master in _LOCAL_MASTERS: - return None + cluster_def = (run_config.session_config.cluster_def + if run_config.session_config else None) - if (not run_config.session_config or - not run_config.session_config.cluster_def.job): - return _DEFAULT_JOB_NAME - cluster_def = run_config.session_config.cluster_def - job_names = set([job.name for job in cluster_def.job]) - if _DEFAULT_JOB_NAME in job_names: - # b/37868888 tracks allowing ClusterSpec propagation to reuse job names. - raise ValueError('Currently, tpu_worker is not an allowed job name.') - if len(job_names) == 1: - return cluster_def.job[0].name - if len(job_names) == 2: - if _DEFAULT_COORDINATOR_JOB_NAME in job_names: - job_names.remove(_DEFAULT_COORDINATOR_JOB_NAME) - return job_names.pop() - # TODO(b/67716447): Include more sophisticated heuristics. - raise ValueError( - 'Could not infer TPU job name. Please specify a tpu_job_name as part ' - 'of your TPUConfig.') + return tpu_system_metadata_lib.master_job(master, cluster_def) @property def tpu_host_placement_function(self): diff --git a/tensorflow/python/tpu/tpu_embedding.py b/tensorflow/python/tpu/tpu_embedding.py index c7f17395f1..3bd9be9be5 100644 --- a/tensorflow/python/tpu/tpu_embedding.py +++ b/tensorflow/python/tpu/tpu_embedding.py @@ -308,7 +308,8 @@ class TPUEmbedding(object): batch_size, mode, master, - optimization_parameters=None): + optimization_parameters=None, + cluster_def=None): """API for using TPU for embedding lookups. Args: @@ -324,6 +325,7 @@ class TPUEmbedding(object): optimization_parameters: `AdagradParameters`, `AdamParameters`, `Stochasticgradientdescentparameters`. Must be set in training and must be `None` in inference. + cluster_def: A ClusterDef object describing the TPU cluster. Raises: ValueError: if any input is invalid. @@ -341,14 +343,20 @@ class TPUEmbedding(object): self._batch_size = batch_size self._master = master + self._cluster_def = cluster_def self._tpu_system_metadata = ( - tpu_system_metadata_lib._query_tpu_system_metadata(self._master)) # pylint: disable=protected-access + tpu_system_metadata_lib._query_tpu_system_metadata( # pylint: disable=protected-access + self._master, cluster_def=self._cluster_def)) if self._tpu_system_metadata.num_cores == 0: raise ValueError('TPUEmbedding needs TPUs, but master {} does not have ' 'TPUs.'.format(self._master)) self._num_hosts = self._tpu_system_metadata.num_hosts - self._hosts = [device.name for device in self._tpu_system_metadata.devices - if 'device:CPU:' in device.name] + master_job_name = tpu_system_metadata_lib.master_job(self._master, + self._cluster_def) + self._hosts = sorted([ + device.name for device in self._tpu_system_metadata.devices + if 'device:CPU:' in device.name and (master_job_name is None or + master_job_name in device.name)]) self._num_cores_per_host = self._tpu_system_metadata.num_of_cores_per_host self._num_cores = self._tpu_system_metadata.num_cores diff --git a/tensorflow/python/tpu/tpu_system_metadata.py b/tensorflow/python/tpu/tpu_system_metadata.py index 866895922a..1c008dfbd4 100644 --- a/tensorflow/python/tpu/tpu_system_metadata.py +++ b/tensorflow/python/tpu/tpu_system_metadata.py @@ -34,6 +34,10 @@ _INITIAL_TPU_SYSTEM_TIMEOUT_IN_MS = 300 * 1000 # 5 mins _TPU_DEVICE_REG = re.compile(r'.*task:(\d+)/.*device:TPU:(\d+)$') +_DEFAULT_JOB_NAME = 'tpu_worker' +_DEFAULT_COORDINATOR_JOB_NAME = 'coordinator' +_LOCAL_MASTERS = ('', 'local') + # _TPUSystemMetadata is used by TPUEstimator to hold TPU configuration, # including num_cores and num_hosts. _TPUSystemMetadata = collections.namedtuple('_TPUSystemMetadata', [ @@ -154,3 +158,42 @@ def get_session_config_with_timeout(timeout_in_secs, cluster_def): config = config_pb2.ConfigProto( operation_timeout_in_ms=timeout_in_secs, cluster_def=cluster_def) return config + + +def master_job(master, cluster_def): + """Returns the canonnical job name to use to place TPU computations on. + + Args: + master: A `string` representing the TensorFlow master to use. + cluster_def: A ClusterDef object describing the TPU cluster. + + + Returns: + A string containing the job name, or None if no job should be specified. + + Raises: + ValueError: If the user needs to specify a tpu_job_name, because we are + unable to infer the job name automatically, or if the user-specified job + names are inappropriate. + """ + # If the user specifies the tpu_job_name, use that. + + if master in _LOCAL_MASTERS: + return None + + if (not cluster_def or not cluster_def.job): + return _DEFAULT_JOB_NAME + job_names = set([job.name for job in cluster_def.job]) + if _DEFAULT_JOB_NAME in job_names: + # b/37868888 tracks allowing ClusterSpec propagation to reuse job names. + raise ValueError('Currently, tpu_worker is not an allowed job name.') + if len(job_names) == 1: + return cluster_def.job[0].name + if len(job_names) == 2: + if _DEFAULT_COORDINATOR_JOB_NAME in job_names: + job_names.remove(_DEFAULT_COORDINATOR_JOB_NAME) + return job_names.pop() + # TODO(b/67716447): Include more sophisticated heuristics. + raise ValueError( + 'Could not infer TPU job name. Please specify a tpu_job_name as part ' + 'of your TPUConfig.') -- GitLab From c6a2c72f83d4100ed71dc677a4ead8cabe479d5b Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 22 Feb 2019 10:07:20 -0800 Subject: [PATCH 348/766] Compress all constant nodes in the graph after the constant folding pass in Grappler. Notice that most of the constants created by the constant folding pass are already in compressed format. This just extends this practice to all constants in the graph that have at least 64 elements and where the achievable compression ratio is grater or equal to 2. PiperOrigin-RevId: 235211073 --- tensorflow/core/grappler/op_types.cc | 4 +- tensorflow/core/grappler/op_types.h | 1 + .../grappler/optimizers/constant_folding.cc | 16 ++++++ .../optimizers/constant_folding_test.cc | 49 +++++++++++++++++++ 4 files changed, 69 insertions(+), 1 deletion(-) diff --git a/tensorflow/core/grappler/op_types.cc b/tensorflow/core/grappler/op_types.cc index 59400dc479..2543b78730 100644 --- a/tensorflow/core/grappler/op_types.cc +++ b/tensorflow/core/grappler/op_types.cc @@ -229,6 +229,8 @@ bool IsGreater(const NodeDef& node) { return node.op() == "Greater"; } bool IsGreaterEqual(const NodeDef& node) { return node.op() == "GreaterEqual"; } +bool IsHostConstant(const NodeDef& node) { return node.op() == "HostConst"; } + bool IsHistogramSummary(const NodeDef& node) { return node.op() == "HistogramSummary"; } @@ -558,7 +560,7 @@ bool GetBoolAttr(const NodeDef& node, const string& name) { } // namespace bool IsPersistent(const NodeDef& node) { - return IsConstant(node) || IsVariable(node); + return IsConstant(node) || IsVariable(node) || IsHostConstant(node); } bool MaybeHasRefInput(const NodeDef& node) { diff --git a/tensorflow/core/grappler/op_types.h b/tensorflow/core/grappler/op_types.h index bc1bb33772..d2f54661c2 100644 --- a/tensorflow/core/grappler/op_types.h +++ b/tensorflow/core/grappler/op_types.h @@ -73,6 +73,7 @@ bool IsFusedBatchNormGrad(const NodeDef& node); bool IsGreater(const NodeDef& node); bool IsGreaterEqual(const NodeDef& node); bool IsHistogramSummary(const NodeDef& node); +bool IsHostConstant(const NodeDef& node); bool IsIdentity(const NodeDef& node); bool IsIdentityN(const NodeDef& node); bool IsIdentityNSingleInput(const NodeDef& node); diff --git a/tensorflow/core/grappler/optimizers/constant_folding.cc b/tensorflow/core/grappler/optimizers/constant_folding.cc index ae3aeb748c..bbd1250c91 100644 --- a/tensorflow/core/grappler/optimizers/constant_folding.cc +++ b/tensorflow/core/grappler/optimizers/constant_folding.cc @@ -807,6 +807,7 @@ Status ConstantFolding::MaterializeConstantValuedNode( "Could not construct Tensor form TensorProto in node: ", input_node->name()); } + tensor->clear_tensor_content(); t.AsProtoField(tensor); } else { *tensor = input_tensor; @@ -3404,6 +3405,20 @@ Status ConstantFolding::RunOptimizationPass(Cluster* cluster, return Status::OK(); } +namespace { +Status CompressConstants(GraphDef* graph) { + for (int i = 0; i < graph->node_size(); ++i) { + NodeDef* node = graph->mutable_node(i); + if ((IsConstant(*node) || IsHostConstant(*node)) && + HasNodeAttr(*node, "value")) { + AttrValue& attr_val = (*node->mutable_attr())["value"]; + tensor::CompressTensorProtoInPlace(attr_val.mutable_tensor()); + } + } + return Status::OK(); +} +} // namespace + Status ConstantFolding::Optimize(Cluster* cluster, const GrapplerItem& item, GraphDef* optimized_graph) { // TensorFlow flushes denormals to zero and rounds to nearest, so we do @@ -3442,6 +3457,7 @@ Status ConstantFolding::Optimize(Cluster* cluster, const GrapplerItem& item, TF_RETURN_IF_ERROR( RunOptimizationPass(cluster, item_to_optimize, optimized_graph)); } while (graph_modified_ || optimized_graph->node_size() != node_count); + TF_RETURN_IF_ERROR(CompressConstants(optimized_graph)); *optimized_graph->mutable_library() = item.graph.library(); *optimized_graph->mutable_versions() = item.graph.versions(); diff --git a/tensorflow/core/grappler/optimizers/constant_folding_test.cc b/tensorflow/core/grappler/optimizers/constant_folding_test.cc index afca3d2257..373e3f0eb1 100644 --- a/tensorflow/core/grappler/optimizers/constant_folding_test.cc +++ b/tensorflow/core/grappler/optimizers/constant_folding_test.cc @@ -3750,6 +3750,55 @@ TEST_F(ConstantFoldingTest, BitcastDenormalFloats) { test::ExpectTensorEqual(tensors[0], tensors_expected[0]); } +TEST_F(ConstantFoldingTest, CompressConstants) { + tensorflow::Scope scope = tensorflow::Scope::NewRootScope(); + Tensor zeros_t(DT_FLOAT, TensorShape({64})); + Tensor ones_t(DT_FLOAT, TensorShape({64})); + for (int i = 0; i < 64; ++i) { + zeros_t.flat()(i) = 0.0f; + ones_t.flat()(i) = 1.0f; + } + Output zeros = ops::Const(scope.WithOpName("zeros"), zeros_t); + Output host_ones = ops::Const(scope.WithOpName("host_ones"), ones_t); + GrapplerItem item; + TF_CHECK_OK(scope.ToGraphDef(&item.graph)); + ASSERT_EQ(item.graph.node(1).name(), "host_ones"); + // There is not C++ api for HostConst, so we manually change the node type + // here. + item.graph.mutable_node(1)->set_op("HostConst"); + item.fetch = {"zeros", "host_ones"}; + auto tensors_expected = EvaluateNodes(item.graph, item.fetch, {}); + + ConstantFolding optimizer(/*cpu_device=*/nullptr); + GraphDef output; + TF_EXPECT_OK(optimizer.Optimize(/*cluster=*/nullptr, item, &output)); + + { + ASSERT_EQ(output.node_size(), 2); + const NodeDef& node = output.node(0); + EXPECT_EQ(node.name(), "zeros"); + EXPECT_EQ(node.op(), "Const"); + const TensorProto& zeroes_t = node.attr().at("value").tensor(); + EXPECT_EQ(zeroes_t.float_val_size(), 1); + EXPECT_EQ(zeroes_t.float_val(0), 0.0f); + } + { + const NodeDef& node = output.node(1); + EXPECT_EQ(node.name(), "host_ones"); + EXPECT_EQ(node.op(), "HostConst"); + const TensorProto& ones_t = node.attr().at("value").tensor(); + EXPECT_EQ(ones_t.float_val_size(), 1); + EXPECT_EQ(ones_t.float_val(0), 1.0f); + } + + auto tensors = EvaluateNodes(output, item.fetch, {}); + ASSERT_EQ(tensors.size(), 2); + ASSERT_EQ(tensors_expected.size(), 2); + for (int i = 0; i < 2; ++i) { + test::ExpectTensorEqual(tensors[i], tensors_expected[i]); + } +} + } // namespace } // namespace grappler } // namespace tensorflow -- GitLab From 809c74f0ef105a419320e1cd16966bf19c891949 Mon Sep 17 00:00:00 2001 From: Allen Lavoie Date: Fri, 22 Feb 2019 10:08:16 -0800 Subject: [PATCH 349/766] Allow tf.Module definitions with tf.function(input_signature=)-decorated methods An unfortunate combination of the name_scope method wrapping not forwarding argspecs and input_signatures inspecting functions for keyword arguments. PiperOrigin-RevId: 235211265 --- tensorflow/python/module/module.py | 11 +++++++++- tensorflow/python/module/module_test.py | 29 +++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/tensorflow/python/module/module.py b/tensorflow/python/module/module.py index 07455bd7f7..21f6e29468 100644 --- a/tensorflow/python/module/module.py +++ b/tensorflow/python/module/module.py @@ -21,6 +21,7 @@ from __future__ import print_function import abc import re import sys +import weakref import six @@ -146,11 +147,19 @@ def wrap_with_name_scope(unbound_method): def wrap_with_name_scope_no_exception(unbound_method): """Patches the given method so it enters the modules name scope.""" + weak_enter_name_scope = None def enter_name_scope(self, *args, **kwargs): with self.name_scope: # tf.Module enters the module name scope for all methods. To disable this # for a particular method annotate it with `@no_module_name_scope`. - return unbound_method(self, *args, **kwargs) + return weak_enter_name_scope().__wrapped__(self, *args, **kwargs) + weak_enter_name_scope = weakref.ref(enter_name_scope) + # make_decorator forwards argspecs through the wrapper, which tf.function + # requires for naming inputs (e.g. default argument names in the result of + # get_concrete_function) and error checking. Calling __wrapped__ lets + # tf_decorator.rewrap replace the called function, currently used to swap in + # the AutoGraph-converted function. + tf_decorator.make_decorator(unbound_method, enter_name_scope) return enter_name_scope diff --git a/tensorflow/python/module/module_test.py b/tensorflow/python/module/module_test.py index 52bb97bc5b..dc6bbe2c94 100644 --- a/tensorflow/python/module/module_test.py +++ b/tensorflow/python/module/module_test.py @@ -26,7 +26,10 @@ import six from tensorflow.python.compat import v2_compat from tensorflow.python.eager import def_function +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_spec from tensorflow.python.module import module from tensorflow.python.ops import variables from tensorflow.python.platform import test @@ -281,6 +284,32 @@ class AbcTest(test.TestCase): self.assertEqual(scope_name, "concrete_module/") self.assertEqual(get_name_scope(), "") + def testModuleDecoratedMethodInputSignature(self): + + class Module(module.Module): + + def no_sig(self, called_with_keyword): + return 2. * called_with_keyword + + @def_function.function( + input_signature=[tensor_spec.TensorSpec([], dtypes.float32)]) + def mutate(self, new_v): + if not hasattr(self, "v"): + self.v = variables.Variable(1.) + if new_v > 0.: + self.v.assign(new_v) + else: + # Autograph should still be running + self.v.assign(-new_v) + + m = Module() + m.mutate(constant_op.constant(2.)) + self.assertEqual(2., m.v.numpy()) + m.mutate(constant_op.constant(-3.)) + self.assertEqual(3., m.v.numpy()) + + m.no_sig(called_with_keyword=1.) # keyword names preserved + def get_name_scope(): with ops.name_scope("x") as ns: -- GitLab From 25996457e87b4f83b47330fbbe818351111b0f13 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 22 Feb 2019 10:22:21 -0800 Subject: [PATCH 350/766] Use Arena for SequencExample allocation in example_parsing_ops.cc Provides better memory locality which speeds up most ops, and makes destruction trivial. PiperOrigin-RevId: 235213734 --- tensorflow/core/kernels/example_parsing_ops.cc | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tensorflow/core/kernels/example_parsing_ops.cc b/tensorflow/core/kernels/example_parsing_ops.cc index 528b3c6bf0..708b52a517 100644 --- a/tensorflow/core/kernels/example_parsing_ops.cc +++ b/tensorflow/core/kernels/example_parsing_ops.cc @@ -591,7 +591,22 @@ class ParseSingleSequenceExampleOp : public OpKernel { OP_REQUIRES_OK(ctx, ctx->output_list("feature_list_dense_values", &feature_list_dense_values)); +#ifdef TENSORFLOW_LITE_PROTOS SequenceExample ex; +#else + // Allocate the SequenceExample on an arena. Provides better memory locality + // and greatly speeds up destruction. + protobuf::ArenaOptions options; + // We have some hint of what the final proto size will be based on the size + // of the serialized bytes- use this to set a custom allocation strategy. + // Note that the default allocation strategy is quite conservative (min + // block size of 256 bytes, and a max of 8 kilobytes). + const size_t block_size = serialized_t().size() * 1.1; + options.start_block_size = std::max(options.start_block_size, block_size); + options.max_block_size = std::max(options.max_block_size, block_size); + protobuf::Arena arena(options); + auto& ex = *protobuf::Arena::CreateMessage(&arena); +#endif OP_REQUIRES( ctx, ParseProtoUnlimited(&ex, serialized_t()), errors::InvalidArgument("Could not parse example input, value: '", -- GitLab From a9dab429ae9b1a902beb72cee96ff640aa0a4142 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 22 Feb 2019 10:29:37 -0800 Subject: [PATCH 351/766] Tiny simplification of TensorProto compression code. PiperOrigin-RevId: 235215099 --- tensorflow/core/framework/tensor_util.cc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tensorflow/core/framework/tensor_util.cc b/tensorflow/core/framework/tensor_util.cc index 65fb72145f..abbf19d1bf 100644 --- a/tensorflow/core/framework/tensor_util.cc +++ b/tensorflow/core/framework/tensor_util.cc @@ -263,14 +263,13 @@ bool CompressRepeatedField(float min_compression_ratio, // Already compressed or invalid. return false; } - T prev_value = TypeHelper::GetValue(num_proto_values - 1, *tensor); + const T last_value = TypeHelper::GetValue(num_proto_values - 1, *tensor); int64 last_index = 0; for (int64 i = num_proto_values - 2; i >= 0 && last_index == 0; --i) { const T cur_value = TypeHelper::GetValue(i, *tensor); - if (PackedValuesNotEqual(cur_value, prev_value)) { + if (PackedValuesNotEqual(cur_value, last_value)) { last_index = i + 1; } - prev_value = cur_value; } const int64 num_truncated_proto_values = last_index + 1; const int64 num_bytes_as_field = -- GitLab From f230b566919e4a7d5fbfacc06f07d0a49aca7ce3 Mon Sep 17 00:00:00 2001 From: Derek Murray Date: Fri, 22 Feb 2019 10:41:13 -0800 Subject: [PATCH 352/766] [tf.data] Remove internal uses of Dataset.output_{shapes,types,classes} in preparation for 2.0. Internal users of these properties should now use dataset_ops.get_legacy_output_{shapes,types,classes}() instead. In a future CL we will remove the properties from the DatasetV2. PiperOrigin-RevId: 235217553 --- .../bigtable/python/ops/bigtable_api.py | 8 +- .../kernel_tests/assert_element_shape_test.py | 27 ++- .../contrib/data/python/ops/batching.py | 9 +- tensorflow/contrib/data/python/ops/sliding.py | 5 +- .../kafka/python/kernel_tests/kafka_test.py | 4 +- .../python/kernel_tests/kinesis_test.py | 7 +- .../contrib/tpu/python/tpu/keras_support.py | 50 +++--- .../benchmarks/map_vectorization_benchmark.py | 3 +- .../bucket_by_sequence_length_test.py | 2 +- .../kernel_tests/copy_to_device_test.py | 100 +++++------ .../experimental/kernel_tests/counter_test.py | 6 +- .../kernel_tests/enumerate_dataset_test.py | 8 +- .../kernel_tests/group_by_reducer_test.py | 5 +- .../make_batched_features_dataset_test.py | 6 +- .../kernel_tests/make_csv_dataset_test.py | 3 +- .../make_tf_record_dataset_test.py | 3 +- .../kernel_tests/map_and_batch_test.py | 15 +- .../choose_fastest_dataset_test.py | 6 +- .../parse_example_dataset_test.py | 7 +- .../kernel_tests/prefetch_to_device_test.py | 50 +++--- .../kernel_tests/rebatch_dataset_test.py | 56 +++--- .../kernel_tests/restructured_dataset_test.py | 5 +- .../experimental/kernel_tests/scan_test.py | 9 +- .../dataset_serialization_test_base.py | 6 +- .../python/data/experimental/ops/batching.py | 75 ++++---- .../python/data/experimental/ops/grouping.py | 7 +- .../data/experimental/ops/interleave_ops.py | 22 +-- .../data/experimental/ops/parsing_ops.py | 14 +- .../data/experimental/ops/prefetching_ops.py | 6 +- .../python/data/experimental/ops/readers.py | 5 +- .../python/data/experimental/ops/writers.py | 11 +- .../python/data/kernel_tests/batch_test.py | 3 +- .../python/data/kernel_tests/cache_test.py | 2 +- .../data/kernel_tests/concatenate_test.py | 10 +- .../data/kernel_tests/filter_test_base.py | 5 +- .../kernel_tests/from_tensor_slices_test.py | 19 +- .../data/kernel_tests/from_tensors_test.py | 164 ++++++++++-------- .../kernel_tests/iterator_cluster_test.py | 6 +- .../python/data/kernel_tests/iterator_test.py | 41 +++-- .../python/data/kernel_tests/map_test.py | 10 +- .../data/kernel_tests/padded_batch_test.py | 7 +- .../python/data/kernel_tests/repeat_test.py | 15 +- .../python/data/kernel_tests/shuffle_test.py | 2 +- .../python/data/kernel_tests/skip_test.py | 5 +- .../python/data/kernel_tests/take_test.py | 5 +- .../python/data/kernel_tests/test_base.py | 12 +- .../python/data/kernel_tests/window_test.py | 6 +- .../python/data/kernel_tests/zip_test.py | 2 +- tensorflow/python/data/ops/dataset_ops.py | 139 ++++++++++++--- tensorflow/python/data/ops/iterator_ops.py | 30 ++-- .../data/ops/multi_device_iterator_ops.py | 12 +- tensorflow/python/data/ops/readers.py | 5 +- tensorflow/python/distribute/input_lib.py | 8 +- .../engine/distributed_training_utils.py | 4 +- tensorflow/python/keras/engine/training.py | 2 +- .../python/keras/engine/training_utils.py | 2 +- tensorflow/python/tpu/datasets.py | 13 +- 57 files changed, 627 insertions(+), 442 deletions(-) diff --git a/tensorflow/contrib/bigtable/python/ops/bigtable_api.py b/tensorflow/contrib/bigtable/python/ops/bigtable_api.py index fa64055dfd..736cf3da49 100644 --- a/tensorflow/contrib/bigtable/python/ops/bigtable_api.py +++ b/tensorflow/contrib/bigtable/python/ops/bigtable_api.py @@ -475,15 +475,17 @@ class BigtableTable(object): """ if timestamp is None: timestamp = -1 # Bigtable server provided timestamp. - for tensor_type in nest.flatten(dataset.output_types): + for tensor_type in nest.flatten( + dataset_ops.get_legacy_output_types(dataset)): if tensor_type != dtypes.string: raise ValueError("Not all elements of the dataset were `tf.string`") - for shape in nest.flatten(dataset.output_shapes): + for shape in nest.flatten(dataset_ops.get_legacy_output_shapes(dataset)): if not shape.is_compatible_with(tensor_shape.scalar()): raise ValueError("Not all elements of the dataset were scalars") if len(column_families) != len(columns): raise ValueError("len(column_families) != len(columns)") - if len(nest.flatten(dataset.output_types)) != len(columns) + 1: + if len(nest.flatten( + dataset_ops.get_legacy_output_types(dataset))) != len(columns) + 1: raise ValueError("A column name must be specified for every component of " "the dataset elements. (e.g.: len(columns) != " "len(dataset.output_types))") diff --git a/tensorflow/contrib/data/python/kernel_tests/assert_element_shape_test.py b/tensorflow/contrib/data/python/kernel_tests/assert_element_shape_test.py index 4db711c1f3..077571fcd2 100644 --- a/tensorflow/contrib/data/python/kernel_tests/assert_element_shape_test.py +++ b/tensorflow/contrib/data/python/kernel_tests/assert_element_shape_test.py @@ -43,10 +43,12 @@ class AssertElementShapeTest(test_base.DatasetTestBase): dataset = dataset_ops.Dataset.range(5).map(create_dataset) expected_shapes = (tensor_shape.TensorShape(2), tensor_shape.TensorShape((3, 4))) - self.assertEqual(expected_shapes, dataset.output_shapes) + self.assertEqual(expected_shapes, + dataset_ops.get_legacy_output_shapes(dataset)) result = dataset.apply(batching.assert_element_shape(expected_shapes)) - self.assertEqual(expected_shapes, result.output_shapes) + self.assertEqual(expected_shapes, + dataset_ops.get_legacy_output_shapes(result)) iterator = dataset_ops.make_initializable_iterator(result) init_op = iterator.initializer @@ -83,12 +85,14 @@ class AssertElementShapeTest(test_base.DatasetTestBase): dataset = dataset_ops.Dataset.range(5).map(create_unknown_shape_dataset) unknown_shapes = (tensor_shape.TensorShape(None), tensor_shape.TensorShape(None)) - self.assertEqual(unknown_shapes, dataset.output_shapes) + self.assertEqual(unknown_shapes, + dataset_ops.get_legacy_output_shapes(dataset)) expected_shapes = (tensor_shape.TensorShape(2), tensor_shape.TensorShape((3, 4))) result = dataset.apply(batching.assert_element_shape(expected_shapes)) - self.assertEqual(expected_shapes, result.output_shapes) + self.assertEqual(expected_shapes, + dataset_ops.get_legacy_output_shapes(result)) iterator = dataset_ops.make_initializable_iterator(result) init_op = iterator.initializer @@ -113,7 +117,8 @@ class AssertElementShapeTest(test_base.DatasetTestBase): dataset = dataset_ops.Dataset.range(3).map(create_unknown_shape_dataset) unknown_shapes = (tensor_shape.TensorShape(None), tensor_shape.TensorShape(None)) - self.assertEqual(unknown_shapes, dataset.output_shapes) + self.assertEqual(unknown_shapes, + dataset_ops.get_legacy_output_shapes(dataset)) wrong_shapes = (tensor_shape.TensorShape(2), tensor_shape.TensorShape((3, 10))) @@ -141,7 +146,8 @@ class AssertElementShapeTest(test_base.DatasetTestBase): # Partial shapes are merged with actual shapes: actual_shapes = (tensor_shape.TensorShape(2), tensor_shape.TensorShape((3, 4))) - self.assertEqual(actual_shapes, result.output_shapes) + self.assertEqual(actual_shapes, + dataset_ops.get_legacy_output_shapes(result)) iterator = dataset_ops.make_initializable_iterator(result) init_op = iterator.initializer @@ -178,12 +184,14 @@ class AssertElementShapeTest(test_base.DatasetTestBase): dataset = dataset_ops.Dataset.range(5).map(create_unknown_shape_dataset) unknown_shapes = (tensor_shape.TensorShape(None), tensor_shape.TensorShape(None)) - self.assertEqual(unknown_shapes, dataset.output_shapes) + self.assertEqual(unknown_shapes, + dataset_ops.get_legacy_output_shapes(dataset)) expected_shapes = (tensor_shape.TensorShape(2), tensor_shape.TensorShape((None, 4))) result = dataset.apply(batching.assert_element_shape(expected_shapes)) - self.assertEqual(expected_shapes, result.output_shapes) + self.assertEqual(expected_shapes, + dataset_ops.get_legacy_output_shapes(result)) iterator = dataset_ops.make_initializable_iterator(result) init_op = iterator.initializer @@ -208,7 +216,8 @@ class AssertElementShapeTest(test_base.DatasetTestBase): dataset = dataset_ops.Dataset.range(3).map(create_unknown_shape_dataset) unknown_shapes = (tensor_shape.TensorShape(None), tensor_shape.TensorShape(None)) - self.assertEqual(unknown_shapes, dataset.output_shapes) + self.assertEqual(unknown_shapes, + dataset_ops.get_legacy_output_shapes(dataset)) wrong_shapes = (tensor_shape.TensorShape(2), tensor_shape.TensorShape((None, 10))) diff --git a/tensorflow/contrib/data/python/ops/batching.py b/tensorflow/contrib/data/python/ops/batching.py index 8c60459ca8..f8bb942c0a 100644 --- a/tensorflow/contrib/data/python/ops/batching.py +++ b/tensorflow/contrib/data/python/ops/batching.py @@ -19,6 +19,7 @@ from __future__ import print_function from tensorflow.contrib.framework import with_shape from tensorflow.python.data.experimental.ops import batching +from tensorflow.python.data.ops import dataset_ops from tensorflow.python.data.util import nest from tensorflow.python.util import deprecation @@ -215,14 +216,14 @@ def assert_element_shape(expected_shapes): return nest.pack_sequence_as(elements, checked_tensors) def _apply_fn(dataset): - output_shapes = _merge_output_shapes(dataset.output_shapes, - expected_shapes) + output_shapes = _merge_output_shapes( + dataset_ops.get_legacy_output_shapes(dataset), expected_shapes) # pylint: disable=protected-access return batching._RestructuredDataset( dataset.map(_check_shape), - dataset.output_types, + dataset_ops.get_legacy_output_types(dataset), output_shapes=output_shapes, - output_classes=dataset.output_classes) + output_classes=dataset_ops.get_legacy_output_classes(dataset)) return _apply_fn diff --git a/tensorflow/contrib/data/python/ops/sliding.py b/tensorflow/contrib/data/python/ops/sliding.py index 6708e01d08..2897c72b44 100644 --- a/tensorflow/contrib/data/python/ops/sliding.py +++ b/tensorflow/contrib/data/python/ops/sliding.py @@ -18,7 +18,6 @@ from __future__ import division from __future__ import print_function from tensorflow.python.data.ops import dataset_ops -from tensorflow.python.data.util import structure from tensorflow.python.framework import dtypes from tensorflow.python.framework import ops from tensorflow.python.ops import gen_experimental_dataset_ops as ged_ops @@ -38,9 +37,7 @@ class _SlideDataset(dataset_ops.UnaryDataset): self._window_shift = ops.convert_to_tensor( window_shift, dtype=dtypes.int64, name="window_shift") - input_structure = structure.convert_legacy_structure( - input_dataset.output_types, input_dataset.output_shapes, - input_dataset.output_classes) + input_structure = dataset_ops.get_structure(input_dataset) self._structure = input_structure._batch(None) # pylint: disable=protected-access variant_tensor = ged_ops.experimental_sliding_window_dataset( self._input_dataset._variant_tensor, # pylint: disable=protected-access diff --git a/tensorflow/contrib/kafka/python/kernel_tests/kafka_test.py b/tensorflow/contrib/kafka/python/kernel_tests/kafka_test.py index 08ebcdb544..3651275f93 100644 --- a/tensorflow/contrib/kafka/python/kernel_tests/kafka_test.py +++ b/tensorflow/contrib/kafka/python/kernel_tests/kafka_test.py @@ -19,6 +19,7 @@ from __future__ import division from __future__ import print_function from tensorflow.contrib.kafka.python.ops import kafka_dataset_ops +from tensorflow.python.data.ops import dataset_ops from tensorflow.python.data.ops import iterator_ops from tensorflow.python.framework import dtypes from tensorflow.python.framework import errors @@ -49,7 +50,8 @@ class KafkaDatasetTest(test.TestCase): topics, group="test", eof=True).repeat(num_epochs) batch_dataset = repeat_dataset.batch(batch_size) - iterator = iterator_ops.Iterator.from_structure(batch_dataset.output_types) + iterator = iterator_ops.Iterator.from_structure( + dataset_ops.get_legacy_output_types(batch_dataset)) init_op = iterator.make_initializer(repeat_dataset) init_batch_op = iterator.make_initializer(batch_dataset) get_next = iterator.get_next() diff --git a/tensorflow/contrib/kinesis/python/kernel_tests/kinesis_test.py b/tensorflow/contrib/kinesis/python/kernel_tests/kinesis_test.py index bf89922318..af7018f836 100644 --- a/tensorflow/contrib/kinesis/python/kernel_tests/kinesis_test.py +++ b/tensorflow/contrib/kinesis/python/kernel_tests/kinesis_test.py @@ -29,6 +29,7 @@ from __future__ import print_function import boto3 from tensorflow.contrib.kinesis.python.ops import kinesis_dataset_ops +from tensorflow.python.data.ops import dataset_ops from tensorflow.python.data.ops import iterator_ops from tensorflow.python.framework import dtypes from tensorflow.python.framework import errors @@ -59,7 +60,8 @@ class KinesisDatasetTest(test.TestCase): stream, read_indefinitely=False).repeat(num_epochs) batch_dataset = repeat_dataset.batch(batch_size) - iterator = iterator_ops.Iterator.from_structure(batch_dataset.output_types) + iterator = iterator_ops.Iterator.from_structure( + dataset_ops.get_legacy_output_types(batch_dataset)) init_op = iterator.make_initializer(repeat_dataset) init_batch_op = iterator.make_initializer(batch_dataset) get_next = iterator.get_next() @@ -102,7 +104,8 @@ class KinesisDatasetTest(test.TestCase): stream, shard, read_indefinitely=False).repeat(num_epochs) batch_dataset = repeat_dataset.batch(batch_size) - iterator = iterator_ops.Iterator.from_structure(batch_dataset.output_types) + iterator = iterator_ops.Iterator.from_structure( + dataset_ops.get_legacy_output_types(batch_dataset)) init_op = iterator.make_initializer(repeat_dataset) init_batch_op = iterator.make_initializer(batch_dataset) get_next = iterator.get_next() diff --git a/tensorflow/contrib/tpu/python/tpu/keras_support.py b/tensorflow/contrib/tpu/python/tpu/keras_support.py index 6ad4e45e96..4946ef8f03 100644 --- a/tensorflow/contrib/tpu/python/tpu/keras_support.py +++ b/tensorflow/contrib/tpu/python/tpu/keras_support.py @@ -725,9 +725,10 @@ class TPUDatasetInfeedManager(TPUInfeedManager): self._dataset = dataset self._tpu_assignment = tpu_assignment - dummy_x_shape = dataset.output_shapes[0].as_list() + dataset_output_shapes = dataset_ops.get_legacy_output_shapes(dataset) + dummy_x_shape = dataset_output_shapes[0].as_list() dummy_x_shape[0] *= tpu_assignment.num_towers - dummy_y_shape = dataset.output_shapes[1].as_list() + dummy_y_shape = dataset_output_shapes[1].as_list() dummy_y_shape[0] *= tpu_assignment.num_towers self._iterator = dataset_ops.make_initializable_iterator(dataset) K.get_session().run(self._iterator.initializer) @@ -743,23 +744,26 @@ class TPUDatasetInfeedManager(TPUInfeedManager): # Use dummy numpy inputs for the rest of Keras' shape checking. We # intercept them when building the model. + dataset_output_types = dataset_ops.get_legacy_output_types(dataset) self._dummy_x = np.zeros( - dummy_x_shape, dtype=dataset.output_types[0].as_numpy_dtype) + dummy_x_shape, dtype=dataset_output_types[0].as_numpy_dtype) self._dummy_y = np.zeros( - dummy_y_shape, dtype=dataset.output_types[1].as_numpy_dtype) + dummy_y_shape, dtype=dataset_output_types[1].as_numpy_dtype) input_specs = [] - if isinstance(self._iterator.output_shapes, tuple): - assert isinstance(self._iterator.output_types, tuple) - assert len(self._iterator.output_shapes) == len( - self._iterator.output_types) - for i in range(len(self._iterator.output_shapes)): - spec = tensor_spec.TensorSpec(self._iterator.output_shapes[i], - self._iterator.output_types[i]) + iterator_output_shapes = dataset_ops.get_legacy_output_shapes( + self._iterator) + iterator_output_types = dataset_ops.get_legacy_output_types(self._iterator) + if isinstance(iterator_output_shapes, tuple): + assert isinstance(iterator_output_types, tuple) + assert len(iterator_output_shapes) == len(iterator_output_types) + for i in range(len(iterator_output_shapes)): + spec = tensor_spec.TensorSpec(iterator_output_shapes[i], + iterator_output_types[i]) input_specs.append(spec) - elif isinstance(self._iterator.output_shapes, tensor_shape.TensorShape): - spec = tensor_spec.TensorSpec(self._iterator.output_shapes, - self._iterator.output_types) + elif isinstance(iterator_output_shapes, tensor_shape.TensorShape): + spec = tensor_spec.TensorSpec(iterator_output_shapes, + iterator_output_types) input_specs.append(spec) # Pre-process the inputs and get_next_ops before caching. @@ -770,24 +774,26 @@ class TPUDatasetInfeedManager(TPUInfeedManager): def _verify_dataset_shape(self, dataset): """Verifies a dataset is of an appropriate shape for TPUs.""" + dataset_output_shapes = dataset_ops.get_legacy_output_shapes(dataset) + dataset_output_classes = dataset_ops.get_legacy_output_classes(dataset) if not isinstance(dataset, dataset_ops.DatasetV2): raise ValueError('The function passed as the `x` parameter did not ' 'return a `tf.data.Dataset`.') - if not isinstance(dataset.output_classes, tuple): + if not isinstance(dataset_output_classes, tuple): raise ValueError('The dataset must return a tuple of tf.Tensors, ' - 'instead it returns: %s' % dataset.output_classes) - if len(dataset.output_classes) != 2: + 'instead it returns: %s' % dataset_output_classes) + if len(dataset_output_classes) != 2: raise ValueError('The dataset must return a 2-element tuple, got ' - '%s output classes instead.' % (dataset.output_classes,)) - for i, cls in enumerate(dataset.output_classes): + '%s output classes instead.' % (dataset_output_classes,)) + for i, cls in enumerate(dataset_output_classes): if cls != ops.Tensor: raise ValueError('The dataset returned a non-Tensor type (%s) at ' 'index %d.' % (cls, i)) - for i, shape in enumerate(dataset.output_shapes): + for i, shape in enumerate(dataset_output_shapes): if not shape: raise ValueError('The dataset returns a scalar tensor in ' 'tuple index %d. Did you forget to batch? ' - '(Output shapes: %s).' % (i, dataset.output_shapes)) + '(Output shapes: %s).' % (i, dataset_output_shapes)) for j, dim in enumerate(shape): if dim.value is None: if j == 0: @@ -800,7 +806,7 @@ class TPUDatasetInfeedManager(TPUInfeedManager): 'currently requires static shapes. The provided ' 'dataset only has a partially defined shape. ' '(Dimension %d of output tensor %d is not statically known ' - 'for output shapes: %s.%s)' % (j, i, dataset.output_shapes, hint)) + 'for output shapes: %s.%s)' % (j, i, dataset_output_shapes, hint)) @property def dummy_x(self): diff --git a/tensorflow/python/data/experimental/benchmarks/map_vectorization_benchmark.py b/tensorflow/python/data/experimental/benchmarks/map_vectorization_benchmark.py index 8f8fbe86df..4e3d58658f 100644 --- a/tensorflow/python/data/experimental/benchmarks/map_vectorization_benchmark.py +++ b/tensorflow/python/data/experimental/benchmarks/map_vectorization_benchmark.py @@ -191,7 +191,8 @@ class MapVectorizationBenchmark(test.Benchmark): base_dataset = base_dataset.repeat() input_size = [ tuple(shape.as_list()) - for shape in nest.flatten(base_dataset.output_shapes) + for shape in nest.flatten( + dataset_ops.get_legacy_output_shapes(base_dataset)) ] self._compare(base_dataset, map_fn, batch_size, input_size, str_id) diff --git a/tensorflow/python/data/experimental/kernel_tests/bucket_by_sequence_length_test.py b/tensorflow/python/data/experimental/kernel_tests/bucket_by_sequence_length_test.py index 0bbf0e9a12..4839bc8e49 100644 --- a/tensorflow/python/data/experimental/kernel_tests/bucket_by_sequence_length_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/bucket_by_sequence_length_test.py @@ -375,7 +375,7 @@ class BucketBySequenceLengthTest(test_base.DatasetTestBase, bucket_batch_sizes=[2, 2, 2], bucket_boundaries=[0, 8], no_padding=no_padding)) - shapes = dataset.output_shapes + shapes = dataset_ops.get_legacy_output_shapes(dataset) self.assertEqual([None, None], shapes[0].as_list()) self.assertEqual([None], shapes[1].as_list()) diff --git a/tensorflow/python/data/experimental/kernel_tests/copy_to_device_test.py b/tensorflow/python/data/experimental/kernel_tests/copy_to_device_test.py index d9fbe9e0e1..34c34b5a33 100644 --- a/tensorflow/python/data/experimental/kernel_tests/copy_to_device_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/copy_to_device_test.py @@ -46,12 +46,10 @@ class CopyToDeviceTest(test_base.DatasetTestBase): iterator = dataset_ops.make_one_shot_iterator(device_dataset) next_element = iterator.get_next() - self.assertEqual(host_dataset.output_types, device_dataset.output_types) - self.assertEqual(host_dataset.output_types, iterator.output_types) - self.assertEqual(host_dataset.output_shapes, device_dataset.output_shapes) - self.assertEqual(host_dataset.output_shapes, iterator.output_shapes) - self.assertEqual(host_dataset.output_classes, device_dataset.output_classes) - self.assertEqual(host_dataset.output_classes, iterator.output_classes) + self.assertTrue(dataset_ops.get_structure(host_dataset).is_compatible_with( + dataset_ops.get_structure(device_dataset))) + self.assertTrue(dataset_ops.get_structure(host_dataset).is_compatible_with( + dataset_ops.get_structure(iterator))) self.assertEqual(dtypes.int64, next_element.dtype) self.assertEqual([], next_element.shape) @@ -73,12 +71,10 @@ class CopyToDeviceTest(test_base.DatasetTestBase): iterator = dataset_ops.make_one_shot_iterator(device_dataset) next_element = iterator.get_next() - self.assertEqual(host_dataset.output_types, device_dataset.output_types) - self.assertEqual(host_dataset.output_types, iterator.output_types) - self.assertEqual(host_dataset.output_shapes, device_dataset.output_shapes) - self.assertEqual(host_dataset.output_shapes, iterator.output_shapes) - self.assertEqual(host_dataset.output_classes, device_dataset.output_classes) - self.assertEqual(host_dataset.output_classes, iterator.output_classes) + self.assertTrue(dataset_ops.get_structure(host_dataset).is_compatible_with( + dataset_ops.get_structure(device_dataset))) + self.assertTrue(dataset_ops.get_structure(host_dataset).is_compatible_with( + dataset_ops.get_structure(iterator))) self.assertEqual(dtypes.int32, next_element.dtype) self.assertEqual((4,), next_element.shape) @@ -99,12 +95,10 @@ class CopyToDeviceTest(test_base.DatasetTestBase): iterator = dataset_ops.make_one_shot_iterator(device_dataset) next_element = iterator.get_next() - self.assertEqual(host_dataset.output_types, device_dataset.output_types) - self.assertEqual(host_dataset.output_types, iterator.output_types) - self.assertEqual(host_dataset.output_shapes, device_dataset.output_shapes) - self.assertEqual(host_dataset.output_shapes, iterator.output_shapes) - self.assertEqual(host_dataset.output_classes, device_dataset.output_classes) - self.assertEqual(host_dataset.output_classes, iterator.output_classes) + self.assertTrue(dataset_ops.get_structure(host_dataset).is_compatible_with( + dataset_ops.get_structure(device_dataset))) + self.assertTrue(dataset_ops.get_structure(host_dataset).is_compatible_with( + dataset_ops.get_structure(iterator))) self.assertEqual(dtypes.int64, next_element.dtype) self.assertEqual([], next_element.shape) @@ -126,12 +120,10 @@ class CopyToDeviceTest(test_base.DatasetTestBase): iterator = dataset_ops.make_one_shot_iterator(device_dataset) next_element = iterator.get_next() - self.assertEqual(host_dataset.output_types, device_dataset.output_types) - self.assertEqual(host_dataset.output_types, iterator.output_types) - self.assertEqual(host_dataset.output_shapes, device_dataset.output_shapes) - self.assertEqual(host_dataset.output_shapes, iterator.output_shapes) - self.assertEqual(host_dataset.output_classes, device_dataset.output_classes) - self.assertEqual(host_dataset.output_classes, iterator.output_classes) + self.assertTrue(dataset_ops.get_structure(host_dataset).is_compatible_with( + dataset_ops.get_structure(device_dataset))) + self.assertTrue(dataset_ops.get_structure(host_dataset).is_compatible_with( + dataset_ops.get_structure(iterator))) self.assertEqual(dtypes.int64, next_element.dtype) self.assertEqual([], next_element.shape) @@ -153,12 +145,10 @@ class CopyToDeviceTest(test_base.DatasetTestBase): iterator = dataset_ops.make_one_shot_iterator(device_dataset) next_element = iterator.get_next() - self.assertEqual(host_dataset.output_types, device_dataset.output_types) - self.assertEqual(host_dataset.output_types, iterator.output_types) - self.assertEqual(host_dataset.output_shapes, device_dataset.output_shapes) - self.assertEqual(host_dataset.output_shapes, iterator.output_shapes) - self.assertEqual(host_dataset.output_classes, device_dataset.output_classes) - self.assertEqual(host_dataset.output_classes, iterator.output_classes) + self.assertTrue(dataset_ops.get_structure(host_dataset).is_compatible_with( + dataset_ops.get_structure(device_dataset))) + self.assertTrue(dataset_ops.get_structure(host_dataset).is_compatible_with( + dataset_ops.get_structure(iterator))) self.assertEqual(dtypes.int64, next_element["a"].dtype) self.assertEqual([], next_element["a"].shape) @@ -180,12 +170,10 @@ class CopyToDeviceTest(test_base.DatasetTestBase): iterator = dataset_ops.make_one_shot_iterator(device_dataset) next_element = iterator.get_next() - self.assertEqual(host_dataset.output_types, device_dataset.output_types) - self.assertEqual(host_dataset.output_types, iterator.output_types) - self.assertEqual(host_dataset.output_shapes, device_dataset.output_shapes) - self.assertEqual(host_dataset.output_shapes, iterator.output_shapes) - self.assertEqual(host_dataset.output_classes, device_dataset.output_classes) - self.assertEqual(host_dataset.output_classes, iterator.output_classes) + self.assertTrue(dataset_ops.get_structure(host_dataset).is_compatible_with( + dataset_ops.get_structure(device_dataset))) + self.assertTrue(dataset_ops.get_structure(host_dataset).is_compatible_with( + dataset_ops.get_structure(iterator))) self.assertEqual(dtypes.int64, next_element["a"].dtype) self.assertEqual([], next_element["a"].shape) @@ -213,12 +201,10 @@ class CopyToDeviceTest(test_base.DatasetTestBase): iterator = dataset_ops.make_one_shot_iterator(device_dataset) next_element = iterator.get_next() - self.assertEqual(host_dataset.output_types, device_dataset.output_types) - self.assertEqual(host_dataset.output_types, iterator.output_types) - self.assertEqual(host_dataset.output_shapes, device_dataset.output_shapes) - self.assertEqual(host_dataset.output_shapes, iterator.output_shapes) - self.assertEqual(host_dataset.output_classes, device_dataset.output_classes) - self.assertEqual(host_dataset.output_classes, iterator.output_classes) + self.assertTrue(dataset_ops.get_structure(host_dataset).is_compatible_with( + dataset_ops.get_structure(device_dataset))) + self.assertTrue(dataset_ops.get_structure(host_dataset).is_compatible_with( + dataset_ops.get_structure(iterator))) self.assertEqual(dtypes.int64, next_element.dtype) @@ -248,12 +234,10 @@ class CopyToDeviceTest(test_base.DatasetTestBase): iterator = dataset_ops.make_one_shot_iterator(device_dataset) next_element = iterator.get_next() - self.assertEqual(host_dataset.output_types, device_dataset.output_types) - self.assertEqual(host_dataset.output_types, iterator.output_types) - self.assertEqual(host_dataset.output_shapes, device_dataset.output_shapes) - self.assertEqual(host_dataset.output_shapes, iterator.output_shapes) - self.assertEqual(host_dataset.output_classes, device_dataset.output_classes) - self.assertEqual(host_dataset.output_classes, iterator.output_classes) + self.assertTrue(dataset_ops.get_structure(host_dataset).is_compatible_with( + dataset_ops.get_structure(device_dataset))) + self.assertTrue(dataset_ops.get_structure(host_dataset).is_compatible_with( + dataset_ops.get_structure(iterator))) self.assertEqual(dtypes.int64, next_element.dtype) @@ -453,12 +437,10 @@ class CopyToDeviceTest(test_base.DatasetTestBase): iterator = dataset_ops.make_initializable_iterator(device_dataset) next_element = iterator.get_next() - self.assertEqual(host_dataset.output_types, device_dataset.output_types) - self.assertEqual(host_dataset.output_types, iterator.output_types) - self.assertEqual(host_dataset.output_shapes, device_dataset.output_shapes) - self.assertEqual(host_dataset.output_shapes, iterator.output_shapes) - self.assertEqual(host_dataset.output_classes, device_dataset.output_classes) - self.assertEqual(host_dataset.output_classes, iterator.output_classes) + self.assertTrue(dataset_ops.get_structure(host_dataset).is_compatible_with( + dataset_ops.get_structure(device_dataset))) + self.assertTrue(dataset_ops.get_structure(host_dataset).is_compatible_with( + dataset_ops.get_structure(iterator))) self.assertEqual(dtypes.int64, next_element.dtype) self.assertEqual([], next_element.shape) @@ -484,12 +466,10 @@ class CopyToDeviceTest(test_base.DatasetTestBase): iterator = dataset_ops.make_initializable_iterator(device_dataset) next_element = iterator.get_next() - self.assertEqual(host_dataset.output_types, device_dataset.output_types) - self.assertEqual(host_dataset.output_types, iterator.output_types) - self.assertEqual(host_dataset.output_shapes, device_dataset.output_shapes) - self.assertEqual(host_dataset.output_shapes, iterator.output_shapes) - self.assertEqual(host_dataset.output_classes, device_dataset.output_classes) - self.assertEqual(host_dataset.output_classes, iterator.output_classes) + self.assertTrue(dataset_ops.get_structure(host_dataset).is_compatible_with( + dataset_ops.get_structure(device_dataset))) + self.assertTrue(dataset_ops.get_structure(host_dataset).is_compatible_with( + dataset_ops.get_structure(iterator))) self.assertEqual(dtypes.int64, next_element.dtype) self.assertEqual([], next_element.shape) diff --git a/tensorflow/python/data/experimental/kernel_tests/counter_test.py b/tensorflow/python/data/experimental/kernel_tests/counter_test.py index 436fa506c4..79e4523ea4 100644 --- a/tensorflow/python/data/experimental/kernel_tests/counter_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/counter_test.py @@ -19,6 +19,7 @@ from __future__ import print_function from tensorflow.python.data.experimental.ops import counter from tensorflow.python.data.kernel_tests import test_base +from tensorflow.python.data.ops import dataset_ops from tensorflow.python.framework import dtypes from tensorflow.python.framework import test_util from tensorflow.python.platform import test @@ -30,8 +31,9 @@ class CounterTest(test_base.DatasetTestBase): def testCounter(self): """Test dataset construction using `count`.""" dataset = counter.Counter(start=3, step=4) - self.assertEqual([], dataset.output_shapes.as_list()) - self.assertEqual(dtypes.int64, dataset.output_types) + self.assertEqual( + [], dataset_ops.get_legacy_output_shapes(dataset).as_list()) + self.assertEqual(dtypes.int64, dataset_ops.get_legacy_output_types(dataset)) get_next = self.getNext(dataset) negative_dataset = counter.Counter(start=0, step=-1) diff --git a/tensorflow/python/data/experimental/kernel_tests/enumerate_dataset_test.py b/tensorflow/python/data/experimental/kernel_tests/enumerate_dataset_test.py index cbc048e3ab..8842f552e7 100644 --- a/tensorflow/python/data/experimental/kernel_tests/enumerate_dataset_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/enumerate_dataset_test.py @@ -37,10 +37,12 @@ class EnumerateDatasetTest(test_base.DatasetTestBase): dataset = dataset_ops.Dataset.from_tensor_slices(components).apply( enumerate_ops.enumerate_dataset(start)) - self.assertEqual(dtypes.int64, dataset.output_types[0]) - self.assertEqual((), dataset.output_shapes[0]) + self.assertEqual(dtypes.int64, + dataset_ops.get_legacy_output_types(dataset)[0]) + dataset_output_shapes = dataset_ops.get_legacy_output_shapes(dataset) + self.assertEqual((), dataset_output_shapes[0]) self.assertEqual([tensor_shape.TensorShape([])] * 3, - [shape for shape in dataset.output_shapes[1]]) + [shape for shape in dataset_output_shapes[1]]) self.assertDatasetProduces(dataset, [(20, (b"a", 1, 37.0)), (21, (b"b", 2, 38.0))]) diff --git a/tensorflow/python/data/experimental/kernel_tests/group_by_reducer_test.py b/tensorflow/python/data/experimental/kernel_tests/group_by_reducer_test.py index 4194f06a34..60b493b5d7 100644 --- a/tensorflow/python/data/experimental/kernel_tests/group_by_reducer_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/group_by_reducer_test.py @@ -120,8 +120,9 @@ class GroupByReducerTest(test_base.DatasetTestBase): for i in range(1, 11): dataset = dataset_ops.Dataset.from_tensors(np.int64(0)).repeat(i).apply( grouping.group_by_reducer(lambda x: x, reducer)) - self.assertEqual([None], dataset.output_shapes[0].as_list()) - self.assertIs(None, dataset.output_shapes[1].ndims) + dataset_output_shapes = dataset_ops.get_legacy_output_shapes(dataset) + self.assertEqual([None], dataset_output_shapes[0].as_list()) + self.assertIs(None, dataset_output_shapes[1].ndims) get_next = self.getNext(dataset) x, y = self.evaluate(get_next()) self.assertAllEqual([0] * (2**i), x) diff --git a/tensorflow/python/data/experimental/kernel_tests/make_batched_features_dataset_test.py b/tensorflow/python/data/experimental/kernel_tests/make_batched_features_dataset_test.py index 1fb6971ecd..2ddff457bc 100644 --- a/tensorflow/python/data/experimental/kernel_tests/make_batched_features_dataset_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/make_batched_features_dataset_test.py @@ -21,6 +21,7 @@ import numpy as np from tensorflow.python.data.experimental.kernel_tests import reader_dataset_ops_test_base from tensorflow.python.data.experimental.ops import readers +from tensorflow.python.data.ops import dataset_ops from tensorflow.python.data.ops import readers as core_readers from tensorflow.python.data.util import nest from tensorflow.python.framework import dtypes @@ -206,8 +207,9 @@ class MakeBatchedFeaturesDatasetTest( label_key="label", num_epochs=None, batch_size=32) - for shape, clazz in zip(nest.flatten(dataset.output_shapes), - nest.flatten(dataset.output_classes)): + for shape, clazz in zip( + nest.flatten(dataset_ops.get_legacy_output_shapes(dataset)), + nest.flatten(dataset_ops.get_legacy_output_classes(dataset))): if issubclass(clazz, ops.Tensor): self.assertEqual(32, shape[0]) diff --git a/tensorflow/python/data/experimental/kernel_tests/make_csv_dataset_test.py b/tensorflow/python/data/experimental/kernel_tests/make_csv_dataset_test.py index 3f371434c0..267e3e8948 100644 --- a/tensorflow/python/data/experimental/kernel_tests/make_csv_dataset_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/make_csv_dataset_test.py @@ -25,6 +25,7 @@ import numpy as np from tensorflow.python.data.experimental.ops import readers from tensorflow.python.data.kernel_tests import test_base +from tensorflow.python.data.ops import dataset_ops from tensorflow.python.data.util import nest from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes @@ -668,7 +669,7 @@ class MakeCsvDatasetTest(test_base.DatasetTestBase): ]] filenames = self._setup_files(inputs) dataset = self._make_csv_dataset(filenames, batch_size=32, num_epochs=None) - for shape in nest.flatten(dataset.output_shapes): + for shape in nest.flatten(dataset_ops.get_legacy_output_shapes(dataset)): self.assertEqual(32, shape[0]) diff --git a/tensorflow/python/data/experimental/kernel_tests/make_tf_record_dataset_test.py b/tensorflow/python/data/experimental/kernel_tests/make_tf_record_dataset_test.py index 9f35aa69a8..88cb27248a 100644 --- a/tensorflow/python/data/experimental/kernel_tests/make_tf_record_dataset_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/make_tf_record_dataset_test.py @@ -19,6 +19,7 @@ from __future__ import print_function from tensorflow.python.data.experimental.kernel_tests import reader_dataset_ops_test_base from tensorflow.python.data.experimental.ops import readers +from tensorflow.python.data.ops import dataset_ops from tensorflow.python.data.util import nest from tensorflow.python.framework import errors from tensorflow.python.framework import test_util @@ -234,7 +235,7 @@ class MakeTFRecordDatasetTest( def testIndefiniteRepeatShapeInference(self): dataset = readers.make_tf_record_dataset( file_pattern=self.test_filenames, num_epochs=None, batch_size=32) - for shape in nest.flatten(dataset.output_shapes): + for shape in nest.flatten(dataset_ops.get_legacy_output_shapes(dataset)): self.assertEqual(32, shape[0]) diff --git a/tensorflow/python/data/experimental/kernel_tests/map_and_batch_test.py b/tensorflow/python/data/experimental/kernel_tests/map_and_batch_test.py index 775dc61e48..9109e6d84e 100644 --- a/tensorflow/python/data/experimental/kernel_tests/map_and_batch_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/map_and_batch_test.py @@ -83,8 +83,10 @@ class MapAndBatchTest(test_base.DatasetTestBase, parameterized.TestCase): # total number of elements. dataset = dataset_fn(14, 28) get_next = self.getNext(dataset) - self.assertEqual([[None] + list(c.shape[1:]) for c in components], - [shape.as_list() for shape in dataset.output_shapes]) + self.assertEqual( + [[None] + list(c.shape[1:]) for c in components], + [shape.as_list() + for shape in dataset_ops.get_legacy_output_shapes(dataset)]) num_batches = (28 * 7) // 14 for i in range(num_batches): result = self.evaluate(get_next()) @@ -143,9 +145,11 @@ class MapAndBatchTest(test_base.DatasetTestBase, parameterized.TestCase): dataset = dataset.with_options(options) if drop_remainder: - self.assertEqual([4, 1], dataset.output_shapes.as_list()) + self.assertEqual( + [4, 1], dataset_ops.get_legacy_output_shapes(dataset).as_list()) else: - self.assertEqual([None, 1], dataset.output_shapes.as_list()) + self.assertEqual( + [None, 1], dataset_ops.get_legacy_output_shapes(dataset).as_list()) expected_output = [[[0], [1], [4], [9]], [[16], [25], [36], [49]]] if not drop_remainder: expected_output.append([[64], [81]]) @@ -164,7 +168,8 @@ class MapAndBatchTest(test_base.DatasetTestBase, parameterized.TestCase): options = dataset_ops.Options() options.experimental_numa_aware = True dataset = dataset.with_options(options) - self.assertEqual([None, 1], dataset.output_shapes.as_list()) + self.assertEqual( + [None, 1], dataset_ops.get_legacy_output_shapes(dataset).as_list()) expected_output = [[[0], [1], [4], [9]], [[16], [25], [36], [49]], [[64], [81]]] self.assertDatasetProduces(dataset, expected_output=expected_output) diff --git a/tensorflow/python/data/experimental/kernel_tests/optimization/choose_fastest_dataset_test.py b/tensorflow/python/data/experimental/kernel_tests/optimization/choose_fastest_dataset_test.py index ec7a85ae11..3e51de9f1e 100644 --- a/tensorflow/python/data/experimental/kernel_tests/optimization/choose_fastest_dataset_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/optimization/choose_fastest_dataset_test.py @@ -38,7 +38,7 @@ class ChooseFastestDatasetTest(test_base.DatasetTestBase, self.assertDatasetProduces( merge, expected_output=[0, 1, 2, 3, 4], - expected_shapes=dataset.output_shapes) + expected_shapes=dataset_ops.get_legacy_output_shapes(dataset)) def testChooseFastestManyInputs(self): dataset = dataset_ops.Dataset.from_tensor_slices([0, 1, 2, 3, 4]) @@ -46,7 +46,7 @@ class ChooseFastestDatasetTest(test_base.DatasetTestBase, self.assertDatasetProduces( merge, expected_output=[0, 1, 2, 3, 4], - expected_shapes=dataset.output_shapes) + expected_shapes=dataset_ops.get_legacy_output_shapes(dataset)) def testChooseFastest(self): dataset = dataset_ops.Dataset.range(600) @@ -59,7 +59,7 @@ class ChooseFastestDatasetTest(test_base.DatasetTestBase, expected_output=[ [i * 2 for i in range(j * 50, (j + 1) * 50)] for j in range(12) ], - expected_shapes=dataset_a.output_shapes) + expected_shapes=dataset_ops.get_legacy_output_shapes(dataset_a)) @parameterized.named_parameters( ("Shapes", [0], [[1, 2, 3]], "must have compatible output shapes."), diff --git a/tensorflow/python/data/experimental/kernel_tests/parse_example_dataset_test.py b/tensorflow/python/data/experimental/kernel_tests/parse_example_dataset_test.py index 4dbb188f2c..5d6787090a 100644 --- a/tensorflow/python/data/experimental/kernel_tests/parse_example_dataset_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/parse_example_dataset_test.py @@ -103,9 +103,12 @@ class ParseExampleDatasetTest(test_base.DatasetTestBase): else np.asarray(input_tensor).size) for k, f in feature_val.items(): if isinstance(f, parsing_ops.FixedLenFeature) and f.shape is not None: - self.assertEqual(dataset.output_shapes[k].as_list()[0], batch_size) + self.assertEqual( + dataset_ops.get_legacy_output_shapes(dataset)[k].as_list()[0], + batch_size) elif isinstance(f, parsing_ops.VarLenFeature): - self.assertEqual(dataset.output_shapes[k].as_list()[1], None) + self.assertEqual( + dataset_ops.get_legacy_output_shapes(dataset)[k].as_list()[1], None) def testEmptySerializedWithAllDefaults(self): sparse_name = "st_a" diff --git a/tensorflow/python/data/experimental/kernel_tests/prefetch_to_device_test.py b/tensorflow/python/data/experimental/kernel_tests/prefetch_to_device_test.py index 238c5cd506..9e3277b236 100644 --- a/tensorflow/python/data/experimental/kernel_tests/prefetch_to_device_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/prefetch_to_device_test.py @@ -42,12 +42,10 @@ class PrefetchToDeviceTest(test_base.DatasetTestBase): iterator = dataset_ops.make_one_shot_iterator(device_dataset) next_element = iterator.get_next() - self.assertEqual(host_dataset.output_types, device_dataset.output_types) - self.assertEqual(host_dataset.output_types, iterator.output_types) - self.assertEqual(host_dataset.output_shapes, device_dataset.output_shapes) - self.assertEqual(host_dataset.output_shapes, iterator.output_shapes) - self.assertEqual(host_dataset.output_classes, device_dataset.output_classes) - self.assertEqual(host_dataset.output_classes, iterator.output_classes) + self.assertTrue(dataset_ops.get_structure(host_dataset).is_compatible_with( + dataset_ops.get_structure(device_dataset))) + self.assertTrue(dataset_ops.get_structure(host_dataset).is_compatible_with( + dataset_ops.get_structure(iterator))) self.assertEqual(dtypes.int64, next_element.dtype) self.assertEqual([], next_element.shape) @@ -70,12 +68,10 @@ class PrefetchToDeviceTest(test_base.DatasetTestBase): iterator = dataset_ops.make_one_shot_iterator(device_dataset) next_element = iterator.get_next() - self.assertEqual(host_dataset.output_types, device_dataset.output_types) - self.assertEqual(host_dataset.output_types, iterator.output_types) - self.assertEqual(host_dataset.output_shapes, device_dataset.output_shapes) - self.assertEqual(host_dataset.output_shapes, iterator.output_shapes) - self.assertEqual(host_dataset.output_classes, device_dataset.output_classes) - self.assertEqual(host_dataset.output_classes, iterator.output_classes) + self.assertTrue(dataset_ops.get_structure(host_dataset).is_compatible_with( + dataset_ops.get_structure(device_dataset))) + self.assertTrue(dataset_ops.get_structure(host_dataset).is_compatible_with( + dataset_ops.get_structure(iterator))) self.assertEqual(dtypes.int64, next_element.dtype) self.assertEqual([], next_element.shape) @@ -96,12 +92,10 @@ class PrefetchToDeviceTest(test_base.DatasetTestBase): iterator = dataset_ops.make_one_shot_iterator(device_dataset) next_element = iterator.get_next() - self.assertEqual(host_dataset.output_types, device_dataset.output_types) - self.assertEqual(host_dataset.output_types, iterator.output_types) - self.assertEqual(host_dataset.output_shapes, device_dataset.output_shapes) - self.assertEqual(host_dataset.output_shapes, iterator.output_shapes) - self.assertEqual(host_dataset.output_classes, device_dataset.output_classes) - self.assertEqual(host_dataset.output_classes, iterator.output_classes) + self.assertTrue(dataset_ops.get_structure(host_dataset).is_compatible_with( + dataset_ops.get_structure(device_dataset))) + self.assertTrue(dataset_ops.get_structure(host_dataset).is_compatible_with( + dataset_ops.get_structure(iterator))) self.assertEqual(dtypes.int64, next_element["a"].dtype) self.assertEqual([], next_element["a"].shape) @@ -127,12 +121,10 @@ class PrefetchToDeviceTest(test_base.DatasetTestBase): iterator = dataset_ops.make_one_shot_iterator(device_dataset) next_element = iterator.get_next() - self.assertEqual(host_dataset.output_types, device_dataset.output_types) - self.assertEqual(host_dataset.output_types, iterator.output_types) - self.assertEqual(host_dataset.output_shapes, device_dataset.output_shapes) - self.assertEqual(host_dataset.output_shapes, iterator.output_shapes) - self.assertEqual(host_dataset.output_classes, device_dataset.output_classes) - self.assertEqual(host_dataset.output_classes, iterator.output_classes) + self.assertTrue(dataset_ops.get_structure(host_dataset).is_compatible_with( + dataset_ops.get_structure(device_dataset))) + self.assertTrue(dataset_ops.get_structure(host_dataset).is_compatible_with( + dataset_ops.get_structure(iterator))) self.assertEqual(dtypes.int64, next_element.dtype) @@ -175,12 +167,10 @@ class PrefetchToDeviceTest(test_base.DatasetTestBase): iterator = dataset_ops.make_initializable_iterator(device_dataset) next_element = iterator.get_next() - self.assertEqual(host_dataset.output_types, device_dataset.output_types) - self.assertEqual(host_dataset.output_types, iterator.output_types) - self.assertEqual(host_dataset.output_shapes, device_dataset.output_shapes) - self.assertEqual(host_dataset.output_shapes, iterator.output_shapes) - self.assertEqual(host_dataset.output_classes, device_dataset.output_classes) - self.assertEqual(host_dataset.output_classes, iterator.output_classes) + self.assertTrue(dataset_ops.get_structure(host_dataset).is_compatible_with( + dataset_ops.get_structure(device_dataset))) + self.assertTrue(dataset_ops.get_structure(host_dataset).is_compatible_with( + dataset_ops.get_structure(iterator))) self.assertEqual(dtypes.int64, next_element.dtype) self.assertEqual([], next_element.shape) diff --git a/tensorflow/python/data/experimental/kernel_tests/rebatch_dataset_test.py b/tensorflow/python/data/experimental/kernel_tests/rebatch_dataset_test.py index 28f9aad5b9..38d3abe6f9 100644 --- a/tensorflow/python/data/experimental/kernel_tests/rebatch_dataset_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/rebatch_dataset_test.py @@ -30,6 +30,10 @@ from tensorflow.python.ops import math_ops from tensorflow.python.platform import test +def _flat_shapes(dataset): + return nest.flatten(dataset_ops.get_legacy_output_shapes(dataset)) + + @parameterized.named_parameters(("WithDropRemainder", True), ("WithoutDropRemainder", False)) @test_util.run_all_in_graph_and_eager_modes @@ -41,10 +45,10 @@ class RebatchDatasetTest(test_base.DatasetTestBase): rebatched_dataset = batching._RebatchDataset(dataset, num_workers=4) self.assertEqual( [[32 if drop_remainder else None]], - [ts.as_list() for ts in nest.flatten(dataset.output_shapes)]) + [ts.as_list() for ts in _flat_shapes(dataset)]) self.assertEqual( [[8 if drop_remainder else None]], - [ts.as_list() for ts in nest.flatten(rebatched_dataset.output_shapes)]) + [ts.as_list() for ts in _flat_shapes(rebatched_dataset)]) expected_output = [[k for k in range(i, i + 8)] for i in range(0, 1024, 8)] # pylint: disable=g-complex-comprehension self.assertDatasetProduces(rebatched_dataset, expected_output) @@ -89,10 +93,10 @@ class RebatchDatasetTest(test_base.DatasetTestBase): rebatched_dataset = batching._RebatchDataset(dataset, num_workers=4) self.assertEqual( [[32 if drop_remainder else None]], - [ts.as_list() for ts in nest.flatten(dataset.output_shapes)]) + [ts.as_list() for ts in _flat_shapes(dataset)]) self.assertEqual( [[8 if drop_remainder else None]], - [ts.as_list() for ts in nest.flatten(rebatched_dataset.output_shapes)]) + [ts.as_list() for ts in _flat_shapes(rebatched_dataset)]) expected_output = [[k for k in range(i, i + 8)] for i in range(0, 1032, 8)] # pylint: disable=g-complex-comprehension self.assertDatasetProduces(rebatched_dataset, expected_output) @@ -103,10 +107,10 @@ class RebatchDatasetTest(test_base.DatasetTestBase): rebatched_dataset = batching._RebatchDataset(dataset, num_workers=4) self.assertEqual( [[32 if drop_remainder else None]], - [ts.as_list() for ts in nest.flatten(dataset.output_shapes)]) + [ts.as_list() for ts in _flat_shapes(dataset)]) self.assertEqual( [[8 if drop_remainder else None]], - [ts.as_list() for ts in nest.flatten(rebatched_dataset.output_shapes)]) + [ts.as_list() for ts in _flat_shapes(rebatched_dataset)]) expected_output = [[k for k in range(i, i + 8)] for i in range(0, 32, 8)] # pylint: disable=g-complex-comprehension if not drop_remainder: @@ -119,7 +123,7 @@ class RebatchDatasetTest(test_base.DatasetTestBase): dataset = dataset.batch(8, drop_remainder=drop_remainder) self.assertEqual( [[8, 4]] if drop_remainder else [[None, None]], - [ts.as_list() for ts in nest.flatten(dataset.output_shapes)]) + [ts.as_list() for ts in _flat_shapes(dataset)]) # Each element is a list of 8 elements where each element is a list of 4. expected_output = [[[j, j + 1, j + 2, j + 3] # pylint: disable=g-complex-comprehension for j in range(i, i + 32, 4)] # generates 8 elements @@ -129,7 +133,7 @@ class RebatchDatasetTest(test_base.DatasetTestBase): rebatched_dataset = batching._RebatchDataset(dataset, 4) self.assertEqual( [[2, 4]] if drop_remainder else [[None, None]], - [ts.as_list() for ts in nest.flatten(rebatched_dataset.output_shapes)]) + [ts.as_list() for ts in _flat_shapes(rebatched_dataset)]) # Each element is a list of 2 elements where each element is a list of 4. expected_output = [[[j, j + 1, j + 2, j + 3] # pylint: disable=g-complex-comprehension for j in range(i, i + 8, 4)] # generates 2 elements @@ -143,10 +147,10 @@ class RebatchDatasetTest(test_base.DatasetTestBase): rebatched_dataset = batching._RebatchDataset(dataset, num_workers=4) self.assertEqual( [[32 if drop_remainder else None]], - [ts.as_list() for ts in nest.flatten(dataset.output_shapes)]) + [ts.as_list() for ts in _flat_shapes(dataset)]) self.assertEqual( [[8 if drop_remainder else None]], - [ts.as_list() for ts in nest.flatten(rebatched_dataset.output_shapes)]) + [ts.as_list() for ts in _flat_shapes(rebatched_dataset)]) expected_output = [[k**2 for k in range(i, i + 8)] # pylint: disable=g-complex-comprehension for i in range(0, 1024, 8)] self.assertDatasetProduces(rebatched_dataset, expected_output) @@ -157,7 +161,7 @@ class RebatchDatasetTest(test_base.DatasetTestBase): rebatched_dataset = batching._RebatchDataset(dataset, num_workers=4) self.assertEqual( [[8, 5]] if drop_remainder else [[None, 5]], - [ts.as_list() for ts in nest.flatten(dataset.output_shapes)]) + [ts.as_list() for ts in _flat_shapes(dataset)]) # Each element is a list of 8 elements in which each element is a list of 5 # elements, first four are numbers and the last one is a padded zero. expected_output = [[[j, j + 1, j + 2, j + 3, 0] # pylint: disable=g-complex-comprehension @@ -166,7 +170,7 @@ class RebatchDatasetTest(test_base.DatasetTestBase): self.assertDatasetProduces(dataset, expected_output) self.assertEqual( [[2, 5]] if drop_remainder else [[None, 5]], - [ts.as_list() for ts in nest.flatten(rebatched_dataset.output_shapes)]) + [ts.as_list() for ts in _flat_shapes(rebatched_dataset)]) # Each element is a list of 2 elements in which each element is a list of 5 # elements, first four are numbers and the last one is a padded zero. expected_output = [[[j, j + 1, j + 2, j + 3, 0] # pylint: disable=g-complex-comprehension @@ -183,10 +187,10 @@ class RebatchDatasetTest(test_base.DatasetTestBase): rebatched_dataset = batching._RebatchDataset(dataset, num_workers=4) self.assertEqual( [[8 if drop_remainder else None]], - [ts.as_list() for ts in nest.flatten(dataset.output_shapes)]) + [ts.as_list() for ts in _flat_shapes(dataset)]) self.assertEqual( [[2 if drop_remainder else None]], - [ts.as_list() for ts in nest.flatten(rebatched_dataset.output_shapes)]) + [ts.as_list() for ts in _flat_shapes(rebatched_dataset)]) expected_output = ([[i, i + 1] for i in range(0, 64, 2)] + [[i, i + 1] for i in range(0, 32, 2)]) self.assertDatasetProduces(rebatched_dataset, expected_output) @@ -199,10 +203,10 @@ class RebatchDatasetTest(test_base.DatasetTestBase): dataset = dataset1.concatenate(dataset2) rebatched_dataset = batching._RebatchDataset(dataset, num_workers=4) self.assertEqual( - [[None]], [ts.as_list() for ts in nest.flatten(dataset.output_shapes)]) + [[None]], [ts.as_list() for ts in _flat_shapes(dataset)]) self.assertEqual( [[None]], - [ts.as_list() for ts in nest.flatten(rebatched_dataset.output_shapes)]) + [ts.as_list() for ts in _flat_shapes(rebatched_dataset)]) expected_output = ([[i, i + 1, i + 2, i + 3] for i in range(0, 64, 4)] + [[i, i + 1] for i in range(0, 32, 2)]) self.assertDatasetProduces(rebatched_dataset, expected_output) @@ -216,10 +220,10 @@ class RebatchDatasetTest(test_base.DatasetTestBase): rebatched_dataset = batching._RebatchDataset(dataset, num_workers=4) self.assertEqual( [[8], [8]] if drop_remainder else [[None], [None]], - [ts.as_list() for ts in nest.flatten(dataset.output_shapes)]) + [ts.as_list() for ts in _flat_shapes(dataset)]) self.assertEqual( [[2], [2]] if drop_remainder else [[None], [None]], - [ts.as_list() for ts in nest.flatten(rebatched_dataset.output_shapes)]) + [ts.as_list() for ts in _flat_shapes(rebatched_dataset)]) expected_output = [([i, i + 1], [i, i + 1]) for i in range(0, 32, 2)] self.assertDatasetProduces(rebatched_dataset, expected_output) @@ -232,10 +236,10 @@ class RebatchDatasetTest(test_base.DatasetTestBase): rebatched_dataset = batching._RebatchDataset(dataset, num_workers=4) self.assertEqual( [[16], [8]] if drop_remainder else [[None], [None]], - [ts.as_list() for ts in nest.flatten(dataset.output_shapes)]) + [ts.as_list() for ts in _flat_shapes(dataset)]) self.assertEqual( [[4], [2]] if drop_remainder else [[None], [None]], - [ts.as_list() for ts in nest.flatten(rebatched_dataset.output_shapes)]) + [ts.as_list() for ts in _flat_shapes(rebatched_dataset)]) expected_output = [([2 * i, 2 * i + 1, 2 * i + 2, 2 * i + 3], [i, i + 1]) for i in range(0, 32, 2)] self.assertDatasetProduces(rebatched_dataset, expected_output) @@ -255,7 +259,7 @@ class RebatchDatasetTest(test_base.DatasetTestBase): 32, drop_remainder=drop_remainder)) self.assertEqual( [[32 if drop_remainder else None]], - [ts.as_list() for ts in nest.flatten(dataset.output_shapes)]) + [ts.as_list() for ts in _flat_shapes(dataset)]) # Two elements where each element is range(32) expected_output = [[k for k in range(32)] for _ in range(2)] # pylint: disable=g-complex-comprehension self.assertDatasetProduces(dataset, expected_output) @@ -263,7 +267,7 @@ class RebatchDatasetTest(test_base.DatasetTestBase): rebatched_dataset = batching._RebatchDataset(dataset, num_workers=4) self.assertEqual( [[8 if drop_remainder else None]], - [ts.as_list() for ts in nest.flatten(rebatched_dataset.output_shapes)]) + [ts.as_list() for ts in _flat_shapes(rebatched_dataset)]) # Two elements where each element is a list of 4 elements where each element # is a list of 8. expected_output = [[k for k in range(i, i + 8)] # pylint: disable=g-complex-comprehension @@ -277,7 +281,7 @@ class RebatchDatasetTest(test_base.DatasetTestBase): 32, drop_remainder=drop_remainder), cycle_length=2) self.assertEqual( [[32 if drop_remainder else None]], - [ts.as_list() for ts in nest.flatten(dataset.output_shapes)]) + [ts.as_list() for ts in _flat_shapes(dataset)]) # Two elements where each element is range(32) expected_output = [[k for k in range(32)] for _ in range(2)] # pylint: disable=g-complex-comprehension self.assertDatasetProduces(dataset, expected_output) @@ -285,7 +289,7 @@ class RebatchDatasetTest(test_base.DatasetTestBase): rebatched_dataset = batching._RebatchDataset(dataset, num_workers=4) self.assertEqual( [[8 if drop_remainder else None]], - [ts.as_list() for ts in nest.flatten(rebatched_dataset.output_shapes)]) + [ts.as_list() for ts in _flat_shapes(rebatched_dataset)]) # List of 4 elements where each element is a list of 8 numbering from 0 to # 31 repeated twice. expected_output = [[k for k in range(i, i + 8)] # pylint: disable=g-complex-comprehension @@ -300,7 +304,7 @@ class RebatchDatasetTest(test_base.DatasetTestBase): num_parallel_calls=2) self.assertEqual( [[32 if drop_remainder else None]], - [ts.as_list() for ts in nest.flatten(dataset.output_shapes)]) + [ts.as_list() for ts in _flat_shapes(dataset)]) # Two elements where each element is range(32) expected_output = [[k for k in range(32)] for _ in range(2)] # pylint: disable=g-complex-comprehension self.assertDatasetProduces(dataset, expected_output) @@ -308,7 +312,7 @@ class RebatchDatasetTest(test_base.DatasetTestBase): rebatched_dataset = batching._RebatchDataset(dataset, num_workers=4) self.assertEqual( [[8 if drop_remainder else None]], - [ts.as_list() for ts in nest.flatten(rebatched_dataset.output_shapes)]) + [ts.as_list() for ts in _flat_shapes(rebatched_dataset)]) # List of 4 elements where each element is a list of 8 numbering from 0 to # 31 repeated twice in collated fashion i.e [0...8], [0...8] etc. expected_output = [[k for k in range(i, i + 8)] # pylint: disable=g-complex-comprehension diff --git a/tensorflow/python/data/experimental/kernel_tests/restructured_dataset_test.py b/tensorflow/python/data/experimental/kernel_tests/restructured_dataset_test.py index ddac02b9e2..88c14f0a6e 100644 --- a/tensorflow/python/data/experimental/kernel_tests/restructured_dataset_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/restructured_dataset_test.py @@ -48,10 +48,11 @@ class RestructuredDatasetTest(test_base.DatasetTestBase): # pylint: disable=protected-access new = batching._RestructuredDataset(dataset, new_types, new_shape_lists) # pylint: enable=protected-access - self.assertEqual(new_types, new.output_types) + self.assertEqual(new_types, dataset_ops.get_legacy_output_types(new)) if new_shape_lists is not None: for expected_shape_list, shape in zip( - nest.flatten(new_shape_lists), nest.flatten(new.output_shapes)): + nest.flatten(new_shape_lists), + nest.flatten(dataset_ops.get_legacy_output_shapes(new))): if expected_shape_list is None: self.assertIs(None, shape.ndims) else: diff --git a/tensorflow/python/data/experimental/kernel_tests/scan_test.py b/tensorflow/python/data/experimental/kernel_tests/scan_test.py index 38e9b1e128..24221a1f0f 100644 --- a/tensorflow/python/data/experimental/kernel_tests/scan_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/scan_test.py @@ -108,9 +108,12 @@ class ScanTest(test_base.DatasetTestBase): dataset = dataset_ops.Dataset.from_tensors(0).repeat(5).apply( scan_ops.scan(([0], 1), _scan_fn)) - self.assertEqual([None], dataset.output_shapes[0][0].as_list()) - self.assertIs(None, dataset.output_shapes[0][1].ndims) - self.assertEqual([], dataset.output_shapes[1].as_list()) + self.assertEqual( + [None], dataset_ops.get_legacy_output_shapes(dataset)[0][0].as_list()) + self.assertIs( + None, dataset_ops.get_legacy_output_shapes(dataset)[0][1].ndims) + self.assertEqual( + [], dataset_ops.get_legacy_output_shapes(dataset)[1].as_list()) next_element = self.getNext(dataset) diff --git a/tensorflow/python/data/experimental/kernel_tests/serialization/dataset_serialization_test_base.py b/tensorflow/python/data/experimental/kernel_tests/serialization/dataset_serialization_test_base.py index ca45ecca4c..0398effdc1 100644 --- a/tensorflow/python/data/experimental/kernel_tests/serialization/dataset_serialization_test_base.py +++ b/tensorflow/python/data/experimental/kernel_tests/serialization/dataset_serialization_test_base.py @@ -668,15 +668,15 @@ class DatasetSerializationTestBase(test.TestCase): def _get_output_types(self, ds_fn): with ops.Graph().as_default(): - return ds_fn().output_types + return dataset_ops.get_legacy_output_types(ds_fn()) def _get_output_shapes(self, ds_fn): with ops.Graph().as_default(): - return ds_fn().output_shapes + return dataset_ops.get_legacy_output_shapes(ds_fn()) def _get_output_classes(self, ds_fn): with ops.Graph().as_default(): - return ds_fn().output_classes + return dataset_ops.get_legacy_output_classes(ds_fn()) def _ckpt_path(self): return os.path.join(self.get_temp_dir(), "iterator") diff --git a/tensorflow/python/data/experimental/ops/batching.py b/tensorflow/python/data/experimental/ops/batching.py index 983f7640b8..a157ea6878 100644 --- a/tensorflow/python/data/experimental/ops/batching.py +++ b/tensorflow/python/data/experimental/ops/batching.py @@ -52,14 +52,15 @@ def batch_window(dataset): Returns: A `Tensor` representing the batch of the entire input dataset. """ - if isinstance(dataset.output_classes, tuple): + dataset_output_classes = dataset_ops.get_legacy_output_classes(dataset) + if isinstance(dataset_output_classes, tuple): raise TypeError("Input dataset expected to have a single component") - if dataset.output_classes is ops.Tensor: + if dataset_output_classes is ops.Tensor: return _batch_dense_window(dataset) - elif dataset.output_classes is sparse_tensor.SparseTensor: + elif dataset_output_classes is sparse_tensor.SparseTensor: return _batch_sparse_window(dataset) else: - raise TypeError("Unsupported dataset type: %s" % dataset.output_classes) + raise TypeError("Unsupported dataset type: %s" % dataset_output_classes) def _batch_dense_window(dataset): @@ -78,8 +79,9 @@ def _batch_dense_window(dataset): def finalize_fn(state): return state - if dataset.output_shapes.is_fully_defined(): - shape = dataset.output_shapes + dataset_output_shapes = dataset_ops.get_legacy_output_shapes(dataset) + if dataset_output_shapes.is_fully_defined(): + shape = dataset_output_shapes else: first_element = get_single_element.get_single_element(dataset.take(1)) shape_reducer = grouping.Reducer(shape_init_fn, shape_reduce_fn, @@ -115,8 +117,9 @@ def _batch_sparse_window(dataset): def finalize_fn(state): return state - if dataset.output_shapes.is_fully_defined(): - shape = dataset.output_shapes + dataset_output_shapes = dataset_ops.get_legacy_output_shapes(dataset) + if dataset_output_shapes.is_fully_defined(): + shape = dataset_output_shapes else: first_element = get_single_element.get_single_element(dataset.take(1)) shape_reducer = grouping.Reducer(shape_init_fn, shape_reduce_fn, @@ -219,17 +222,18 @@ def padded_batch_window(dataset, padded_shape, padding_value=None): Raises: ValueError: if invalid arguments are provided. """ - if not issubclass(dataset.output_classes, + dataset_output_classes = dataset_ops.get_legacy_output_classes(dataset) + if not issubclass(dataset_output_classes, (ops.Tensor, sparse_tensor.SparseTensor)): raise TypeError("Input dataset expected to have a single tensor component") - if issubclass(dataset.output_classes, (ops.Tensor)): + if issubclass(dataset_output_classes, (ops.Tensor)): return _padded_batch_dense_window(dataset, padded_shape, padding_value) - elif issubclass(dataset.output_classes, (sparse_tensor.SparseTensor)): + elif issubclass(dataset_output_classes, (sparse_tensor.SparseTensor)): if padding_value is not None: raise ValueError("Padding value not allowed for sparse tensors") return _padded_batch_sparse_window(dataset, padded_shape) else: - raise TypeError("Unsupported dataset type: %s" % dataset.output_classes) + raise TypeError("Unsupported dataset type: %s" % dataset_output_classes) def _padded_batch_dense_window(dataset, padded_shape, padding_value=None): @@ -265,12 +269,13 @@ def _padded_batch_dense_window(dataset, padded_shape, padding_value=None): padded_shape = get_single_element.get_single_element( dataset.apply(grouping.group_by_reducer(key_fn, max_reducer))) + dataset_output_types = dataset_ops.get_legacy_output_types(dataset) if padding_value is None: - if dataset.output_types == dtypes.string: + if dataset_output_types == dtypes.string: padding_value = "" - elif dataset.output_types == dtypes.bool: + elif dataset_output_types == dtypes.bool: padding_value = False - elif dataset.output_types == dtypes.variant: + elif dataset_output_types == dtypes.variant: raise TypeError("Unable to create padding for field of type 'variant'") else: padding_value = 0 @@ -278,7 +283,7 @@ def _padded_batch_dense_window(dataset, padded_shape, padding_value=None): def batch_init_fn(_): batch_shape = array_ops.concat( [np.array([0], dtype=np.int32), padded_shape], 0) - return gen_array_ops.empty(batch_shape, dtype=dataset.output_types) + return gen_array_ops.empty(batch_shape, dtype=dataset_output_types) def batch_reduce_fn(state, value): return array_ops.concat([state, [value]], 0) @@ -354,7 +359,8 @@ class _UnbatchDataset(dataset_ops.UnaryDataset): def __init__(self, input_dataset): """See `unbatch()` for more details.""" - flat_shapes = nest.flatten(input_dataset.output_shapes) + input_shapes = dataset_ops.get_legacy_output_shapes(input_dataset) + flat_shapes = nest.flatten(input_shapes) if any(s.ndims == 0 for s in flat_shapes): raise ValueError("Cannot unbatch an input with scalar components.") known_batch_dim = tensor_shape.Dimension(None) @@ -367,9 +373,9 @@ class _UnbatchDataset(dataset_ops.UnaryDataset): self._input_dataset = input_dataset self._structure = structure.convert_legacy_structure( - input_dataset.output_types, - nest.map_structure(lambda s: s[1:], input_dataset.output_shapes), - input_dataset.output_classes) + dataset_ops.get_legacy_output_types(input_dataset), + nest.map_structure(lambda s: s[1:], input_shapes), + dataset_ops.get_legacy_output_classes(input_dataset)) variant_tensor = ged_ops.experimental_unbatch_dataset( self._input_dataset._variant_tensor, # pylint: disable=protected-access @@ -427,9 +433,9 @@ def unbatch(): # original dataset. restructured_dataset = _RestructuredDataset( normalized_dataset, - dataset.output_types, - dataset.output_shapes, - dataset.output_classes, + dataset_ops.get_legacy_output_types(dataset), + dataset_ops.get_legacy_output_shapes(dataset), + dataset_ops.get_legacy_output_classes(dataset), allow_unsafe_cast=True) return _UnbatchDataset(restructured_dataset) @@ -502,25 +508,27 @@ class _RestructuredDataset(dataset_ops.UnaryDataset): """ self._input_dataset = dataset + input_types = dataset_ops.get_legacy_output_types(dataset) if not allow_unsafe_cast: # Validate that the types are compatible. output_types = nest.map_structure(dtypes.as_dtype, output_types) - flat_original_types = nest.flatten(dataset.output_types) + flat_original_types = nest.flatten(input_types) flat_new_types = nest.flatten(output_types) if flat_original_types != flat_new_types: raise ValueError( "Dataset with output types %r cannot be restructured to have " "output types %r" % (dataset.output_types, output_types)) + input_shapes = dataset_ops.get_legacy_output_shapes(dataset) if output_shapes is None: # Inherit shapes from the original `dataset`. output_shapes = nest.pack_sequence_as( - output_types, nest.flatten(dataset.output_shapes)) + output_types, nest.flatten(input_shapes)) else: if not allow_unsafe_cast: # Validate that the shapes are compatible. nest.assert_same_structure(output_types, output_shapes) - flat_original_shapes = nest.flatten(dataset.output_shapes) + flat_original_shapes = nest.flatten(input_shapes) flat_new_shapes = nest.flatten_up_to(output_types, output_shapes) for original_shape, new_shape in zip(flat_original_shapes, @@ -528,14 +536,16 @@ class _RestructuredDataset(dataset_ops.UnaryDataset): if not original_shape.is_compatible_with(new_shape): raise ValueError( "Dataset with output shapes %r cannot be restructured to have " - "incompatible output shapes %r" % (dataset.output_shapes, + "incompatible output shapes %r" % (input_shapes, output_shapes)) output_shapes = nest.map_structure_up_to( output_types, tensor_shape.as_shape, output_shapes) + + input_classes = dataset_ops.get_legacy_output_classes(dataset) if output_classes is None: # Inherit class types from the original `dataset`. output_classes = nest.pack_sequence_as( - output_types, nest.flatten(dataset.output_classes)) + output_types, nest.flatten(input_classes)) self._structure = structure.convert_legacy_structure( output_types, output_shapes, output_classes) @@ -728,12 +738,13 @@ class _RebatchDataset(dataset_ops.UnaryDataset): output_dims[0] = output_dims[0] // num_workers return tensor_shape.TensorShape(output_dims) - output_shapes = nest.map_structure(recalculate_output_shapes, - input_dataset.output_shapes) + input_types = dataset_ops.get_legacy_output_types(self._input_dataset) + input_shapes = dataset_ops.get_legacy_output_shapes(self._input_dataset) + input_classes = dataset_ops.get_legacy_output_classes(self._input_dataset) + output_shapes = nest.map_structure(recalculate_output_shapes, input_shapes) self._structure = structure.convert_legacy_structure( - self._input_dataset.output_types, output_shapes, - self._input_dataset.output_classes) + input_types, output_shapes, input_classes) variant_tensor = ged_ops.experimental_rebatch_dataset( self._input_dataset._variant_tensor, # pylint: disable=protected-access num_workers=num_workers, diff --git a/tensorflow/python/data/experimental/ops/grouping.py b/tensorflow/python/data/experimental/ops/grouping.py index 4e83acf6bb..a4a324e5a1 100644 --- a/tensorflow/python/data/experimental/ops/grouping.py +++ b/tensorflow/python/data/experimental/ops/grouping.py @@ -229,9 +229,9 @@ def bucket_by_sequence_length(element_length_func, dtype=dtypes.int64) bucket_boundary = boundaries[bucket_id] none_filler = bucket_boundary - 1 - shapes = make_padded_shapes( - padded_shapes or grouped_dataset.output_shapes, - none_filler=none_filler) + input_shapes = dataset_ops.get_legacy_output_shapes(grouped_dataset) + shapes = make_padded_shapes(padded_shapes or input_shapes, + none_filler=none_filler) return grouped_dataset.padded_batch( batch_size, shapes, padding_values, drop_remainder=drop_remainder) @@ -276,6 +276,7 @@ class _GroupByReducerDataset(dataset_ops.UnaryDataset): "`key_func` must return a single tf.int64 tensor. " "Got type=%s and shape=%s" % (self._key_func.output_types, self._key_func.output_shapes)) + def _make_init_func(self, init_func): """Make wrapping defun for init_func.""" self._init_func = dataset_ops.StructuredFunctionWrapper( diff --git a/tensorflow/python/data/experimental/ops/interleave_ops.py b/tensorflow/python/data/experimental/ops/interleave_ops.py index f4b7123df1..9cfb37f1ee 100644 --- a/tensorflow/python/data/experimental/ops/interleave_ops.py +++ b/tensorflow/python/data/experimental/ops/interleave_ops.py @@ -24,7 +24,6 @@ from tensorflow.python.data.util import nest from tensorflow.python.data.util import structure from tensorflow.python.framework import dtypes from tensorflow.python.framework import ops -from tensorflow.python.framework import tensor_shape from tensorflow.python.ops import array_ops from tensorflow.python.ops import gen_experimental_dataset_ops from tensorflow.python.ops import gen_stateless_random_ops @@ -97,22 +96,25 @@ class _DirectedInterleaveDataset(dataset_ops.Dataset): self._selector_input = selector_input self._data_inputs = list(data_inputs) + first_output_types = dataset_ops.get_legacy_output_types(data_inputs[0]) + first_output_classes = dataset_ops.get_legacy_output_classes(data_inputs[0]) + for data_input in data_inputs[1:]: - if (data_input.output_types != data_inputs[0].output_types or - data_input.output_classes != data_inputs[0].output_classes): + if (dataset_ops.get_legacy_output_types(data_input) != first_output_types + or dataset_ops.get_legacy_output_classes(data_input) + != first_output_classes): raise TypeError("All datasets must have the same type and class.") - output_shapes = self._data_inputs[0].output_shapes + output_shapes = dataset_ops.get_legacy_output_shapes(self._data_inputs[0]) for data_input in self._data_inputs[1:]: output_shapes = nest.pack_sequence_as(output_shapes, [ ts1.most_specific_compatible_shape(ts2) for (ts1, ts2) in zip( nest.flatten(output_shapes), - nest.flatten(data_input.output_shapes)) + nest.flatten(dataset_ops.get_legacy_output_shapes(data_input))) ]) self._structure = structure.convert_legacy_structure( - data_inputs[0].output_types, output_shapes, - data_inputs[0].output_classes) + first_output_types, output_shapes, first_output_classes) super(_DirectedInterleaveDataset, self).__init__() def _as_variant_tensor(self): @@ -259,10 +261,8 @@ def choose_from_datasets_v2(datasets, choice_dataset): TypeError: If the `datasets` or `choice_dataset` arguments have the wrong type. """ - if not (choice_dataset.output_types == dtypes.int64 - and choice_dataset.output_shapes.is_compatible_with( - tensor_shape.scalar()) - and choice_dataset.output_classes == ops.Tensor): + if not dataset_ops.get_structure(choice_dataset).is_compatible_with( + structure.TensorStructure(dtypes.int64, [])): raise TypeError("`choice_dataset` must be a dataset of scalar " "`tf.int64` tensors.") return _DirectedInterleaveDataset(choice_dataset, datasets) diff --git a/tensorflow/python/data/experimental/ops/parsing_ops.py b/tensorflow/python/data/experimental/ops/parsing_ops.py index a5ca96e89b..f6cf2cea2b 100644 --- a/tensorflow/python/data/experimental/ops/parsing_ops.py +++ b/tensorflow/python/data/experimental/ops/parsing_ops.py @@ -57,14 +57,12 @@ class _ParseExampleDataset(dataset_ops.UnaryDataset): self._dense_defaults = dense_defaults_vec self._dense_shapes = dense_shapes self._dense_types = dense_types - dense_output_shapes = [ - self._input_dataset.output_shapes.concatenate(shape) - for shape in dense_shape_as_shape - ] - sparse_output_shapes = [ - self._input_dataset.output_shapes.concatenate([None]) - for _ in range(len(sparse_keys)) - ] + input_dataset_shape = dataset_ops.get_legacy_output_shapes( + self._input_dataset) + dense_output_shapes = [input_dataset_shape.concatenate(shape) + for shape in dense_shape_as_shape] + sparse_output_shapes = [input_dataset_shape.concatenate([None]) + for _ in range(len(sparse_keys))] output_shapes = dict( zip(self._dense_keys + self._sparse_keys, diff --git a/tensorflow/python/data/experimental/ops/prefetching_ops.py b/tensorflow/python/data/experimental/ops/prefetching_ops.py index e21ff8e9da..6aa6c3ac84 100644 --- a/tensorflow/python/data/experimental/ops/prefetching_ops.py +++ b/tensorflow/python/data/experimental/ops/prefetching_ops.py @@ -141,8 +141,10 @@ class _CopyToDeviceDataset(dataset_ops.UnaryUnchangedStructureDataset): """ with ops.device(self._source_device_string): iterator = iterator_ops.Iterator.from_string_handle( - string_handle, self.output_types, self.output_shapes, - self.output_classes) + string_handle, + dataset_ops.get_legacy_output_types(self), + dataset_ops.get_legacy_output_shapes(self), + dataset_ops.get_legacy_output_classes(self)) return self._element_structure._to_tensor_list(iterator.get_next()) # pylint: disable=protected-access next_func_concrete = _next_func._get_concrete_function_internal() # pylint: disable=protected-access diff --git a/tensorflow/python/data/experimental/ops/readers.py b/tensorflow/python/data/experimental/ops/readers.py index 24a399ab4e..a94b184723 100644 --- a/tensorflow/python/data/experimental/ops/readers.py +++ b/tensorflow/python/data/experimental/ops/readers.py @@ -833,7 +833,8 @@ def make_batched_features_dataset_v2(file_pattern, sloppy=sloppy_ordering)) # Extract values if the `Example` tensors are stored as key-value tuples. - if dataset.output_types == (dtypes.string, dtypes.string): + if dataset_ops.get_legacy_output_types(dataset) == ( + dtypes.string, dtypes.string): dataset = dataset_ops.MapDataset( dataset, lambda _, v: v, use_inter_op_parallelism=False) @@ -961,7 +962,7 @@ class SqlDatasetV2(dataset_ops.DatasetSource): lambda dtype: structure.TensorStructure(dtype, []), output_types)) variant_tensor = gen_experimental_dataset_ops.experimental_sql_dataset( self._driver_name, self._data_source_name, self._query, - nest.flatten(self.output_types), nest.flatten(self.output_shapes)) + **dataset_ops.flat_structure(self)) super(SqlDatasetV2, self).__init__(variant_tensor) @property diff --git a/tensorflow/python/data/experimental/ops/writers.py b/tensorflow/python/data/experimental/ops/writers.py index 49eae14652..de1c636263 100644 --- a/tensorflow/python/data/experimental/ops/writers.py +++ b/tensorflow/python/data/experimental/ops/writers.py @@ -19,9 +19,9 @@ from __future__ import print_function from tensorflow.python.data.ops import dataset_ops from tensorflow.python.data.util import convert +from tensorflow.python.data.util import structure from tensorflow.python.framework import dtypes from tensorflow.python.framework import ops -from tensorflow.python.framework import tensor_shape from tensorflow.python.ops import gen_experimental_dataset_ops from tensorflow.python.util.tf_export import tf_export @@ -50,11 +50,12 @@ class TFRecordWriter(object): """ if not isinstance(dataset, dataset_ops.DatasetV2): raise TypeError("`dataset` must be a `tf.data.Dataset` object.") - if (dataset.output_types != dtypes.string or - dataset.output_shapes != tensor_shape.scalar()): + if not dataset_ops.get_structure(dataset).is_compatible_with( + structure.TensorStructure(dtypes.string, [])): raise TypeError( "`dataset` must produce scalar `DT_STRING` tensors whereas it " - "produces shape {0} and types {1}".format(dataset.output_shapes, - dataset.output_types)) + "produces shape {0} and types {1}".format( + dataset_ops.get_legacy_output_shapes(dataset), + dataset_ops.get_legacy_output_types(dataset))) return gen_experimental_dataset_ops.experimental_dataset_to_tf_record( dataset._variant_tensor, self._filename, self._compression_type) # pylint: disable=protected-access diff --git a/tensorflow/python/data/kernel_tests/batch_test.py b/tensorflow/python/data/kernel_tests/batch_test.py index 2551250346..4ee9d20de3 100644 --- a/tensorflow/python/data/kernel_tests/batch_test.py +++ b/tensorflow/python/data/kernel_tests/batch_test.py @@ -70,7 +70,8 @@ class BatchTest(test_base.DatasetTestBase, parameterized.TestCase): else: dim0 = None self.assertEqual( - [ts.as_list() for ts in nest.flatten(dataset.output_shapes)], + [ts.as_list() for ts in nest.flatten( + dataset_ops.get_legacy_output_shapes(dataset))], [[dim0] + list(c.shape[1:]) for c in components]) num_full_batches = (count * 7) // batch_size diff --git a/tensorflow/python/data/kernel_tests/cache_test.py b/tensorflow/python/data/kernel_tests/cache_test.py index 4806101d8c..aa1fd6e5ae 100644 --- a/tensorflow/python/data/kernel_tests/cache_test.py +++ b/tensorflow/python/data/kernel_tests/cache_test.py @@ -59,7 +59,7 @@ class FileCacheTest(test_base.DatasetTestBase): self.assertEqual( tuple([c.shape[1:] for c in components]), - dataset_fn().output_shapes) + dataset_ops.get_legacy_output_shapes(dataset_fn())) get_next = self.getNext(dataset_fn()) diff --git a/tensorflow/python/data/kernel_tests/concatenate_test.py b/tensorflow/python/data/kernel_tests/concatenate_test.py index 5d8bfdc8f3..384fd289f1 100644 --- a/tensorflow/python/data/kernel_tests/concatenate_test.py +++ b/tensorflow/python/data/kernel_tests/concatenate_test.py @@ -45,8 +45,10 @@ class ConcatenateTest(test_base.DatasetTestBase): dataset_to_concatenate = dataset_ops.Dataset.from_tensor_slices( to_concatenate_components) concatenated = input_dataset.concatenate(dataset_to_concatenate) - self.assertEqual(concatenated.output_shapes, (tensor_shape.TensorShape( - [20]), tensor_shape.TensorShape([15]), tensor_shape.TensorShape([]))) + self.assertEqual( + dataset_ops.get_legacy_output_shapes(concatenated), + (tensor_shape.TensorShape([20]), tensor_shape.TensorShape([15]), + tensor_shape.TensorShape([]))) get_next = self.getNext(concatenated) @@ -76,7 +78,9 @@ class ConcatenateTest(test_base.DatasetTestBase): concatenated = input_dataset.concatenate(dataset_to_concatenate) self.assertEqual( [ts.as_list() - for ts in nest.flatten(concatenated.output_shapes)], [[20], [None]]) + for ts in nest.flatten( + dataset_ops.get_legacy_output_shapes(concatenated))], + [[20], [None]]) get_next = self.getNext(concatenated) for i in range(9): result = self.evaluate(get_next()) diff --git a/tensorflow/python/data/kernel_tests/filter_test_base.py b/tensorflow/python/data/kernel_tests/filter_test_base.py index 4c53fa3990..f6e5d285f2 100644 --- a/tensorflow/python/data/kernel_tests/filter_test_base.py +++ b/tensorflow/python/data/kernel_tests/filter_test_base.py @@ -51,8 +51,9 @@ class FilterTestBase(test_base.DatasetTestBase): dataset, lambda x, _y, _z: math_ops.equal( math_ops.mod(x, modulus), 0)) # pylint: enable=g-long-lambda - self.assertEqual([c.shape[1:] for c in components], - [shape for shape in dataset.output_shapes]) + self.assertEqual( + [c.shape[1:] for c in components], + [shape for shape in dataset_ops.get_legacy_output_shapes(dataset)]) get_next = self.getNext(dataset) for _ in range(count): for i in [x for x in range(7) if x**2 % modulus == 0]: diff --git a/tensorflow/python/data/kernel_tests/from_tensor_slices_test.py b/tensorflow/python/data/kernel_tests/from_tensor_slices_test.py index 72db638771..ef46f8eef7 100644 --- a/tensorflow/python/data/kernel_tests/from_tensor_slices_test.py +++ b/tensorflow/python/data/kernel_tests/from_tensor_slices_test.py @@ -43,8 +43,9 @@ class FromTensorSlicesTest(test_base.DatasetTestBase): dataset = dataset_ops.Dataset.from_tensor_slices(components) get_next = self.getNext(dataset) - self.assertEqual([c.shape[1:] for c in components], - [shape for shape in dataset.output_shapes]) + self.assertEqual( + [c.shape[1:] for c in components], + [shape for shape in dataset_ops.get_legacy_output_shapes(dataset)]) for i in range(4): results = self.evaluate(get_next()) @@ -68,7 +69,7 @@ class FromTensorSlicesTest(test_base.DatasetTestBase): self.assertEqual( [tensor_shape.TensorShape(c.dense_shape[1:]) for c in components], - [shape for shape in dataset.output_shapes]) + [shape for shape in dataset_ops.get_legacy_output_shapes(dataset)]) expected = [ (sparse_tensor.SparseTensorValue( @@ -117,7 +118,7 @@ class FromTensorSlicesTest(test_base.DatasetTestBase): self.assertEqual([ tensor_shape.TensorShape(c.dense_shape[1:]) if sparse_tensor.is_sparse(c) else c.shape[1:] for c in components - ], [shape for shape in dataset.output_shapes]) + ], [shape for shape in dataset_ops.get_legacy_output_shapes(dataset)]) expected = [ (sparse_tensor.SparseTensorValue( @@ -161,10 +162,12 @@ class FromTensorSlicesTest(test_base.DatasetTestBase): dataset = dataset_ops.Dataset.from_tensor_slices(components) get_next = self.getNext(dataset) - self.assertEqual(dtypes.int32, dataset.output_types["foo"]) - self.assertEqual(dtypes.float32, dataset.output_types["bar"]) - self.assertEqual((), dataset.output_shapes["foo"]) - self.assertEqual((1,), dataset.output_shapes["bar"]) + self.assertEqual(dtypes.int32, + dataset_ops.get_legacy_output_types(dataset)["foo"]) + self.assertEqual(dtypes.float32, + dataset_ops.get_legacy_output_types(dataset)["bar"]) + self.assertEqual((), dataset_ops.get_legacy_output_shapes(dataset)["foo"]) + self.assertEqual((1,), dataset_ops.get_legacy_output_shapes(dataset)["bar"]) for i in range(3): results = self.evaluate(get_next()) diff --git a/tensorflow/python/data/kernel_tests/from_tensors_test.py b/tensorflow/python/data/kernel_tests/from_tensors_test.py index 82ccdebc7f..e9f1084e04 100644 --- a/tensorflow/python/data/kernel_tests/from_tensors_test.py +++ b/tensorflow/python/data/kernel_tests/from_tensors_test.py @@ -45,8 +45,9 @@ class FromTensorsTest(test_base.DatasetTestBase): dataset = dataset_ops.Dataset.from_tensors(components) - self.assertEqual([c.shape for c in components], - nest.flatten(dataset.output_shapes)) + self.assertEqual( + [c.shape for c in components], + nest.flatten(dataset_ops.get_legacy_output_shapes(dataset))) self.assertDatasetProduces(dataset, expected_output=[components]) @@ -65,7 +66,7 @@ class FromTensorsTest(test_base.DatasetTestBase): self.assertEqual( [tensor_shape.TensorShape(c.dense_shape) for c in components], - [shape for shape in dataset.output_shapes]) + [shape for shape in dataset_ops.get_legacy_output_shapes(dataset)]) self.assertDatasetProduces(dataset, expected_output=[components]) def testFromTensorsMixed(self): @@ -84,7 +85,7 @@ class FromTensorsTest(test_base.DatasetTestBase): self.assertEqual([ tensor_shape.TensorShape(c.dense_shape) if sparse_tensor.is_sparse(c) else c.shape for c in components - ], [shape for shape in dataset.output_shapes]) + ], [shape for shape in dataset_ops.get_legacy_output_shapes(dataset)]) self.assertDatasetProduces(dataset, expected_output=[components]) @@ -95,51 +96,67 @@ class FromTensorsTest(test_base.DatasetTestBase): np.array([8, 9, 10], dtype=np.int64)) dataset = dataset_ops.Dataset.from_tensors(components) - self.assertEquals((dtypes.int64, (dtypes.float64, dtypes.float64), - dtypes.int64), dataset.output_types) - self.assertEquals(([3], ([2], [2]), [3]), dataset.output_shapes) + self.assertEqual((dtypes.int64, (dtypes.float64, dtypes.float64), + dtypes.int64), + dataset_ops.get_legacy_output_types(dataset)) + self.assertEqual(([3], ([2], [2]), [3]), + dataset_ops.get_legacy_output_shapes(dataset)) dataset = dataset.shuffle(10, 10) - self.assertEquals((dtypes.int64, (dtypes.float64, dtypes.float64), - dtypes.int64), dataset.output_types) - self.assertEquals(([3], ([2], [2]), [3]), dataset.output_shapes) + self.assertEqual((dtypes.int64, (dtypes.float64, dtypes.float64), + dtypes.int64), + dataset_ops.get_legacy_output_types(dataset)) + self.assertEqual(([3], ([2], [2]), [3]), + dataset_ops.get_legacy_output_shapes(dataset)) dataset = dataset.repeat(-1) - self.assertEquals((dtypes.int64, (dtypes.float64, dtypes.float64), - dtypes.int64), dataset.output_types) - self.assertEquals(([3], ([2], [2]), [3]), dataset.output_shapes) + self.assertEqual((dtypes.int64, (dtypes.float64, dtypes.float64), + dtypes.int64), + dataset_ops.get_legacy_output_types(dataset)) + self.assertEqual(([3], ([2], [2]), [3]), + dataset_ops.get_legacy_output_shapes(dataset)) dataset = dataset.filter(lambda x, y, z: True) - self.assertEquals((dtypes.int64, (dtypes.float64, dtypes.float64), - dtypes.int64), dataset.output_types) - self.assertEquals(([3], ([2], [2]), [3]), dataset.output_shapes) + self.assertEqual((dtypes.int64, (dtypes.float64, dtypes.float64), + dtypes.int64), + dataset_ops.get_legacy_output_types(dataset)) + self.assertEqual(([3], ([2], [2]), [3]), + dataset_ops.get_legacy_output_shapes(dataset)) dataset = dataset.take(5) - self.assertEquals((dtypes.int64, (dtypes.float64, dtypes.float64), - dtypes.int64), dataset.output_types) - self.assertEquals(([3], ([2], [2]), [3]), dataset.output_shapes) + self.assertEqual((dtypes.int64, (dtypes.float64, dtypes.float64), + dtypes.int64), + dataset_ops.get_legacy_output_types(dataset)) + self.assertEqual(([3], ([2], [2]), [3]), + dataset_ops.get_legacy_output_shapes(dataset)) dataset = dataset.map(lambda x, y, z: ((x, z), (y[0], y[1]))) - self.assertEquals(((dtypes.int64, dtypes.int64), - (dtypes.float64, dtypes.float64)), dataset.output_types) - self.assertEquals((([3], [3]), ([2], [2])), dataset.output_shapes) + self.assertEqual(((dtypes.int64, dtypes.int64), + (dtypes.float64, dtypes.float64)), + dataset_ops.get_legacy_output_types(dataset)) + self.assertEqual((([3], [3]), ([2], [2])), + dataset_ops.get_legacy_output_shapes(dataset)) dataset = dataset.flat_map( lambda x, y: dataset_ops.Dataset.from_tensors(((x[0], x[1]), (y[0], y[1]))) ) - self.assertEquals(((dtypes.int64, dtypes.int64), - (dtypes.float64, dtypes.float64)), dataset.output_types) - self.assertEquals((([3], [3]), ([2], [2])), dataset.output_shapes) + self.assertEqual(((dtypes.int64, dtypes.int64), + (dtypes.float64, dtypes.float64)), + dataset_ops.get_legacy_output_types(dataset)) + self.assertEqual((([3], [3]), ([2], [2])), + dataset_ops.get_legacy_output_shapes(dataset)) dataset = dataset.batch(32) - self.assertEquals(((dtypes.int64, dtypes.int64), - (dtypes.float64, dtypes.float64)), dataset.output_types) - self.assertEquals((([None, 3], [None, 3]), ([None, 2], [None, 2])), - nest.pack_sequence_as(dataset.output_shapes, [ - s.as_list() - for s in nest.flatten(dataset.output_shapes) - ])) + self.assertEqual(((dtypes.int64, dtypes.int64), + (dtypes.float64, dtypes.float64)), + dataset_ops.get_legacy_output_types(dataset)) + dataset_output_shapes = dataset_ops.get_legacy_output_shapes(dataset) + self.assertEqual((([None, 3], [None, 3]), ([None, 2], [None, 2])), + nest.pack_sequence_as(dataset_output_shapes, [ + s.as_list() + for s in nest.flatten(dataset_output_shapes) + ])) # Define a separate set of components with matching leading # dimension for the from-slices constructor. @@ -148,10 +165,11 @@ class FromTensorsTest(test_base.DatasetTestBase): np.array([10, 11, 12], dtype=np.int64)) dataset = dataset_ops.Dataset.from_tensor_slices(components_for_slices) - self.assertEquals((dtypes.int64, - (dtypes.float64, dtypes.float64), dtypes.int64), - dataset.output_types) - self.assertEquals(([], ([], []), []), dataset.output_shapes) + self.assertEqual((dtypes.int64, + (dtypes.float64, dtypes.float64), dtypes.int64), + dataset_ops.get_legacy_output_types(dataset)) + self.assertEqual(([], ([], []), []), + dataset_ops.get_legacy_output_shapes(dataset)) # TODO(b/117581999): more specific shapes in eager mode. @test_util.run_deprecated_v1 @@ -169,60 +187,70 @@ class FromTensorsTest(test_base.DatasetTestBase): get_next = self.getNext(dataset) (w, x), (y, z) = get_next() - self.assertEquals(dtypes.int64, w.dtype) - self.assertEquals(dtypes.int64, x.dtype) - self.assertEquals(dtypes.float64, y.dtype) - self.assertEquals(dtypes.float64, z.dtype) - self.assertEquals([None, 3], w.shape.as_list()) - self.assertEquals([None, 3], x.shape.as_list()) - self.assertEquals([None, 2], y.shape.as_list()) - self.assertEquals([None, 2], z.shape.as_list()) + self.assertEqual(dtypes.int64, w.dtype) + self.assertEqual(dtypes.int64, x.dtype) + self.assertEqual(dtypes.float64, y.dtype) + self.assertEqual(dtypes.float64, z.dtype) + self.assertEqual([None, 3], w.shape.as_list()) + self.assertEqual([None, 3], x.shape.as_list()) + self.assertEqual([None, 2], y.shape.as_list()) + self.assertEqual([None, 2], z.shape.as_list()) get_next = self.getNext(dataset) (w, x), (y, z) = get_next() - self.assertEquals(dtypes.int64, w.dtype) - self.assertEquals(dtypes.int64, x.dtype) - self.assertEquals(dtypes.float64, y.dtype) - self.assertEquals(dtypes.float64, z.dtype) - self.assertEquals([None, 3], w.shape.as_list()) - self.assertEquals([None, 3], x.shape.as_list()) - self.assertEquals([None, 2], y.shape.as_list()) - self.assertEquals([None, 2], z.shape.as_list()) + self.assertEqual(dtypes.int64, w.dtype) + self.assertEqual(dtypes.int64, x.dtype) + self.assertEqual(dtypes.float64, y.dtype) + self.assertEqual(dtypes.float64, z.dtype) + self.assertEqual([None, 3], w.shape.as_list()) + self.assertEqual([None, 3], x.shape.as_list()) + self.assertEqual([None, 2], y.shape.as_list()) + self.assertEqual([None, 2], z.shape.as_list()) def testNestedDict(self): components = {"a": {"aa": 1, "ab": [2.0, 2.0]}, "b": [3, 3, 3]} dataset = dataset_ops.Dataset.from_tensors(components) - self.assertEquals(dtypes.int32, dataset.output_types["a"]["aa"]) - self.assertEquals(dtypes.float32, dataset.output_types["a"]["ab"]) - self.assertEquals(dtypes.int32, dataset.output_types["b"]) - self.assertEquals([], dataset.output_shapes["a"]["aa"]) - self.assertEquals([2], dataset.output_shapes["a"]["ab"]) - self.assertEquals([3], dataset.output_shapes["b"]) + self.assertEqual(dtypes.int32, + dataset_ops.get_legacy_output_types(dataset)["a"]["aa"]) + self.assertEqual(dtypes.float32, + dataset_ops.get_legacy_output_types(dataset)["a"]["ab"]) + self.assertEqual(dtypes.int32, + dataset_ops.get_legacy_output_types(dataset)["b"]) + self.assertEqual([], + dataset_ops.get_legacy_output_shapes(dataset)["a"]["aa"]) + self.assertEqual([2], + dataset_ops.get_legacy_output_shapes(dataset)["a"]["ab"]) + self.assertEqual([3], + dataset_ops.get_legacy_output_shapes(dataset)["b"]) def testNonSequenceNestedStructure(self): components = np.array([1, 2, 3], dtype=np.int64) dataset = dataset_ops.Dataset.from_tensors(components) - self.assertEquals(dtypes.int64, dataset.output_types) - self.assertEquals([3], dataset.output_shapes) + self.assertEqual(dtypes.int64, + dataset_ops.get_legacy_output_types(dataset)) + self.assertEqual([3], dataset_ops.get_legacy_output_shapes(dataset)) dataset = dataset.filter( lambda x: math_ops.reduce_all(math_ops.equal(x, components))) - self.assertEquals(dtypes.int64, dataset.output_types) - self.assertEquals([3], dataset.output_shapes) + self.assertEqual(dtypes.int64, + dataset_ops.get_legacy_output_types(dataset)) + self.assertEqual([3], dataset_ops.get_legacy_output_shapes(dataset)) dataset = dataset.map(lambda x: array_ops.stack([x, x])) - self.assertEquals(dtypes.int64, dataset.output_types) - self.assertEquals([2, 3], dataset.output_shapes) + self.assertEqual(dtypes.int64, + dataset_ops.get_legacy_output_types(dataset)) + self.assertEqual([2, 3], dataset_ops.get_legacy_output_shapes(dataset)) dataset = dataset.flat_map( lambda x: dataset_ops.Dataset.from_tensor_slices(x)) - self.assertEquals(dtypes.int64, dataset.output_types) - self.assertEquals([3], dataset.output_shapes) + self.assertEqual(dtypes.int64, + dataset_ops.get_legacy_output_types(dataset)) + self.assertEqual([3], dataset_ops.get_legacy_output_shapes(dataset)) get_next = self.getNext(dataset) - self.assertEquals(dtypes.int64, get_next().dtype) - self.assertEquals([3], get_next().shape) + self.assertEqual(dtypes.int64, get_next().dtype) + self.assertEqual([3], get_next().shape) # TODO(b/121264236): needs mechanism for multiple device in eager mode. def testSkipEagerSplitPipelineFailsWithPlacementError(self): diff --git a/tensorflow/python/data/kernel_tests/iterator_cluster_test.py b/tensorflow/python/data/kernel_tests/iterator_cluster_test.py index 23d3b6a439..ef198869e4 100644 --- a/tensorflow/python/data/kernel_tests/iterator_cluster_test.py +++ b/tensorflow/python/data/kernel_tests/iterator_cluster_test.py @@ -53,7 +53,8 @@ class IteratorClusterTest(test.TestCase): with ops.device("/job:worker/replica:0/task:0/cpu:0"): remote_it = iterator_ops.Iterator.from_string_handle( - iterator_3_handle, dataset_3.output_types, dataset_3.output_shapes) + iterator_3_handle, dataset_ops.get_legacy_output_types(dataset_3), + dataset_ops.get_legacy_output_shapes(dataset_3)) get_next_op = remote_it.get_next() with session.Session(worker[0].target) as sess: @@ -69,7 +70,8 @@ class IteratorClusterTest(test.TestCase): @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, dataset_ops.get_legacy_output_types(dataset_3), + dataset_ops.get_legacy_output_shapes(dataset_3)) return remote_iterator.get_next() with ops.device(device0): diff --git a/tensorflow/python/data/kernel_tests/iterator_test.py b/tensorflow/python/data/kernel_tests/iterator_test.py index 916cf8bb45..b33292c805 100644 --- a/tensorflow/python/data/kernel_tests/iterator_test.py +++ b/tensorflow/python/data/kernel_tests/iterator_test.py @@ -313,7 +313,8 @@ class IteratorTest(test.TestCase, parameterized.TestCase): self.assertEqual(dataset_3.output_types, iterator.output_types) self.assertEqual(dataset_4.output_types, iterator.output_types) - self.assertEqual([None], iterator.output_shapes.as_list()) + self.assertEqual( + [None], dataset_ops.get_legacy_output_shapes(iterator).as_list()) with self.cached_session() as sess: # The iterator is initially uninitialized. @@ -408,12 +409,14 @@ class IteratorTest(test.TestCase, parameterized.TestCase): handle_placeholder = array_ops.placeholder(dtypes.string, shape=[]) feedable_iterator = iterator_ops.Iterator.from_string_handle( - handle_placeholder, dataset_3.output_types, dataset_3.output_shapes) + handle_placeholder, dataset_ops.get_legacy_output_types(dataset_3), + dataset_ops.get_legacy_output_shapes(dataset_3)) next_element = feedable_iterator.get_next() - self.assertEqual(dataset_3.output_types, feedable_iterator.output_types) - self.assertEqual(dataset_4.output_types, feedable_iterator.output_types) - self.assertEqual([], feedable_iterator.output_shapes) + self.assertTrue(dataset_ops.get_structure(dataset_3).is_compatible_with( + dataset_ops.get_structure(feedable_iterator))) + self.assertTrue(dataset_ops.get_structure(dataset_4).is_compatible_with( + dataset_ops.get_structure(feedable_iterator))) with self.cached_session() as sess: iterator_3_handle = sess.run(iterator_3.string_handle()) @@ -465,12 +468,14 @@ class IteratorTest(test.TestCase, parameterized.TestCase): handle_placeholder = array_ops.placeholder(dtypes.string, shape=[]) feedable_iterator = iterator_ops.Iterator.from_string_handle( - handle_placeholder, dataset_3.output_types, dataset_3.output_shapes) + handle_placeholder, dataset_ops.get_legacy_output_types(dataset_3), + dataset_ops.get_legacy_output_shapes(dataset_3)) next_element = feedable_iterator.get_next() - self.assertEqual(dataset_3.output_types, feedable_iterator.output_types) - self.assertEqual(dataset_4.output_types, feedable_iterator.output_types) - self.assertEqual([], feedable_iterator.output_shapes) + self.assertTrue(dataset_ops.get_structure(dataset_3).is_compatible_with( + dataset_ops.get_structure(feedable_iterator))) + self.assertTrue(dataset_ops.get_structure(dataset_4).is_compatible_with( + dataset_ops.get_structure(feedable_iterator))) with self.cached_session() as sess: iterator_3_handle = sess.run(iterator_3.string_handle()) @@ -601,7 +606,8 @@ class IteratorTest(test.TestCase, parameterized.TestCase): @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, dataset_ops.get_legacy_output_types(dataset_3), + dataset_ops.get_legacy_output_shapes(dataset_3)) return remote_iterator.get_next() with ops.device("/job:localhost/replica:0/task:0/cpu:0"): @@ -677,7 +683,8 @@ class IteratorTest(test.TestCase, parameterized.TestCase): @function.Defun(dtypes.string) def loading_func(h): remote_itr = iterator_ops.Iterator.from_string_handle( - h, itr.output_types, itr.output_shapes) + h, dataset_ops.get_legacy_output_types(itr), + dataset_ops.get_legacy_output_shapes(itr)) return remote_itr.get_next() def map_fn(target, handle): @@ -714,7 +721,8 @@ class IteratorTest(test.TestCase, parameterized.TestCase): def _remote_fn(h): handle = script_ops.py_func(_encode_raw, [h], dtypes.string) remote_iterator = iterator_ops.Iterator.from_string_handle( - handle, dataset_3.output_types, dataset_3.output_shapes) + handle, dataset_ops.get_legacy_output_types(dataset_3), + dataset_ops.get_legacy_output_shapes(dataset_3)) return remote_iterator.get_next() with ops.device("/job:localhost/replica:0/task:0/device:GPU:0"): @@ -865,9 +873,12 @@ class IteratorTest(test.TestCase, parameterized.TestCase): self.assertTrue(iterator._element_structure.is_compatible_with( expected_element_structure)) - self.assertEqual(expected_output_classes, iterator.output_classes) - self.assertEqual(expected_output_types, iterator.output_types) - self.assertEqual(expected_output_shapes, iterator.output_shapes) + self.assertEqual(expected_output_classes, + dataset_ops.get_legacy_output_classes(iterator)) + self.assertEqual(expected_output_types, + dataset_ops.get_legacy_output_types(iterator)) + self.assertEqual(expected_output_shapes, + dataset_ops.get_legacy_output_shapes(iterator)) def testIteratorGetNextName(self): with ops.Graph().as_default(): diff --git a/tensorflow/python/data/kernel_tests/map_test.py b/tensorflow/python/data/kernel_tests/map_test.py index 4badcffd46..f5cdf7789f 100644 --- a/tensorflow/python/data/kernel_tests/map_test.py +++ b/tensorflow/python/data/kernel_tests/map_test.py @@ -95,8 +95,9 @@ class MapTest(test_base.DatasetTestBase, parameterized.TestCase): dataset = dataset_ops.Dataset.from_tensor_slices(components).map( _map_fn).repeat(count) - self.assertEqual([c.shape[1:] for c in components], - [shape for shape in dataset.output_shapes]) + self.assertEqual( + [c.shape[1:] for c in components], + [shape for shape in dataset_ops.get_legacy_output_shapes(dataset)]) return dataset def testMapDataset(self): @@ -161,8 +162,9 @@ class MapTest(test_base.DatasetTestBase, parameterized.TestCase): _map_fn, num_parallel_calls=num_parallel_calls).prefetch( output_buffer_size).repeat(count) - self.assertEqual([c.shape[1:] for c in components], - [shape for shape in dataset.output_shapes]) + self.assertEqual( + [c.shape[1:] for c in components], + [shape for shape in dataset_ops.get_legacy_output_shapes(dataset)]) return dataset def testParallelMapDataset(self): diff --git a/tensorflow/python/data/kernel_tests/padded_batch_test.py b/tensorflow/python/data/kernel_tests/padded_batch_test.py index 042af7a6f9..b54749002c 100644 --- a/tensorflow/python/data/kernel_tests/padded_batch_test.py +++ b/tensorflow/python/data/kernel_tests/padded_batch_test.py @@ -185,9 +185,10 @@ class PaddedBatchTest(test_base.DatasetTestBase, parameterized.TestCase): dynamic_padding_from_tensor_shapes, dynamic_padding_from_lists, dynamic_padding_from_lists_with_minus_one, dynamic_padding_from_tensors ]: - self.assertEqual([None, None], dataset.output_shapes[0].as_list()) - self.assertEqual([None, None, None], dataset.output_shapes[1].as_list()) - self.assertEqual([None, 37], dataset.output_shapes[2].as_list()) + dataset_output_shapes = dataset_ops.get_legacy_output_shapes(dataset) + self.assertEqual([None, None], dataset_output_shapes[0].as_list()) + self.assertEqual([None, None, None], dataset_output_shapes[1].as_list()) + self.assertEqual([None, 37], dataset_output_shapes[2].as_list()) def testPaddedBatchSparseError(self): diff --git a/tensorflow/python/data/kernel_tests/repeat_test.py b/tensorflow/python/data/kernel_tests/repeat_test.py index 4ef2fc1bfc..8a8537b30c 100644 --- a/tensorflow/python/data/kernel_tests/repeat_test.py +++ b/tensorflow/python/data/kernel_tests/repeat_test.py @@ -37,8 +37,9 @@ class RepeatTest(test_base.DatasetTestBase): def do_test(count): dataset = dataset_ops.Dataset.from_tensors(components).repeat(count) - self.assertEqual([c.shape for c in components], - [shape for shape in dataset.output_shapes]) + self.assertEqual( + [c.shape for c in components], + [shape for shape in dataset_ops.get_legacy_output_shapes(dataset)]) self.assertDatasetProduces(dataset, [components] * count) # Test a finite repetition. @@ -54,8 +55,9 @@ class RepeatTest(test_base.DatasetTestBase): # NOTE(mrry): There's not a good way to test that the sequence # actually is infinite. dataset = dataset_ops.Dataset.from_tensors(components).repeat(-1) - self.assertEqual([c.shape for c in components], - [shape for shape in dataset.output_shapes]) + self.assertEqual( + [c.shape for c in components], + [shape for shape in dataset_ops.get_legacy_output_shapes(dataset)]) get_next = self.getNext(dataset) for _ in range(17): results = self.evaluate(get_next()) @@ -69,8 +71,9 @@ class RepeatTest(test_base.DatasetTestBase): dataset = dataset_ops.Dataset.from_tensors(components).repeat( inner_count).repeat(outer_count) - self.assertEqual([c.shape for c in components], - [shape for shape in dataset.output_shapes]) + self.assertEqual( + [c.shape for c in components], + [shape for shape in dataset_ops.get_legacy_output_shapes(dataset)]) self.assertDatasetProduces(dataset, [components] * (inner_count * outer_count)) diff --git a/tensorflow/python/data/kernel_tests/shuffle_test.py b/tensorflow/python/data/kernel_tests/shuffle_test.py index 13df870938..ea0eeaf686 100644 --- a/tensorflow/python/data/kernel_tests/shuffle_test.py +++ b/tensorflow/python/data/kernel_tests/shuffle_test.py @@ -51,7 +51,7 @@ class ShuffleTest(test_base.DatasetTestBase, parameterized.TestCase): self.assertEqual( tuple([c.shape[1:] for c in components]), - shuffle_dataset.output_shapes) + dataset_ops.get_legacy_output_shapes(shuffle_dataset)) return shuffle_dataset else: return repeat_dataset diff --git a/tensorflow/python/data/kernel_tests/skip_test.py b/tensorflow/python/data/kernel_tests/skip_test.py index c22be57692..74dc8b7f55 100644 --- a/tensorflow/python/data/kernel_tests/skip_test.py +++ b/tensorflow/python/data/kernel_tests/skip_test.py @@ -33,8 +33,9 @@ class SkipTest(test_base.DatasetTestBase): def do_test(count): dataset = dataset_ops.Dataset.from_tensor_slices(components).skip(count) - self.assertEqual([c.shape[1:] for c in components], - [shape for shape in dataset.output_shapes]) + self.assertEqual( + [c.shape[1:] for c in components], + [shape for shape in dataset_ops.get_legacy_output_shapes(dataset)]) start_range = min(count, 10) if count != -1 else 10 self.assertDatasetProduces( dataset, diff --git a/tensorflow/python/data/kernel_tests/take_test.py b/tensorflow/python/data/kernel_tests/take_test.py index 03a7ece2d8..665ed59a7b 100644 --- a/tensorflow/python/data/kernel_tests/take_test.py +++ b/tensorflow/python/data/kernel_tests/take_test.py @@ -33,8 +33,9 @@ class TakeTest(test_base.DatasetTestBase): def do_test(count): dataset = dataset_ops.Dataset.from_tensor_slices(components).take(count) - self.assertEqual([c.shape[1:] for c in components], - [shape for shape in dataset.output_shapes]) + self.assertEqual( + [c.shape[1:] for c in components], + [shape for shape in dataset_ops.get_legacy_output_shapes(dataset)]) num_output = min(count, 10) if count != -1 else 10 self.assertDatasetProduces( dataset, [tuple(components[0][i:i + 1]) for i in range(num_output)]) diff --git a/tensorflow/python/data/kernel_tests/test_base.py b/tensorflow/python/data/kernel_tests/test_base.py index 57df29ead5..bc63a3481f 100644 --- a/tensorflow/python/data/kernel_tests/test_base.py +++ b/tensorflow/python/data/kernel_tests/test_base.py @@ -138,7 +138,8 @@ class DatasetTestBase(test.TestCase): self.evaluate(get_next()) return if expected_shapes: - self.assertEqual(expected_shapes, dataset.output_shapes) + self.assertEqual(expected_shapes, + dataset_ops.get_legacy_output_shapes(dataset)) self.assertGreater(num_test_iterations, 0) for _ in range(num_test_iterations): get_next = self.getNext( @@ -154,9 +155,12 @@ class DatasetTestBase(test.TestCase): def assertDatasetsEqual(self, dataset1, dataset2): """Checks that datasets are equal. Supports both graph and eager mode.""" - self.assertEqual(dataset1.output_types, dataset2.output_types) - self.assertEqual(dataset1.output_classes, dataset2.output_classes) - flattened_types = nest.flatten(dataset1.output_types) + self.assertTrue(dataset_ops.get_structure(dataset1).is_compatible_with( + dataset_ops.get_structure(dataset2))) + self.assertTrue(dataset_ops.get_structure(dataset2).is_compatible_with( + dataset_ops.get_structure(dataset1))) + flattened_types = nest.flatten( + dataset_ops.get_legacy_output_types(dataset1)) next1 = self.getNext(dataset1) next2 = self.getNext(dataset2) diff --git a/tensorflow/python/data/kernel_tests/window_test.py b/tensorflow/python/data/kernel_tests/window_test.py index a7b4d86fcf..78163b3548 100644 --- a/tensorflow/python/data/kernel_tests/window_test.py +++ b/tensorflow/python/data/kernel_tests/window_test.py @@ -81,9 +81,9 @@ class WindowTest(test_base.DatasetTestBase, parameterized.TestCase): drop_remainder=drop_remainder).flat_map(_flat_map_fn) get_next = self.getNext(dataset) - self.assertEqual( - [[None] + list(c.shape[1:]) for c in components], - [ts.as_list() for ts in nest.flatten(dataset.output_shapes)]) + self.assertEqual([[None] + list(c.shape[1:]) for c in components], + [ts.as_list() for ts in nest.flatten( + dataset_ops.get_legacy_output_shapes(dataset))]) num_full_batches = max(0, (count * 7 - ((size - 1) * stride + 1)) // shift + 1) diff --git a/tensorflow/python/data/kernel_tests/zip_test.py b/tensorflow/python/data/kernel_tests/zip_test.py index 477c9fa7da..72f739e4e4 100644 --- a/tensorflow/python/data/kernel_tests/zip_test.py +++ b/tensorflow/python/data/kernel_tests/zip_test.py @@ -81,7 +81,7 @@ class ZipTest(test_base.DatasetTestBase): dataset = dataset_ops.Dataset.zip((datasets[0], (datasets[1], datasets[2]))) self.assertEqual( - dataset.output_shapes, + dataset_ops.get_legacy_output_shapes(dataset), (tensor_shape.TensorShape([20]), (tensor_shape.TensorShape([22]), tensor_shape.TensorShape([])))) diff --git a/tensorflow/python/data/ops/dataset_ops.py b/tensorflow/python/data/ops/dataset_ops.py index e185ba78a0..2226c8e00d 100644 --- a/tensorflow/python/data/ops/dataset_ops.py +++ b/tensorflow/python/data/ops/dataset_ops.py @@ -262,9 +262,9 @@ class DatasetV2(object): return self._element_structure._to_legacy_output_types() # pylint: disable=protected-access def __repr__(self): - output_shapes = nest.map_structure(str, self.output_shapes) + output_shapes = nest.map_structure(str, get_legacy_output_shapes(self)) output_shapes = str(output_shapes).replace("'", "") - output_types = nest.map_structure(repr, self.output_types) + output_types = nest.map_structure(repr, get_legacy_output_types(self)) output_types = str(output_types).replace("'", "") return ("<%s shapes: %s, types: %s>" % (type(self).__name__, output_shapes, output_types)) @@ -1164,7 +1164,7 @@ class DatasetV2(object): ```python d = tf.data.Dataset.from_tensor_slices([1, 2, 3]) - + d = d.filter(lambda x: x < 3) # [1, 2] # `tf.math.equal(x, y)` is required for equality comparison @@ -1484,10 +1484,12 @@ class DatasetV1(DatasetV2): else: six.reraise(ValueError, err) + # pylint: disable=protected-access return iterator_ops.Iterator( gen_dataset_ops.one_shot_iterator( dataset_factory=_make_dataset, **flat_structure(self)), - None, self.output_types, self.output_shapes, self.output_classes) + None, get_legacy_output_types(self), get_legacy_output_shapes(self), + get_legacy_output_classes(self)) @deprecation.deprecated( None, "Use `for ... in dataset:` to iterate over a dataset. If using " @@ -1540,9 +1542,10 @@ class DatasetV1(DatasetV2): initializer = gen_dataset_ops.make_iterator( dataset._variant_tensor, # pylint: disable=protected-access iterator_resource) - return iterator_ops.Iterator(iterator_resource, initializer, - dataset.output_types, dataset.output_shapes, - dataset.output_classes) + # pylint: disable=protected-access + return iterator_ops.Iterator( + iterator_resource, initializer, get_legacy_output_types(dataset), + get_legacy_output_shapes(dataset), get_legacy_output_classes(dataset)) @property def _element_structure(self): @@ -1852,6 +1855,86 @@ def make_initializable_iterator(dataset, shared_name=None): return DatasetV1Adapter(dataset)._make_initializable_iterator(shared_name) # pylint: disable=protected-access +# TODO(b/110122868): Replace this method with a public API for reflecting on +# dataset structure. +def get_structure(dataset_or_iterator): + """Returns the `tf.data.experimental.Structure` of a `Dataset` or `Iterator`. + + Args: + dataset_or_iterator: A `tf.data.Dataset`, `tf.data.Iterator`, or + `EagerIterator`. + + Returns: + A `tf.data.experimental.Structure` representing the structure of the + elements of `dataset_or_iterator`. + + Raises: + TypeError: If `dataset_or_iterator` is not a dataset or iterator object. + """ + try: + ret = dataset_or_iterator._element_structure # pylint: disable=protected-access + if isinstance(ret, structure_lib.Structure): + return ret + except AttributeError: + pass + raise TypeError("`dataset_or_iterator` must be a Dataset or Iterator object, " + "but got %s." % type(dataset_or_iterator)) + + +# TODO(b/110122868): Remove all uses of this method. +def get_legacy_output_shapes(dataset_or_iterator): + """Returns the output shapes of a `Dataset` or `Iterator`. + + This utility method replaces the deprecated-in-V2 + `tf.compat.v1.Dataset.output_shapes` property. + + Args: + dataset_or_iterator: A `tf.data.Dataset`, `tf.data.Iterator`, or + `EagerIterator`. + + Returns: + A nested structure of `tf.TensorShape` objects corresponding to each + component of an element of the given dataset or iterator. + """ + return get_structure(dataset_or_iterator)._to_legacy_output_shapes() # pylint: disable=protected-access + + +# TODO(b/110122868): Remove all uses of this method. +def get_legacy_output_types(dataset_or_iterator): + """Returns the output shapes of a `Dataset` or `Iterator`. + + This utility method replaces the deprecated-in-V2 + `tf.compat.v1.Dataset.output_types` property. + + Args: + dataset_or_iterator: A `tf.data.Dataset`, `tf.data.Iterator`, or + `EagerIterator`. + + Returns: + A nested structure of `tf.DType` objects corresponding to each component + of an element of this dataset. + """ + return get_structure(dataset_or_iterator)._to_legacy_output_types() # pylint: disable=protected-access + + +# TODO(b/110122868): Remove all uses of this method. +def get_legacy_output_classes(dataset_or_iterator): + """Returns the output classes of a `Dataset` or `Iterator`. + + This utility method replaces the deprecated-in-V2 + `tf.compat.v1.Dataset.output_classes` property. + + Args: + dataset_or_iterator: A `tf.data.Dataset`, `tf.data.Iterator`, or + `EagerIterator`. + + Returns: + A nested structure of Python `type` or `tf.data.experimental.Structure` + objects corresponding to each component of an element of this dataset. + """ + return get_structure(dataset_or_iterator)._to_legacy_output_classes() # pylint: disable=protected-access + + @tf_export("data.Options") class Options(options_lib.OptionsBase): """Represents options for tf.data.Dataset. @@ -2451,24 +2534,25 @@ class ConcatenateDataset(DatasetV2): self._input_dataset = input_dataset self._dataset_to_concatenate = dataset_to_concatenate - output_types = input_dataset.output_types - if output_types != dataset_to_concatenate.output_types: + output_types = get_legacy_output_types(input_dataset) + if output_types != get_legacy_output_types(dataset_to_concatenate): raise TypeError( "Two datasets to concatenate have different types %s and %s" % - (output_types, dataset_to_concatenate.output_types)) + (output_types, get_legacy_output_types(dataset_to_concatenate))) output_classes = input_dataset.output_classes - if output_classes != dataset_to_concatenate.output_classes: + if output_classes != get_legacy_output_classes(dataset_to_concatenate): raise TypeError( "Two datasets to concatenate have different classes %s and %s" % - (output_classes, dataset_to_concatenate.output_classes)) + (output_classes, get_legacy_output_classes(dataset_to_concatenate))) - input_shapes = self._input_dataset.output_shapes + input_shapes = get_legacy_output_shapes(self._input_dataset) output_shapes = nest.pack_sequence_as(input_shapes, [ ts1.most_specific_compatible_shape(ts2) for (ts1, ts2) in zip( nest.flatten(input_shapes), - nest.flatten(self._dataset_to_concatenate.output_shapes)) + nest.flatten(get_legacy_output_shapes( + self._dataset_to_concatenate))) ]) self._structure = structure_lib.convert_legacy_structure( @@ -2791,7 +2875,8 @@ def _default_padding(input_dataset): else: return np.zeros_like(t.as_numpy_dtype()) - return nest.map_structure(make_zero, input_dataset.output_types) + return nest.map_structure( + make_zero, get_legacy_output_types(input_dataset)) class PaddedBatchDataset(UnaryDataset): @@ -2801,7 +2886,7 @@ class PaddedBatchDataset(UnaryDataset): drop_remainder): """See `Dataset.batch()` for details.""" self._input_dataset = input_dataset - if sparse.any_sparse(input_dataset.output_classes): + if sparse.any_sparse(get_legacy_output_classes(input_dataset)): # TODO(b/63669786): support batching of sparse tensors raise TypeError( "Batching of padded sparse tensors is not currently supported") @@ -2812,22 +2897,22 @@ class PaddedBatchDataset(UnaryDataset): padding_values if padding_values is not None else _default_padding(input_dataset)) - flat_padded_shapes = nest.flatten_up_to(input_dataset.output_shapes, - padded_shapes) + input_shapes = get_legacy_output_shapes(input_dataset) + flat_padded_shapes = nest.flatten_up_to(input_shapes, padded_shapes) flat_padded_shapes_as_tensors = [] for input_component_shape, padded_shape in zip( - nest.flatten(input_dataset.output_shapes), flat_padded_shapes): + nest.flatten(input_shapes), flat_padded_shapes): flat_padded_shapes_as_tensors.append( _padded_shape_to_tensor(padded_shape, input_component_shape)) - self._padded_shapes = nest.pack_sequence_as(input_dataset.output_shapes, + self._padded_shapes = nest.pack_sequence_as(input_shapes, flat_padded_shapes_as_tensors) self._padding_values = nest.map_structure_up_to( - input_dataset.output_shapes, _padding_value_to_tensor, padding_values, - input_dataset.output_types) + input_shapes, _padding_value_to_tensor, padding_values, + get_legacy_output_types(input_dataset)) self._drop_remainder = ops.convert_to_tensor( drop_remainder, dtype=dtypes.bool, name="drop_remainder") @@ -2840,8 +2925,8 @@ class PaddedBatchDataset(UnaryDataset): output_shapes = nest.map_structure( _padded_shape_to_batch_shape, self._padded_shapes) self._structure = structure_lib.convert_legacy_structure( - self._input_dataset.output_types, output_shapes, - self._input_dataset.output_classes) + get_legacy_output_types(self._input_dataset), output_shapes, + get_legacy_output_classes(self._input_dataset)) # pylint: disable=protected-access # TODO(jsimsa): Switch to using v2 only any time after 6/30/2018. @@ -3129,9 +3214,9 @@ class WindowDataset(UnaryDataset): DatasetStructure(structure_lib.convert_legacy_structure( output_type, output_shape, output_class)) for output_class, output_shape, output_type in zip( - nest.flatten(input_dataset.output_classes), - nest.flatten(input_dataset.output_shapes), - nest.flatten(input_dataset.output_types)) + nest.flatten(get_legacy_output_classes(input_dataset)), + nest.flatten(get_legacy_output_shapes(input_dataset)), + nest.flatten(get_legacy_output_types(input_dataset))) ]) self._structure = structure_lib.NestedStructure(nest_of_structures) variant_tensor = gen_dataset_ops.window_dataset( diff --git a/tensorflow/python/data/ops/iterator_ops.py b/tensorflow/python/data/ops/iterator_ops.py index efa9020951..070e94111d 100644 --- a/tensorflow/python/data/ops/iterator_ops.py +++ b/tensorflow/python/data/ops/iterator_ops.py @@ -333,28 +333,39 @@ class Iterator(trackable.Trackable): element structure. """ with ops.name_scope(name, "make_initializer") as name: - nest.assert_same_structure(self.output_types, dataset.output_types) - nest.assert_same_structure(self.output_shapes, dataset.output_shapes) + # pylint: disable=protected-access + # NOTE(mrry): Cannot depend on `dataset_ops.get_legacy_output*()` due + # to that creating a circular dependency. + dataset_output_types = ( + dataset._element_structure._to_legacy_output_types()) + dataset_output_shapes = ( + dataset._element_structure._to_legacy_output_shapes()) + dataset_output_classes = ( + dataset._element_structure._to_legacy_output_classes()) + # pylint: enable=protected-access + + nest.assert_same_structure(self.output_types, dataset_output_types) + nest.assert_same_structure(self.output_shapes, dataset_output_shapes) for iterator_class, dataset_class in zip( nest.flatten(self.output_classes), - nest.flatten(dataset.output_classes)): + nest.flatten(dataset_output_classes)): if iterator_class is not dataset_class: raise TypeError( "Expected output classes %r but got dataset with output class %r." - % (self.output_classes, dataset.output_classes)) + % (self.output_classes, dataset_output_classes)) for iterator_dtype, dataset_dtype in zip( - nest.flatten(self.output_types), nest.flatten(dataset.output_types)): + nest.flatten(self.output_types), nest.flatten(dataset_output_types)): if iterator_dtype != dataset_dtype: raise TypeError( "Expected output types %r but got dataset with output types %r." % - (self.output_types, dataset.output_types)) + (self.output_types, dataset_output_types)) for iterator_shape, dataset_shape in zip( nest.flatten(self.output_shapes), nest.flatten( - dataset.output_shapes)): + dataset_output_shapes)): if not iterator_shape.is_compatible_with(dataset_shape): raise TypeError("Expected output shapes compatible with %r but got " "dataset with output shapes %r." % - (self.output_shapes, dataset.output_shapes)) + (self.output_shapes, dataset_output_shapes)) with ops.colocate_with(self._iterator_resource): return gen_dataset_ops.make_iterator( dataset._variant_tensor, self._iterator_resource, name=name) # pylint: disable=protected-access @@ -525,8 +536,7 @@ class EagerIterator(trackable.Trackable): # pylint: disable=protected-access dataset = dataset._apply_options() ds_variant = dataset._variant_tensor - self._structure = structure_lib.convert_legacy_structure( - dataset.output_types, dataset.output_shapes, dataset.output_classes) + self._structure = dataset._element_structure self._flat_output_types = self._structure._flat_types self._flat_output_shapes = self._structure._flat_shapes with ops.colocate_with(ds_variant): diff --git a/tensorflow/python/data/ops/multi_device_iterator_ops.py b/tensorflow/python/data/ops/multi_device_iterator_ops.py index 2592282104..36e1e311d3 100644 --- a/tensorflow/python/data/ops/multi_device_iterator_ops.py +++ b/tensorflow/python/data/ops/multi_device_iterator_ops.py @@ -322,13 +322,5 @@ class MultiDeviceIterator(object): self._device_iterators[i]._resource) @property - def output_types(self): - return self._dataset.output_types - - @property - def output_shapes(self): - return self._dataset.output_shapes - - @property - def output_classes(self): - return self._dataset.output_classes + def _element_structure(self): + return dataset_ops.get_structure(self._dataset) diff --git a/tensorflow/python/data/ops/readers.py b/tensorflow/python/data/ops/readers.py index 5e61bcf6be..477ff94a8d 100644 --- a/tensorflow/python/data/ops/readers.py +++ b/tensorflow/python/data/ops/readers.py @@ -197,10 +197,11 @@ class TFRecordDatasetV2(dataset_ops.DatasetV2): ValueError: If any argument does not have the expected shape. """ if isinstance(filenames, dataset_ops.DatasetV2): - if filenames.output_types != dtypes.string: + if dataset_ops.get_legacy_output_types(filenames) != dtypes.string: raise TypeError( "`filenames` must be a `tf.data.Dataset` of `tf.string` elements.") - if not filenames.output_shapes.is_compatible_with(tensor_shape.scalar()): + if not dataset_ops.get_legacy_output_shapes(filenames).is_compatible_with( + tensor_shape.scalar()): raise ValueError( "`filenames` must be a `tf.data.Dataset` of scalar `tf.string` " "elements.") diff --git a/tensorflow/python/distribute/input_lib.py b/tensorflow/python/distribute/input_lib.py index 14b153b219..2204e3df45 100644 --- a/tensorflow/python/distribute/input_lib.py +++ b/tensorflow/python/distribute/input_lib.py @@ -260,6 +260,8 @@ class DatasetIterator(InputIteratorImpl): worker_devices) iterators.append(iterator) + self._element_structure = dataset._element_structure # pylint: disable=protected-access + super(DatasetIterator, self).__init__(input_workers, iterators) @@ -313,15 +315,15 @@ class _SingleWorkerDatasetIterator(object): @property def output_classes(self): - return self._iterator.output_classes + return dataset_ops.get_legacy_output_classes(self._iterator) @property def output_shapes(self): - return self._iterator.output_shapes + return dataset_ops.get_legacy_output_shapes(self._iterator) @property def output_types(self): - return self._iterator.output_types + return dataset_ops.get_legacy_output_types(self._iterator) class _SingleWorkerCallableIterator(object): diff --git a/tensorflow/python/keras/engine/distributed_training_utils.py b/tensorflow/python/keras/engine/distributed_training_utils.py index 0c9fdbb54c..709fa97832 100644 --- a/tensorflow/python/keras/engine/distributed_training_utils.py +++ b/tensorflow/python/keras/engine/distributed_training_utils.py @@ -398,7 +398,7 @@ def is_tpu_strategy(strategy): def is_dataset_shape_fully_defined(dataset): """Returns whether a dataset contains a final partial batch.""" - shapes = nest.flatten(dataset.output_shapes) + shapes = nest.flatten(dataset_ops.get_legacy_output_shapes(dataset)) unknown_shapes = [s for s in shapes if not s.is_fully_defined()] return not unknown_shapes @@ -498,7 +498,7 @@ def get_input_params(distribution_strategy, first_x_value, steps, batch_size, def get_batch_dimension(iterator): - shapes = nest.flatten(iterator.output_shapes) + shapes = nest.flatten(dataset_ops.get_legacy_output_shapes(iterator)) # Take the batch size from the first element, as it should be the same for # all. dims = shapes[0].dims diff --git a/tensorflow/python/keras/engine/training.py b/tensorflow/python/keras/engine/training.py index ee5baf4553..56f7b5499d 100644 --- a/tensorflow/python/keras/engine/training.py +++ b/tensorflow/python/keras/engine/training.py @@ -1765,7 +1765,7 @@ class Model(Network): if isinstance(x, (dataset_ops.DatasetV2, iterator_ops.Iterator, iterator_ops.EagerIterator)): ds_batch_size = tensor_shape.as_dimension( - nest.flatten(x.output_shapes)[0][0]).value + nest.flatten(dataset_ops.get_legacy_output_shapes(x))[0][0]).value if ds_batch_size is not None and ds_batch_size != static_batch_size: raise ValueError('The batch output shape of your `Dataset` is {}, ' 'which is incompatible with the specified batch ' diff --git a/tensorflow/python/keras/engine/training_utils.py b/tensorflow/python/keras/engine/training_utils.py index 8c9b49cfcd..5964cc1a57 100644 --- a/tensorflow/python/keras/engine/training_utils.py +++ b/tensorflow/python/keras/engine/training_utils.py @@ -1613,7 +1613,7 @@ def set_run_eagerly_for_dict_structure(model, x): model.run_eagerly = True if (isinstance(x, (iterator_ops.Iterator, iterator_ops.EagerIterator, dataset_ops.DatasetV2))): - for item in x.output_shapes: + for item in dataset_ops.get_legacy_output_shapes(x): if isinstance(item, dict): model.run_eagerly = True return diff --git a/tensorflow/python/tpu/datasets.py b/tensorflow/python/tpu/datasets.py index bc0cd41d21..0dd7fb774e 100644 --- a/tensorflow/python/tpu/datasets.py +++ b/tensorflow/python/tpu/datasets.py @@ -159,14 +159,17 @@ def StreamingFilesDataset(files, @function.Defun(dtypes.string) def LoadingFunc(h): remote_iterator = iterator_ops.Iterator.from_string_handle( - h, source_dataset.output_types, source_dataset.output_shapes) + h, dataset_ops.get_legacy_output_types(source_dataset), + dataset_ops.get_legacy_output_shapes(source_dataset)) return remote_iterator.get_next() def MapFn(unused_input): - if isinstance(source_dataset.output_types, dtypes.DType): - output_types = [source_dataset.output_types] - elif isinstance(source_dataset.output_types, (list, tuple)): - output_types = source_dataset.output_types + source_dataset_output_types = dataset_ops.get_legacy_output_types( + source_dataset) + if isinstance(source_dataset_output_types, dtypes.DType): + output_types = [source_dataset_output_types] + elif isinstance(source_dataset_output_types, (list, tuple)): + output_types = source_dataset_output_types else: raise ValueError('source dataset has invalid output types') remote_calls = functional_ops.remote_call( -- GitLab From 78e5608e0580a591b1917a54b57177c7eead4437 Mon Sep 17 00:00:00 2001 From: Derek Murray Date: Fri, 22 Feb 2019 10:43:11 -0800 Subject: [PATCH 353/766] Fix tf.dtypes.as_dtypes() error message when invalid argument is a multi-element tuple. Previously, the tuple would be unpacked in string formatting, and if it had more than one element we would get an argument unpacking error, instead of the intended TypeError. PiperOrigin-RevId: 235217943 --- tensorflow/python/framework/dtypes.py | 2 +- tensorflow/python/framework/dtypes_test.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/tensorflow/python/framework/dtypes.py b/tensorflow/python/framework/dtypes.py index 6638be219d..8440e82a59 100644 --- a/tensorflow/python/framework/dtypes.py +++ b/tensorflow/python/framework/dtypes.py @@ -725,4 +725,4 @@ def as_dtype(type_value): pass raise TypeError( - "Cannot convert value %r to a TensorFlow DType." % type_value) + "Cannot convert value %r to a TensorFlow DType." % (type_value,)) diff --git a/tensorflow/python/framework/dtypes_test.py b/tensorflow/python/framework/dtypes_test.py index 7dd2a792d1..126516a66e 100644 --- a/tensorflow/python/framework/dtypes_test.py +++ b/tensorflow/python/framework/dtypes_test.py @@ -311,6 +311,10 @@ class TypesTest(test_util.TensorFlowTestCase): reconstructed = ctor(*args) self.assertEquals(reconstructed, dtype) + def testAsDtypeInvalidArgument(self): + with self.assertRaises(TypeError): + dtypes.as_dtype((dtypes.int32, dtypes.float32)) + if __name__ == "__main__": googletest.main() -- GitLab From 838873524f8503bd067c338bf341705b335f7f9b Mon Sep 17 00:00:00 2001 From: Gunhan Gulsoy Date: Fri, 22 Feb 2019 10:49:01 -0800 Subject: [PATCH 354/766] Remove dependency on variable ops kernels in xla PiperOrigin-RevId: 235219008 --- tensorflow/compiler/jit/BUILD | 3 +-- tensorflow/compiler/jit/kernels/BUILD | 1 - tensorflow/compiler/jit/kernels/xla_ops.cc | 1 - tensorflow/compiler/jit/xla_compilation_cache.cc | 1 - tensorflow/compiler/jit/xla_launch_util.h | 2 +- 5 files changed, 2 insertions(+), 6 deletions(-) diff --git a/tensorflow/compiler/jit/BUILD b/tensorflow/compiler/jit/BUILD index 121de401ce..6a1d0f30d4 100644 --- a/tensorflow/compiler/jit/BUILD +++ b/tensorflow/compiler/jit/BUILD @@ -264,11 +264,11 @@ cc_library( "//tensorflow/compiler/xla/service:device_memory_allocator", "//tensorflow/core:core_cpu_internal", "//tensorflow/core:framework", + "//tensorflow/core:framework_internal", "//tensorflow/core:gpu_runtime", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", "//tensorflow/core:protos_all_cc", - "//tensorflow/core/kernels:variable_ops", "@com_google_absl//absl/algorithm:container", "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/memory", @@ -292,7 +292,6 @@ cc_library( "//tensorflow/core:lib", "//tensorflow/core:lib_internal", "//tensorflow/core:protos_all_cc", - "//tensorflow/core/kernels:variable_ops", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/strings", "@com_google_absl//absl/types:optional", diff --git a/tensorflow/compiler/jit/kernels/BUILD b/tensorflow/compiler/jit/kernels/BUILD index d0fa2c40be..bd816e749c 100644 --- a/tensorflow/compiler/jit/kernels/BUILD +++ b/tensorflow/compiler/jit/kernels/BUILD @@ -28,7 +28,6 @@ cc_library( "//tensorflow/core:lib", "//tensorflow/core:state_ops_op_lib", "//tensorflow/core:stream_executor_no_cuda", - "//tensorflow/core/kernels:variable_ops", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/memory", ], diff --git a/tensorflow/compiler/jit/kernels/xla_ops.cc b/tensorflow/compiler/jit/kernels/xla_ops.cc index 997ef6e14b..a5f5bc68c1 100644 --- a/tensorflow/compiler/jit/kernels/xla_ops.cc +++ b/tensorflow/compiler/jit/kernels/xla_ops.cc @@ -35,7 +35,6 @@ limitations under the License. #include "tensorflow/core/framework/op_kernel.h" #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/framework/types.h" -#include "tensorflow/core/kernels/variable_ops.h" #include "tensorflow/core/lib/core/errors.h" #include "tensorflow/core/lib/core/status.h" #include "tensorflow/core/platform/env.h" diff --git a/tensorflow/compiler/jit/xla_compilation_cache.cc b/tensorflow/compiler/jit/xla_compilation_cache.cc index 611515cf33..9f95846307 100644 --- a/tensorflow/compiler/jit/xla_compilation_cache.cc +++ b/tensorflow/compiler/jit/xla_compilation_cache.cc @@ -31,7 +31,6 @@ limitations under the License. #include "tensorflow/core/framework/types.h" #include "tensorflow/core/graph/graph_constructor.h" #include "tensorflow/core/graph/node_builder.h" -#include "tensorflow/core/kernels/variable_ops.h" #include "tensorflow/core/lib/hash/hash.h" #include "tensorflow/core/platform/env.h" #include "tensorflow/core/platform/logging.h" diff --git a/tensorflow/compiler/jit/xla_launch_util.h b/tensorflow/compiler/jit/xla_launch_util.h index 554227f09d..c915b7118d 100644 --- a/tensorflow/compiler/jit/xla_launch_util.h +++ b/tensorflow/compiler/jit/xla_launch_util.h @@ -26,9 +26,9 @@ limitations under the License. #include "tensorflow/compiler/xla/service/device_memory_allocator.h" #include "tensorflow/compiler/xla/service/owning_device_memory.h" #include "tensorflow/core/framework/allocation_description.pb.h" +#include "tensorflow/core/framework/resource_var.h" #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/framework/types.h" -#include "tensorflow/core/kernels/variable_ops.h" #include "tensorflow/core/lib/core/status.h" #include "tensorflow/core/lib/gtl/array_slice.h" -- GitLab From cc5cb4fbcef93dcde6d8b3e877333412e133e675 Mon Sep 17 00:00:00 2001 From: James Ring Date: Fri, 22 Feb 2019 10:59:36 -0800 Subject: [PATCH 355/766] Update java release version (1.13.0rc2) PiperOrigin-RevId: 235221008 --- 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/spark-tensorflow-connector/pom.xml | 2 +- tensorflow/java/maven/tensorflow-hadoop/pom.xml | 2 +- tensorflow/java/maven/tensorflow/pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tensorflow/java/maven/libtensorflow/pom.xml b/tensorflow/java/maven/libtensorflow/pom.xml index 4d9f629fa2..7e04af42be 100644 --- a/tensorflow/java/maven/libtensorflow/pom.xml +++ b/tensorflow/java/maven/libtensorflow/pom.xml @@ -6,7 +6,7 @@ org.tensorflow parentpom - 1.13.0-rc0 + 1.13.0-rc2 ../ libtensorflow diff --git a/tensorflow/java/maven/libtensorflow_jni/pom.xml b/tensorflow/java/maven/libtensorflow_jni/pom.xml index 49a4359249..8a4d471fa4 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.13.0-rc0 + 1.13.0-rc2 ../ libtensorflow_jni diff --git a/tensorflow/java/maven/libtensorflow_jni_gpu/pom.xml b/tensorflow/java/maven/libtensorflow_jni_gpu/pom.xml index b54da039e5..2e2625641d 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.13.0-rc0 + 1.13.0-rc2 ../ libtensorflow_jni_gpu diff --git a/tensorflow/java/maven/pom.xml b/tensorflow/java/maven/pom.xml index 8dfb5d7aa3..2cd600541d 100644 --- a/tensorflow/java/maven/pom.xml +++ b/tensorflow/java/maven/pom.xml @@ -6,7 +6,7 @@ 4.0.0 org.tensorflow parentpom - 1.13.0-rc0 + 1.13.0-rc2 pom https://www.tensorflow.org diff --git a/tensorflow/java/maven/proto/pom.xml b/tensorflow/java/maven/proto/pom.xml index 65f545bdde..b79ee472c3 100644 --- a/tensorflow/java/maven/proto/pom.xml +++ b/tensorflow/java/maven/proto/pom.xml @@ -6,7 +6,7 @@ org.tensorflow parentpom - 1.13.0-rc0 + 1.13.0-rc2 ../ proto diff --git a/tensorflow/java/maven/spark-tensorflow-connector/pom.xml b/tensorflow/java/maven/spark-tensorflow-connector/pom.xml index 1ca1e91891..70091ad157 100644 --- a/tensorflow/java/maven/spark-tensorflow-connector/pom.xml +++ b/tensorflow/java/maven/spark-tensorflow-connector/pom.xml @@ -6,7 +6,7 @@ org.tensorflow spark-tensorflow-connector_2.11 jar - 1.13.0-rc0 + 1.13.0-rc2 spark-tensorflow-connector https://www.tensorflow.org TensorFlow TFRecord connector for Apache Spark DataFrames diff --git a/tensorflow/java/maven/tensorflow-hadoop/pom.xml b/tensorflow/java/maven/tensorflow-hadoop/pom.xml index df86c50f86..94d6801cda 100644 --- a/tensorflow/java/maven/tensorflow-hadoop/pom.xml +++ b/tensorflow/java/maven/tensorflow-hadoop/pom.xml @@ -5,7 +5,7 @@ org.tensorflow tensorflow-hadoop jar - 1.13.0-rc0 + 1.13.0-rc2 tensorflow-hadoop https://www.tensorflow.org TensorFlow TFRecord InputFormat/OutputFormat for Apache Hadoop diff --git a/tensorflow/java/maven/tensorflow/pom.xml b/tensorflow/java/maven/tensorflow/pom.xml index a318e7fa08..5f78f89b93 100644 --- a/tensorflow/java/maven/tensorflow/pom.xml +++ b/tensorflow/java/maven/tensorflow/pom.xml @@ -6,7 +6,7 @@ org.tensorflow parentpom - 1.13.0-rc0 + 1.13.0-rc2 ../ tensorflow -- GitLab From b5b52ec370374adcab63db5e1c46ef4897e83ccc Mon Sep 17 00:00:00 2001 From: Pavithra Vijay Date: Fri, 22 Feb 2019 11:19:25 -0800 Subject: [PATCH 356/766] - Disable `run_eagerly` and distribution strategy if there are symbolic tensors added to the model using `add_metric` or `add_loss`. - Added error message for when subclassed model/Sequential model without input_shape is used with DS. - Add error message when `run_eagerly` is enabled with DS. PiperOrigin-RevId: 235225004 --- .../contrib/distribute/python/keras_test.py | 22 ----- .../distribute/python/keras_utils_test.py | 94 +++++++++++++++++++ tensorflow/python/keras/engine/base_layer.py | 10 ++ .../python/keras/engine/base_layer_utils.py | 9 +- tensorflow/python/keras/engine/training.py | 30 ++++++ .../python/keras/engine/training_test.py | 91 ++++++++++++------ 6 files changed, 201 insertions(+), 55 deletions(-) diff --git a/tensorflow/contrib/distribute/python/keras_test.py b/tensorflow/contrib/distribute/python/keras_test.py index 77e241974f..d1e991ec24 100644 --- a/tensorflow/contrib/distribute/python/keras_test.py +++ b/tensorflow/contrib/distribute/python/keras_test.py @@ -1282,28 +1282,6 @@ class TestDistributionStrategyWithKerasModels(test.TestCase, model.predict(inputs, steps=1) model.evaluate(inputs, targets, steps=1) - # TODO(b/124377929): Remove error assertions once subclassed models - # are supported in DistributedStrategy. - @combinations.generate(all_strategy_combinations_minus_default()) - def test_distribution_strategy_on_subclassed_model(self, distribution): - with distribution.scope(): - model = simple_subclassed_model() - optimizer = rmsprop.RMSPropOptimizer(learning_rate=0.001) - loss = 'mse' - model.compile(optimizer, loss) - - inputs = np.zeros((64, 3), dtype=np.float32) - targets = np.zeros((64, 2), dtype=np.float32) - - with self.assertRaisesRegexp(AttributeError, 'has no attribute'): - model.fit(inputs, targets, epochs=1, steps_per_epoch=2) - - with self.assertRaisesRegexp(AttributeError, 'has no attribute'): - model.predict(inputs, steps=1) - - with self.assertRaisesRegexp(AttributeError, 'has no attribute'): - model.evaluate(inputs, targets, steps=1) - @combinations.generate(all_strategy_combinations_minus_default()) def test_distribution_strategy_one_dimensional(self, distribution): with distribution.scope(): diff --git a/tensorflow/contrib/distribute/python/keras_utils_test.py b/tensorflow/contrib/distribute/python/keras_utils_test.py index 36eaee77f2..7a3e47f895 100644 --- a/tensorflow/contrib/distribute/python/keras_utils_test.py +++ b/tensorflow/contrib/distribute/python/keras_utils_test.py @@ -34,6 +34,7 @@ from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes from tensorflow.python.keras.engine import distributed_training_utils from tensorflow.python.keras.optimizer_v2 import gradient_descent as gradient_descent_keras +from tensorflow.python.ops import math_ops from tensorflow.python.training import gradient_descent @@ -318,6 +319,99 @@ class TestDistributionStrategyErrorCases(test.TestCase, parameterized.TestCase): verbose=0, callbacks=[keras.callbacks.ReduceLROnPlateau()]) + @combinations.generate( + combinations.combine( + distribution=[combinations.one_device_strategy], mode=['graph'])) + def test_distribution_strategy_with_add_metric_add_loss(self, distribution): + with distribution.scope(): + x = keras.layers.Input(shape=(1,)) + y = keras.layers.Dense(1, kernel_initializer='ones')(x) + + err_msg = ( + 'We currently do not support compiling the model with distribution ' + r'strategy if `model.add_loss\(tensor\)` or ' + r'`model.add_metric\(tensor\)` has been called.') + + # Test with add_metric. + model = keras.models.Model(x, y) + model.add_metric( + math_ops.reduce_sum(y), name='metric_1', aggregation='mean') + with self.assertRaisesRegex(ValueError, err_msg): + model.compile('sgd',) + + # Test with add_loss. + model = keras.models.Model(x, y) + model.add_loss(math_ops.reduce_mean(y)) + with self.assertRaisesRegex(ValueError, err_msg): + model.compile('sgd',) + + @combinations.generate( + combinations.combine( + distribution=[combinations.one_device_strategy], mode=['eager'])) + def test_distribution_strategy_with_run_eagerly(self, distribution): + with distribution.scope(): + x = keras.layers.Input(shape=(1,)) + y = keras.layers.Dense(1, kernel_initializer='ones')(x) + model = keras.models.Model(x, y) + + err_msg = ('We currently do not support enabling `run_eagerly` with ' + 'distribution strategy.') + with self.assertRaisesRegex(ValueError, err_msg): + model.compile('sgd', run_eagerly=True) + + # TODO(b/124377929): Remove error assertions once subclassed models + # are supported in DistributedStrategy. + @combinations.generate( + combinations.combine( + distribution=[ + combinations.mirrored_strategy_with_gpu_and_cpu, + combinations.core_mirrored_strategy_with_gpu_and_cpu + ], + mode=['graph', 'eager'])) + def test_distribution_strategy_on_subclassed_model(self, distribution): + with distribution.scope(): + + class _SimpleMLP(keras.Model): + + def __init__(self, num_labels): + super(_SimpleMLP, self).__init__() + self.dense = keras.layers.Dense(num_labels) + + def call(self, inputs): + return self.dense(inputs) + + model = _SimpleMLP(3) + + with self.assertRaisesRegexp( + ValueError, + 'We currently do not support distribution strategy with a ' + '`Sequential` model that is created without ' + '`input_shape`/`input_dim` set in its first layer or ' + 'a subclassed model.'): + model.compile('sgd') + + @combinations.generate( + combinations.combine( + distribution=[ + combinations.mirrored_strategy_with_gpu_and_cpu, + combinations.core_mirrored_strategy_with_gpu_and_cpu + ], + mode=['graph', 'eager'])) + def test_distribution_strategy_on_deferred_sequential_model( + self, distribution): + with distribution.scope(): + model = keras.models.Sequential() + model.add(keras.layers.Dense(16, activation='relu')) + model.add(keras.layers.Dense(3, activation='softmax')) + + with self.assertRaisesRegexp( + ValueError, + 'We currently do not support distribution strategy with a ' + '`Sequential` model that is created without ' + '`input_shape`/`input_dim` set in its first layer or ' + 'a subclassed model.'): + model.compile('sgd') + class TestDistributionStrategyWithLossMasking(test.TestCase, parameterized.TestCase): diff --git a/tensorflow/python/keras/engine/base_layer.py b/tensorflow/python/keras/engine/base_layer.py index 9730ecbef5..49f3031755 100644 --- a/tensorflow/python/keras/engine/base_layer.py +++ b/tensorflow/python/keras/engine/base_layer.py @@ -214,6 +214,12 @@ class Layer(trackable.Trackable): else: self._initial_weights = None + # This flag is used to keep track of whether symbolic tensors are added to + # the model outside of the call context. This is required for disabling + # `run_eagerly` on compile. + # TODO(b/124303407): Remove this flag after we add support for the use case. + self._contains_symbolic_tensors = False + def build(self, input_shape): """Creates the variables of the layer (optional, for subclass implementers). @@ -763,6 +769,8 @@ class Layer(trackable.Trackable): # Ignoring constant values as this does not affect the gradients. return if tf_utils.is_symbolic_tensor(loss): + if not base_layer_utils.is_in_call_context(): + self._contains_symbolic_tensors = True self._losses.append(_tag_unconditional(loss)) else: self._eager_losses.append(_tag_unconditional(loss)) @@ -1337,6 +1345,8 @@ class Layer(trackable.Trackable): self._metrics.append(metric_obj) def _symbolic_add_metric(self, value, aggregation=None, name=None): + if not base_layer_utils.is_in_call_context(): + self._contains_symbolic_tensors = True if aggregation is None: # Iterate over the metrics and check if the given metric exists already. # This can happen when a metric instance is created in subclassed model diff --git a/tensorflow/python/keras/engine/base_layer_utils.py b/tensorflow/python/keras/engine/base_layer_utils.py index 19143dbbdf..3ae1100d16 100644 --- a/tensorflow/python/keras/engine/base_layer_utils.py +++ b/tensorflow/python/keras/engine/base_layer_utils.py @@ -308,7 +308,7 @@ def needs_keras_history(tensors): Bool, whether at least one Tensor needs to be wrapped. """ input_tensors = nest.flatten(tensors) - if getattr(_call_context, 'in_call', False) or all( + if is_in_call_context() or all( getattr(tensor, '_keras_history', None) is not None for tensor in input_tensors): # KerasHistory already set. @@ -316,6 +316,11 @@ def needs_keras_history(tensors): return uses_keras_history(tensors) +def is_in_call_context(): + """Returns true if inside of a model/layer '__call__'.""" + return getattr(_call_context, 'in_call', False) + + def uses_keras_history(tensors): """Check if at least one Tensor originates from a `keras.Input`. @@ -376,7 +381,7 @@ def mark_checked(tensors): @tf_contextlib.contextmanager def call_context(): """Scope that marks when we are currently inside a Layer/Model's `call`.""" - was_in_call = getattr(_call_context, 'in_call', False) + was_in_call = is_in_call_context() _call_context.in_call = True try: yield diff --git a/tensorflow/python/keras/engine/training.py b/tensorflow/python/keras/engine/training.py index 56f7b5499d..312b77faa8 100644 --- a/tensorflow/python/keras/engine/training.py +++ b/tensorflow/python/keras/engine/training.py @@ -206,6 +206,12 @@ class Model(Network): `optimizer`, `loss`, `metrics` or `sample_weight_mode`. """ run_eagerly = kwargs.pop('run_eagerly', None) + if run_eagerly and getattr(self, '_contains_symbolic_tensors', False): + raise ValueError( + 'We currently do not support enabling `run_eagerly` on compile if ' + '`model.add_loss(tensor)` or `model.add_metric(tensor)` ' + 'has been called.') + self._run_eagerly = run_eagerly optimizer = optimizers.get(optimizer) @@ -241,6 +247,23 @@ class Model(Network): raise ValueError('target_tensors is not supported with ' 'DistributionStrategy.') + if run_eagerly: + raise ValueError( + 'We currently do not support enabling `run_eagerly` with ' + 'distribution strategy.') + + if getattr(self, '_contains_symbolic_tensors', False): + raise ValueError( + 'We currently do not support compiling the model with distribution ' + 'strategy if `model.add_loss(tensor)` or `model.add_metric(tensor)`' + ' has been called.') + + if not self.built or not self.inputs or not self.outputs: + raise ValueError( + 'We currently do not support distribution strategy with a ' + '`Sequential` model that is created without `input_shape`/' + '`input_dim` set in its first layer or a subclassed model.') + loss = loss or {} self.optimizer = optimizer @@ -2736,10 +2759,17 @@ class Model(Network): # itself isn't dynamic. # Obtain symbolic outputs by calling the model. with K.get_graph().as_default(): + contains_symbolic_tensors = getattr( + self, '_contains_symbolic_tensors', False) if self._expects_training_arg: outputs = self.call(inputs, training=training) else: outputs = self.call(inputs) + # Reset to the previously saved value. If `call()` had `add_metric` + # or `add_loss`, then `_contains_symbolic_tensors` will have been set + # to True since we are not in `__call__` context. Hence we are + # resetting to the old value here. + self._contains_symbolic_tensors = contains_symbolic_tensors else: # Case: network's `call` is dynamic. try: diff --git a/tensorflow/python/keras/engine/training_test.py b/tensorflow/python/keras/engine/training_test.py index c83e669e9a..a9e17d4e9b 100644 --- a/tensorflow/python/keras/engine/training_test.py +++ b/tensorflow/python/keras/engine/training_test.py @@ -962,9 +962,6 @@ class TrainingTest(keras_parameterized.TestCase): @keras_parameterized.run_all_keras_modes def test_add_loss_correctness(self): - if testing_utils.should_run_eagerly(): - self.skipTest('b/124303407') - class Bias(keras.layers.Layer): def build(self, input_shape): @@ -987,16 +984,26 @@ class TrainingTest(keras_parameterized.TestCase): with keras.backend.get_graph().as_default(): model.add_loss(keras.losses.MeanAbsoluteError()(targets, outputs)) - model.compile( - keras.optimizer_v2.gradient_descent.SGD(0.033333), - loss=keras.losses.MeanAbsoluteError(), - target_tensors=[targets], - run_eagerly=testing_utils.should_run_eagerly()) + if testing_utils.should_run_eagerly(): + with self.assertRaisesRegex( + ValueError, + 'We currently do not support enabling `run_eagerly` on compile if ' + r'`model.add_loss\(tensor\)` or `model.add_metric\(tensor\)` ' + 'has been called.'): + model.compile('sgd', run_eagerly=True) + return + else: + model.compile( + keras.optimizer_v2.gradient_descent.SGD(0.033333), + loss=keras.losses.MeanAbsoluteError(), + target_tensors=[targets], + run_eagerly=False) - x = np.array([[0.], [1.], [2.]]) - y = np.array([[0.5], [2.], [3.5]]) - history = model.fit(x, y, batch_size=3, epochs=5) - self.assertAllClose(history.history['loss'], [3., 2.7, 2.4, 2.1, 1.8], 1e-3) + x = np.array([[0.], [1.], [2.]]) + y = np.array([[0.5], [2.], [3.5]]) + history = model.fit(x, y, batch_size=3, epochs=5) + self.assertAllClose(history.history['loss'], [3., 2.7, 2.4, 2.1, 1.8], + 1e-3) class TestExceptionsAndWarnings(keras_parameterized.TestCase): @@ -2423,9 +2430,6 @@ class TestTrainingWithMetrics(keras_parameterized.TestCase): @keras_parameterized.run_all_keras_modes def test_add_metric_with_tensor_on_model(self): - if testing_utils.should_run_eagerly(): - self.skipTest('b/124303407') - x = keras.layers.Input(shape=(1,)) y = keras.layers.Dense(1, kernel_initializer='ones')(x) model = keras.models.Model(x, y) @@ -2436,8 +2440,17 @@ class TestTrainingWithMetrics(keras_parameterized.TestCase): # (y_true, y_pred, sample_Weight) with keras.backend.get_graph().as_default(): model.add_metric(metrics_module.Mean(name='metric_2')(y)) - model.compile( - 'sgd', loss='mse', run_eagerly=testing_utils.should_run_eagerly()) + + if testing_utils.should_run_eagerly(): + with self.assertRaisesRegex( + ValueError, + 'We currently do not support enabling `run_eagerly` on compile if ' + r'`model.add_loss\(tensor\)` or `model.add_metric\(tensor\)` ' + 'has been called.'): + model.compile('sgd', run_eagerly=True) + return + else: + model.compile('sgd', loss='mse', run_eagerly=False) inputs = np.ones(shape=(10, 1)) targets = np.ones(shape=(10, 1)) @@ -2537,11 +2550,21 @@ class TestTrainingWithMetrics(keras_parameterized.TestCase): math_ops.reduce_sum(y), name='metric_1', aggregation='mean') with keras.backend.get_graph().as_default(): model.add_metric(metrics_module.Mean(name='metric_2')(y)) - model.compile( - 'sgd', - loss='mse', - metrics=[metrics_module.Accuracy('acc')], - run_eagerly=testing_utils.should_run_eagerly()) + + if testing_utils.should_run_eagerly(): + with self.assertRaisesRegex( + ValueError, + 'We currently do not support enabling `run_eagerly` on compile if ' + r'`model.add_loss\(tensor\)` or `model.add_metric\(tensor\)` ' + 'has been called.'): + model.compile('sgd', run_eagerly=True) + return + else: + model.compile( + 'sgd', + loss='mse', + metrics=[metrics_module.Accuracy('acc')], + run_eagerly=False) # Verify that the metrics added using `compile` and `add_metric` API are # included @@ -2693,9 +2716,6 @@ class TestTrainingWithMetrics(keras_parameterized.TestCase): @keras_parameterized.run_all_keras_modes def test_add_metric_correctness(self): - if testing_utils.should_run_eagerly(): - self.skipTest('b/124303407') - inputs = keras.Input(shape=(1,)) targets = keras.Input(shape=(1,)) @@ -2724,12 +2744,21 @@ class TestTrainingWithMetrics(keras_parameterized.TestCase): model.add_metric( metrics_module.MeanAbsoluteError(name='mae_3')(targets, outputs)) - model.compile( - loss='mae', - optimizer=keras.optimizer_v2.gradient_descent.SGD(0.1), - metrics=[metrics_module.MeanAbsoluteError(name='mae_4')], - target_tensors=[targets], - run_eagerly=testing_utils.should_run_eagerly()) + if testing_utils.should_run_eagerly(): + with self.assertRaisesRegex( + ValueError, + 'We currently do not support enabling `run_eagerly` on compile if ' + r'`model.add_loss\(tensor\)` or `model.add_metric\(tensor\)` ' + 'has been called.'): + model.compile('sgd', run_eagerly=True) + return + else: + model.compile( + loss='mae', + optimizer=keras.optimizer_v2.gradient_descent.SGD(0.1), + metrics=[metrics_module.MeanAbsoluteError(name='mae_4')], + target_tensors=[targets], + run_eagerly=False) x = np.array([[0.], [1.], [2.]]) y = np.array([[0.5], [2.], [3.5]]) -- GitLab From 8349a6130fe341288974a4405b0e18381b521023 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 22 Feb 2019 11:24:49 -0800 Subject: [PATCH 357/766] Block ~KernelAndDeviceOp() until the execution of deferred ops finishes. PiperOrigin-RevId: 235226116 --- .../common_runtime/eager/kernel_and_device.cc | 21 +++++++++++++++++++ .../common_runtime/eager/kernel_and_device.h | 12 +++++++++++ tensorflow/core/common_runtime/executor.cc | 8 ++++--- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/tensorflow/core/common_runtime/eager/kernel_and_device.cc b/tensorflow/core/common_runtime/eager/kernel_and_device.cc index 60b8075882..8ea45d5dfc 100644 --- a/tensorflow/core/common_runtime/eager/kernel_and_device.cc +++ b/tensorflow/core/common_runtime/eager/kernel_and_device.cc @@ -54,6 +54,16 @@ KernelAndDeviceFunc::~KernelAndDeviceFunc() { } } +KernelAndDeviceOp::~KernelAndDeviceOp() { + // Make sure that the device execution has finished before deleting cm_. + { + mutex_lock lock(num_deferred_ops_mu_); + while (num_deferred_ops_ > 0) { + no_deferred_ops_cv_.wait(lock); + } + } +} + Status KernelAndDeviceOp::Init(const NodeDef& ndef, GraphCollector* graph_collector) { OpKernel* k = nullptr; @@ -198,6 +208,17 @@ Status KernelAndDeviceOp::Run(ScopedStepContainer* step_container, params.cancellation_manager = &cm_; cm_.Reset(); params.log_memory = log_memory_; + params.inc_num_deferred_ops_function = [this]() { + mutex_lock lock(num_deferred_ops_mu_); + num_deferred_ops_++; + }; + params.dec_num_deferred_ops_function = [this]() { + mutex_lock lock(num_deferred_ops_mu_); + num_deferred_ops_--; + if (num_deferred_ops_ == 0) { + no_deferred_ops_cv_.notify_all(); + } + }; std::unique_ptr step_stats_collector; if (stats != nullptr) { step_stats_collector.reset(new StepStatsCollector(step_stats)); diff --git a/tensorflow/core/common_runtime/eager/kernel_and_device.h b/tensorflow/core/common_runtime/eager/kernel_and_device.h index 027168dd6e..6f95f2e88f 100644 --- a/tensorflow/core/common_runtime/eager/kernel_and_device.h +++ b/tensorflow/core/common_runtime/eager/kernel_and_device.h @@ -72,6 +72,7 @@ class KernelAndDevice { default_runner_([](std::function f) { f(); }), collective_executor_(std::move(collective_executor)) {} + // Not thread safe. virtual ~KernelAndDevice() {} // TODO(ashankar): Handle list-valued inputs. @@ -135,6 +136,8 @@ class KernelAndDeviceOp final : public KernelAndDevice { rendez_(rendez), log_memory_(log_memory) {} + virtual ~KernelAndDeviceOp(); + Status Init(const NodeDef& ndef, GraphCollector* graph_collector) override; using KernelAndDevice::Run; @@ -162,6 +165,15 @@ class KernelAndDeviceOp final : public KernelAndDevice { Rendezvous* const rendez_; checkpoint::TensorSliceReaderCacheWrapper slice_reader_cache_; const bool log_memory_; + + // For deferred ops, AsyncOpKernel::DoneCallback is called once the op is + // enqueued to device. The execution of the op may not finish when + // device_->Compute returns. We rely on no_deferred_ops_cv_ to know when the + // execution has finished. + // Available via OpKernelContext to every OpKernel invocation. + mutex num_deferred_ops_mu_; + condition_variable no_deferred_ops_cv_; + int64 num_deferred_ops_ GUARDED_BY(num_deferred_ops_mu_) = 0; }; // Represents a multi-device function. Functions can also be run using diff --git a/tensorflow/core/common_runtime/executor.cc b/tensorflow/core/common_runtime/executor.cc index e4022ad3af..ad3049b67b 100644 --- a/tensorflow/core/common_runtime/executor.cc +++ b/tensorflow/core/common_runtime/executor.cc @@ -1280,7 +1280,7 @@ class ExecutorState { // Available via OpKernelContext to every OpKernel invocation. mutex num_deferred_ops_mu_; - condition_variable num_deferred_ops_cv_; + condition_variable no_deferred_ops_cv_; int64 num_deferred_ops_ GUARDED_BY(num_deferred_ops_mu_) = 0; mutex mu_; @@ -1648,7 +1648,9 @@ void ExecutorState::Process(TaggedNode tagged_node, int64 scheduled_nsec) { params.dec_num_deferred_ops_function = [this]() { mutex_lock lock(num_deferred_ops_mu_); num_deferred_ops_--; - num_deferred_ops_cv_.notify_all(); + if (num_deferred_ops_ == 0) { + no_deferred_ops_cv_.notify_all(); + } }; Status s; @@ -2478,7 +2480,7 @@ void ExecutorState::Finish() { { mutex_lock lock(num_deferred_ops_mu_); while (num_deferred_ops_ > 0) { - num_deferred_ops_cv_.wait(lock); + no_deferred_ops_cv_.wait(lock); } } -- GitLab From 56756d99a00eb54768985a27dd7739d37f83a3d7 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 22 Feb 2019 11:27:16 -0800 Subject: [PATCH 358/766] Work around missing support for complex multiplication in Cuda on Windows. PiperOrigin-RevId: 235226607 --- tensorflow/core/kernels/reduction_gpu_kernels.cu.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tensorflow/core/kernels/reduction_gpu_kernels.cu.h b/tensorflow/core/kernels/reduction_gpu_kernels.cu.h index 1cc695878f..d6e6e45418 100644 --- a/tensorflow/core/kernels/reduction_gpu_kernels.cu.h +++ b/tensorflow/core/kernels/reduction_gpu_kernels.cu.h @@ -40,13 +40,6 @@ namespace functor { typedef Eigen::GpuDevice GPUDevice; -template -struct Square { - __host__ __device__ T operator()(const T& a) const { - return a * Eigen::numext::conj(a); - } -}; - template struct Sqrt { __host__ __device__ T operator()(const T& a) const { @@ -112,6 +105,13 @@ struct Prod> { } }; +template +struct Square { + __host__ __device__ T operator()(const T& a) const { + return Prod()(a, Eigen::numext::conj(a)); + } +}; + template struct DividesBy { T divisor; -- GitLab From f513d6abeca4bbeb8f245d95dddc721e2cfb453d Mon Sep 17 00:00:00 2001 From: Francois Chollet Date: Fri, 22 Feb 2019 11:28:14 -0800 Subject: [PATCH 359/766] Fix progbar formatting. PiperOrigin-RevId: 235226794 --- tensorflow/python/keras/utils/generic_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/keras/utils/generic_utils.py b/tensorflow/python/keras/utils/generic_utils.py index 85457720dc..f5a3203f3b 100644 --- a/tensorflow/python/keras/utils/generic_utils.py +++ b/tensorflow/python/keras/utils/generic_utils.py @@ -392,7 +392,7 @@ class Progbar(object): if self.target is not None: numdigits = int(np.log10(self.target)) + 1 - bar = ('%' + str(numdigits) + 'd/%d') % (current, self.target) + bar = ('%' + str(numdigits) + 'd/%d [') % (current, self.target) prog = float(current) / self.target prog_width = int(self.width * prog) if prog_width > 0: -- GitLab From 254b798b16ecd3282e24af6893c79383698af647 Mon Sep 17 00:00:00 2001 From: Derek Murray Date: Fri, 22 Feb 2019 11:37:47 -0800 Subject: [PATCH 360/766] [tf.data] Use `get_legacy_output_classes()` in `ConcatenateDataset` and `WindowDataset`. PiperOrigin-RevId: 235228690 --- tensorflow/python/data/ops/dataset_ops.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/data/ops/dataset_ops.py b/tensorflow/python/data/ops/dataset_ops.py index 2226c8e00d..91a07a5534 100644 --- a/tensorflow/python/data/ops/dataset_ops.py +++ b/tensorflow/python/data/ops/dataset_ops.py @@ -2540,7 +2540,7 @@ class ConcatenateDataset(DatasetV2): "Two datasets to concatenate have different types %s and %s" % (output_types, get_legacy_output_types(dataset_to_concatenate))) - output_classes = input_dataset.output_classes + output_classes = get_legacy_output_classes(input_dataset) if output_classes != get_legacy_output_classes(dataset_to_concatenate): raise TypeError( "Two datasets to concatenate have different classes %s and %s" % @@ -3209,7 +3209,7 @@ class WindowDataset(UnaryDataset): self._drop_remainder = ops.convert_to_tensor( drop_remainder, dtype=dtypes.bool, name="drop_remainder") nest_of_structures = nest.pack_sequence_as( - input_dataset.output_classes, + get_legacy_output_classes(input_dataset), [ DatasetStructure(structure_lib.convert_legacy_structure( output_type, output_shape, output_class)) -- GitLab From 590d8b5630d0f1002fbb5585990cb061445e1096 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 22 Feb 2019 11:39:19 -0800 Subject: [PATCH 361/766] Internal change PiperOrigin-RevId: 235228999 --- tensorflow/python/BUILD | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tensorflow/python/BUILD b/tensorflow/python/BUILD index 1f2ea28f38..04a740e1f3 100644 --- a/tensorflow/python/BUILD +++ b/tensorflow/python/BUILD @@ -271,7 +271,10 @@ tf_py_test( ":client_testlib", ":platform", ], - tags = ["no_windows"], + tags = [ + "no_windows", + "nomac", + ], ) tf_py_test( @@ -279,10 +282,7 @@ tf_py_test( size = "small", srcs = ["platform/app_test.py"], additional_deps = [":platform"], - tags = [ - "nomac", - "notap", - ], + tags = ["notap"], ) cc_library( -- GitLab From fa52a2027fb361b3fc3e31e3b1fd658e1309ad29 Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Fri, 22 Feb 2019 11:40:12 -0800 Subject: [PATCH 362/766] Patch Simple/Union/Slurm/TPUClusterResolver to use the new num_accelerators function prototype. PiperOrigin-RevId: 235229164 --- .../python/collective_all_reduce_strategy.py | 2 +- .../collective_all_reduce_strategy_test.py | 4 ++-- .../python/parameter_server_strategy.py | 2 +- .../python/parameter_server_strategy_test.py | 4 ++-- .../cluster_resolver/cluster_resolver.py | 16 ++++++++-------- .../cluster_resolver/cluster_resolver_test.py | 12 ++++++------ .../cluster_resolver/slurm_cluster_resolver.py | 5 ++--- .../cluster_resolver/tpu_cluster_resolver.py | 8 +++----- .../tpu_cluster_resolver_test.py | 2 +- .../distribute/collective_all_reduce_strategy.py | 9 +++++---- .../distribute/parameter_server_strategy.py | 9 +++++---- ...uster_resolver.-simple-cluster-resolver.pbtxt | 4 ++-- ...luster_resolver.-slurm-cluster-resolver.pbtxt | 2 +- ...luster_resolver.-t-p-u-cluster-resolver.pbtxt | 2 +- ...ribute.cluster_resolver.-union-resolver.pbtxt | 2 +- ...uster_resolver.-simple-cluster-resolver.pbtxt | 4 ++-- ...luster_resolver.-slurm-cluster-resolver.pbtxt | 2 +- ...luster_resolver.-t-p-u-cluster-resolver.pbtxt | 2 +- ...ribute.cluster_resolver.-union-resolver.pbtxt | 2 +- 19 files changed, 46 insertions(+), 47 deletions(-) diff --git a/tensorflow/contrib/distribute/python/collective_all_reduce_strategy.py b/tensorflow/contrib/distribute/python/collective_all_reduce_strategy.py index 1974162798..cc09a1a723 100644 --- a/tensorflow/contrib/distribute/python/collective_all_reduce_strategy.py +++ b/tensorflow/contrib/distribute/python/collective_all_reduce_strategy.py @@ -65,6 +65,6 @@ class CollectiveAllReduceExtended( cluster_spec=tfconfig.cluster_spec(), task_type=tfconfig.task_type, task_id=tfconfig.task_id, - num_accelerators=num_gpus_per_worker) + num_accelerators={"GPU": num_gpus_per_worker}) super(CollectiveAllReduceExtended, self).__init__( container_strategy, cluster_resolver=cluster_resolver) diff --git a/tensorflow/contrib/distribute/python/collective_all_reduce_strategy_test.py b/tensorflow/contrib/distribute/python/collective_all_reduce_strategy_test.py index ee7640dd1c..50f3b83eb4 100644 --- a/tensorflow/contrib/distribute/python/collective_all_reduce_strategy_test.py +++ b/tensorflow/contrib/distribute/python/collective_all_reduce_strategy_test.py @@ -79,11 +79,11 @@ def create_test_objects(cluster_spec=None, cluster_spec=multi_worker_util.normalize_cluster_spec(cluster_spec), task_type=task_type, task_id=task_id, - num_accelerators=num_gpus) + num_accelerators={'GPU': num_gpus}) target = 'grpc://' + cluster_spec[task_type][task_id] else: cluster_resolver = SimpleClusterResolver( - ClusterSpec({}), num_accelerators=num_gpus) + ClusterSpec({}), num_accelerators={'GPU': num_gpus}) target = '' strategy = MockCollectiveAllReduceStrategy(cluster_resolver) diff --git a/tensorflow/contrib/distribute/python/parameter_server_strategy.py b/tensorflow/contrib/distribute/python/parameter_server_strategy.py index e42bc50fdc..be86332225 100644 --- a/tensorflow/contrib/distribute/python/parameter_server_strategy.py +++ b/tensorflow/contrib/distribute/python/parameter_server_strategy.py @@ -158,7 +158,7 @@ class ParameterServerExtended(CoreParameterServerExtended): cluster_spec=tfconfig.cluster_spec(), task_type=tfconfig.task_type, task_id=tfconfig.task_id, - num_accelerators=num_gpus_per_worker) + num_accelerators={'GPU': num_gpus_per_worker}) super(ParameterServerExtended, self).__init__( container_strategy, cluster_resolver=cluster_resolver) diff --git a/tensorflow/contrib/distribute/python/parameter_server_strategy_test.py b/tensorflow/contrib/distribute/python/parameter_server_strategy_test.py index 3de2041ae3..18f6904959 100644 --- a/tensorflow/contrib/distribute/python/parameter_server_strategy_test.py +++ b/tensorflow/contrib/distribute/python/parameter_server_strategy_test.py @@ -91,11 +91,11 @@ def create_test_objects(cluster_spec=None, cluster_spec=multi_worker_util.normalize_cluster_spec(cluster_spec), task_type=task_type, task_id=task_id, - num_accelerators=num_gpus) + num_accelerators={'GPU': num_gpus}) target = 'grpc://' + cluster_spec[WORKER][task_id] else: cluster_resolver = SimpleClusterResolver( - ClusterSpec({}), num_accelerators=num_gpus) + ClusterSpec({}), num_accelerators={'GPU': num_gpus}) target = '' distribution = MockCoreParameterServerStrategy(cluster_resolver) diff --git a/tensorflow/python/distribute/cluster_resolver/cluster_resolver.py b/tensorflow/python/distribute/cluster_resolver/cluster_resolver.py index 22b93f0330..e269040665 100644 --- a/tensorflow/python/distribute/cluster_resolver/cluster_resolver.py +++ b/tensorflow/python/distribute/cluster_resolver/cluster_resolver.py @@ -160,7 +160,7 @@ class SimpleClusterResolver(ClusterResolver): """Simple implementation of ClusterResolver that accepts a ClusterSpec.""" def __init__(self, cluster_spec, master='', task_type=None, task_id=None, - environment='', num_accelerators=0, + environment='', num_accelerators=None, rpc_layer=None): """Creates a SimpleClusterResolver from a ClusterSpec.""" super(SimpleClusterResolver, self).__init__() @@ -168,6 +168,7 @@ class SimpleClusterResolver(ClusterResolver): self._task_type = task_type self._task_id = task_id self._environment = environment + self._num_accelerators = num_accelerators self._rpc_layer = rpc_layer @@ -227,23 +228,23 @@ class SimpleClusterResolver(ClusterResolver): def num_accelerators(self, task_type=None, task_id=None, - accelerator_type='GPU', config_proto=None): """Returns the number of accelerator cores per worker. The SimpleClusterResolver does not do automatic detection of accelerators, so a TensorFlow session will never be created, and thus all arguments are - unused and we simply return whatever was passed in when this object was - initialized. + unused and we simply assume that the type of accelerator is a GPU and return + the value in provided to us in the constructor. Args: task_type: Unused. task_id: Unused. - accelerator_type: Unused. config_proto: Unused. """ # Unused - del task_type, task_id, accelerator_type, config_proto + del task_type, task_id, config_proto + if self._num_accelerators is None: + return {} return self._num_accelerators @property @@ -419,10 +420,9 @@ class UnionClusterResolver(ClusterResolver): def num_accelerators(self, task_type=None, task_id=None, - accelerator_type='GPU', config_proto=None): return self._cluster_resolvers[0].num_accelerators( - task_type, task_id, accelerator_type, config_proto) + task_type, task_id, config_proto) @property def rpc_layer(self): diff --git a/tensorflow/python/distribute/cluster_resolver/cluster_resolver_test.py b/tensorflow/python/distribute/cluster_resolver/cluster_resolver_test.py index 019d223eb0..9978cb47c1 100644 --- a/tensorflow/python/distribute/cluster_resolver/cluster_resolver_test.py +++ b/tensorflow/python/distribute/cluster_resolver/cluster_resolver_test.py @@ -122,13 +122,13 @@ class UnionClusterResolverTest(test.TestCase): simple_resolver = SimpleClusterResolver(base_cluster_spec, task_type="ps", task_id=1, environment="cloud", - num_accelerators=8, + num_accelerators={"GPU": 8}, rpc_layer="grpc") self.assertEqual(simple_resolver.task_type, "ps") self.assertEqual(simple_resolver.task_id, 1) self.assertEqual(simple_resolver.environment, "cloud") - self.assertEqual(simple_resolver.num_accelerators(), 8) + self.assertEqual(simple_resolver.num_accelerators(), {"GPU": 8}) self.assertEqual(simple_resolver.rpc_layer, "grpc") def testOverrideSimpleClusterResolver(self): @@ -139,7 +139,7 @@ class UnionClusterResolverTest(test.TestCase): simple_resolver = SimpleClusterResolver(base_cluster_spec, task_type="ps", task_id=1, environment="cloud", - num_accelerators=8, + num_accelerators={"GPU": 8}, rpc_layer="grpc") simple_resolver.task_type = "worker" @@ -187,7 +187,7 @@ class UnionClusterResolverTest(test.TestCase): }) resolver1 = SimpleClusterResolver(cluster_spec_1, task_type="ps", task_id=1, environment="cloud", - num_accelerators=8, + num_accelerators={"GPU": 8}, rpc_layer="grpc") cluster_spec_2 = server_lib.ClusterSpec({ @@ -196,7 +196,7 @@ class UnionClusterResolverTest(test.TestCase): }) resolver2 = SimpleClusterResolver(cluster_spec_2, task_type="worker", task_id=2, environment="local", - num_accelerators=16, + num_accelerators={"GPU": 16}, rpc_layer="http") union_resolver = UnionClusterResolver(resolver1, resolver2) @@ -204,7 +204,7 @@ class UnionClusterResolverTest(test.TestCase): self.assertEqual(union_resolver.task_type, "ps") self.assertEqual(union_resolver.task_id, 1) self.assertEqual(union_resolver.environment, "cloud") - self.assertEqual(union_resolver.num_accelerators(), 8) + self.assertEqual(union_resolver.num_accelerators(), {"GPU": 8}) self.assertEqual(union_resolver.rpc_layer, "grpc") union_resolver.task_type = "worker" diff --git a/tensorflow/python/distribute/cluster_resolver/slurm_cluster_resolver.py b/tensorflow/python/distribute/cluster_resolver/slurm_cluster_resolver.py index 04675f4d17..0e49cebee2 100644 --- a/tensorflow/python/distribute/cluster_resolver/slurm_cluster_resolver.py +++ b/tensorflow/python/distribute/cluster_resolver/slurm_cluster_resolver.py @@ -220,8 +220,7 @@ class SlurmClusterResolver(ClusterResolver): def num_accelerators(self, task_type=None, task_id=None, - accelerator_type='GPU', config_proto=None): # Unused, since this is set in __init__ manually. - del task_type, task_id, accelerator_type, config_proto - return self._gpus_per_node + del task_type, task_id, config_proto + return {'GPU': self._gpus_per_node} diff --git a/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver.py b/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver.py index abf628be82..364e7d715e 100644 --- a/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver.py +++ b/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver.py @@ -460,7 +460,6 @@ class TPUClusterResolver(ClusterResolver): def num_accelerators(self, task_type=None, task_id=None, - accelerator_type='TPU', config_proto=None): """Returns the number of TPU cores per worker. @@ -471,7 +470,6 @@ class TPUClusterResolver(ClusterResolver): Args: task_type: Unused. task_id: Unused. - accelerator_type: Unused. config_proto: Used to create a connection to a TPU master in order to retrieve the system metadata. @@ -498,9 +496,9 @@ class TPUClusterResolver(ClusterResolver): raise RuntimeError(error_message) if device_details.total_cores: - return TPUClusterResolver._verify_and_return_same_core_count( - device_details.device_map) - return 0 + return {'TPU': TPUClusterResolver._verify_and_return_same_core_count( + device_details.device_map)} + return {'TPU': 0} @property def environment(self): diff --git a/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver_test.py b/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver_test.py index 7f06dc168b..48119e63b1 100644 --- a/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver_test.py +++ b/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver_test.py @@ -661,7 +661,7 @@ class TPUClusterResolverTest(test.TestCase): mock_list_devices.return_value = device_list resolver = TPUClusterResolver(tpu='') - self.assertEqual(resolver.num_accelerators(), 2) + self.assertEqual(resolver.num_accelerators(), {'TPU': 2}) @mock.patch.object(session.BaseSession, 'list_devices') @mock.patch.object(TPUClusterResolver, diff --git a/tensorflow/python/distribute/collective_all_reduce_strategy.py b/tensorflow/python/distribute/collective_all_reduce_strategy.py index a9d092784d..2cf22d29bc 100644 --- a/tensorflow/python/distribute/collective_all_reduce_strategy.py +++ b/tensorflow/python/distribute/collective_all_reduce_strategy.py @@ -83,14 +83,15 @@ class CollectiveAllReduceExtended(mirrored_strategy.MirroredExtended): else: self._initialize_local(cluster_resolver) # Save the num_gpus_per_worker for configure method. - self._num_gpus_per_worker = cluster_resolver.num_accelerators() + self._num_gpus_per_worker = ( + cluster_resolver.num_accelerators().get("GPU", 0)) def _initialize_local(self, cluster_resolver): """Initializes the object for local training.""" self._is_chief = True self._num_workers = 1 - num_gpus = cluster_resolver.num_accelerators() + num_gpus = cluster_resolver.num_accelerators().get("GPU", 0) if num_gpus: local_devices = tuple("/device:GPU:%d" % i for i in range(num_gpus)) else: @@ -118,7 +119,7 @@ class CollectiveAllReduceExtended(mirrored_strategy.MirroredExtended): # TODO(yuefengz): The `num_gpus` is only for this particular task. It # assumes all workers have the same number of GPUs. We should remove this # assumption by querying all tasks for their numbers of GPUs. - num_gpus = cluster_resolver.num_accelerators() + num_gpus = cluster_resolver.num_accelerators().get("GPU", 0) cluster_spec = multi_worker_util.normalize_cluster_spec( cluster_resolver.cluster_spec()) task_type = cluster_resolver.task_type @@ -310,7 +311,7 @@ class CollectiveAllReduceExtended(mirrored_strategy.MirroredExtended): cluster_spec=multi_worker_util.normalize_cluster_spec(cluster_spec), task_type=task_type, task_id=task_id, - num_accelerators=self._num_gpus_per_worker) + num_accelerators={"GPU": self._num_gpus_per_worker}) self._initialize_multi_worker(cluster_resolver) assert isinstance(self._get_cross_device_ops(), cross_device_ops_lib.CollectiveAllReduce) diff --git a/tensorflow/python/distribute/parameter_server_strategy.py b/tensorflow/python/distribute/parameter_server_strategy.py index 535327b9fe..342291d588 100644 --- a/tensorflow/python/distribute/parameter_server_strategy.py +++ b/tensorflow/python/distribute/parameter_server_strategy.py @@ -112,7 +112,8 @@ class ParameterServerStrategyExtended( else: self._initialize_local(cluster_resolver) # Save the num_gpus_per_worker for configure method. - self._num_gpus_per_worker = cluster_resolver.num_accelerators() + self._num_gpus_per_worker = ( + cluster_resolver.num_accelerators().get("GPU", 0)) def _initialize_multi_worker(self, cluster_resolver): """Initialize devices for multiple workers. @@ -129,7 +130,7 @@ class ParameterServerStrategyExtended( Raises: ValueError: if the cluster doesn't have ps jobs. """ - num_gpus = cluster_resolver.num_accelerators() + num_gpus = cluster_resolver.num_accelerators().get("GPU", 0) cluster_spec = cluster_resolver.cluster_spec() task_type = cluster_resolver.task_type task_id = cluster_resolver.task_id @@ -200,7 +201,7 @@ class ParameterServerStrategyExtended( """Initialize internal devices for local training.""" worker_device = device_util.canonicalize("/device:CPU:0") self._input_host_device = numpy_dataset.SingleDevice(worker_device) - num_gpus = cluster_resolver.num_accelerators() + num_gpus = cluster_resolver.num_accelerators().get("GPU", 0) # Define compute devices which is a list of device strings and one for each # replica. When there are GPUs, replicate operations on these GPUs. # Otherwise, place operations on CPU. @@ -467,7 +468,7 @@ class ParameterServerStrategyExtended( cluster_spec=multi_worker_util.normalize_cluster_spec(cluster_spec), task_type=task_type, task_id=task_id, - num_accelerators=self._num_gpus_per_worker) + num_accelerators={"GPU": self._num_gpus_per_worker}) self._initialize_multi_worker(cluster_resolver) if session_config: diff --git a/tensorflow/tools/api/golden/v1/tensorflow.distribute.cluster_resolver.-simple-cluster-resolver.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.distribute.cluster_resolver.-simple-cluster-resolver.pbtxt index 4e80e3af30..d39ddc7e40 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.distribute.cluster_resolver.-simple-cluster-resolver.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.distribute.cluster_resolver.-simple-cluster-resolver.pbtxt @@ -21,7 +21,7 @@ tf_class { } member_method { name: "__init__" - argspec: "args=[\'self\', \'cluster_spec\', \'master\', \'task_type\', \'task_id\', \'environment\', \'num_accelerators\', \'rpc_layer\'], varargs=None, keywords=None, defaults=[\'\', \'None\', \'None\', \'\', \'0\', \'None\'], " + argspec: "args=[\'self\', \'cluster_spec\', \'master\', \'task_type\', \'task_id\', \'environment\', \'num_accelerators\', \'rpc_layer\'], varargs=None, keywords=None, defaults=[\'\', \'None\', \'None\', \'\', \'None\', \'None\'], " } member_method { name: "cluster_spec" @@ -33,6 +33,6 @@ tf_class { } member_method { name: "num_accelerators" - argspec: "args=[\'self\', \'task_type\', \'task_id\', \'accelerator_type\', \'config_proto\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'GPU\', \'None\'], " + argspec: "args=[\'self\', \'task_type\', \'task_id\', \'config_proto\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'None\'], " } } diff --git a/tensorflow/tools/api/golden/v1/tensorflow.distribute.cluster_resolver.-slurm-cluster-resolver.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.distribute.cluster_resolver.-slurm-cluster-resolver.pbtxt index 971ea3dca4..b487626520 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.distribute.cluster_resolver.-slurm-cluster-resolver.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.distribute.cluster_resolver.-slurm-cluster-resolver.pbtxt @@ -25,6 +25,6 @@ tf_class { } member_method { name: "num_accelerators" - argspec: "args=[\'self\', \'task_type\', \'task_id\', \'accelerator_type\', \'config_proto\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'GPU\', \'None\'], " + argspec: "args=[\'self\', \'task_type\', \'task_id\', \'config_proto\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'None\'], " } } diff --git a/tensorflow/tools/api/golden/v1/tensorflow.distribute.cluster_resolver.-t-p-u-cluster-resolver.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.distribute.cluster_resolver.-t-p-u-cluster-resolver.pbtxt index ca22c85ac0..dbc76c2481 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.distribute.cluster_resolver.-t-p-u-cluster-resolver.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.distribute.cluster_resolver.-t-p-u-cluster-resolver.pbtxt @@ -29,6 +29,6 @@ tf_class { } member_method { name: "num_accelerators" - argspec: "args=[\'self\', \'task_type\', \'task_id\', \'accelerator_type\', \'config_proto\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'TPU\', \'None\'], " + argspec: "args=[\'self\', \'task_type\', \'task_id\', \'config_proto\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'None\'], " } } diff --git a/tensorflow/tools/api/golden/v1/tensorflow.distribute.cluster_resolver.-union-resolver.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.distribute.cluster_resolver.-union-resolver.pbtxt index 179848aca3..ecd77ad192 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.distribute.cluster_resolver.-union-resolver.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.distribute.cluster_resolver.-union-resolver.pbtxt @@ -33,6 +33,6 @@ tf_class { } member_method { name: "num_accelerators" - argspec: "args=[\'self\', \'task_type\', \'task_id\', \'accelerator_type\', \'config_proto\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'GPU\', \'None\'], " + argspec: "args=[\'self\', \'task_type\', \'task_id\', \'config_proto\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'None\'], " } } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.distribute.cluster_resolver.-simple-cluster-resolver.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.distribute.cluster_resolver.-simple-cluster-resolver.pbtxt index 4e80e3af30..d39ddc7e40 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.distribute.cluster_resolver.-simple-cluster-resolver.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.distribute.cluster_resolver.-simple-cluster-resolver.pbtxt @@ -21,7 +21,7 @@ tf_class { } member_method { name: "__init__" - argspec: "args=[\'self\', \'cluster_spec\', \'master\', \'task_type\', \'task_id\', \'environment\', \'num_accelerators\', \'rpc_layer\'], varargs=None, keywords=None, defaults=[\'\', \'None\', \'None\', \'\', \'0\', \'None\'], " + argspec: "args=[\'self\', \'cluster_spec\', \'master\', \'task_type\', \'task_id\', \'environment\', \'num_accelerators\', \'rpc_layer\'], varargs=None, keywords=None, defaults=[\'\', \'None\', \'None\', \'\', \'None\', \'None\'], " } member_method { name: "cluster_spec" @@ -33,6 +33,6 @@ tf_class { } member_method { name: "num_accelerators" - argspec: "args=[\'self\', \'task_type\', \'task_id\', \'accelerator_type\', \'config_proto\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'GPU\', \'None\'], " + argspec: "args=[\'self\', \'task_type\', \'task_id\', \'config_proto\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'None\'], " } } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.distribute.cluster_resolver.-slurm-cluster-resolver.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.distribute.cluster_resolver.-slurm-cluster-resolver.pbtxt index 971ea3dca4..b487626520 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.distribute.cluster_resolver.-slurm-cluster-resolver.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.distribute.cluster_resolver.-slurm-cluster-resolver.pbtxt @@ -25,6 +25,6 @@ tf_class { } member_method { name: "num_accelerators" - argspec: "args=[\'self\', \'task_type\', \'task_id\', \'accelerator_type\', \'config_proto\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'GPU\', \'None\'], " + argspec: "args=[\'self\', \'task_type\', \'task_id\', \'config_proto\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'None\'], " } } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.distribute.cluster_resolver.-t-p-u-cluster-resolver.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.distribute.cluster_resolver.-t-p-u-cluster-resolver.pbtxt index ca22c85ac0..dbc76c2481 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.distribute.cluster_resolver.-t-p-u-cluster-resolver.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.distribute.cluster_resolver.-t-p-u-cluster-resolver.pbtxt @@ -29,6 +29,6 @@ tf_class { } member_method { name: "num_accelerators" - argspec: "args=[\'self\', \'task_type\', \'task_id\', \'accelerator_type\', \'config_proto\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'TPU\', \'None\'], " + argspec: "args=[\'self\', \'task_type\', \'task_id\', \'config_proto\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'None\'], " } } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.distribute.cluster_resolver.-union-resolver.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.distribute.cluster_resolver.-union-resolver.pbtxt index 179848aca3..ecd77ad192 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.distribute.cluster_resolver.-union-resolver.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.distribute.cluster_resolver.-union-resolver.pbtxt @@ -33,6 +33,6 @@ tf_class { } member_method { name: "num_accelerators" - argspec: "args=[\'self\', \'task_type\', \'task_id\', \'accelerator_type\', \'config_proto\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'GPU\', \'None\'], " + argspec: "args=[\'self\', \'task_type\', \'task_id\', \'config_proto\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'None\'], " } } -- GitLab From 8d0a53de97f0c4592f81efca1d4bb74ca9309fef Mon Sep 17 00:00:00 2001 From: Pavithra Vijay Date: Fri, 22 Feb 2019 11:43:54 -0800 Subject: [PATCH 363/766] Add module aliases for v2 initializers: tf.initializers = tf.keras.initializers PiperOrigin-RevId: 235229813 --- tensorflow/api_template.__init__.py | 1 + tensorflow/compat_template.__init__.py | 1 + .../tensorflow.initializers.-constant.pbtxt | 18 +++ ...nsorflow.initializers.-glorot-normal.pbtxt | 19 ++++ ...sorflow.initializers.-glorot-uniform.pbtxt | 19 ++++ .../tensorflow.initializers.-identity.pbtxt | 18 +++ ...tensorflow.initializers.-initializer.pbtxt | 16 +++ .../v2/tensorflow.initializers.-ones.pbtxt | 17 +++ .../tensorflow.initializers.-orthogonal.pbtxt | 18 +++ ...nsorflow.initializers.-random-normal.pbtxt | 18 +++ ...sorflow.initializers.-random-uniform.pbtxt | 18 +++ ...rflow.initializers.-truncated-normal.pbtxt | 18 +++ ...rflow.initializers.-variance-scaling.pbtxt | 18 +++ .../v2/tensorflow.initializers.-zeros.pbtxt | 17 +++ .../v2/tensorflow.initializers.constant.pbtxt | 18 +++ ...ensorflow.initializers.glorot_normal.pbtxt | 19 ++++ ...nsorflow.initializers.glorot_uniform.pbtxt | 19 ++++ .../v2/tensorflow.initializers.identity.pbtxt | 18 +++ .../v2/tensorflow.initializers.ones.pbtxt | 17 +++ .../tensorflow.initializers.orthogonal.pbtxt | 18 +++ .../golden/v2/tensorflow.initializers.pbtxt | 107 ++++++++++++++++++ .../v2/tensorflow.initializers.zeros.pbtxt | 17 +++ .../tools/api/golden/v2/tensorflow.pbtxt | 4 + 23 files changed, 453 insertions(+) create mode 100644 tensorflow/tools/api/golden/v2/tensorflow.initializers.-constant.pbtxt create mode 100644 tensorflow/tools/api/golden/v2/tensorflow.initializers.-glorot-normal.pbtxt create mode 100644 tensorflow/tools/api/golden/v2/tensorflow.initializers.-glorot-uniform.pbtxt create mode 100644 tensorflow/tools/api/golden/v2/tensorflow.initializers.-identity.pbtxt create mode 100644 tensorflow/tools/api/golden/v2/tensorflow.initializers.-initializer.pbtxt create mode 100644 tensorflow/tools/api/golden/v2/tensorflow.initializers.-ones.pbtxt create mode 100644 tensorflow/tools/api/golden/v2/tensorflow.initializers.-orthogonal.pbtxt create mode 100644 tensorflow/tools/api/golden/v2/tensorflow.initializers.-random-normal.pbtxt create mode 100644 tensorflow/tools/api/golden/v2/tensorflow.initializers.-random-uniform.pbtxt create mode 100644 tensorflow/tools/api/golden/v2/tensorflow.initializers.-truncated-normal.pbtxt create mode 100644 tensorflow/tools/api/golden/v2/tensorflow.initializers.-variance-scaling.pbtxt create mode 100644 tensorflow/tools/api/golden/v2/tensorflow.initializers.-zeros.pbtxt create mode 100644 tensorflow/tools/api/golden/v2/tensorflow.initializers.constant.pbtxt create mode 100644 tensorflow/tools/api/golden/v2/tensorflow.initializers.glorot_normal.pbtxt create mode 100644 tensorflow/tools/api/golden/v2/tensorflow.initializers.glorot_uniform.pbtxt create mode 100644 tensorflow/tools/api/golden/v2/tensorflow.initializers.identity.pbtxt create mode 100644 tensorflow/tools/api/golden/v2/tensorflow.initializers.ones.pbtxt create mode 100644 tensorflow/tools/api/golden/v2/tensorflow.initializers.orthogonal.pbtxt create mode 100644 tensorflow/tools/api/golden/v2/tensorflow.initializers.pbtxt create mode 100644 tensorflow/tools/api/golden/v2/tensorflow.initializers.zeros.pbtxt diff --git a/tensorflow/api_template.__init__.py b/tensorflow/api_template.__init__.py index ddcacfcbe2..7bd6b72239 100644 --- a/tensorflow/api_template.__init__.py +++ b/tensorflow/api_template.__init__.py @@ -122,5 +122,6 @@ if hasattr(_current_module, 'keras'): losses = keras.losses metrics = keras.metrics optimizers = keras.optimizers + initializers = keras.initializers # pylint: enable=undefined-variable diff --git a/tensorflow/compat_template.__init__.py b/tensorflow/compat_template.__init__.py index 2cf68c9cd8..49cb74f19e 100644 --- a/tensorflow/compat_template.__init__.py +++ b/tensorflow/compat_template.__init__.py @@ -54,3 +54,4 @@ if hasattr(_current_module, 'keras'): losses = keras.losses metrics = keras.metrics optimizers = keras.optimizers + initializers = keras.initializers diff --git a/tensorflow/tools/api/golden/v2/tensorflow.initializers.-constant.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.initializers.-constant.pbtxt new file mode 100644 index 0000000000..502fda18fd --- /dev/null +++ b/tensorflow/tools/api/golden/v2/tensorflow.initializers.-constant.pbtxt @@ -0,0 +1,18 @@ +path: "tensorflow.initializers.Constant" +tf_class { + is_instance: "" + is_instance: "" + is_instance: "" + member_method { + name: "__init__" + argspec: "args=[\'self\', \'value\'], varargs=None, keywords=None, defaults=[\'0\'], " + } + member_method { + name: "from_config" + argspec: "args=[\'cls\', \'config\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "get_config" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.initializers.-glorot-normal.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.initializers.-glorot-normal.pbtxt new file mode 100644 index 0000000000..06beddc818 --- /dev/null +++ b/tensorflow/tools/api/golden/v2/tensorflow.initializers.-glorot-normal.pbtxt @@ -0,0 +1,19 @@ +path: "tensorflow.initializers.GlorotNormal" +tf_class { + is_instance: "" + is_instance: "" + is_instance: "" + is_instance: "" + member_method { + name: "__init__" + argspec: "args=[\'self\', \'seed\'], varargs=None, keywords=None, defaults=[\'None\'], " + } + member_method { + name: "from_config" + argspec: "args=[\'cls\', \'config\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "get_config" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.initializers.-glorot-uniform.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.initializers.-glorot-uniform.pbtxt new file mode 100644 index 0000000000..f37448f634 --- /dev/null +++ b/tensorflow/tools/api/golden/v2/tensorflow.initializers.-glorot-uniform.pbtxt @@ -0,0 +1,19 @@ +path: "tensorflow.initializers.GlorotUniform" +tf_class { + is_instance: "" + is_instance: "" + is_instance: "" + is_instance: "" + member_method { + name: "__init__" + argspec: "args=[\'self\', \'seed\'], varargs=None, keywords=None, defaults=[\'None\'], " + } + member_method { + name: "from_config" + argspec: "args=[\'cls\', \'config\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "get_config" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.initializers.-identity.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.initializers.-identity.pbtxt new file mode 100644 index 0000000000..fc6f16b04f --- /dev/null +++ b/tensorflow/tools/api/golden/v2/tensorflow.initializers.-identity.pbtxt @@ -0,0 +1,18 @@ +path: "tensorflow.initializers.Identity" +tf_class { + is_instance: "" + is_instance: "" + is_instance: "" + member_method { + name: "__init__" + argspec: "args=[\'self\', \'gain\'], varargs=None, keywords=None, defaults=[\'1.0\'], " + } + member_method { + name: "from_config" + argspec: "args=[\'cls\', \'config\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "get_config" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.initializers.-initializer.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.initializers.-initializer.pbtxt new file mode 100644 index 0000000000..03a69732c6 --- /dev/null +++ b/tensorflow/tools/api/golden/v2/tensorflow.initializers.-initializer.pbtxt @@ -0,0 +1,16 @@ +path: "tensorflow.initializers.Initializer" +tf_class { + is_instance: "" + is_instance: "" + member_method { + name: "__init__" + } + member_method { + name: "from_config" + argspec: "args=[\'cls\', \'config\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "get_config" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.initializers.-ones.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.initializers.-ones.pbtxt new file mode 100644 index 0000000000..c2ff715c64 --- /dev/null +++ b/tensorflow/tools/api/golden/v2/tensorflow.initializers.-ones.pbtxt @@ -0,0 +1,17 @@ +path: "tensorflow.initializers.Ones" +tf_class { + is_instance: "" + is_instance: "" + is_instance: "" + member_method { + name: "__init__" + } + member_method { + name: "from_config" + argspec: "args=[\'cls\', \'config\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "get_config" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.initializers.-orthogonal.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.initializers.-orthogonal.pbtxt new file mode 100644 index 0000000000..303752f934 --- /dev/null +++ b/tensorflow/tools/api/golden/v2/tensorflow.initializers.-orthogonal.pbtxt @@ -0,0 +1,18 @@ +path: "tensorflow.initializers.Orthogonal" +tf_class { + is_instance: "" + is_instance: "" + is_instance: "" + member_method { + name: "__init__" + argspec: "args=[\'self\', \'gain\', \'seed\'], varargs=None, keywords=None, defaults=[\'1.0\', \'None\'], " + } + member_method { + name: "from_config" + argspec: "args=[\'cls\', \'config\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "get_config" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.initializers.-random-normal.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.initializers.-random-normal.pbtxt new file mode 100644 index 0000000000..c1b1dfb4d2 --- /dev/null +++ b/tensorflow/tools/api/golden/v2/tensorflow.initializers.-random-normal.pbtxt @@ -0,0 +1,18 @@ +path: "tensorflow.initializers.RandomNormal" +tf_class { + is_instance: "" + is_instance: "" + is_instance: "" + member_method { + name: "__init__" + argspec: "args=[\'self\', \'mean\', \'stddev\', \'seed\'], varargs=None, keywords=None, defaults=[\'0.0\', \'0.05\', \'None\'], " + } + member_method { + name: "from_config" + argspec: "args=[\'cls\', \'config\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "get_config" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.initializers.-random-uniform.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.initializers.-random-uniform.pbtxt new file mode 100644 index 0000000000..eac13a1324 --- /dev/null +++ b/tensorflow/tools/api/golden/v2/tensorflow.initializers.-random-uniform.pbtxt @@ -0,0 +1,18 @@ +path: "tensorflow.initializers.RandomUniform" +tf_class { + is_instance: "" + is_instance: "" + is_instance: "" + member_method { + name: "__init__" + argspec: "args=[\'self\', \'minval\', \'maxval\', \'seed\'], varargs=None, keywords=None, defaults=[\'-0.05\', \'0.05\', \'None\'], " + } + member_method { + name: "from_config" + argspec: "args=[\'cls\', \'config\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "get_config" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.initializers.-truncated-normal.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.initializers.-truncated-normal.pbtxt new file mode 100644 index 0000000000..b796faef0c --- /dev/null +++ b/tensorflow/tools/api/golden/v2/tensorflow.initializers.-truncated-normal.pbtxt @@ -0,0 +1,18 @@ +path: "tensorflow.initializers.TruncatedNormal" +tf_class { + is_instance: "" + is_instance: "" + is_instance: "" + member_method { + name: "__init__" + argspec: "args=[\'self\', \'mean\', \'stddev\', \'seed\'], varargs=None, keywords=None, defaults=[\'0.0\', \'0.05\', \'None\'], " + } + member_method { + name: "from_config" + argspec: "args=[\'cls\', \'config\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "get_config" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.initializers.-variance-scaling.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.initializers.-variance-scaling.pbtxt new file mode 100644 index 0000000000..ea46406d3a --- /dev/null +++ b/tensorflow/tools/api/golden/v2/tensorflow.initializers.-variance-scaling.pbtxt @@ -0,0 +1,18 @@ +path: "tensorflow.initializers.VarianceScaling" +tf_class { + is_instance: "" + is_instance: "" + is_instance: "" + member_method { + name: "__init__" + argspec: "args=[\'self\', \'scale\', \'mode\', \'distribution\', \'seed\'], varargs=None, keywords=None, defaults=[\'1.0\', \'fan_in\', \'truncated_normal\', \'None\'], " + } + member_method { + name: "from_config" + argspec: "args=[\'cls\', \'config\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "get_config" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.initializers.-zeros.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.initializers.-zeros.pbtxt new file mode 100644 index 0000000000..2596186705 --- /dev/null +++ b/tensorflow/tools/api/golden/v2/tensorflow.initializers.-zeros.pbtxt @@ -0,0 +1,17 @@ +path: "tensorflow.initializers.Zeros" +tf_class { + is_instance: "" + is_instance: "" + is_instance: "" + member_method { + name: "__init__" + } + member_method { + name: "from_config" + argspec: "args=[\'cls\', \'config\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "get_config" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.initializers.constant.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.initializers.constant.pbtxt new file mode 100644 index 0000000000..f26775f4c8 --- /dev/null +++ b/tensorflow/tools/api/golden/v2/tensorflow.initializers.constant.pbtxt @@ -0,0 +1,18 @@ +path: "tensorflow.initializers.constant" +tf_class { + is_instance: "" + is_instance: "" + is_instance: "" + member_method { + name: "__init__" + argspec: "args=[\'self\', \'value\'], varargs=None, keywords=None, defaults=[\'0\'], " + } + member_method { + name: "from_config" + argspec: "args=[\'cls\', \'config\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "get_config" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.initializers.glorot_normal.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.initializers.glorot_normal.pbtxt new file mode 100644 index 0000000000..0af16f3ae8 --- /dev/null +++ b/tensorflow/tools/api/golden/v2/tensorflow.initializers.glorot_normal.pbtxt @@ -0,0 +1,19 @@ +path: "tensorflow.initializers.glorot_normal" +tf_class { + is_instance: "" + is_instance: "" + is_instance: "" + is_instance: "" + member_method { + name: "__init__" + argspec: "args=[\'self\', \'seed\'], varargs=None, keywords=None, defaults=[\'None\'], " + } + member_method { + name: "from_config" + argspec: "args=[\'cls\', \'config\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "get_config" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.initializers.glorot_uniform.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.initializers.glorot_uniform.pbtxt new file mode 100644 index 0000000000..d8272f2c64 --- /dev/null +++ b/tensorflow/tools/api/golden/v2/tensorflow.initializers.glorot_uniform.pbtxt @@ -0,0 +1,19 @@ +path: "tensorflow.initializers.glorot_uniform" +tf_class { + is_instance: "" + is_instance: "" + is_instance: "" + is_instance: "" + member_method { + name: "__init__" + argspec: "args=[\'self\', \'seed\'], varargs=None, keywords=None, defaults=[\'None\'], " + } + member_method { + name: "from_config" + argspec: "args=[\'cls\', \'config\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "get_config" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.initializers.identity.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.initializers.identity.pbtxt new file mode 100644 index 0000000000..c11593fe31 --- /dev/null +++ b/tensorflow/tools/api/golden/v2/tensorflow.initializers.identity.pbtxt @@ -0,0 +1,18 @@ +path: "tensorflow.initializers.identity" +tf_class { + is_instance: "" + is_instance: "" + is_instance: "" + member_method { + name: "__init__" + argspec: "args=[\'self\', \'gain\'], varargs=None, keywords=None, defaults=[\'1.0\'], " + } + member_method { + name: "from_config" + argspec: "args=[\'cls\', \'config\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "get_config" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.initializers.ones.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.initializers.ones.pbtxt new file mode 100644 index 0000000000..436465c10a --- /dev/null +++ b/tensorflow/tools/api/golden/v2/tensorflow.initializers.ones.pbtxt @@ -0,0 +1,17 @@ +path: "tensorflow.initializers.ones" +tf_class { + is_instance: "" + is_instance: "" + is_instance: "" + member_method { + name: "__init__" + } + member_method { + name: "from_config" + argspec: "args=[\'cls\', \'config\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "get_config" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.initializers.orthogonal.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.initializers.orthogonal.pbtxt new file mode 100644 index 0000000000..6603f66736 --- /dev/null +++ b/tensorflow/tools/api/golden/v2/tensorflow.initializers.orthogonal.pbtxt @@ -0,0 +1,18 @@ +path: "tensorflow.initializers.orthogonal" +tf_class { + is_instance: "" + is_instance: "" + is_instance: "" + member_method { + name: "__init__" + argspec: "args=[\'self\', \'gain\', \'seed\'], varargs=None, keywords=None, defaults=[\'1.0\', \'None\'], " + } + member_method { + name: "from_config" + argspec: "args=[\'cls\', \'config\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "get_config" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.initializers.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.initializers.pbtxt new file mode 100644 index 0000000000..b10b8e6ce9 --- /dev/null +++ b/tensorflow/tools/api/golden/v2/tensorflow.initializers.pbtxt @@ -0,0 +1,107 @@ +path: "tensorflow.initializers" +tf_module { + member { + name: "Constant" + mtype: "" + } + member { + name: "GlorotNormal" + mtype: "" + } + member { + name: "GlorotUniform" + mtype: "" + } + member { + name: "Identity" + mtype: "" + } + member { + name: "Initializer" + mtype: "" + } + member { + name: "Ones" + mtype: "" + } + member { + name: "Orthogonal" + mtype: "" + } + member { + name: "RandomNormal" + mtype: "" + } + member { + name: "RandomUniform" + mtype: "" + } + member { + name: "TruncatedNormal" + mtype: "" + } + member { + name: "VarianceScaling" + mtype: "" + } + member { + name: "Zeros" + mtype: "" + } + member { + name: "constant" + mtype: "" + } + member { + name: "glorot_normal" + mtype: "" + } + member { + name: "glorot_uniform" + mtype: "" + } + member { + name: "identity" + mtype: "" + } + member { + name: "ones" + mtype: "" + } + member { + name: "orthogonal" + mtype: "" + } + member { + name: "zeros" + mtype: "" + } + member_method { + name: "deserialize" + argspec: "args=[\'config\', \'custom_objects\'], varargs=None, keywords=None, defaults=[\'None\'], " + } + member_method { + name: "get" + argspec: "args=[\'identifier\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "he_normal" + argspec: "args=[\'seed\'], varargs=None, keywords=None, defaults=[\'None\'], " + } + member_method { + name: "he_uniform" + argspec: "args=[\'seed\'], varargs=None, keywords=None, defaults=[\'None\'], " + } + member_method { + name: "lecun_normal" + argspec: "args=[\'seed\'], varargs=None, keywords=None, defaults=[\'None\'], " + } + member_method { + name: "lecun_uniform" + argspec: "args=[\'seed\'], varargs=None, keywords=None, defaults=[\'None\'], " + } + member_method { + name: "serialize" + argspec: "args=[\'initializer\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.initializers.zeros.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.initializers.zeros.pbtxt new file mode 100644 index 0000000000..27774af69a --- /dev/null +++ b/tensorflow/tools/api/golden/v2/tensorflow.initializers.zeros.pbtxt @@ -0,0 +1,17 @@ +path: "tensorflow.initializers.zeros" +tf_class { + is_instance: "" + is_instance: "" + is_instance: "" + member_method { + name: "__init__" + } + member_method { + name: "from_config" + argspec: "args=[\'cls\', \'config\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "get_config" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.pbtxt index 66489e6891..534c7732f5 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.pbtxt @@ -176,6 +176,10 @@ tf_module { name: "image" mtype: "" } + member { + name: "initializers" + mtype: "" + } member { name: "int16" mtype: "" -- GitLab From dab94104e13a7ab5192303c8b2784e3c36af60ac Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 22 Feb 2019 11:56:28 -0800 Subject: [PATCH 364/766] Automated rollback of commit 4bf9ea4295a0eff68ab737ab41399ab8bb3464ef PiperOrigin-RevId: 235232101 --- .../compiler/xla/service/layout_assignment.cc | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tensorflow/compiler/xla/service/layout_assignment.cc b/tensorflow/compiler/xla/service/layout_assignment.cc index bf1d1c233f..aa791ea195 100644 --- a/tensorflow/compiler/xla/service/layout_assignment.cc +++ b/tensorflow/compiler/xla/service/layout_assignment.cc @@ -1019,6 +1019,16 @@ std::unique_ptr LayoutAssignment::ChooseOperandLayoutFromOutputLayout( Shape operand_shape = operand->shape(); *operand_shape.mutable_layout() = LayoutUtil::GetDefaultLayoutForShape(operand_shape); + if (ShapeUtil::ReshapeIsBitcast(operand_shape, output_shape_with_layout)) { + return absl::make_unique(operand_shape.layout()); + } + if (operand_shape.rank() == output_shape.rank()) { + *operand_shape.mutable_layout() = output_layout; + if (ShapeUtil::ReshapeIsBitcast(operand_shape, + output_shape_with_layout)) { + return absl::make_unique(output_layout); + } + } auto aligned_operand_shape = ShapeUtil::AlignLayouts(output_shape_with_layout, operand_shape); if (aligned_operand_shape) { @@ -1080,6 +1090,16 @@ std::unique_ptr LayoutAssignment::ChooseOutputLayoutFromOperandLayout( Shape output_shape = user->shape(); *output_shape.mutable_layout() = LayoutUtil::GetDefaultLayoutForShape(output_shape); + if (ShapeUtil::ReshapeIsBitcast(output_shape, operand_shape_with_layout)) { + return absl::make_unique(output_shape.layout()); + } + if (operand->shape().rank() == output_shape.rank()) { + *output_shape.mutable_layout() = operand_layout; + if (ShapeUtil::ReshapeIsBitcast(output_shape, + operand_shape_with_layout)) { + return absl::make_unique(operand_layout); + } + } auto aligned_user_shape = ShapeUtil::AlignLayouts(operand_shape_with_layout, output_shape); if (aligned_user_shape) { -- GitLab From a0c1fec6449109f357ef57e2933626a4e9bf6348 Mon Sep 17 00:00:00 2001 From: William Chargin Date: Fri, 22 Feb 2019 12:00:37 -0800 Subject: [PATCH 365/766] Fix callbacks_test path separator on Windows. In cl/234179031, we improved this test to use `os.path.join` rather than a hard-coded `+ "/tb"` in a few places, but apparently missed some. Tested: After 1036s/tb/something_else/, tests still pass on my local gLinux desktop. Before this change, tests would fail after that substitution, albeit not for the same reason as the traceback in b/125762969: we get a normal `assertItemsEqual` failure rather than an ENOENT. Running Kokoro Windows presubmits per go/tf-testing passes. RELNOTES: n/a PiperOrigin-RevId: 235232824 --- tensorflow/python/keras/callbacks_test.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tensorflow/python/keras/callbacks_test.py b/tensorflow/python/keras/callbacks_test.py index 2a14debfdd..80204017d7 100644 --- a/tensorflow/python/keras/callbacks_test.py +++ b/tensorflow/python/keras/callbacks_test.py @@ -1092,8 +1092,7 @@ class TestTensorBoardV2(keras_parameterized.TestCase): def test_TensorBoard_weight_histograms(self): model = self._get_model() x, y = np.ones((10, 10, 10, 1)), np.ones((10, 1)) - temp_dir = self.get_temp_dir() + '/tb' - tb_cbk = keras.callbacks.TensorBoard(temp_dir, histogram_freq=1) + tb_cbk = keras.callbacks.TensorBoard(self.logdir, histogram_freq=1) with _mock_summary_api() as summary_file: model.fit( @@ -1122,9 +1121,8 @@ class TestTensorBoardV2(keras_parameterized.TestCase): def test_TensorBoard_weight_images(self): model = self._get_model() x, y = np.ones((10, 10, 10, 1)), np.ones((10, 1)) - temp_dir = self.get_temp_dir() + '/tb' tb_cbk = keras.callbacks.TensorBoard( - temp_dir, histogram_freq=1, write_images=True) + self.logdir, histogram_freq=1, write_images=True) with _mock_summary_api() as summary_file: model.fit( -- GitLab From 198cb0e2de2de8d1bd89d71330bf2f5a4311eb84 Mon Sep 17 00:00:00 2001 From: Sanjoy Das Date: Fri, 22 Feb 2019 12:02:42 -0800 Subject: [PATCH 366/766] [TF:XLA] Bump open source llvm revision to r354665 PiperOrigin-RevId: 235233320 --- tensorflow/workspace.bzl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl index 937962fa22..091f1371cf 100755 --- a/tensorflow/workspace.bzl +++ b/tensorflow/workspace.bzl @@ -513,11 +513,11 @@ def tf_workspace(path_prefix = "", tf_repo_name = ""): tf_http_archive( name = "llvm", build_file = clean_dep("//third_party/llvm:llvm.autogenerated.BUILD"), - sha256 = "8399a5e0111f52c5d8d3df3638cb997b3f01e1bc7288ee95d77e97b62a47ba18", - strip_prefix = "llvm-f2cd363b4b915b54ba0f25066f876c16d59fede6", + sha256 = "dd88ca5853dde6200d205fc01f6fb2b2f30ef31d62ceb579e0c4081b2e6af399", + strip_prefix = "llvm-1d53cf7ef3397d91f5a1310e0c4542e7b8c4b682", urls = [ - "https://mirror.bazel.build/github.com/llvm-mirror/llvm/archive/f2cd363b4b915b54ba0f25066f876c16d59fede6.tar.gz", - "https://github.com/llvm-mirror/llvm/archive/f2cd363b4b915b54ba0f25066f876c16d59fede6.tar.gz", + "https://mirror.bazel.build/github.com/llvm-mirror/llvm/archive/1d53cf7ef3397d91f5a1310e0c4542e7b8c4b682.tar.gz", + "https://github.com/llvm-mirror/llvm/archive/1d53cf7ef3397d91f5a1310e0c4542e7b8c4b682.tar.gz", ], ) -- GitLab From 56915ed7d290fff6e37859181781687590a2e974 Mon Sep 17 00:00:00 2001 From: Rick Chao Date: Fri, 22 Feb 2019 12:18:05 -0800 Subject: [PATCH 367/766] Remove early_stopping.py from estimator/contrib in favor of estimator/python/estimator/early_stopping.py. And the test. PiperOrigin-RevId: 235236119 --- .../contrib/estimator/python/estimator/early_stopping.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/contrib/estimator/python/estimator/early_stopping.py b/tensorflow/contrib/estimator/python/estimator/early_stopping.py index 11856ece38..47f568ed3d 100644 --- a/tensorflow/contrib/estimator/python/estimator/early_stopping.py +++ b/tensorflow/contrib/estimator/python/estimator/early_stopping.py @@ -23,7 +23,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow_estimator.contrib.estimator.python.estimator import early_stopping +from tensorflow_estimator.python.estimator import early_stopping # Include attrs that start with single underscore. _HAS_DYNAMIC_ATTRIBUTES = True @@ -31,4 +31,4 @@ early_stopping.__all__ = [ s for s in dir(early_stopping) if not s.startswith('__') ] -from tensorflow_estimator.contrib.estimator.python.estimator.early_stopping import * +from tensorflow_estimator.python.estimator.early_stopping import * -- GitLab From 0b3d833edcaf5872ea4b7c2db35365eba711239b Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 22 Feb 2019 12:30:54 -0800 Subject: [PATCH 368/766] Clarify behavior of fit/predict/evaluate when inputs are Datasets and step args are None. PiperOrigin-RevId: 235238247 --- tensorflow/python/keras/engine/training.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tensorflow/python/keras/engine/training.py b/tensorflow/python/keras/engine/training.py index 312b77faa8..8c935f49b5 100644 --- a/tensorflow/python/keras/engine/training.py +++ b/tensorflow/python/keras/engine/training.py @@ -637,11 +637,15 @@ class Model(Network): next epoch. When training with input tensors such as TensorFlow data tensors, the default `None` is equal to the number of samples in your dataset divided by - the batch size, or 1 if that cannot be determined. + the batch size, or 1 if that cannot be determined. If x is a + `tf.data` dataset or a dataset iterator, and 'steps_per_epoch' + is None, the epoch will run until the input dataset is exhausted. validation_steps: Only relevant if `validation_data` is provided and is a dataset or dataset iterator. Total number of steps (batches of samples) to draw before stopping when performing validation - at the end of every epoch. + at the end of every epoch. If validation_data is a `tf.data` dataset + or a dataset iterator, and 'validation_steps' is None, validation + will run until the `validation_data` dataset is exhausted. validation_freq: Only relevant if validation data is provided. Integer or `collections.Container` instance (e.g. list, tuple, etc.). If an integer, specifies how many training epochs to run before a new @@ -926,6 +930,8 @@ class Model(Network): Total number of steps (batches of samples) before declaring the evaluation round finished. Ignored with the default value of `None`. + If x is a `tf.data` dataset or a dataset iterator, and `steps` is + None, 'evaluate' will run until the dataset is exhausted. callbacks: List of `keras.callbacks.Callback` instances. List of callbacks to apply during evaluation. See [callbacks](/api_docs/python/tf/keras/callbacks). @@ -1074,7 +1080,9 @@ class Model(Network): verbose: Verbosity mode, 0 or 1. steps: Total number of steps (batches of samples) before declaring the prediction round finished. - Ignored with the default value of `None`. + Ignored with the default value of `None`. If x is a `tf.data` + dataset or a dataset iterator, and `steps` is None, `predict` will + run until the input dataset is exhausted. callbacks: List of `keras.callbacks.Callback` instances. List of callbacks to apply during prediction. See [callbacks](/api_docs/python/tf/keras/callbacks). -- GitLab From eb741cedf304b411af6cae8ec2d60578cd3980a1 Mon Sep 17 00:00:00 2001 From: Pavithra Vijay Date: Fri, 22 Feb 2019 12:47:46 -0800 Subject: [PATCH 369/766] Automate clear losses at the end of a forward pass for custom training loops PiperOrigin-RevId: 235241120 --- tensorflow/python/keras/engine/base_layer.py | 29 ++++++++++++++++--- tensorflow/python/keras/engine/network.py | 5 +--- .../python/keras/engine/training_eager.py | 1 - .../python/keras/engine/training_test.py | 24 +++++++++++++++ 4 files changed, 50 insertions(+), 9 deletions(-) diff --git a/tensorflow/python/keras/engine/base_layer.py b/tensorflow/python/keras/engine/base_layer.py index 49f3031755..2208b4a973 100644 --- a/tensorflow/python/keras/engine/base_layer.py +++ b/tensorflow/python/keras/engine/base_layer.py @@ -556,6 +556,13 @@ class Layer(trackable.Trackable): # pass to __call__, hence we set previous_mask as the default value. kwargs['mask'] = previous_mask + # Clear eager losses on top level model call. + # We are clearing the losses only on the top level model call and not on + # every layer/mode call because layer/model may be reused. + if (context.executing_eagerly() and + not base_layer_utils.is_in_call_context()): + self._clear_losses() + with base_layer_utils.call_context(): # Check input assumptions set after layer building, e.g. input shape. if build_graph: @@ -775,6 +782,15 @@ class Layer(trackable.Trackable): else: self._eager_losses.append(_tag_unconditional(loss)) + @trackable.no_automatic_dependency_tracking + def _clear_losses(self): + """Used every step in eager to reset losses.""" + self._eager_losses = [] + if hasattr(self, '_layers'): + for layer in trackable_layer_utils.filter_empty_layer_containers( + self._layers): + layer._clear_losses() + @doc_controls.for_subclass_implementers def add_metric(self, value, aggregation=None, name=None): """Adds metric tensor to the layer. @@ -1715,11 +1731,16 @@ class Layer(trackable.Trackable): super(Layer, self).__setattr__(name, value) def _gather_children_attribute(self, attribute): - assert attribute in {'weights', 'trainable_weights', - 'non_trainable_weights', 'updates', 'losses'} + assert attribute in { + 'weights', 'trainable_weights', 'non_trainable_weights', 'updates', + 'losses' + } if hasattr(self, '_layers'): - return list(itertools.chain.from_iterable( - getattr(layer, attribute) for layer in self._layers)) + nested_layers = trackable_layer_utils.filter_empty_layer_containers( + self._layers) + return list( + itertools.chain.from_iterable( + getattr(layer, attribute) for layer in nested_layers)) return [] # This is a hack so that the is_layer (within diff --git a/tensorflow/python/keras/engine/network.py b/tensorflow/python/keras/engine/network.py index a6fdfad833..b10aa98c7f 100644 --- a/tensorflow/python/keras/engine/network.py +++ b/tensorflow/python/keras/engine/network.py @@ -549,10 +549,7 @@ class Network(base_layer.Layer): """Used every step in eager to reset losses.""" self._eager_losses = [] for layer in self.layers: - if isinstance(layer, Network): - layer._clear_losses() - else: - layer._eager_losses = [] + layer._clear_losses() @property def updates(self): diff --git a/tensorflow/python/keras/engine/training_eager.py b/tensorflow/python/keras/engine/training_eager.py index 6fdb19c523..bc750244b1 100644 --- a/tensorflow/python/keras/engine/training_eager.py +++ b/tensorflow/python/keras/engine/training_eager.py @@ -191,7 +191,6 @@ def _model_loss(model, if custom_losses: total_loss += losses_utils.scale_loss_for_distribution( math_ops.add_n(custom_losses)) - model._clear_losses() return outs, total_loss, output_losses, aggregated_output_losses, masks diff --git a/tensorflow/python/keras/engine/training_test.py b/tensorflow/python/keras/engine/training_test.py index a9e17d4e9b..686cc2db36 100644 --- a/tensorflow/python/keras/engine/training_test.py +++ b/tensorflow/python/keras/engine/training_test.py @@ -1005,6 +1005,30 @@ class TrainingTest(keras_parameterized.TestCase): self.assertAllClose(history.history['loss'], [3., 2.7, 2.4, 2.1, 1.8], 1e-3) + @keras_parameterized.run_all_keras_modes + def test_clear_losses(self): + + class LayerWithSharedNestedLossLayer(keras.layers.Layer): + + def __init__(self): + super(LayerWithSharedNestedLossLayer, self).__init__() + self.loss_layer = keras.layers.ActivityRegularization() + self.add_weight(shape=(1,), regularizer='l2') + + def call(self, x): + x = self.loss_layer(x) + return self.loss_layer(x) + + inputs = keras.Input(shape=(1,)) + outputs = LayerWithSharedNestedLossLayer()(inputs) + model = keras.Model(inputs, outputs) + + model(array_ops.ones((1, 1))) + self.assertEqual(len(model.losses), 3) # Weight loss + 2 activity losses. + + model(array_ops.ones((1, 1))) + self.assertEqual(len(model.losses), 3) # Losses are reset upon __call__. + class TestExceptionsAndWarnings(keras_parameterized.TestCase): -- GitLab From 61aced4825c7a2b0ebc3003bcc4f821bef49f0a8 Mon Sep 17 00:00:00 2001 From: Rohan Jain Date: Fri, 22 Feb 2019 12:53:41 -0800 Subject: [PATCH 370/766] Splitting out the memory usage tests into their own test file as that was making the test suite for MultiDeviceIterator too big and timing out. PiperOrigin-RevId: 235242183 --- tensorflow/python/data/kernel_tests/BUILD | 15 +++ .../data/kernel_tests/memory_cleanup_test.py | 108 ++++++++++++++++++ .../multi_device_iterator_test.py | 71 ------------ 3 files changed, 123 insertions(+), 71 deletions(-) create mode 100644 tensorflow/python/data/kernel_tests/memory_cleanup_test.py diff --git a/tensorflow/python/data/kernel_tests/BUILD b/tensorflow/python/data/kernel_tests/BUILD index af1c6ab97c..d31877f204 100644 --- a/tensorflow/python/data/kernel_tests/BUILD +++ b/tensorflow/python/data/kernel_tests/BUILD @@ -444,6 +444,21 @@ cuda_py_test( ], ) +cuda_py_test( + name = "memory_cleanup_test", + size = "medium", + srcs = ["memory_cleanup_test.py"], + additional_deps = [ + ":test_base", + "@absl_py//absl/testing:parameterized", + "//tensorflow/core:protos_all_py", + "//tensorflow/python/data/ops:dataset_ops", + "//tensorflow/python/data/ops:multi_device_iterator_ops", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework_test_lib", + ], +) + cuda_py_test( name = "optional_test", size = "small", diff --git a/tensorflow/python/data/kernel_tests/memory_cleanup_test.py b/tensorflow/python/data/kernel_tests/memory_cleanup_test.py new file mode 100644 index 0000000000..2b5a655d7e --- /dev/null +++ b/tensorflow/python/data/kernel_tests/memory_cleanup_test.py @@ -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. +# ============================================================================== +"""Verify that memory usage is minimal in eager mode.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import time +import six + +from tensorflow.core.protobuf import config_pb2 +from tensorflow.python.data.kernel_tests import test_base +from tensorflow.python.data.ops import dataset_ops +from tensorflow.python.data.ops import multi_device_iterator_ops +from tensorflow.python.eager import context +from tensorflow.python.framework import ops +from tensorflow.python.framework import test_util +from tensorflow.python.platform import test +from tensorflow.python.platform import tf_logging as logging + + +# memory_profiler might not be available in the OSS version of TensorFlow. +try: + import memory_profiler # pylint:disable=g-import-not-at-top +except ImportError: + memory_profiler = None + + +@test_util.run_all_in_graph_and_eager_modes +class MemoryCleanupTest(test_base.DatasetTestBase): + + def assertNotIncreasingMemory(self, + f, + num_iters=100000, + increase_threshold_absolute_mb=10): + """Assert memory usage doesn't increase beyond given threshold for f.""" + with context.eager_mode(): + # Warm up. + f() + # Wait for background threads to start up and take over memory. + # FIXME: The nature of this test leaves few other options. Maybe there + # is a better way to do this. + time.sleep(4) + initial = memory_profiler.memory_usage(-1)[0] + for _ in six.moves.range(num_iters): + f() + increase = memory_profiler.memory_usage(-1)[0] - initial + logging.info("Memory increase observed: %f MB" % increase) + assert increase < increase_threshold_absolute_mb, ( + "Increase is too high. Initial memory usage: %f MB. Increase: %f MB. " + "Maximum allowed increase: %f") % (initial, increase, + increase_threshold_absolute_mb) + + @test_util.run_v1_only("b/121264236") + def testEagerMemoryUsageWithReset(self): + if not context.executing_eagerly(): + self.skipTest("Only eager mode test") + if memory_profiler is None: + self.skipTest("memory_profiler required to run this test") + + dataset = dataset_ops.Dataset.range(10) + multi_device_iterator = multi_device_iterator_ops.MultiDeviceIterator( + dataset, ["/cpu:1", "/cpu:2"]) + + def f(): + self.evaluate(multi_device_iterator.get_next()) + multi_device_iterator._eager_reset() + + self.assertNotIncreasingMemory( + f, num_iters=100, increase_threshold_absolute_mb=50) + + @test_util.run_v1_only("b/121264236") + def testEagerMemoryUsageWithRecreation(self): + if not context.executing_eagerly(): + self.skipTest("Only eager mode test") + if memory_profiler is None: + self.skipTest("memory_profiler required to run this test") + + dataset = dataset_ops.Dataset.range(10) + + def f(): + multi_device_iterator = multi_device_iterator_ops.MultiDeviceIterator( + dataset, ["/cpu:1", "/cpu:2"]) + self.evaluate(multi_device_iterator.get_next()) + del multi_device_iterator + + # TODO(b/123316347): Reduce threshold once bug is fixed. + self.assertNotIncreasingMemory( + f, num_iters=100, increase_threshold_absolute_mb=500) + + +if __name__ == "__main__": + ops.enable_eager_execution( + config=config_pb2.ConfigProto(device_count={"CPU": 3, "GPU": 1})) + test.main() diff --git a/tensorflow/python/data/kernel_tests/multi_device_iterator_test.py b/tensorflow/python/data/kernel_tests/multi_device_iterator_test.py index a040a64013..c379afcb16 100644 --- a/tensorflow/python/data/kernel_tests/multi_device_iterator_test.py +++ b/tensorflow/python/data/kernel_tests/multi_device_iterator_test.py @@ -18,9 +18,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -import time from absl.testing import parameterized -import six from tensorflow.core.protobuf import config_pb2 from tensorflow.python.data.experimental.ops import optimization @@ -34,44 +32,12 @@ from tensorflow.python.framework import ops from tensorflow.python.framework import test_util from tensorflow.python.ops import array_ops from tensorflow.python.platform import test -from tensorflow.python.platform import tf_logging as logging - - -# memory_profiler might not be available in the OSS version of TensorFlow. -try: - import memory_profiler # pylint:disable=g-import-not-at-top -except ImportError: - memory_profiler = None @test_util.run_all_in_graph_and_eager_modes class MultiDeviceIteratorTest(test_base.DatasetTestBase, parameterized.TestCase): - def assertNotIncreasingMemory(self, - f, - num_iters=100000, - increase_threshold_absolute_mb=10): - """Assert memory usage doesn't increase beyond given threshold for f.""" - - with context.eager_mode(): - # Warm up. - f() - - # Wait for background threads to start up and take over memory. - # FIXME: The nature of this test leaves few other options. Maybe there - # is a better way to do this. - time.sleep(4) - initial = memory_profiler.memory_usage(-1)[0] - for _ in six.moves.range(num_iters): - f() - increase = memory_profiler.memory_usage(-1)[0] - initial - logging.info("Memory increase observed: %f MB" % increase) - assert increase < increase_threshold_absolute_mb, ( - "Increase is too high. Initial memory usage: %f MB. Increase: %f MB. " - "Maximum allowed increase: %f") % (initial, increase, - increase_threshold_absolute_mb) - @parameterized.parameters(0, 1, 42,) @test_util.run_v1_only("b/121264236") def testInitOnly(self, num_inits): @@ -102,43 +68,6 @@ class MultiDeviceIteratorTest(test_base.DatasetTestBase, self.evaluate(elem_on_1) self.evaluate(elem_on_2) - @test_util.run_v1_only("b/121264236") - def testEagerMemoryUsageWithReset(self): - if not context.executing_eagerly(): - self.skipTest("Only eager mode test") - if memory_profiler is None: - self.skipTest("memory_profiler required to run this test") - - dataset = dataset_ops.Dataset.range(10) - multi_device_iterator = multi_device_iterator_ops.MultiDeviceIterator( - dataset, ["/cpu:1", "/cpu:2"]) - - def f(): - self.evaluate(multi_device_iterator.get_next()) - multi_device_iterator._eager_reset() - - self.assertNotIncreasingMemory( - f, num_iters=100, increase_threshold_absolute_mb=50) - - @test_util.run_v1_only("b/121264236") - def testEagerMemoryUsageWithRecreation(self): - if not context.executing_eagerly(): - self.skipTest("Only eager mode test") - if memory_profiler is None: - self.skipTest("memory_profiler required to run this test") - - dataset = dataset_ops.Dataset.range(10) - - def f(): - multi_device_iterator = multi_device_iterator_ops.MultiDeviceIterator( - dataset, ["/cpu:1", "/cpu:2"]) - self.evaluate(multi_device_iterator.get_next()) - del multi_device_iterator - - # TODO(b/123316347): Reduce threshold once bug is fixed. - self.assertNotIncreasingMemory( - f, num_iters=100, increase_threshold_absolute_mb=500) - @test_util.run_v1_only("b/121264236") def testOneOnSameDevice(self): with ops.device("/cpu:0"): -- GitLab From fbe459fdbe3eea2924ea52408d1ebe33117319e8 Mon Sep 17 00:00:00 2001 From: Justin Lebar Date: Fri, 22 Feb 2019 13:10:58 -0800 Subject: [PATCH 371/766] [XLA] Add tests for edge cases of sqrt(x) and pow(x, 0.5). These are subtly different: - sqrt(-inf) = nan, but pow(-inf, 0.5) = +inf Add exhaustive tests for sqrt, pow(0.5), and move the exhaustive fp16 test for log into the new file. Doing this requires a nontrivial rewrite of the exhaustive test; hopefully this is the last time. While we're here, we enable a few more exhaustive tests. Also remove the special-case handling in GPU elemental IR emitter for pow(x, 0.5) now that we have a sqrt HLO. PiperOrigin-RevId: 235245101 --- tensorflow/compiler/xla/client/lib/BUILD | 1 + .../xla/client/lib/math_exhaustive_test.cc | 4 +- .../compiler/xla/client/lib/math_test.cc | 37 ++ .../xla/service/gpu/elemental_ir_emitter.cc | 34 +- tensorflow/compiler/xla/tests/BUILD | 5 +- .../exhaustive_f32_elementwise_op_test.cc | 237 --------- .../compiler/xla/tests/exhaustive_op_test.cc | 450 ++++++++++++++++++ 7 files changed, 495 insertions(+), 273 deletions(-) delete mode 100644 tensorflow/compiler/xla/tests/exhaustive_f32_elementwise_op_test.cc create mode 100644 tensorflow/compiler/xla/tests/exhaustive_op_test.cc diff --git a/tensorflow/compiler/xla/client/lib/BUILD b/tensorflow/compiler/xla/client/lib/BUILD index c5dea5f180..b8c0be4833 100644 --- a/tensorflow/compiler/xla/client/lib/BUILD +++ b/tensorflow/compiler/xla/client/lib/BUILD @@ -195,6 +195,7 @@ xla_test( name = "math_test", srcs = ["math_test.cc"], deps = [ + ":constants", ":math", "//tensorflow/compiler/xla:literal_util", "//tensorflow/compiler/xla:shape_util", diff --git a/tensorflow/compiler/xla/client/lib/math_exhaustive_test.cc b/tensorflow/compiler/xla/client/lib/math_exhaustive_test.cc index 7f423d54db..ea6640d10e 100644 --- a/tensorflow/compiler/xla/client/lib/math_exhaustive_test.cc +++ b/tensorflow/compiler/xla/client/lib/math_exhaustive_test.cc @@ -95,6 +95,7 @@ class MathExhaustiveTest : public ClientLibraryTestBase, // Checks a function's behavior on all fp16 values. // // TODO(jlebar): asin and lgamma tests fail on interpreter. +#if !defined(XLA_BACKEND_DOES_NOT_SUPPORT_FLOAT16) XLA_TEST_P(MathExhaustiveTest, DISABLED_ON_INTERPRETER(F16)) { const Testcase& tc = GetParam(); XlaBuilder b(TestName()); @@ -137,6 +138,7 @@ XLA_TEST_P(MathExhaustiveTest, DISABLED_ON_INTERPRETER(F16)) { tc.op(param); ComputeAndCompareR1(&b, expected_result, {}, tc.error); } +#endif // TODO(b/123355973): The following tests from math.cc are missing. // @@ -163,10 +165,10 @@ XLA_TEST_P(MathExhaustiveTest, DISABLED_ON_INTERPRETER(F16)) { // TODO(b/123355973): Test bf16 and f32. // TODO(b/123355973): Get rid of skip_infs / skip_neg_zero below if possible. // TODO(b/123355973): Reduce lgamma error if possible; it is very high. +// TODO(b/123355973): Move these into exhaustive_op_test. INSTANTIATE_TEST_CASE_P( MathExhaustiveTest_Instantiation, MathExhaustiveTest, ::testing::ValuesIn(std::vector{ - Testcase{"sqrt", Sqrt, std::sqrt}.set_skip_neg_inf(), Testcase{"rsqrt", Rsqrt, [](float x) { return 1 / std::sqrt(x); }} .set_tolerance(0.05, 0.05) .set_skip_infs() diff --git a/tensorflow/compiler/xla/client/lib/math_test.cc b/tensorflow/compiler/xla/client/lib/math_test.cc index bdfb0575f5..f5ba3e7805 100644 --- a/tensorflow/compiler/xla/client/lib/math_test.cc +++ b/tensorflow/compiler/xla/client/lib/math_test.cc @@ -14,6 +14,7 @@ limitations under the License. ==============================================================================*/ #include "tensorflow/compiler/xla/client/lib/math.h" +#include "tensorflow/compiler/xla/client/lib/constants.h" #include "tensorflow/compiler/xla/client/xla_builder.h" #include "tensorflow/compiler/xla/literal_util.h" #include "tensorflow/compiler/xla/primitive_util.h" @@ -104,6 +105,37 @@ class MathTypedTest : public MathTest { {true, false, false, false, false, false, false}), {}, error_spec_); } + + // sqrt(x) == pow(x, 0.5) except that + // + // pow(-inf, 0.5) == inf, while + // sqrt(-inf) == nan. + // + // Check that none of our backends are incorrectly assuming that sqrt(x) == + // pow(x, 0.5) without checking this edge case. + // + // For good measure, we also check pow with an exponent other than 0.5. + void TestSqrtPowInequivalence() { + SetFastMathDisabled(true); + + // Tests disable constant folding by default, but this test needs it + // enabled, otherwise we don't tickle the bug we're trying to catch. + // Specifically, without constant folding, the constants we pass to Pow + // below are hidden behind a reshape that's never folded away! + mutable_debug_options()->clear_xla_disable_hlo_passes(); + + const T inf(std::numeric_limits::infinity()); + const T nan(std::numeric_limits::quiet_NaN()); + + XlaBuilder b(TestName()); + auto x = AddParam(LiteralUtil::CreateR1({-inf}), &b); + ConstantR1(&b, {-inf}); + ConcatInDim( + &b, {Sqrt(x), Pow(x, ScalarLike(x, 0.5)), Pow(x, ScalarLike(x, 0.3))}, + 0); + std::vector expected = {nan, inf, inf}; + ComputeAndCompareR1(&b, expected, {}, error_spec_); + } }; // TODO(b/123355973): Add bfloat16 to TestTypes once it's working. @@ -119,6 +151,9 @@ XLA_TYPED_TEST(MathTypedTest, LogEdgeCases) { this->TestLogEdgeCases(); } XLA_TYPED_TEST(MathTypedTest, Log1pEdgeCases) { this->TestLog1pEdgeCases(); } XLA_TYPED_TEST(MathTypedTest, IsInfOrNan) { this->TestIsInfOrNan(); } XLA_TYPED_TEST(MathTypedTest, IsNegZero) { this->TestIsNegZero(); } +XLA_TYPED_TEST(MathTypedTest, SqrtPowInequivalence) { + this->TestSqrtPowInequivalence(); +} // Check that certain ops only support real, floating-point inputs. // @@ -239,6 +274,7 @@ XLA_TEST_F(MathTest, Lgamma) { ComputeAndCompareR1(&builder, expected, {}, error_spec_); } +#if !defined(XLA_BACKEND_DOES_NOT_SUPPORT_FLOAT16) XLA_TEST_F(MathTest, LgammaF16) { SetFastMathDisabled(true); @@ -259,6 +295,7 @@ XLA_TEST_F(MathTest, LgammaF16) { }; ComputeAndCompareR1(&b, expected, {}, ErrorSpec{0.1}); } +#endif XLA_TEST_F(MathTest, Digamma) { XlaBuilder builder(TestName()); diff --git a/tensorflow/compiler/xla/service/gpu/elemental_ir_emitter.cc b/tensorflow/compiler/xla/service/gpu/elemental_ir_emitter.cc index dd74788a0e..b36ed5c6e0 100644 --- a/tensorflow/compiler/xla/service/gpu/elemental_ir_emitter.cc +++ b/tensorflow/compiler/xla/service/gpu/elemental_ir_emitter.cc @@ -19,6 +19,7 @@ limitations under the License. #include #include +#include "llvm/IR/DerivedTypes.h" #include "tensorflow/core/platform/logging.h" #include "tensorflow/core/platform/types.h" // IWYU pragma: no_include "llvm/IR/Attributes.gen.inc" @@ -191,39 +192,6 @@ StatusOr GpuElementalIrEmitter::EmitPowerOp( PrimitiveType lhs_input_type = op->operand(0)->shape().element_type(); PrimitiveType rhs_input_type = op->operand(1)->shape().element_type(); PrimitiveType output_type = op->shape().element_type(); - llvm::Type* llvm_ty = lhs_value->getType(); - - auto make_sqrt = [&, this]() -> StatusOr { - // NVPTX has four relevant square root instructions: - // sqrt.approx{.ftz}.f32 - // sqrt.rn{.ftz}.f32 - // sqrt.rn.f64 - // rsqrt.approx.f64 - // We rely on LLVM's NVPTX backend to pick the right one based on our - // fast-math options. (If fast-math is enabled, llvm may compute the 64-bit - // sqrt from the rsqrt approximation.) - return EmitLlvmIntrinsicMathCall("llvm.sqrt", {lhs_value}, {lhs_input_type}, - output_type); - }; - - const HloInstruction* rhs = op->operand(1); - if (IsFPLiteralWithValue(rhs, .5)) { - VLOG(10) << "emitting pow(A, .5) as sqrt(A): " << op->ToString(); - return make_sqrt(); - } - - if (IsFPLiteralWithValue(rhs, -.5)) { - VLOG(10) << "emitting pow(A, -.5) as 1/sqrt(A): " << op->ToString(); - // LLVM's NVPTX backend knows how to transform 1/sqrt(A) into the NVPTX - // rsqrt.approx instruction. - // - // TODO(jlebar): Does this happen with fastmath disabled? If not, should - // we force-enable it? - TF_ASSIGN_OR_RETURN(auto* sqrt, make_sqrt()); - return FDiv(llvm::ConstantFP::get(llvm_ty, 1), sqrt); - } - - VLOG(10) << "emitting pow as regular call to pow(): " << op->ToString(); return EmitLibdeviceMathCall("__nv_pow", {lhs_value, rhs_value}, {lhs_input_type, rhs_input_type}, output_type); } diff --git a/tensorflow/compiler/xla/tests/BUILD b/tensorflow/compiler/xla/tests/BUILD index 5628547566..7158708e9c 100644 --- a/tensorflow/compiler/xla/tests/BUILD +++ b/tensorflow/compiler/xla/tests/BUILD @@ -674,8 +674,8 @@ xla_test( ) xla_test( - name = "exhaustive_f32_elementwise_op_test", - srcs = ["exhaustive_f32_elementwise_op_test.cc"], + name = "exhaustive_op_test", + srcs = ["exhaustive_op_test.cc"], real_hardware_only = True, # Very slow on the interpreter. shard_count = 48, tags = [ @@ -687,6 +687,7 @@ xla_test( ":client_library_test_base", ":literal_test_util", "//tensorflow/compiler/xla/client:xla_builder", + "//tensorflow/compiler/xla/client/lib:constants", "//tensorflow/compiler/xla/client/lib:math", "//tensorflow/compiler/xla/tests:xla_internal_test_main", "//tensorflow/core:lib", diff --git a/tensorflow/compiler/xla/tests/exhaustive_f32_elementwise_op_test.cc b/tensorflow/compiler/xla/tests/exhaustive_f32_elementwise_op_test.cc deleted file mode 100644 index b961e61026..0000000000 --- a/tensorflow/compiler/xla/tests/exhaustive_f32_elementwise_op_test.cc +++ /dev/null @@ -1,237 +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 "absl/base/casts.h" -#include "tensorflow/compiler/xla/client/lib/math.h" -#include "tensorflow/compiler/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" - -namespace xla { -namespace { - -class ExhaustiveF32ElementwiseOpTest - : public ClientLibraryTestBase, - public ::testing::WithParamInterface> { - protected: - ErrorSpec error_spec_{0.0001, 0.0001}; - - bool IsClose(float expected, float actual) { - float abs_err = std::abs(expected - actual); - float rel_err = abs_err / std::abs(expected); - return abs_err < error_spec_.abs || rel_err < error_spec_.rel || - (std::isnan(expected) && std::isnan(actual)) || - (std::isinf(expected) && std::isinf(actual) && - (expected > 0) == (actual > 0)); - } - - template - void ExhaustivelyTestF32Op(EnqueueOpTy enqueue_op, - float (*evaluate_op)(float), - std::pair known_incorrect_range) { - SetFastMathDisabled(true); - - int64 begin, end; - std::tie(begin, end) = GetParam(); - int64 input_size = end - begin; - - if (begin >= known_incorrect_range.first && - end <= known_incorrect_range.second) { - LOG(INFO) << absl::StreamFormat( - "Skipping this shard, as the range under test, [%d, %d), falls " - "entirely within the known-incorrect range [%d, %d).", - begin, end, known_incorrect_range.first, - known_incorrect_range.second); - return; - } - - LOG(INFO) << "Checking range [" << begin << ", " << end << ")"; - - XlaBuilder builder(TestName()); - - auto ith_input_elem = [&](int64 i) -> float { - i += begin; - // If the operation is known to be buggy on a specific input clamp that - // input to 0 under the assumption that the op is at least correct on 0. - if (i >= known_incorrect_range.first && - i < known_incorrect_range.second) { - return 0; - } - return absl::bit_cast(i); - }; - - Literal input_literal = - LiteralUtil::CreateFromDimensions(F32, {input_size}); - absl::Span input_arr = input_literal.data(); - for (int64 i = 0; i < input_size; i++) { - input_arr[i] = ith_input_elem(i); - } - auto input = Parameter(&builder, 0, input_literal.shape(), "input"); - enqueue_op(&builder, input); - TF_ASSERT_OK_AND_ASSIGN(XlaComputation comp, builder.Build()); - - // Build and run the computation using the LocalClient API, rather than the - // plain Client API, which is used by ClientLibraryTestBase. This is - // because the plain Client API results does more memcpys to/from Literals, - // and that's slow given that we're touching a lot of data here. - // - // Copy debug options from ClientLibraryTestBase. In particular, we're - // interested in disabling constant folding. - ExecutableBuildOptions build_opts; - *build_opts.mutable_debug_options() = *mutable_debug_options(); - TF_ASSERT_OK_AND_ASSIGN( - auto executable, - client_->Compile(comp, {&input_literal.shape()}, build_opts)); - - TF_ASSERT_OK_AND_ASSIGN( - ScopedShapedBuffer input_data, - client_->LiteralToShapedBuffer(input_literal, /*device_ordinal=*/0)); - - ExecutableRunOptions run_opts; - run_opts.set_allocator(client_->backend().memory_allocator()); - run_opts.set_intra_op_thread_pool( - client_->backend().eigen_intra_op_thread_pool_device()); - TF_ASSERT_OK_AND_ASSIGN(ScopedShapedBuffer result, - executable->Run({&input_data}, run_opts)); - - TF_ASSERT_OK_AND_ASSIGN(Literal result_literal, - client_->ShapedBufferToLiteral(result)); - - // We essentially reimplement LiteralTestUtil::Near here because - // a) this streamlined implementation is much faster, and - // b) we can print out better error messages (namely, we can print out - // which floating-point value input failed, while LiteralTestUtil::Near - // can only print out the input index that failed). - // c) we need special handling of certain inputs. For example, we say that - // a denormal input has multiple correct outputs (namely, f(x) and f(0)) - // and just needs to be close to one of them. - absl::Span result_arr = result_literal.data(); - ASSERT_EQ(result_arr.size(), input_arr.size()); - int64 mismatches = 0; - // Hoisting this out of the loop is a nice speedup on shards that have many - // denormals. - const float expected_at_zero = evaluate_op(0); - for (int64 i = 0; i < input_arr.size(); ++i) { - float input = ith_input_elem(i); - float actual = result_arr[i]; - float expected = evaluate_op(input); - if (IsClose(expected, actual)) { - continue; - } - - constexpr int64 kMaxMismatchesPrinted = 1000; - if (std::fpclassify(input) == FP_SUBNORMAL) { - // For denormal inputs, we accept answers that are close to either - // - evaluate_op(input) OR - // - evaluate_op(0). - if (IsClose(expected_at_zero, actual)) { - continue; - } - ++mismatches; - if (mismatches < kMaxMismatchesPrinted || VLOG_IS_ON(2)) { - // Use %0.9g because that's guaranteed to print an f32 to full - // precision. - LOG(ERROR) << absl::StreamFormat( - "Mismatch on denormal value %0.9g (0x%08x). Expected either " - "%0.9g (0x%08x) (evaluated at true value) or %0.9g (0x%08x) " - "(evaluated at zero), but got %0.9g (0x%08x).", - input, absl::bit_cast(input), // - expected, absl::bit_cast(expected), // - expected_at_zero, absl::bit_cast(expected_at_zero), - actual, absl::bit_cast(actual)); - } - } else { - mismatches++; - if (mismatches < kMaxMismatchesPrinted || VLOG_IS_ON(2)) { - LOG(ERROR) << absl::StreamFormat( - "Mismatch on %0.9g (0x%08x). Expected %0.9g (0x%08x), but got " - "%0.9g (0x%08x).", - input, absl::bit_cast(input), // - expected, absl::bit_cast(expected), // - actual, absl::bit_cast(actual)); - } - } - - if (mismatches == kMaxMismatchesPrinted && !VLOG_IS_ON(2)) { - LOG(ERROR) << "Not printing any more mismatches; pass " - "--vmodule=exhaustive_f32_elementwise_op_test=2 to see " - "all of them."; - } - } - EXPECT_EQ(mismatches, 0); - } -}; - -XLA_TEST_P(ExhaustiveF32ElementwiseOpTest, LogF32) { -#if !defined(XLA_TEST_BACKEND_CPU) && !defined(XLA_TEST_BACKEND_GPU) - error_spec_ = ErrorSpec{0.001, 0.001}; -#endif - ExhaustivelyTestF32Op( - [](XlaBuilder* builder, const XlaOp& input) { Log(input); }, std::log, - /*known_incorrect_range=*/{0, 0}); -} - -XLA_TEST_P(ExhaustiveF32ElementwiseOpTest, ExpF32) { -#ifdef XLA_TEST_BACKEND_CPU - // TODO(b/73142289): The vectorized Exp 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 = {1107296256 + 11583654, - 1107296256 + 11629080}; -#else - std::pair known_incorrect_range = {0, 0}; -#endif - - ExhaustivelyTestF32Op( - [](XlaBuilder* builder, const XlaOp& input) { Exp(input); }, std::exp, - known_incorrect_range); -} - -XLA_TEST_P(ExhaustiveF32ElementwiseOpTest, TanhF32) { - ExhaustivelyTestF32Op( - [](XlaBuilder* builder, const XlaOp& input) { Tanh(input); }, std::tanh, - /*known_incorrect_range=*/{0, 0}); -} - -XLA_TEST_P(ExhaustiveF32ElementwiseOpTest, ErfF32) { - ExhaustivelyTestF32Op( - [](XlaBuilder* builder, const XlaOp& input) { Erf(input); }, std::erf, - /*known_incorrect_range=*/{0, 0}); -} - -XLA_TEST_P(ExhaustiveF32ElementwiseOpTest, ErfcF32) { - ExhaustivelyTestF32Op( - [](XlaBuilder* builder, const XlaOp& input) { Erfc(input); }, std::erfc, - /*known_incorrect_range=*/{0, 0}); -} - -std::vector> CreateExhaustiveParameters() { - // We break up the 2^32-element space into small'ish chunks to keep peak - // memory usage low. - std::vector> result; - const int64 step = 1 << 25; - for (int64 i = 0; i < (1l << 32); i += step) { - result.push_back({i, i + step}); - } - return result; -} - -INSTANTIATE_TEST_CASE_P(ExhaustiveF32ElementwiseOpTestInstance, - ExhaustiveF32ElementwiseOpTest, - ::testing::ValuesIn(CreateExhaustiveParameters())); -} // namespace -} // namespace xla diff --git a/tensorflow/compiler/xla/tests/exhaustive_op_test.cc b/tensorflow/compiler/xla/tests/exhaustive_op_test.cc new file mode 100644 index 0000000000..1d94937656 --- /dev/null +++ b/tensorflow/compiler/xla/tests/exhaustive_op_test.cc @@ -0,0 +1,450 @@ +/* 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 "absl/base/casts.h" +#include "tensorflow/compiler/xla/client/lib/constants.h" +#include "tensorflow/compiler/xla/client/lib/math.h" +#include "tensorflow/compiler/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" + +namespace xla { +namespace { + +using Eigen::half; + +// For f32, f16, and bf16, we need 9, 5, and 4 decimal places of precision to be +// guaranteed that we're printing the full number. +// +// If we have a floating-point number with S significand bits, we need +// +// ceil(1 + S * log_10(2)) ~= ceil(1 + S * 0.30103) +// +// decimal digits to be guaranteed that we're printing the full number. For +// F32/F16/BF16 this works out to 9/5/4 digits. See +// https://people.eecs.berkeley.edu/~wkahan/Math128/BinDecBin.pdf +string StringifyNum(float x) { + return absl::StrFormat("%0.9g (0x%08x)", x, absl::bit_cast(x)); +} + +string StringifyNum(half x) { + return absl::StrFormat("%0.5g (0x%04x)", static_cast(x), + absl::bit_cast(x)); +} + +string StringifyNum(bfloat16 x) { + return absl::StrFormat("%0.4g (0x%04x)", static_cast(x), + absl::bit_cast(x)); +} + +// Test parameter is a tuple containing +// - primitive type under test, +// - (begin, end) range under test, as zero-extended int64s bitcast to the +// primtive type under test. +class ExhaustiveOpTest + : public ClientLibraryTestBase, + public ::testing::WithParamInterface< + std::tuple>> { + public: + ExhaustiveOpTest() + : ty_(std::get<0>(GetParam())), platform_(client_->platform()->Name()) {} + + void Run(std::function enqueue_op, + float (*evaluate_op)(float)) { + SetFastMathDisabled(true); + + // Run all HLO passes. In particular, constant folding is disabled by + // default for tests, but we need to run it in order to tickle some bugs. + mutable_debug_options()->clear_xla_disable_hlo_passes(); + + PrimitiveType ty; + std::tie(ty, std::ignore) = GetParam(); + + switch (ty) { + case F32: + SetDefaultErrSpec(0.0001, 0.0001); + RunImpl(enqueue_op, evaluate_op); + break; + case F16: + SetDefaultErrSpec(0.001, 0.001); + RunImpl(enqueue_op, evaluate_op); + break; + case BF16: + SetDefaultErrSpec(0.001, 0.01); + RunImpl(enqueue_op, evaluate_op); + break; + default: + LOG(FATAL) << "Unhandled type."; + } + } + + void SetDefaultErrSpec(float abs_err, float rel_err) { + if (!abs_err_.has_value()) { + abs_err_ = abs_err; + } + if (!rel_err_.has_value()) { + rel_err_ = rel_err; + } + } + + template + void RunImpl(std::function enqueue_op, + float (*evaluate_op)(float)) { + static_assert( + sizeof(T) == sizeof(IntegralT), + "IntegralT must be an unsigned integer type of the same width as T."); + + PrimitiveType ty; + std::pair test_range; + std::tie(ty, test_range) = GetParam(); + int64 begin, end; + std::tie(begin, end) = test_range; + + if (begin >= known_incorrect_begin_ && end <= known_incorrect_end_) { + LOG(INFO) << absl::StreamFormat( + "Skipping this shard, as the range under test, [%d, %d), falls " + "entirely within the known-incorrect range [%d, %d).", + begin, end, known_incorrect_begin_, known_incorrect_end_); + return; + } + + LOG(INFO) << "Checking range [" << begin << ", " << end << ")"; + + int64 input_size = end - begin; + Literal input_literal = LiteralUtil::CreateFromDimensions(ty, {input_size}); + absl::Span input_arr = input_literal.data(); + for (int64 i = 0; i < input_size; i++) { + IntegralT input_val = i + begin; + // If the operation is known to be buggy on a specific input clamp that + // input to 0 under the assumption that the op is at least correct on 0. + if (input_val >= known_incorrect_begin_ && + input_val < known_incorrect_end_) { + input_arr[i] = T{0}; + } else { + input_arr[i] = absl::bit_cast(input_val); + } + } + + TF_ASSERT_OK_AND_ASSIGN(Literal result_literal, + BuildAndRunComputation(enqueue_op, input_literal)); + ExpectNear(input_literal, result_literal, evaluate_op); + } + + StatusOr BuildAndRunComputation( + const std::function& enqueue_op, + const Literal& input_literal) { + XlaBuilder builder(TestName()); + auto input = Parameter(&builder, 0, input_literal.shape(), "input"); + enqueue_op(input); + TF_ASSIGN_OR_RETURN(XlaComputation comp, builder.Build()); + + // Build and run the computation using the LocalClient API, rather than the + // plain Client API, which is used by ClientLibraryTestBase. This is + // because the plain Client API results does more memcpys to/from Literals, + // and that's slow given that we're touching a lot of data here. + // + // Copy debug options from ClientLibraryTestBase. In particular, we're + // interested in disabling constant folding. + ExecutableBuildOptions build_opts; + *build_opts.mutable_debug_options() = *mutable_debug_options(); + TF_ASSIGN_OR_RETURN( + auto executable, + client_->Compile(comp, {&input_literal.shape()}, build_opts)); + + TF_ASSIGN_OR_RETURN( + ScopedShapedBuffer input_data, + client_->LiteralToShapedBuffer(input_literal, /*device_ordinal=*/0)); + + ExecutableRunOptions run_opts; + run_opts.set_allocator(client_->backend().memory_allocator()); + run_opts.set_intra_op_thread_pool( + client_->backend().eigen_intra_op_thread_pool_device()); + TF_ASSIGN_OR_RETURN(ScopedShapedBuffer result, + executable->Run({&input_data}, run_opts)); + + TF_ASSIGN_OR_RETURN(Literal result_literal, + client_->ShapedBufferToLiteral(result)); + return std::move(result_literal); + } + + template + bool IsClose(T expected, T actual) { + float expected_f32 = static_cast(expected); + float actual_f32 = static_cast(actual); + float abs_err = std::abs(expected_f32 - actual_f32); + float rel_err = abs_err / std::abs(expected_f32); + if (strict_signed_zeros_ && actual == T{0} && expected == T{0}) { + // Check sign of zero. + return std::signbit(actual_f32) == std::signbit(expected_f32); + } + return abs_err < *abs_err_ || rel_err < *rel_err_ || + (std::isnan(expected_f32) && std::isnan(actual_f32)) || + (std::isinf(expected_f32) && std::isinf(actual_f32) && + (expected_f32 > 0) == (actual_f32 > 0)); + } + + template + void ExpectNear(const Literal& input_literal, const Literal& result_literal, + float (*evaluate_op)(float)) { + // We essentially reimplement LiteralTestUtil::Near here because + // a) this streamlined implementation is much faster, and + // b) we can print out better error messages (namely, we can print out + // which floating-point value input failed, while LiteralTestUtil::Near + // can only print out the input index that failed). + // c) we need special handling of certain inputs. For example, we say that + // a denormal input has multiple correct outputs (namely, f(x) and f(0)) + // and just needs to be close to one of them. + absl::Span input_arr = input_literal.data(); + absl::Span result_arr = result_literal.data(); + ASSERT_EQ(result_arr.size(), input_arr.size()); + int64 mismatches = 0; + // Hoisting these out of the loop is a nice speedup on shards that have many + // denormals. + const T expected_at_pos_zero = static_cast(evaluate_op(0)); + const T expected_at_neg_zero = static_cast(evaluate_op(-0.0)); + for (int64 i = 0; i < input_arr.size(); ++i) { + T input = input_arr[i]; + float input_f32 = static_cast(input); + T actual = result_arr[i]; + T expected = static_cast(evaluate_op(input_f32)); + + if (IsClose(expected, actual)) { + continue; + } + + // Easy case: If `input` is not denormal and !IsClose(expected, actual), + // print an error. + // + // TODO(jlebar): This doesn't correctly detect f16 and bfloat16 denormals! + // This seems to be OK for now, but at some point we may need to implement + // fpclassify for half and bfloat. + if (std::fpclassify(input_f32) != FP_SUBNORMAL) { + PrintMismatch(&mismatches, [&] { + return absl::StrFormat("Mismatch on %s. Expected %s, but got %s.", + StringifyNum(input), StringifyNum(expected), + StringifyNum(actual)); + }); + continue; + } + + // Otherwise, `input` is denormal. For denormal inputs, we accept answers + // that are close to any of: + // + // - evaluate_op(input) + // - evaluate_op(+/-0), where the sign of 0 equal to the sign of + // `input`, + // - if relaxed_denormal_signs_, evaluate_op(-/+0), where the sign of + // 0 is the opposite of `input`. + T sign_preserving_ftz_expected = + std::signbit(input_f32) ? expected_at_neg_zero : expected_at_pos_zero; + T sign_nonpreserving_ftz_expected = + std::signbit(input_f32) ? expected_at_pos_zero : expected_at_neg_zero; + if (IsClose(sign_preserving_ftz_expected, actual) || + (relaxed_denormal_signs_ && + IsClose(sign_nonpreserving_ftz_expected, actual))) { + continue; + } + + if (relaxed_denormal_signs_) { + PrintMismatch(&mismatches, [&] { + return absl::StrFormat( + "Mismatch on denormal value %s. Expected one of:\n" + " %10s (evaluated at full-precision value)\n" + " %10s (evaluated after flushing to sign-preserving zero)\n" + " %10s (evaluated after flushing to non-sign-preserving " + "zero)\n" + "but got %s.", + StringifyNum(input), StringifyNum(expected), + StringifyNum(sign_preserving_ftz_expected), + StringifyNum(sign_nonpreserving_ftz_expected), + StringifyNum(actual)); + }); + } else { + PrintMismatch(&mismatches, [&] { + return absl::StrFormat( + "Mismatch on denormal value %s. Expected one of:\n" + " %10s (evaluated at full-precision value)\n" + " %10s (evaluated after flushing to sign-preserving zero)\n" + "but got %s.", + StringifyNum(input), StringifyNum(expected), + StringifyNum(sign_preserving_ftz_expected), StringifyNum(actual)); + }); + } + } + EXPECT_EQ(mismatches, 0); + } + + template + void PrintMismatch(int64* mismatches, const ErrorGenerator& err_generator) { + // We send a few mismatches to gunit so they show up nicely in test logs. + // Then we send more to LOG(ERROR). The remainder we squelch unless we're + // at vlog level 2. + constexpr int64 kMaxMismatchesLoggedToGunit = 10; + constexpr int64 kMaxMismatchesLoggedToErr = 1000; + + (*mismatches)++; + if (*mismatches < kMaxMismatchesLoggedToGunit) { + FAIL() << err_generator(); + } else if (*mismatches < kMaxMismatchesLoggedToErr || VLOG_IS_ON(2)) { + LOG(ERROR) << err_generator(); + } else if (*mismatches == kMaxMismatchesLoggedToErr) { + LOG(ERROR) << "Not printing any more mismatches; pass " + "--vmodule=exhaustive_f32__op_test=2 to see " + "all of them."; + } + } + + // The following members are set during construction so testcases can read + // these values and use them e.g. to influence the values given to the mutable + // members below. + + // The primitive type under test. + const PrimitiveType ty_; + + // The platform under test. + const string platform_; + + // Tests can set the following variables for control over execution. This is + // safe because each XLA_TEST_P instantiates a new instance of this class. + + // Testing will ignore the given range (encoded as bitwise representations of + // the type under test zero-extended to int64). + int64 known_incorrect_begin_ = 0; + int64 known_incorrect_end_ = 0; + + // If unset, reasonable defaults will be used depending on the type under + // test. + absl::optional abs_err_; + absl::optional rel_err_; + + // If true, will consider -0 not near to +0 and vice versa. Note that + // +epsilon may still be considered close to -0, depending on the error spec; + // this only covers the case when both `expected` and `actual` are equal to 0. + bool strict_signed_zeros_ = false; + + // If true, allows denormals to be flushed to non-sign-preserving 0. + // + // For example, normally we'd expect sqrt(-denormal) to be either nan (sqrt of + // a negative number) or -inf (flush the denormal to sign-perserving zero, + // then sqrt(-0)). But with this as true, we'll also accept 0 (sqrt(0)). + // + // XLA:GPU preserves denormal signs, but other backends don't. + bool relaxed_denormal_signs_ = platform_ != "CUDA"; +}; + +XLA_TEST_P(ExhaustiveOpTest, Log) { + if (platform_ != "Host" && platform_ != "CUDA" && ty_ == F32) { + abs_err_ = 0.001; + rel_err_ = 0.001; + } + + Run(Log, std::log); +} + +XLA_TEST_P(ExhaustiveOpTest, Exp) { + if (platform_ == "Host" && ty_ == F32) { + // TODO(b/73142289): The vectorized Exp implementation gives results outside + // our error spec in this range. + known_incorrect_begin_ = 1107296256 + 11583654; + known_incorrect_end_ = 1107296256 + 11629080; + } else if (platform_ == "Host" && ty_ == BF16) { + // TODO(jlebar): Is this a rounding error? Why doesn't it occur on XLA:GPU? + // + // Mismatch on 88.5 (0x42b1). + // Expected 2.72491739e+38 (0x7f4d), but got inf (0x7f80). + known_incorrect_begin_ = 0x42b1; + known_incorrect_end_ = 0x42b2; + } + + Run(Exp, std::exp); +} + +// It feels a little overkill to exhaustively test sqrt and pow(x, 0.5), but +// this *did* find a bug, namely that some backends were assuming sqrt(x) == +// pow(x, 0.5), but this is not true for x == -inf. +XLA_TEST_P(ExhaustiveOpTest, PowOneHalf) { + Run([](XlaOp x) { return Pow(x, ScalarLike(x, 0.5)); }, + +[](float x) { return std::pow(x, 0.5f); }); +} + +XLA_TEST_P(ExhaustiveOpTest, Rsqrt) { + Run( + Rsqrt, +[](float x) { return 1 / std::sqrt(x); }); +} + +XLA_TEST_P(ExhaustiveOpTest, Sqrt) { + if (platform_ == "Host" || platform_ == "CUDA") { + strict_signed_zeros_ = true; + } + + Run(Sqrt, std::sqrt); +} + +XLA_TEST_P(ExhaustiveOpTest, Tanh) { + // TODO(jlebar): Enable this test for (b)f16. + if (ty_ == F16 || ty_ == BF16) { + return; + } + Run(Tanh, std::tanh); +} +XLA_TEST_P(ExhaustiveOpTest, Erf) { + // TODO(jlebar): Enable this test for (b)f16. + if (ty_ == F16 || ty_ == BF16) { + return; + } + Run(Erf, std::erf); +} +XLA_TEST_P(ExhaustiveOpTest, Erfc) { + // TODO(jlebar): Enable this test for (b)f16. + if (ty_ == F16 || ty_ == BF16) { + return; + } + Run(Erfc, std::erfc); +} + +std::vector> CreateExhaustiveF32Ranges() { + // We break up the 2^32-element space into small'ish chunks to keep peak + // memory usage low. + std::vector> result; + const int64 step = 1 << 25; + for (int64 i = 0; i < (1l << 32); i += step) { + result.push_back({i, i + step}); + } + return result; +} + +INSTANTIATE_TEST_SUITE_P( + F32, ExhaustiveOpTest, + ::testing::Combine(::testing::Values(F32), + ::testing::ValuesIn(CreateExhaustiveF32Ranges()))); + +#if !defined(XLA_BACKEND_DOES_NOT_SUPPORT_FLOAT16) +INSTANTIATE_TEST_SUITE_P( + F16, ExhaustiveOpTest, + ::testing::Combine(::testing::Values(F16), + ::testing::Values(std::make_pair(0, 1 << 16)))); +#endif + +#if defined(XLA_BACKEND_SUPPORTS_BFLOAT16) +INSTANTIATE_TEST_SUITE_P( + BF16, ExhaustiveOpTest, + ::testing::Combine(::testing::Values(BF16), + ::testing::Values(std::make_pair(0, 1 << 16)))); +#endif + +} // namespace +} // namespace xla -- GitLab From 5528b56647d39908ab0c9d2e97ff939d18f9b30f Mon Sep 17 00:00:00 2001 From: Allen Lavoie Date: Fri, 22 Feb 2019 13:26:41 -0800 Subject: [PATCH 372/766] Ignore get_configs that aren't JSON-serializable when saving checkpoints This is extra "nice to have" metadata, and otherwise it looks like a checkpointing error. Not worth bothering people about. PiperOrigin-RevId: 235247436 --- tensorflow/python/training/tracking/base.py | 12 ++++++++---- tensorflow/python/training/tracking/base_test.py | 13 +++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/tensorflow/python/training/tracking/base.py b/tensorflow/python/training/tracking/base.py index 4a8960d3ca..f1f1fcba7c 100644 --- a/tensorflow/python/training/tracking/base.py +++ b/tensorflow/python/training/tracking/base.py @@ -851,10 +851,14 @@ class Trackable(object): """Serializes `self.get_config()` for saving.""" dereferenced_self = weak_self() if dereferenced_self: - return json.dumps( - dereferenced_self, - default=serialization.get_json_type, - sort_keys=True).encode("utf8") + try: + return json.dumps( + dereferenced_self, + default=serialization.get_json_type, + sort_keys=True).encode("utf8") + except TypeError: + # Even if get_config worked objects may have produced garbage. + return "" else: return "" return {OBJECT_CONFIG_JSON_KEY: functools.partial( diff --git a/tensorflow/python/training/tracking/base_test.py b/tensorflow/python/training/tracking/base_test.py index 4a74417e3b..d76e20edf7 100644 --- a/tensorflow/python/training/tracking/base_test.py +++ b/tensorflow/python/training/tracking/base_test.py @@ -83,6 +83,19 @@ class InterfaceTests(test.TestCase): with self.assertRaisesRegexp(AssertionError, "foo_attr"): status.assert_consumed() + def testBuggyGetConfig(self): + + class NotSerializable(object): + pass + + class GetConfigRaisesError(base.Trackable): + + def get_config(self): + return NotSerializable() + + util.Checkpoint(obj=GetConfigRaisesError()).save( + os.path.join(self.get_temp_dir(), "ckpt")) + if __name__ == "__main__": ops.enable_eager_execution() -- GitLab From 25642bfb042ceb7a18a35804c0abd46ac54c21c0 Mon Sep 17 00:00:00 2001 From: Allen Lavoie Date: Fri, 22 Feb 2019 13:28:22 -0800 Subject: [PATCH 373/766] Automated rollback of commit 809c74f0ef105a419320e1cd16966bf19c891949 PiperOrigin-RevId: 235247680 --- tensorflow/python/module/module.py | 11 +--------- tensorflow/python/module/module_test.py | 29 ------------------------- 2 files changed, 1 insertion(+), 39 deletions(-) diff --git a/tensorflow/python/module/module.py b/tensorflow/python/module/module.py index 21f6e29468..07455bd7f7 100644 --- a/tensorflow/python/module/module.py +++ b/tensorflow/python/module/module.py @@ -21,7 +21,6 @@ from __future__ import print_function import abc import re import sys -import weakref import six @@ -147,19 +146,11 @@ def wrap_with_name_scope(unbound_method): def wrap_with_name_scope_no_exception(unbound_method): """Patches the given method so it enters the modules name scope.""" - weak_enter_name_scope = None def enter_name_scope(self, *args, **kwargs): with self.name_scope: # tf.Module enters the module name scope for all methods. To disable this # for a particular method annotate it with `@no_module_name_scope`. - return weak_enter_name_scope().__wrapped__(self, *args, **kwargs) - weak_enter_name_scope = weakref.ref(enter_name_scope) - # make_decorator forwards argspecs through the wrapper, which tf.function - # requires for naming inputs (e.g. default argument names in the result of - # get_concrete_function) and error checking. Calling __wrapped__ lets - # tf_decorator.rewrap replace the called function, currently used to swap in - # the AutoGraph-converted function. - tf_decorator.make_decorator(unbound_method, enter_name_scope) + return unbound_method(self, *args, **kwargs) return enter_name_scope diff --git a/tensorflow/python/module/module_test.py b/tensorflow/python/module/module_test.py index dc6bbe2c94..52bb97bc5b 100644 --- a/tensorflow/python/module/module_test.py +++ b/tensorflow/python/module/module_test.py @@ -26,10 +26,7 @@ import six from tensorflow.python.compat import v2_compat from tensorflow.python.eager import def_function -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_spec from tensorflow.python.module import module from tensorflow.python.ops import variables from tensorflow.python.platform import test @@ -284,32 +281,6 @@ class AbcTest(test.TestCase): self.assertEqual(scope_name, "concrete_module/") self.assertEqual(get_name_scope(), "") - def testModuleDecoratedMethodInputSignature(self): - - class Module(module.Module): - - def no_sig(self, called_with_keyword): - return 2. * called_with_keyword - - @def_function.function( - input_signature=[tensor_spec.TensorSpec([], dtypes.float32)]) - def mutate(self, new_v): - if not hasattr(self, "v"): - self.v = variables.Variable(1.) - if new_v > 0.: - self.v.assign(new_v) - else: - # Autograph should still be running - self.v.assign(-new_v) - - m = Module() - m.mutate(constant_op.constant(2.)) - self.assertEqual(2., m.v.numpy()) - m.mutate(constant_op.constant(-3.)) - self.assertEqual(3., m.v.numpy()) - - m.no_sig(called_with_keyword=1.) # keyword names preserved - def get_name_scope(): with ops.name_scope("x") as ns: -- GitLab From 826027dbd4277a2636fc2935ed245700fd01e7cd Mon Sep 17 00:00:00 2001 From: Nick Felt Date: Fri, 22 Feb 2019 13:39:19 -0800 Subject: [PATCH 374/766] Change TF 2.0 tf.summary.SummaryWriter to map 1:1 to underlying resource This changes the resource model for the TF 2.0 tf.summary.SummaryWriter as returned by tf.summary.create_file_writer(). Previously, we preserved the 1.x tf.contrib.summary.create_file_writer() semantics where underlying resources were shared by logdir to facilitate reuse of a single eventfile per directory, which avoids accidentally overwriting an existing eventfile (which is possible if both are created within the same second) and is better supported by TensorBoard. Unfortunately, that sharing behavior resulted in buggy and confusing resource deletion behavior as described in #25707, and is also out of alignment with the overall TF 2.0 philosophy that resource lifetimes should match the lifetimes of their encapsulating Python objects. The new SummaryWriter wrapper object fixes this by generating a unique resource name for each instance. Inside @tf.functions, this requires lifting initialization out using init_scope(), which means in-graph tensors can no longer be passed to create_file_writer(), and the returned SummaryWriter must be stored in a non-local variable so that it outlives the tracing phase and is still alive when the function graph executes. A future change will add better error messaging for the latter circumstance. To keep the SummaryWriter <-> resource mapping 1:1, this also changes the close() method so that in eager mode it will permanently close the SummaryWriter and prevent re-initialization. (A future change may do this for graph mode as well.) To mitigate the risk of eventfile name collisions, the new wrapper bakes the PID and a TF-generated UID into the filename suffix. Finally, this cleans up the public API surface by 1) moving the "logdir=None means no-op" behavior into a dedicated create_noop_writer() factory function and 2) hiding SummaryWriter.__init__() from the public API by making SummaryWriter an abstract base class; instances should be obtained via factory functions anyway. Fixes #25707. A subsequent change will update the TF 2.0 Keras callback to call create_file_writer_v2(), which should address #25524 and #24632. PiperOrigin-RevId: 235249883 --- tensorflow/python/BUILD | 1 + tensorflow/python/kernel_tests/BUILD | 1 + .../python/kernel_tests/summary_ops_test.py | 255 ++++++++++------ tensorflow/python/ops/summary_ops_v2.py | 272 +++++++++++++----- .../tensorflow.summary.-summary-writer.pbtxt | 1 - .../api/golden/v2/tensorflow.summary.pbtxt | 4 + 6 files changed, 387 insertions(+), 147 deletions(-) diff --git a/tensorflow/python/BUILD b/tensorflow/python/BUILD index 04a740e1f3..e6ea4fd36b 100644 --- a/tensorflow/python/BUILD +++ b/tensorflow/python/BUILD @@ -3303,6 +3303,7 @@ py_library( ":smart_cond", ":summary_op_util", ":summary_ops_gen", + ":tensor_util", ":training_util", ":util", "//tensorflow/core:protos_all_py", diff --git a/tensorflow/python/kernel_tests/BUILD b/tensorflow/python/kernel_tests/BUILD index ea45896970..085c1d76b2 100644 --- a/tensorflow/python/kernel_tests/BUILD +++ b/tensorflow/python/kernel_tests/BUILD @@ -1093,6 +1093,7 @@ cuda_py_test( "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", "//tensorflow/python:dtypes", + "//tensorflow/python:errors", "//tensorflow/python:framework_ops", "//tensorflow/python:framework_test_lib", "//tensorflow/python:lib", diff --git a/tensorflow/python/kernel_tests/summary_ops_test.py b/tensorflow/python/kernel_tests/summary_ops_test.py index b7a05136c4..0b4d58f608 100644 --- a/tensorflow/python/kernel_tests/summary_ops_test.py +++ b/tensorflow/python/kernel_tests/summary_ops_test.py @@ -19,7 +19,6 @@ from __future__ import division from __future__ import print_function import os -import time import unittest from tensorflow.core.framework import graph_pb2 @@ -32,6 +31,7 @@ from tensorflow.python.eager import context from tensorflow.python.eager import def_function 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_spec from tensorflow.python.framework import tensor_util @@ -52,7 +52,7 @@ class SummaryOpsCoreTest(test_util.TensorFlowTestCase): def testWrite(self): logdir = self.get_temp_dir() with context.eager_mode(): - with summary_ops.create_file_writer(logdir).as_default(): + with summary_ops.create_file_writer_v2(logdir).as_default(): output = summary_ops.write('tag', 42, step=12) self.assertTrue(output.numpy()) events = events_from_logdir(logdir) @@ -64,11 +64,12 @@ class SummaryOpsCoreTest(test_util.TensorFlowTestCase): def testWrite_fromFunction(self): logdir = self.get_temp_dir() - @def_function.function - def f(): - with summary_ops.create_file_writer(logdir).as_default(): - return summary_ops.write('tag', 42, step=12) with context.eager_mode(): + writer = summary_ops.create_file_writer_v2(logdir) + @def_function.function + def f(): + with writer.as_default(): + return summary_ops.write('tag', 42, step=12) output = f() self.assertTrue(output.numpy()) events = events_from_logdir(logdir) @@ -83,7 +84,7 @@ class SummaryOpsCoreTest(test_util.TensorFlowTestCase): metadata = summary_pb2.SummaryMetadata() metadata.plugin_data.plugin_name = 'foo' with context.eager_mode(): - with summary_ops.create_file_writer(logdir).as_default(): + with summary_ops.create_file_writer_v2(logdir).as_default(): summary_ops.write('obj', 0, 0, metadata=metadata) summary_ops.write('bytes', 0, 0, metadata=metadata.SerializeToString()) m = constant_op.constant(metadata.SerializeToString()) @@ -104,7 +105,7 @@ class SummaryOpsCoreTest(test_util.TensorFlowTestCase): def testWrite_ndarray(self): logdir = self.get_temp_dir() with context.eager_mode(): - with summary_ops.create_file_writer(logdir).as_default(): + with summary_ops.create_file_writer_v2(logdir).as_default(): summary_ops.write('tag', [[1, 2], [3, 4]], step=12) events = events_from_logdir(logdir) value = events[1].summary.value[0] @@ -114,7 +115,7 @@ class SummaryOpsCoreTest(test_util.TensorFlowTestCase): logdir = self.get_temp_dir() with context.eager_mode(): t = constant_op.constant([[1, 2], [3, 4]]) - with summary_ops.create_file_writer(logdir).as_default(): + with summary_ops.create_file_writer_v2(logdir).as_default(): summary_ops.write('tag', t, step=12) expected = t.numpy() events = events_from_logdir(logdir) @@ -123,11 +124,12 @@ class SummaryOpsCoreTest(test_util.TensorFlowTestCase): def testWrite_tensor_fromFunction(self): logdir = self.get_temp_dir() - @def_function.function - def f(t): - with summary_ops.create_file_writer(logdir).as_default(): - summary_ops.write('tag', t, step=12) with context.eager_mode(): + writer = summary_ops.create_file_writer_v2(logdir) + @def_function.function + def f(t): + with writer.as_default(): + summary_ops.write('tag', t, step=12) t = constant_op.constant([[1, 2], [3, 4]]) f(t) expected = t.numpy() @@ -138,7 +140,7 @@ class SummaryOpsCoreTest(test_util.TensorFlowTestCase): def testWrite_stringTensor(self): logdir = self.get_temp_dir() with context.eager_mode(): - with summary_ops.create_file_writer(logdir).as_default(): + with summary_ops.create_file_writer_v2(logdir).as_default(): summary_ops.write('tag', [b'foo', b'bar'], step=12) events = events_from_logdir(logdir) value = events[1].summary.value[0] @@ -168,7 +170,7 @@ class SummaryOpsCoreTest(test_util.TensorFlowTestCase): def testWrite_recordIf_constant(self): logdir = self.get_temp_dir() with context.eager_mode(): - with summary_ops.create_file_writer(logdir).as_default(): + with summary_ops.create_file_writer_v2(logdir).as_default(): self.assertTrue(summary_ops.write('default', 1, step=0)) with summary_ops.record_if(True): self.assertTrue(summary_ops.write('set_on', 1, step=0)) @@ -181,16 +183,17 @@ class SummaryOpsCoreTest(test_util.TensorFlowTestCase): def testWrite_recordIf_constant_fromFunction(self): logdir = self.get_temp_dir() - @def_function.function - def f(): - with summary_ops.create_file_writer(logdir).as_default(): - # Use assertAllEqual instead of assertTrue since it works in a defun. - self.assertAllEqual(summary_ops.write('default', 1, step=0), True) - with summary_ops.record_if(True): - self.assertAllEqual(summary_ops.write('set_on', 1, step=0), True) - with summary_ops.record_if(False): - self.assertAllEqual(summary_ops.write('set_off', 1, step=0), False) with context.eager_mode(): + writer = summary_ops.create_file_writer_v2(logdir) + @def_function.function + def f(): + with writer.as_default(): + # Use assertAllEqual instead of assertTrue since it works in a defun. + self.assertAllEqual(summary_ops.write('default', 1, step=0), True) + with summary_ops.record_if(True): + self.assertAllEqual(summary_ops.write('set_on', 1, step=0), True) + with summary_ops.record_if(False): + self.assertAllEqual(summary_ops.write('set_off', 1, step=0), False) f() events = events_from_logdir(logdir) self.assertEqual(3, len(events)) @@ -204,7 +207,7 @@ class SummaryOpsCoreTest(test_util.TensorFlowTestCase): def record_fn(): step.assign_add(1) return int(step % 2) == 0 - with summary_ops.create_file_writer(logdir).as_default(): + with summary_ops.create_file_writer_v2(logdir).as_default(): with summary_ops.record_if(record_fn): self.assertTrue(summary_ops.write('tag', 1, step=step)) self.assertFalse(summary_ops.write('tag', 1, step=step)) @@ -220,6 +223,7 @@ class SummaryOpsCoreTest(test_util.TensorFlowTestCase): def testWrite_recordIf_callable_fromFunction(self): logdir = self.get_temp_dir() with context.eager_mode(): + writer = summary_ops.create_file_writer_v2(logdir) step = variables.Variable(-1, dtype=dtypes.int64) @def_function.function def record_fn(): @@ -227,7 +231,7 @@ class SummaryOpsCoreTest(test_util.TensorFlowTestCase): return math_ops.equal(step % 2, 0) @def_function.function def f(): - with summary_ops.create_file_writer(logdir).as_default(): + with writer.as_default(): with summary_ops.record_if(record_fn): return [ summary_ops.write('tag', 1, step=step), @@ -243,13 +247,14 @@ class SummaryOpsCoreTest(test_util.TensorFlowTestCase): def testWrite_recordIf_tensorInput_fromFunction(self): logdir = self.get_temp_dir() - @def_function.function(input_signature=[ - tensor_spec.TensorSpec(shape=[], dtype=dtypes.int64)]) - def f(step): - with summary_ops.create_file_writer(logdir).as_default(): - with summary_ops.record_if(math_ops.equal(step % 2, 0)): - return summary_ops.write('tag', 1, step=step) with context.eager_mode(): + writer = summary_ops.create_file_writer_v2(logdir) + @def_function.function(input_signature=[ + tensor_spec.TensorSpec(shape=[], dtype=dtypes.int64)]) + def f(step): + with writer.as_default(): + with summary_ops.record_if(math_ops.equal(step % 2, 0)): + return summary_ops.write('tag', 1, step=step) self.assertTrue(f(0)) self.assertFalse(f(1)) self.assertTrue(f(2)) @@ -311,77 +316,152 @@ class SummaryOpsCoreTest(test_util.TensorFlowTestCase): class SummaryWriterTest(test_util.TensorFlowTestCase): - def testWriterInitAndClose(self): + def testCreate_withInitAndClose(self): logdir = self.get_temp_dir() with context.eager_mode(): - writer = summary_ops.create_file_writer( + writer = summary_ops.create_file_writer_v2( logdir, max_queue=1000, flush_millis=1000000) - files = gfile.Glob(os.path.join(logdir, '*')) - self.assertEqual(1, len(files)) - file1 = files[0] - self.assertEqual(1, len(events_from_file(file1))) # file_version Event + get_total = lambda: len(events_from_logdir(logdir)) + self.assertEqual(1, get_total()) # file_version Event # Calling init() again while writer is open has no effect writer.init() - self.assertEqual(1, len(events_from_file(file1))) + self.assertEqual(1, get_total()) with writer.as_default(): summary_ops.write('tag', 1, step=0) - self.assertEqual(1, len(events_from_file(file1))) + self.assertEqual(1, get_total()) # Calling .close() should do an implicit flush writer.close() - self.assertEqual(2, len(events_from_file(file1))) - # Calling init() on a closed writer should start a new file - time.sleep(1.1) # Ensure filename has a different timestamp - writer.init() - files = gfile.Glob(os.path.join(logdir, '*')) - self.assertEqual(2, len(files)) - files.remove(file1) - file2 = files[0] - self.assertEqual(1, len(events_from_file(file2))) # file_version - self.assertEqual(2, len(events_from_file(file1))) # should be unchanged - - def testSharedName(self): + self.assertEqual(2, get_total()) + + def testCreate_fromFunction(self): + logdir = self.get_temp_dir() + @def_function.function + def f(): + # Returned SummaryWriter must be stored in a non-local variable so it + # lives throughout the function execution. + if not hasattr(f, 'writer'): + f.writer = summary_ops.create_file_writer_v2(logdir) + with context.eager_mode(): + f() + event_files = gfile.Glob(os.path.join(logdir, '*')) + self.assertEqual(1, len(event_files)) + + def testCreate_graphTensorArgument_raisesError(self): logdir = self.get_temp_dir() + with context.graph_mode(): + logdir_tensor = constant_op.constant(logdir) with context.eager_mode(): - # Create with default shared name (should match logdir) - writer1 = summary_ops.create_file_writer(logdir) + with self.assertRaisesRegex( + ValueError, 'Invalid graph Tensor argument.*logdir'): + summary_ops.create_file_writer_v2(logdir_tensor) + self.assertEmpty(gfile.Glob(os.path.join(logdir, '*'))) + + def testCreate_fromFunction_graphTensorArgument_raisesError(self): + logdir = self.get_temp_dir() + @def_function.function + def f(): + summary_ops.create_file_writer_v2(constant_op.constant(logdir)) + with context.eager_mode(): + with self.assertRaisesRegex( + ValueError, 'Invalid graph Tensor argument.*logdir'): + f() + self.assertEmpty(gfile.Glob(os.path.join(logdir, '*'))) + + def testCreate_fromFunction_unpersistedResource_raisesError(self): + logdir = self.get_temp_dir() + @def_function.function + def f(): + with summary_ops.create_file_writer_v2(logdir).as_default(): + pass # Calling .as_default() is enough to indicate use. + with context.eager_mode(): + # TODO(nickfelt): change this to a better error + with self.assertRaisesRegex( + errors.NotFoundError, 'Resource.*does not exist'): + f() + # Even though we didn't use it, an event file will have been created. + self.assertEqual(1, len(gfile.Glob(os.path.join(logdir, '*')))) + + def testNoSharing(self): + # Two writers with the same logdir should not share state. + logdir = self.get_temp_dir() + with context.eager_mode(): + writer1 = summary_ops.create_file_writer_v2(logdir) with writer1.as_default(): summary_ops.write('tag', 1, step=1) - summary_ops.flush() - # Create with explicit logdir shared name (should be same resource/file) - shared_name = 'logdir:' + logdir - writer2 = summary_ops.create_file_writer(logdir, name=shared_name) + event_files = gfile.Glob(os.path.join(logdir, '*')) + self.assertEqual(1, len(event_files)) + file1 = event_files[0] + + writer2 = summary_ops.create_file_writer_v2(logdir) with writer2.as_default(): summary_ops.write('tag', 1, step=2) - summary_ops.flush() - # Create with different shared name (should be separate resource/file) - time.sleep(1.1) # Ensure filename has a different timestamp - writer3 = summary_ops.create_file_writer(logdir, name='other') - with writer3.as_default(): - summary_ops.write('tag', 1, step=3) - summary_ops.flush() + event_files = gfile.Glob(os.path.join(logdir, '*')) + self.assertEqual(2, len(event_files)) + event_files.remove(file1) + file2 = event_files[0] - event_files = iter(sorted(gfile.Glob(os.path.join(logdir, '*')))) + # Extra writes to ensure interleaved usage works. + with writer1.as_default(): + summary_ops.write('tag', 1, step=1) + with writer2.as_default(): + summary_ops.write('tag', 1, step=2) - # First file has tags "one" and "two" - events = iter(events_from_file(next(event_files))) + events = iter(events_from_file(file1)) self.assertEqual('brain.Event:2', next(events).file_version) self.assertEqual(1, next(events).step) + self.assertEqual(1, next(events).step) + self.assertRaises(StopIteration, lambda: next(events)) + events = iter(events_from_file(file2)) + self.assertEqual('brain.Event:2', next(events).file_version) + self.assertEqual(2, next(events).step) self.assertEqual(2, next(events).step) self.assertRaises(StopIteration, lambda: next(events)) - # Second file has tag "three" - events = iter(events_from_file(next(event_files))) + def testNoSharing_fromFunction(self): + logdir = self.get_temp_dir() + @def_function.function + def f1(): + if not hasattr(f1, 'writer'): + f1.writer = summary_ops.create_file_writer_v2(logdir) + with f1.writer.as_default(): + summary_ops.write('tag', 1, step=1) + @def_function.function + def f2(): + if not hasattr(f2, 'writer'): + f2.writer = summary_ops.create_file_writer_v2(logdir) + with f2.writer.as_default(): + summary_ops.write('tag', 1, step=2) + with context.eager_mode(): + f1() + event_files = gfile.Glob(os.path.join(logdir, '*')) + self.assertEqual(1, len(event_files)) + file1 = event_files[0] + + f2() + event_files = gfile.Glob(os.path.join(logdir, '*')) + self.assertEqual(2, len(event_files)) + event_files.remove(file1) + file2 = event_files[0] + + # Extra writes to ensure interleaved usage works. + f1() + f2() + + events = iter(events_from_file(file1)) self.assertEqual('brain.Event:2', next(events).file_version) - self.assertEqual(3, next(events).step) + self.assertEqual(1, next(events).step) + self.assertEqual(1, next(events).step) + self.assertRaises(StopIteration, lambda: next(events)) + events = iter(events_from_file(file2)) + self.assertEqual('brain.Event:2', next(events).file_version) + self.assertEqual(2, next(events).step) + self.assertEqual(2, next(events).step) self.assertRaises(StopIteration, lambda: next(events)) - - # No more files - self.assertRaises(StopIteration, lambda: next(event_files)) def testMaxQueue(self): logdir = self.get_temp_dir() with context.eager_mode(): - with summary_ops.create_file_writer( + with summary_ops.create_file_writer_v2( logdir, max_queue=1, flush_millis=999999).as_default(): get_total = lambda: len(events_from_logdir(logdir)) # Note: First tf.Event is always file_version. @@ -396,7 +476,7 @@ class SummaryWriterTest(test_util.TensorFlowTestCase): logdir = self.get_temp_dir() get_total = lambda: len(events_from_logdir(logdir)) with context.eager_mode(): - writer = summary_ops.create_file_writer( + writer = summary_ops.create_file_writer_v2( logdir, max_queue=1000, flush_millis=1000000) self.assertEqual(1, get_total()) # file_version Event with writer.as_default(): @@ -412,7 +492,7 @@ class SummaryWriterTest(test_util.TensorFlowTestCase): def testFlushFunction(self): logdir = self.get_temp_dir() with context.eager_mode(): - writer = summary_ops.create_file_writer( + writer = summary_ops.create_file_writer_v2( logdir, max_queue=999999, flush_millis=999999) with writer.as_default(), summary_ops.always_record_summaries(): get_total = lambda: len(events_from_logdir(logdir)) @@ -436,9 +516,24 @@ class SummaryWriterTest(test_util.TensorFlowTestCase): @test_util.assert_no_new_pyobjects_executing_eagerly def testEagerMemory(self): logdir = self.get_temp_dir() - with summary_ops.create_file_writer(logdir).as_default(): + with summary_ops.create_file_writer_v2(logdir).as_default(): summary_ops.write('tag', 1, step=0) + def testClose_preventsLaterUse(self): + logdir = self.get_temp_dir() + with context.eager_mode(): + writer = summary_ops.create_file_writer_v2(logdir) + writer.close() + writer.close() # redundant close() is a no-op + writer.flush() # redundant flush() is a no-op + with self.assertRaisesRegex(RuntimeError, 'already closed'): + writer.init() + with self.assertRaisesRegex(RuntimeError, 'already closed'): + with writer.as_default(): + self.fail('should not get here') + with self.assertRaisesRegex(RuntimeError, 'already closed'): + writer.set_as_default() + def testClose_closesOpenFile(self): try: import psutil # pylint: disable=g-import-not-at-top @@ -448,7 +543,7 @@ class SummaryWriterTest(test_util.TensorFlowTestCase): get_open_filenames = lambda: set(info[0] for info in proc.open_files()) logdir = self.get_temp_dir() with context.eager_mode(): - writer = summary_ops.create_file_writer(logdir) + writer = summary_ops.create_file_writer_v2(logdir) files = gfile.Glob(os.path.join(logdir, '*')) self.assertEqual(1, len(files)) eventfile = files[0] @@ -465,7 +560,7 @@ class SummaryWriterTest(test_util.TensorFlowTestCase): get_open_filenames = lambda: set(info[0] for info in proc.open_files()) logdir = self.get_temp_dir() with context.eager_mode(): - writer = summary_ops.create_file_writer(logdir) + writer = summary_ops.create_file_writer_v2(logdir) files = gfile.Glob(os.path.join(logdir, '*')) self.assertEqual(1, len(files)) eventfile = files[0] diff --git a/tensorflow/python/ops/summary_ops_v2.py b/tensorflow/python/ops/summary_ops_v2.py index cde00c766e..bb570bafee 100644 --- a/tensorflow/python/ops/summary_ops_v2.py +++ b/tensorflow/python/ops/summary_ops_v2.py @@ -19,6 +19,8 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +import abc +import functools import getpass import os import re @@ -34,6 +36,7 @@ from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes from tensorflow.python.framework import ops from tensorflow.python.framework import smart_cond +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 gen_summary_ops @@ -124,65 +127,120 @@ def never_record_summaries(): @tf_export("summary.SummaryWriter", v1=[]) +@six.add_metaclass(abc.ABCMeta) class SummaryWriter(object): - """Encapsulates a stateful summary writer resource. + """Interface representing a stateful summary writer object.""" - See also: - - `tf.summary.create_file_writer` - - `tf.summary.create_db_writer` - """ + @abc.abstractmethod + def set_as_default(self): + """Enables this summary writer for the current thread.""" + raise NotImplementedError() + + @abc.abstractmethod + @tf_contextlib.contextmanager + def as_default(self): + """Returns a context manager that enables summary writing.""" + raise NotImplementedError() + + def init(self): + """Initializes the summary writer.""" + raise NotImplementedError() + + def flush(self): + """Flushes any buffered data.""" + raise NotImplementedError() + + def close(self): + """Flushes and closes the summary writer.""" + raise NotImplementedError() + + +class ResourceSummaryWriter(SummaryWriter): + """Implementation of SummaryWriter using a SummaryWriterInterface resource.""" - def __init__(self, resource, init_op_fn): - self._resource = resource - # TODO(nickfelt): cache constructed ops in graph mode + def __init__(self, shared_name, init_op_fn, name=None, v2=False): + self._resource = gen_summary_ops.summary_writer( + shared_name=shared_name, name=name) + # TODO(nickfelt): cache other constructed ops in graph mode self._init_op_fn = init_op_fn - if context.executing_eagerly() and self._resource is not None: + self._init_op = init_op_fn(self._resource) + self._v2 = v2 + self._closed = False + if context.executing_eagerly(): self._resource_deleter = resource_variable_ops.EagerResourceDeleter( handle=self._resource, handle_device="cpu:0") + else: + global _SUMMARY_WRITER_INIT_OP + key = ops.get_default_graph()._graph_key # pylint: disable=protected-access + _SUMMARY_WRITER_INIT_OP.setdefault(key, []).append(self._init_op) def set_as_default(self): """Enables this summary writer for the current thread.""" + if self._v2 and context.executing_eagerly() and self._closed: + raise RuntimeError("SummaryWriter is already closed") context.context().summary_writer_resource = self._resource @tf_contextlib.contextmanager def as_default(self): - """Enables summary writing within a `with` block.""" - if self._resource is None: + """Returns a context manager that enables summary writing.""" + if self._v2 and context.executing_eagerly() and self._closed: + raise RuntimeError("SummaryWriter is already closed") + old = context.context().summary_writer_resource + try: + context.context().summary_writer_resource = self._resource yield self - else: - old = context.context().summary_writer_resource - try: - context.context().summary_writer_resource = self._resource - yield self - # Flushes the summary writer in eager mode or in graph functions, but - # not in legacy graph mode (you're on your own there). - with ops.device("cpu:0"): - gen_summary_ops.flush_summary_writer(self._resource) - finally: - context.context().summary_writer_resource = old + # Flushes the summary writer in eager mode or in graph functions, but + # not in legacy graph mode (you're on your own there). + self.flush() + finally: + context.context().summary_writer_resource = old def init(self): - """Operation to initialize the summary writer resource.""" - if self._resource is not None: - return self._init_op_fn() + """Initializes the summary writer.""" + if self._v2: + if context.executing_eagerly() and self._closed: + raise RuntimeError("SummaryWriter is already closed") + return self._init_op + # Legacy behavior allows re-initializing the resource. + return self._init_op_fn(self._resource) - def _flush(self): + def flush(self): + """Flushes any buffered data.""" + if self._v2 and context.executing_eagerly() and self._closed: + return return _flush_fn(writer=self) - def flush(self): - """Operation to force the summary writer to flush any buffered data.""" - if self._resource is not None: - return self._flush() + def close(self): + """Flushes and closes the summary writer.""" + if self._v2 and context.executing_eagerly() and self._closed: + return + try: + with ops.control_dependencies([self.flush()]): + with ops.device("cpu:0"): + return gen_summary_ops.close_summary_writer(self._resource) + finally: + if self._v2 and context.executing_eagerly(): + self._closed = True - def _close(self): - with ops.control_dependencies([self.flush()]): - with ops.device("cpu:0"): - return gen_summary_ops.close_summary_writer(self._resource) + +class NoopSummaryWriter(SummaryWriter): + """A summary writer that does nothing, for create_noop_writer().""" + + def set_as_default(self): + pass + + @tf_contextlib.contextmanager + def as_default(self): + yield + + def init(self): + pass + + def flush(self): + pass def close(self): - """Operation to flush and close the summary writer resource.""" - if self._resource is not None: - return self._close() + pass @tf_export(v1=["summary.initialize"]) @@ -228,6 +286,66 @@ def initialize( @tf_export("summary.create_file_writer", v1=[]) +def create_file_writer_v2(logdir, + max_queue=None, + flush_millis=None, + filename_suffix=None, + name=None): + """Creates a summary file writer for the given log directory. + + Args: + logdir: a string specifying the directory in which to write an event file. + max_queue: the largest number of summaries to keep in a queue; will + flush once the queue gets bigger than this. Defaults to 10. + flush_millis: the largest interval between flushes. Defaults to 120,000. + filename_suffix: optional suffix for the event file name. Defaults to `.v2`. + name: a name for the op that creates the writer. + + Returns: + A SummaryWriter object. + """ + if logdir is None: + raise ValueError("logdir cannot be None") + inside_function = ops.inside_function() + with ops.name_scope(name, "create_file_writer") as scope, ops.device("cpu:0"): + # Run init inside an init_scope() to hoist it out of tf.functions. + with ops.init_scope(): + if context.executing_eagerly(): + _check_create_file_writer_args( + inside_function, + logdir=logdir, + max_queue=max_queue, + flush_millis=flush_millis, + filename_suffix=filename_suffix) + logdir = ops.convert_to_tensor(logdir, dtype=dtypes.string) + if max_queue is None: + max_queue = constant_op.constant(10) + if flush_millis is None: + flush_millis = constant_op.constant(2 * 60 * 1000) + if filename_suffix is None: + filename_suffix = constant_op.constant(".v2") + # Prepend the PID and a process-local UID to the filename suffix to avoid + # filename collisions within the machine (the filename already contains + # the hostname to avoid cross-machine collisions). + unique_prefix = constant_op.constant(".%s.%s" % (os.getpid(), ops.uid())) + filename_suffix = unique_prefix + filename_suffix + # Use a unique shared_name to prevent resource sharing. + if context.executing_eagerly(): + shared_name = context.shared_name() + else: + shared_name = ops._name_from_scope_name(scope) # pylint: disable=protected-access + return ResourceSummaryWriter( + shared_name=shared_name, + init_op_fn=functools.partial( + gen_summary_ops.create_summary_file_writer, + logdir=logdir, + max_queue=max_queue, + flush_millis=flush_millis, + filename_suffix=filename_suffix), + name=name, + v2=True) + + def create_file_writer(logdir, max_queue=None, flush_millis=None, @@ -254,7 +372,7 @@ def create_file_writer(logdir, summary writer. """ if logdir is None: - return SummaryWriter(None, None) + return NoopSummaryWriter() logdir = str(logdir) with ops.device("cpu:0"): if max_queue is None: @@ -265,13 +383,14 @@ def create_file_writer(logdir, filename_suffix = constant_op.constant(".v2") if name is None: name = "logdir:" + logdir - return _make_summary_writer( - name, - gen_summary_ops.create_summary_file_writer, - logdir=logdir, - max_queue=max_queue, - flush_millis=flush_millis, - filename_suffix=filename_suffix) + return ResourceSummaryWriter( + shared_name=name, + init_op_fn=functools.partial( + gen_summary_ops.create_summary_file_writer, + logdir=logdir, + max_queue=max_queue, + flush_millis=flush_millis, + filename_suffix=filename_suffix)) def create_db_writer(db_uri, @@ -316,26 +435,23 @@ def create_db_writer(db_uri, "experiment_name", _EXPERIMENT_NAME_PATTERNS, experiment_name) run_name = _cleanse_string("run_name", _RUN_NAME_PATTERNS, run_name) user_name = _cleanse_string("user_name", _USER_NAME_PATTERNS, user_name) - return _make_summary_writer( - name, - gen_summary_ops.create_summary_db_writer, - db_uri=db_uri, - experiment_name=experiment_name, - run_name=run_name, - user_name=user_name) - - -def _make_summary_writer(name, factory, **kwargs): - resource = gen_summary_ops.summary_writer(shared_name=name) - init_op_fn = lambda: factory(resource, **kwargs) - init_op = init_op_fn() - if not context.executing_eagerly(): - # TODO(apassos): Consider doing this instead. - # ops.get_default_session().run(init_op) - global _SUMMARY_WRITER_INIT_OP - key = ops.get_default_graph()._graph_key # pylint: disable=protected-access - _SUMMARY_WRITER_INIT_OP.setdefault(key, []).append(init_op) - return SummaryWriter(resource, init_op_fn) + return ResourceSummaryWriter( + shared_name=name, + init_op_fn=functools.partial( + gen_summary_ops.create_summary_db_writer, + db_uri=db_uri, + experiment_name=experiment_name, + run_name=run_name, + user_name=user_name)) + + +@tf_export("summary.create_noop_writer", v1=[]) +def create_noop_writer(): + """Returns a summary writer that does nothing. + + This is useful as a placeholder in code that expects a context manager. + """ + return NoopSummaryWriter() def _cleanse_string(name, pattern, value): @@ -436,7 +552,7 @@ def write(tag, tensor, step, metadata=None, name=None): tag: string tag used to identify the summary (e.g. in TensorBoard), usually generated with `tf.summary.summary_scope` tensor: the Tensor holding the summary data to write - step: `int64`-castable monotic step value for this summary + step: `int64`-castable monotonic step value for this summary metadata: Optional SummaryMetadata, as a proto or serialized bytes name: Optional string name for this op. @@ -734,6 +850,30 @@ def _choose_step(step): return step +def _check_create_file_writer_args(inside_function, **kwargs): + """Helper to check the validity of arguments to a create_file_writer() call. + + Args: + inside_function: whether the create_file_writer() call is in a tf.function + **kwargs: the arguments to check, as kwargs to give them names. + + Raises: + ValueError: if the arguments are graph tensors. + """ + for arg_name, arg in kwargs.items(): + if not isinstance(arg, ops.EagerTensor) and tensor_util.is_tensor(arg): + if inside_function: + raise ValueError( + "Invalid graph Tensor argument \"%s=%s\" to create_file_writer() " + "inside an @tf.function. The create call will be lifted into the " + "outer eager execution context, so it cannot consume graph tensors " + "defined inside the function body." % (arg_name, arg)) + else: + raise ValueError( + "Invalid graph Tensor argument \"%s=%s\" to eagerly executed " + "create_file_writer()." % (arg_name, arg)) + + def run_metadata(name, data, step): """Writes entire RunMetadata summary. diff --git a/tensorflow/tools/api/golden/v2/tensorflow.summary.-summary-writer.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.summary.-summary-writer.pbtxt index 6715c14e16..8f45ecbf77 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.summary.-summary-writer.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.summary.-summary-writer.pbtxt @@ -4,7 +4,6 @@ tf_class { is_instance: "" member_method { name: "__init__" - argspec: "args=[\'self\', \'resource\', \'init_op_fn\'], varargs=None, keywords=None, defaults=None" } member_method { name: "as_default" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.summary.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.summary.pbtxt index 85edef9d7e..bdc9c83553 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.summary.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.summary.pbtxt @@ -12,6 +12,10 @@ tf_module { name: "create_file_writer" argspec: "args=[\'logdir\', \'max_queue\', \'flush_millis\', \'filename_suffix\', \'name\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'None\', \'None\'], " } + member_method { + name: "create_noop_writer" + argspec: "args=[], varargs=None, keywords=None, defaults=None" + } member_method { name: "flush" argspec: "args=[\'writer\', \'name\'], varargs=None, keywords=None, defaults=[\'None\', \'None\'], " -- GitLab From d56074d1e7d204e14f97dd9670425e643a9868c6 Mon Sep 17 00:00:00 2001 From: Scott Zhu Date: Fri, 22 Feb 2019 13:52:12 -0800 Subject: [PATCH 375/766] Change the export name for LSTMStateTuple to only export in v1. PiperOrigin-RevId: 235252398 --- tensorflow/python/ops/rnn_cell_impl.py | 2 +- ...low.nn.rnn_cell.-l-s-t-m-state-tuple.pbtxt | 27 ------------------- .../golden/v2/tensorflow.nn.rnn_cell.pbtxt | 4 --- tensorflow/tools/compatibility/renames_v2.py | 1 + 4 files changed, 2 insertions(+), 32 deletions(-) delete mode 100644 tensorflow/tools/api/golden/v2/tensorflow.nn.rnn_cell.-l-s-t-m-state-tuple.pbtxt diff --git a/tensorflow/python/ops/rnn_cell_impl.py b/tensorflow/python/ops/rnn_cell_impl.py index cb9377df36..f33362a4e0 100644 --- a/tensorflow/python/ops/rnn_cell_impl.py +++ b/tensorflow/python/ops/rnn_cell_impl.py @@ -606,7 +606,7 @@ class GRUCell(LayerRNNCell): _LSTMStateTuple = collections.namedtuple("LSTMStateTuple", ("c", "h")) -@tf_export("nn.rnn_cell.LSTMStateTuple") +@tf_export(v1=["nn.rnn_cell.LSTMStateTuple"]) class LSTMStateTuple(_LSTMStateTuple): """Tuple used by LSTM Cells for `state_size`, `zero_state`, and output state. diff --git a/tensorflow/tools/api/golden/v2/tensorflow.nn.rnn_cell.-l-s-t-m-state-tuple.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.nn.rnn_cell.-l-s-t-m-state-tuple.pbtxt deleted file mode 100644 index 1de8a55dcc..0000000000 --- a/tensorflow/tools/api/golden/v2/tensorflow.nn.rnn_cell.-l-s-t-m-state-tuple.pbtxt +++ /dev/null @@ -1,27 +0,0 @@ -path: "tensorflow.nn.rnn_cell.LSTMStateTuple" -tf_class { - is_instance: "" - is_instance: "" - is_instance: "" - member { - name: "c" - mtype: "" - } - member { - name: "dtype" - mtype: "" - } - member { - name: "h" - mtype: "" - } - member_method { - name: "__init__" - } - member_method { - name: "count" - } - member_method { - name: "index" - } -} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.nn.rnn_cell.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.nn.rnn_cell.pbtxt index 953722467b..87062b1a5c 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.nn.rnn_cell.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.nn.rnn_cell.pbtxt @@ -4,10 +4,6 @@ tf_module { name: "DeviceWrapper" mtype: "" } - member { - name: "LSTMStateTuple" - mtype: "" - } member { name: "RNNCell" mtype: "" diff --git a/tensorflow/tools/compatibility/renames_v2.py b/tensorflow/tools/compatibility/renames_v2.py index 67425bba6c..d399b3f321 100644 --- a/tensorflow/tools/compatibility/renames_v2.py +++ b/tensorflow/tools/compatibility/renames_v2.py @@ -447,6 +447,7 @@ renames = { 'tf.nn.rnn_cell.DropoutWrapper': 'tf.compat.v1.nn.rnn_cell.DropoutWrapper', 'tf.nn.rnn_cell.GRUCell': 'tf.compat.v1.nn.rnn_cell.GRUCell', 'tf.nn.rnn_cell.LSTMCell': 'tf.compat.v1.nn.rnn_cell.LSTMCell', + 'tf.nn.rnn_cell.LSTMStateTuple': 'tf.compat.v1.nn.rnn_cell.LSTMStateTuple', 'tf.nn.rnn_cell.MultiRNNCell': 'tf.compat.v1.nn.rnn_cell.MultiRNNCell', 'tf.nn.rnn_cell.ResidualWrapper': 'tf.compat.v1.nn.rnn_cell.ResidualWrapper', 'tf.nn.static_bidirectional_rnn': 'tf.compat.v1.nn.static_bidirectional_rnn', -- GitLab From e42a66b26372845ea41aea6c12bc3e4f09f1efc5 Mon Sep 17 00:00:00 2001 From: Dan Moldovan Date: Fri, 22 Feb 2019 14:05:33 -0800 Subject: [PATCH 376/766] Fix rewrap to always return the updated entity. Use it as such at the autograph integration point. In turn, temporarily disable autograph for dataset functions until we reduce the conversion overhead. Fixes #25159. With thanks to @ktaebum for identifying this fix. PiperOrigin-RevId: 235255035 --- tensorflow/python/data/ops/dataset_ops.py | 3 +++ .../data/ops/multi_device_iterator_ops.py | 25 ++++++++++++++----- tensorflow/python/eager/def_function.py | 2 +- tensorflow/python/framework/func_graph.py | 3 ++- tensorflow/python/util/tf_decorator.py | 14 ++++++++++- 5 files changed, 38 insertions(+), 9 deletions(-) diff --git a/tensorflow/python/data/ops/dataset_ops.py b/tensorflow/python/data/ops/dataset_ops.py index 91a07a5534..3f50738f17 100644 --- a/tensorflow/python/data/ops/dataset_ops.py +++ b/tensorflow/python/data/ops/dataset_ops.py @@ -2366,6 +2366,8 @@ class StructuredFunctionWrapper(object): else: defun_kwargs.update({"func_name": func_name}) + # TODO(b/124254153): Enable autograph once the overhead is low enough. + # TODO(mdan): Make sure autograph recurses into _wrapper_helper when on. @eager_function.defun_with_attributes( input_signature=[ tensor_spec.TensorSpec(input_shape, input_type) # pylint: disable=g-complex-comprehension @@ -2373,6 +2375,7 @@ class StructuredFunctionWrapper(object): self._input_structure._flat_shapes, self._input_structure._flat_types) ], + autograph=False, attributes=defun_kwargs) def wrapper_fn(*args): # pylint: disable=missing-docstring ret = _wrapper_helper(*args) diff --git a/tensorflow/python/data/ops/multi_device_iterator_ops.py b/tensorflow/python/data/ops/multi_device_iterator_ops.py index 36e1e311d3..f253f8bf42 100644 --- a/tensorflow/python/data/ops/multi_device_iterator_ops.py +++ b/tensorflow/python/data/ops/multi_device_iterator_ops.py @@ -42,13 +42,15 @@ class _PerDeviceGenerator(dataset_ops.DatasetV2): gen_dataset_ops.multi_device_iterator_to_string_handle( multi_device_iterator_resource)) - @function.defun() + # TODO(b/124254153): Enable autograph once the overhead is low enough. + @function.defun(autograph=False) # Pure graph code. def _init_func(): return multi_device_iterator_string_handle init_func_concrete = _init_func._get_concrete_function_internal() # pylint: disable=protected-access - @function.defun() + # TODO(b/124254153): Enable autograph once the overhead is low enough. + @function.defun(autograph=False) # Pure graph code. def _remote_init_func(): return functional_ops.remote_call( target=source_device, @@ -59,7 +61,10 @@ class _PerDeviceGenerator(dataset_ops.DatasetV2): self._init_func = _remote_init_func._get_concrete_function_internal() # pylint: disable=protected-access self._init_captured_args = self._init_func.captured_inputs - @function.defun(input_signature=[tensor_spec.TensorSpec([], dtypes.string)]) + # TODO(b/124254153): Enable autograph once the overhead is low enough. + @function.defun( + input_signature=[tensor_spec.TensorSpec([], dtypes.string)], + autograph=False) # Pure graph code. def _next_func(string_handle): # pylint: disable=protected-access multi_device_iterator = ( @@ -76,9 +81,11 @@ class _PerDeviceGenerator(dataset_ops.DatasetV2): next_func_concrete = _next_func._get_concrete_function_internal() # pylint: disable=protected-access + # TODO(b/124254153): Enable autograph once the overhead is low enough. @function.defun_with_attributes( input_signature=[tensor_spec.TensorSpec([], dtypes.string)], - attributes={"experimental_ints_on_device": True}) + attributes={"experimental_ints_on_device": True}, + autograph=False) # Pure graph code. def _remote_next_func(string_handle): return functional_ops.remote_call( target=source_device, @@ -94,13 +101,19 @@ class _PerDeviceGenerator(dataset_ops.DatasetV2): if arg == incarnation_id: self._incarnation_id_index = i - @function.defun(input_signature=[tensor_spec.TensorSpec([], dtypes.string)]) + # TODO(b/124254153): Enable autograph once the overhead is low enough. + @function.defun( + input_signature=[tensor_spec.TensorSpec([], dtypes.string)], + autograph=False) # Pure graph code. def _finalize_func(unused_string_handle): return array_ops.constant(0, dtypes.int64) finalize_func_concrete = _finalize_func._get_concrete_function_internal() # pylint: disable=protected-access - @function.defun(input_signature=[tensor_spec.TensorSpec([], dtypes.string)]) + # TODO(b/124254153): Enable autograph once the overhead is low enough. + @function.defun( + input_signature=[tensor_spec.TensorSpec([], dtypes.string)], + autograph=False) # Pure graph code. def _remote_finalize_func(string_handle): return functional_ops.remote_call( target=source_device, diff --git a/tensorflow/python/eager/def_function.py b/tensorflow/python/eager/def_function.py index 1d54973487..330b2e0a76 100644 --- a/tensorflow/python/eager/def_function.py +++ b/tensorflow/python/eager/def_function.py @@ -514,7 +514,7 @@ class Function(object): """Make and call a `ConcreteFunction` which initializes variables.""" # Note: using defun here avoids an infinite recursion. - @function_lib.defun + @function_lib.defun(autograph=False) # Pure graph code. def initialize_variables(): for v, init in initializer_map.items(): with ops.init_scope(): diff --git a/tensorflow/python/framework/func_graph.py b/tensorflow/python/framework/func_graph.py index 9097a8dd1f..aee8d4c658 100644 --- a/tensorflow/python/framework/func_graph.py +++ b/tensorflow/python/framework/func_graph.py @@ -633,7 +633,8 @@ def func_graph_from_py_func(name, # Wrapping around a decorator allows checks like tf_inspect.getargspec # to be accurate. converted_func = tf_decorator.make_decorator(original_func, wrapper) - tf_decorator.rewrap(python_func, original_func, converted_func) + python_func = tf_decorator.rewrap(python_func, original_func, + converted_func) func_outputs = python_func(*func_args, **func_kwargs) diff --git a/tensorflow/python/util/tf_decorator.py b/tensorflow/python/util/tf_decorator.py index 6c71f70c4e..033d35b2fe 100644 --- a/tensorflow/python/util/tf_decorator.py +++ b/tensorflow/python/util/tf_decorator.py @@ -138,6 +138,10 @@ def rewrap(decorator_func, previous_target, new_target): decorator_func: Callable returned by `wrap`. previous_target: Callable that needs to be replaced. new_target: Callable to replace previous_target with. + + Returns: + The updated decorator. If decorator_func is not a tf_decorator, new_target + is returned. """ # Because the process mutates the decorator, we only need to alter the # innermost function that wraps previous_target. @@ -145,14 +149,20 @@ def rewrap(decorator_func, previous_target, new_target): innermost_decorator = None target = None while hasattr(cur, '_tf_decorator'): + assert cur is not None innermost_decorator = cur target = getattr(cur, '_tf_decorator') if target.decorated_target is previous_target: break cur = target.decorated_target + # If decorator_func is not a decorator, new_target replaces it directly. if innermost_decorator is None: - return + # Consistency check. The caller should always pass the result of + # tf_decorator.unwrap as previous_target. If decorator_func is not a + # decorator, that will have returned decorator_func itself. + assert decorator_func is previous_target + return new_target target.decorated_target = new_target @@ -168,6 +178,8 @@ def rewrap(decorator_func, previous_target, new_target): else: innermost_decorator.__wrapped__ = new_target + return decorator_func + def unwrap(maybe_tf_decorator): """Unwraps an object into a list of TFDecorators and a final target. -- GitLab From 7f4ac773b642e208ab954c940c51885b69042440 Mon Sep 17 00:00:00 2001 From: Nupur Garg Date: Fri, 22 Feb 2019 14:24:53 -0800 Subject: [PATCH 377/766] Internal change. PiperOrigin-RevId: 235258552 --- .../python/framework/convert_to_constants.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tensorflow/python/framework/convert_to_constants.py b/tensorflow/python/framework/convert_to_constants.py index f96e599174..7753d62114 100644 --- a/tensorflow/python/framework/convert_to_constants.py +++ b/tensorflow/python/framework/convert_to_constants.py @@ -81,7 +81,15 @@ def convert_variables_to_constants_v2(func): get_name = lambda name: name.split(":")[0] map_name_to_node = {get_name(node.name): node for node in graph_def.node} - variables_generator = func.graph.variables + # TODO(b/125838789): Use `func.graph.captures`. + # Get mapping from input name to variable value. + tensor_data = {} + input_tensors = func.inputs[-len(func.captured_inputs):] + for var in func.graph.variables: + index = func.captured_inputs.index(var.handle) + tensor = input_tensors[index] + tensor_data[get_name(tensor.name)] = var.numpy() + resource_identities = {} resource_placeholders = {} for node in graph_def.node: @@ -98,11 +106,9 @@ def convert_variables_to_constants_v2(func): "to the ReadVariableOp.") # Build a map of Placeholder ops that are inputs to ReadVariableOps to the # variable's dtype and data. - # TODO(nupurgarg): Confirm relationship between variables in - # `func.graph.variables` and ReadVariableOps in `graph_def.nodes`. resource_placeholders[input_name] = { "dtype": node.attr["dtype"], - "data": next(variables_generator).numpy(), + "data": tensor_data[input_name], } # Reconstruct the graph with constants in place of variables. -- GitLab From 466e0aa14d5aa3e098a48b3a1ad33a17d310fc89 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 22 Feb 2019 14:44:27 -0800 Subject: [PATCH 378/766] Internal Change PiperOrigin-RevId: 235262171 --- tensorflow/python/training/tracking/BUILD | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tensorflow/python/training/tracking/BUILD b/tensorflow/python/training/tracking/BUILD index dfb7325c53..9d49d67816 100644 --- a/tensorflow/python/training/tracking/BUILD +++ b/tensorflow/python/training/tracking/BUILD @@ -93,7 +93,10 @@ tf_py_test( "//tensorflow/python/keras:engine", "//tensorflow/python/keras:layers", ], - tags = ["nomac"], + tags = [ + "nomac" + + "no_windows", + ], ) py_library( -- GitLab From e937cf22ddcbc1dd6bdc410ce2c9f9b5493fb69b Mon Sep 17 00:00:00 2001 From: Justin Lebar Date: Fri, 22 Feb 2019 14:48:40 -0800 Subject: [PATCH 379/766] [XLA] Run bf16 erf(c) at f32 precision. bf16 doesn't have sufficient precision for intermediate results. (Not very surprising!) PiperOrigin-RevId: 235262964 --- tensorflow/compiler/xla/client/lib/math.cc | 39 +++++++++++++++++-- .../xla/client/lib/math_exhaustive_test.cc | 6 --- .../compiler/xla/tests/exhaustive_op_test.cc | 24 ++---------- 3 files changed, 38 insertions(+), 31 deletions(-) diff --git a/tensorflow/compiler/xla/client/lib/math.cc b/tensorflow/compiler/xla/client/lib/math.cc index f3fe3d0b5e..5ac1615b42 100644 --- a/tensorflow/compiler/xla/client/lib/math.cc +++ b/tensorflow/compiler/xla/client/lib/math.cc @@ -27,6 +27,26 @@ limitations under the License. namespace xla { +// Returns operation(operand), except if `operand` is BF16, first converts it to +// F32, and then converts the result down to BF16. +static XlaOp DoWithBF16Upcast(XlaOp operand, + const std::function& operation) { + auto& b = *operand.builder(); + return b.ReportErrorOrReturn([&]() -> StatusOr { + TF_ASSIGN_OR_RETURN(auto shape, b.GetShape(operand)); + bool is_bf16 = shape.element_type() == BF16; + + if (is_bf16) { + operand = ConvertElementType(operand, F32); + } + XlaOp result = operation(operand); + if (is_bf16) { + result = ConvertElementType(result, BF16); + } + return result; + }); +} + // TODO(jlebar): Use this function in more places in this file to restrict the // domain of other functions. static Status EnsureOperandIsRealFp(absl::string_view op_name, XlaOp operand) { @@ -183,11 +203,17 @@ XlaOp Erfc(XlaOp x) { auto& b = *x.builder(); return b.ReportErrorOrReturn([&]() -> StatusOr { TF_RETURN_IF_ERROR(EnsureOperandIsRealFp("Erfc", x)); + // erfc(x) = // erfc_impl(x) if x > 1 // 1 - erf_impl(x) otherwise - return Select(Gt(Abs(x), ScalarLike(x, 1)), ErfcImpl(x), - ScalarLike(x, 1) - ErfImpl(x)); + // + // Erf(c)Impl don't have enough precision when run with bf16 intermediates + // (not surprising!), so upcast to f32 in this case. + return DoWithBF16Upcast(x, [](XlaOp x) { + return Select(Gt(Abs(x), ScalarLike(x, 1)), ErfcImpl(x), + ScalarLike(x, 1) - ErfImpl(x)); + }); }); } @@ -198,8 +224,13 @@ XlaOp Erf(XlaOp x) { // erf(x) = // erf_impl(x) if x < 1 // 1 - erfc_impl(x) otherwise - return Select(Lt(Abs(x), ScalarLike(x, 1)), ErfImpl(x), - ScalarLike(x, 1) - ErfcImpl(x)); + // + // Erf(c)Impl don't have enough precision when run with bf16 intermediates + // (not surprising!), so upcast to f32 in this case. + return DoWithBF16Upcast(x, [](XlaOp x) { + return Select(Lt(Abs(x), ScalarLike(x, 1)), ErfImpl(x), + ScalarLike(x, 1) - ErfcImpl(x)); + }); }); } diff --git a/tensorflow/compiler/xla/client/lib/math_exhaustive_test.cc b/tensorflow/compiler/xla/client/lib/math_exhaustive_test.cc index ea6640d10e..2d6c8797d1 100644 --- a/tensorflow/compiler/xla/client/lib/math_exhaustive_test.cc +++ b/tensorflow/compiler/xla/client/lib/math_exhaustive_test.cc @@ -169,14 +169,8 @@ XLA_TEST_P(MathExhaustiveTest, DISABLED_ON_INTERPRETER(F16)) { INSTANTIATE_TEST_CASE_P( MathExhaustiveTest_Instantiation, MathExhaustiveTest, ::testing::ValuesIn(std::vector{ - Testcase{"rsqrt", Rsqrt, [](float x) { return 1 / std::sqrt(x); }} - .set_tolerance(0.05, 0.05) - .set_skip_infs() - .set_skip_neg_zero(), Testcase{"square", Square, [](float x) { return x * x; }}, Testcase{"reciprocal", Reciprocal, [](float x) { return 1 / x; }}, - Testcase{"erf", Erf, std::erf}.set_tolerance(0.001, 0.0001), - Testcase{"erfc", Erfc, std::erfc}.set_tolerance(0.001, 0.0001), Testcase{"lgamma", Lgamma, std::lgamma} .set_tolerance(0.1, 0.15) .set_fewer_infs_ok(), diff --git a/tensorflow/compiler/xla/tests/exhaustive_op_test.cc b/tensorflow/compiler/xla/tests/exhaustive_op_test.cc index 1d94937656..2100d2acd3 100644 --- a/tensorflow/compiler/xla/tests/exhaustive_op_test.cc +++ b/tensorflow/compiler/xla/tests/exhaustive_op_test.cc @@ -394,27 +394,9 @@ XLA_TEST_P(ExhaustiveOpTest, Sqrt) { Run(Sqrt, std::sqrt); } -XLA_TEST_P(ExhaustiveOpTest, Tanh) { - // TODO(jlebar): Enable this test for (b)f16. - if (ty_ == F16 || ty_ == BF16) { - return; - } - Run(Tanh, std::tanh); -} -XLA_TEST_P(ExhaustiveOpTest, Erf) { - // TODO(jlebar): Enable this test for (b)f16. - if (ty_ == F16 || ty_ == BF16) { - return; - } - Run(Erf, std::erf); -} -XLA_TEST_P(ExhaustiveOpTest, Erfc) { - // TODO(jlebar): Enable this test for (b)f16. - if (ty_ == F16 || ty_ == BF16) { - return; - } - Run(Erfc, std::erfc); -} +XLA_TEST_P(ExhaustiveOpTest, Tanh) { Run(Tanh, std::tanh); } +XLA_TEST_P(ExhaustiveOpTest, Erf) { Run(Erf, std::erf); } +XLA_TEST_P(ExhaustiveOpTest, Erfc) { Run(Erfc, std::erfc); } std::vector> CreateExhaustiveF32Ranges() { // We break up the 2^32-element space into small'ish chunks to keep peak -- GitLab From 9d7878cd2c5fc1656384f40de4bd42422603752f Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 22 Feb 2019 14:59:14 -0800 Subject: [PATCH 380/766] Run profiler by default if user uses tensorboard callback in keras PiperOrigin-RevId: 235264885 --- tensorflow/python/eager/profiler.py | 42 +++++++++++++---- tensorflow/python/keras/BUILD | 2 + tensorflow/python/keras/callbacks.py | 43 +++++++++++++---- tensorflow/python/keras/callbacks_v1.py | 46 +++++++++++++++---- tensorflow/python/keras/callbacks_v1_test.py | 5 ++ ...orflow.keras.callbacks.-tensor-board.pbtxt | 2 +- ...orflow.keras.callbacks.-tensor-board.pbtxt | 2 +- 7 files changed, 115 insertions(+), 27 deletions(-) diff --git a/tensorflow/python/eager/profiler.py b/tensorflow/python/eager/profiler.py index 659c0cc6e6..8a19057c31 100644 --- a/tensorflow/python/eager/profiler.py +++ b/tensorflow/python/eager/profiler.py @@ -12,12 +12,29 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Profiler for eager mode.""" +"""TensorFlow 2.0 Profiler for both Eager Mode and Graph Mode. + +The profiler has two mode: +- Programmatic Mode: start(), stop() and Profiler class. It will perform + when calling start() or create Profiler class and will stop + when calling stop() or destroying Profiler class. +- On-demand Mode: start_profiler_server(). It will perform profiling when + receive profiling request. + +NOTE: Only one active profiler session is allowed. Use of simultaneous +Programmatic Mode and On-demand Mode is undefined and will likely fail. + +NOTE: The Keras TensorBoard callback will automatically perform sampled +profiling. Before enabling customized profiling, set the callback flag +"profile_batches=[]" to disable automatic sampled profiling. +customized profiling. +""" from __future__ import absolute_import from __future__ import division from __future__ import print_function +import datetime import os import threading @@ -37,8 +54,6 @@ _run_num = 0 def start(): """Start profiling. - Only one active profiling session is allowed. - Raises: AssertionError: If another profiling session is running. """ @@ -86,6 +101,21 @@ def stop(): return result +def save(logdir, result): + """Save profile result to TensorBoard logdir. + + Args: + logdir: log directory read by TensorBoard. + result: profiling result returned by stop(). + """ + plugin_dir = os.path.join( + logdir, LOGDIR_PLUGIN, + datetime.datetime.now().strftime('%Y-%m-%d_%H:%M:%S')) + gfile.MakeDirs(plugin_dir) + with gfile.Open(os.path.join(plugin_dir, 'local.trace'), 'wb') as f: + f.write(result) + + def start_profiler_server(port): """Start a profiler grpc server that listens to given port. @@ -126,8 +156,4 @@ class Profiler(object): def __exit__(self, typ, value, tb): result = stop() - plugin_dir = os.path.join(self._logdir, LOGDIR_PLUGIN, - 'run{}'.format(_run_num)) - gfile.MakeDirs(plugin_dir) - with gfile.Open(os.path.join(plugin_dir, 'local.trace'), 'wb') as f: - f.write(result) + save(self._logdir, result) diff --git a/tensorflow/python/keras/BUILD b/tensorflow/python/keras/BUILD index 8743dbaa13..926f354450 100755 --- a/tensorflow/python/keras/BUILD +++ b/tensorflow/python/keras/BUILD @@ -221,6 +221,7 @@ py_library( ":backend", ":engine_utils", ":mode_keys", + "//tensorflow/python/eager:profiler", ], ) @@ -233,6 +234,7 @@ py_library( deps = [ ":backend", ":engine_utils", + "//tensorflow/python/eager:profiler", ], ) diff --git a/tensorflow/python/keras/callbacks.py b/tensorflow/python/keras/callbacks.py index 817d47a1ad..f6975b0a19 100644 --- a/tensorflow/python/keras/callbacks.py +++ b/tensorflow/python/keras/callbacks.py @@ -32,6 +32,7 @@ import six from tensorflow.python.data.ops import iterator_ops from tensorflow.python.eager import context +from tensorflow.python.eager import profiler from tensorflow.python.framework import ops from tensorflow.python.keras import backend as K from tensorflow.python.keras.utils.data_utils import Sequence @@ -1101,20 +1102,21 @@ class LearningRateScheduler(Callback): @keras_export('keras.callbacks.TensorBoard', v1=[]) class TensorBoard(Callback): # pylint: disable=line-too-long - """TensorBoard basic visualizations. - - This callback writes a log for TensorBoard, which allows - you to visualize dynamic graphs of your training and test - metrics, as well as activation histograms for the different - layers in your model. + """Enable visualizations for TensorBoard. TensorBoard is a visualization tool provided with TensorFlow. + This callback logs events for TensorBoard, including: + * Metrics summary plots + * Training graph visualization + * Activation histograms + * Sampled profiling + If you have installed TensorFlow with pip, you should be able to launch TensorBoard from the command line: ```sh - tensorboard --logdir=/full_path_to_your_logs + tensorboard --logdir=path_to_your_logs ``` You can find more information about TensorBoard @@ -1137,6 +1139,9 @@ class TensorBoard(Callback): callback will write the metrics and losses to TensorBoard every 1000 samples. Note that writing too frequently to TensorBoard can slow down your training. + profile_batch: Profile the batch to sample compute characteristics. By + default, it will profile the second batch. Set profile_batch=0 to + disable profiling. Raises: ValueError: If histogram_freq is set and no validation data is provided. @@ -1150,6 +1155,7 @@ class TensorBoard(Callback): write_graph=True, write_images=False, update_freq='epoch', + profile_batch=2, **kwargs): super(TensorBoard, self).__init__() self._validate_kwargs(kwargs) @@ -1172,6 +1178,9 @@ class TensorBoard(Callback): self._writers = [] # file writers to be closed self._train_writer = None # set in `_initialize_writers` self._validation_writer = None # set in `_initialize_writers` + self._profile_batch = profile_batch + # One profiler session is running if it is True. + self._is_profiling = False # TensorBoard should only write summaries on the chief when in a # Multi-Worker setting. @@ -1240,8 +1249,16 @@ class TensorBoard(Callback): self._validation_writer = create_writer('validation') self._writers.append(self._validation_writer) + def on_train_begin(self, logs=None): + if self._profile_batch == 1: + profiler.start() + self._is_profiling = True + def on_batch_end(self, batch, logs=None): - """Writes scalar summaries for metrics on every training batch.""" + """Writes scalar summaries for metrics on every training batch. + + Performs profiling if current batch is in profiler_batches. + """ # Don't output batch_size and batch number as TensorBoard summaries logs = logs or {} self._samples_seen += logs.get('size', 1) @@ -1250,6 +1267,13 @@ class TensorBoard(Callback): self._log_metrics(logs, prefix='batch_', step=self._total_batches_seen) self._samples_seen_at_last_write = self._samples_seen self._total_batches_seen += 1 + if self._is_profiling: + profiler.save(self.log_dir, profiler.stop()) + self._is_profiling = False + elif (not self._is_profiling and + self._total_batches_seen == self._profile_batch - 1): + profiler.start() + self._is_profiling = True def on_epoch_end(self, epoch, logs=None): """Runs metrics and histogram summaries at epoch end.""" @@ -1261,6 +1285,9 @@ class TensorBoard(Callback): def on_train_end(self, logs=None): self._close_writers() + if self._is_profiling: + profiler.save(self.log_dir, profiler.stop()) + self._is_profiling = False def _log_metrics(self, logs, prefix, step): """Writes metrics out as custom scalar summaries. diff --git a/tensorflow/python/keras/callbacks_v1.py b/tensorflow/python/keras/callbacks_v1.py index 980eacd356..86194c29c5 100644 --- a/tensorflow/python/keras/callbacks_v1.py +++ b/tensorflow/python/keras/callbacks_v1.py @@ -24,6 +24,7 @@ import os import numpy as np from tensorflow.python.eager import context +from tensorflow.python.eager import profiler from tensorflow.python.framework import dtypes from tensorflow.python.keras import backend as K from tensorflow.python.keras import callbacks @@ -40,20 +41,21 @@ from tensorflow.python.util.tf_export import keras_export @keras_export(v1=['keras.callbacks.TensorBoard']) class TensorBoard(callbacks.Callback): # pylint: disable=line-too-long - """TensorBoard basic visualizations. - - This callback writes a log for TensorBoard, which allows - you to visualize dynamic graphs of your training and test - metrics, as well as activation histograms for the different - layers in your model. + """Enable visualizations for TensorBoard. TensorBoard is a visualization tool provided with TensorFlow. + This callback logs events for TensorBoard, including: + * Metrics summary plots + * Training graph visualization + * Activation histograms + * Sampled profiling + If you have installed TensorFlow with pip, you should be able to launch TensorBoard from the command line: ```sh - tensorboard --logdir=/full_path_to_your_logs + tensorboard --logdir=path_to_your_logs ``` You can find more information about TensorBoard @@ -96,6 +98,9 @@ class TensorBoard(callbacks.Callback): callback will write the metrics and losses to TensorBoard every 1000 samples. Note that writing too frequently to TensorBoard can slow down your training. + profile_batch: Profile the batch to sample compute characteristics. By + default, it will profile the second batch. Set profile_batch=0 to + disable profiling. Raises: ValueError: If histogram_freq is set and no validation data is provided. @@ -120,7 +125,8 @@ class TensorBoard(callbacks.Callback): embeddings_layer_names=None, embeddings_metadata=None, embeddings_data=None, - update_freq='epoch'): + update_freq='epoch', + profile_batch=2): super(TensorBoard, self).__init__() self.log_dir = log_dir self.histogram_freq = histogram_freq @@ -147,6 +153,10 @@ class TensorBoard(callbacks.Callback): self.update_freq = update_freq self._samples_seen = 0 self._samples_seen_at_last_write = 0 + # TODO(fishx): Add a link to the full profiler tutorial. + self._profile_batch = profile_batch + # One profiler session is running if it is True. + self._is_profiling = False def _init_writer(self, model): """Sets file writer.""" @@ -329,7 +339,10 @@ class TensorBoard(callbacks.Callback): self.writer.flush() def on_batch_end(self, batch, logs=None): - """Writes scalar summaries for metrics on every training batch.""" + """Writes scalar summaries for metrics on every training batch. + + Performs profiling if current batch is in profiler_batches. + """ # Don't output batch_size and batch number as TensorBoard summaries logs = logs or {} self._samples_seen += logs.get('size', 1) @@ -341,6 +354,18 @@ class TensorBoard(callbacks.Callback): self._write_custom_summaries(self._total_batches_seen, batch_logs) self._samples_seen_at_last_write = self._samples_seen self._total_batches_seen += 1 + if self._is_profiling: + profiler.save(self.log_dir, profiler.stop()) + self._is_profiling = False + elif (not self._is_profiling and + self._total_batches_seen == self._profile_batch - 1): + profiler.start() + self._is_profiling = True + + def on_train_begin(self, logs=None): + if self._profile_batch == 1: + profiler.start() + self._is_profiling = True def on_epoch_begin(self, epoch, logs=None): """Add histogram op to Model eval_function callbacks, reset batch count.""" @@ -421,4 +446,7 @@ class TensorBoard(callbacks.Callback): i += self.batch_size def on_train_end(self, logs=None): + if self._is_profiling: + profiler.save(self.log_dir, profiler.stop()) + self._is_profiling = False self.writer.close() diff --git a/tensorflow/python/keras/callbacks_v1_test.py b/tensorflow/python/keras/callbacks_v1_test.py index fdb6af938b..7cb02b84c8 100644 --- a/tensorflow/python/keras/callbacks_v1_test.py +++ b/tensorflow/python/keras/callbacks_v1_test.py @@ -452,6 +452,7 @@ class TestTensorBoardV1(test.TestCase): tb_cbk.writer = FileWriterStub(temp_dir) tb_cbk.on_batch_end(0, {'acc': 5.0}) + tb_cbk.on_train_end() batch_step, batch_summary = tb_cbk.writer.batch_summary self.assertEqual(batch_step, 0) self.assertEqual(batch_summary.value[0].simple_value, 5.0) @@ -459,6 +460,7 @@ class TestTensorBoardV1(test.TestCase): tb_cbk = callbacks_v1.TensorBoard(temp_dir, update_freq='epoch') tb_cbk.writer = FileWriterStub(temp_dir) tb_cbk.on_epoch_end(0, {'acc': 10.0}) + tb_cbk.on_train_end() epoch_step, epoch_summary = tb_cbk.writer.epoch_summary self.assertEqual(epoch_step, 0) self.assertEqual(epoch_summary.value[0].simple_value, 10.0) @@ -530,6 +532,7 @@ class TestTensorBoardV1(test.TestCase): self.assertEqual(tb_cbk.writer.batch_summaries, []) tb_cbk.on_epoch_end(0, {'acc': 10.0, 'size': 1}) self.assertEqual(len(tb_cbk.writer.epoch_summaries), 1) + tb_cbk.on_train_end() # Batch mode tb_cbk = callbacks_v1.TensorBoard(temp_dir, update_freq='batch') @@ -540,6 +543,7 @@ class TestTensorBoardV1(test.TestCase): tb_cbk.on_batch_end(0, {'acc': 5.0, 'size': 1}) self.assertEqual(len(tb_cbk.writer.batch_summaries), 2) self.assertFalse(tb_cbk.writer.epoch_summaries) + tb_cbk.on_train_end() # Integer mode tb_cbk = callbacks_v1.TensorBoard(temp_dir, update_freq=20) @@ -556,6 +560,7 @@ class TestTensorBoardV1(test.TestCase): tb_cbk.on_batch_end(0, {'acc': 10.0, 'size': 10}) self.assertEqual(len(tb_cbk.writer.batch_summaries), 2) self.assertFalse(tb_cbk.writer.epoch_summaries) + tb_cbk.on_train_end() if __name__ == '__main__': diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.callbacks.-tensor-board.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.callbacks.-tensor-board.pbtxt index 66bacd8094..4504633d4a 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.callbacks.-tensor-board.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.callbacks.-tensor-board.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" member_method { name: "__init__" - argspec: "args=[\'self\', \'log_dir\', \'histogram_freq\', \'batch_size\', \'write_graph\', \'write_grads\', \'write_images\', \'embeddings_freq\', \'embeddings_layer_names\', \'embeddings_metadata\', \'embeddings_data\', \'update_freq\'], varargs=None, keywords=None, defaults=[\'./logs\', \'0\', \'32\', \'True\', \'False\', \'False\', \'0\', \'None\', \'None\', \'None\', \'epoch\'], " + argspec: "args=[\'self\', \'log_dir\', \'histogram_freq\', \'batch_size\', \'write_graph\', \'write_grads\', \'write_images\', \'embeddings_freq\', \'embeddings_layer_names\', \'embeddings_metadata\', \'embeddings_data\', \'update_freq\', \'profile_batch\'], varargs=None, keywords=None, defaults=[\'./logs\', \'0\', \'32\', \'True\', \'False\', \'False\', \'0\', \'None\', \'None\', \'None\', \'epoch\', \'2\'], " } member_method { name: "on_batch_begin" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.callbacks.-tensor-board.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.callbacks.-tensor-board.pbtxt index abf4286b69..9edf666e6d 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.callbacks.-tensor-board.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.callbacks.-tensor-board.pbtxt @@ -5,7 +5,7 @@ tf_class { is_instance: "" member_method { name: "__init__" - argspec: "args=[\'self\', \'log_dir\', \'histogram_freq\', \'write_graph\', \'write_images\', \'update_freq\'], varargs=None, keywords=kwargs, defaults=[\'./logs\', \'0\', \'True\', \'False\', \'epoch\'], " + argspec: "args=[\'self\', \'log_dir\', \'histogram_freq\', \'write_graph\', \'write_images\', \'update_freq\', \'profile_batch\'], varargs=None, keywords=kwargs, defaults=[\'./logs\', \'0\', \'True\', \'False\', \'epoch\', \'2\'], " } member_method { name: "on_batch_begin" -- GitLab From 9611d2b3c90c12c7a520c85d278160f6bb532a13 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 22 Feb 2019 15:07:15 -0800 Subject: [PATCH 381/766] Disable flaky tensorflow/python/saved_model:load_test on MacOS. PiperOrigin-RevId: 235266596 --- tensorflow/python/saved_model/BUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/python/saved_model/BUILD b/tensorflow/python/saved_model/BUILD index 01d4818879..6ba639f423 100644 --- a/tensorflow/python/saved_model/BUILD +++ b/tensorflow/python/saved_model/BUILD @@ -393,6 +393,7 @@ tf_py_test( "//tensorflow/python/eager:test", "//tensorflow/python/training/tracking:tracking", ], + tags = ["no_mac"], # TODO(b/124822121): Re-enable this test. ) tf_py_test( -- GitLab From 10247defdc79c10ba9028fafb24b048cfe45513a Mon Sep 17 00:00:00 2001 From: Justin Lebar Date: Fri, 22 Feb 2019 15:17:36 -0800 Subject: [PATCH 382/766] [XLA] Fix precision in lgamma once and for all. Fix two bugs in our lgamma implementation. - We were losing precision when computing pi * x for x = 1 - ?, resulting in incorrect values for lgamma for x = - ?. Fixed by computing pi * (1 - x) instead. This works because we're passing this to sin, which is symmetric around the line Y=0.5, and x was already normalized to be in range [0,1]. - We were overflowing to infinity when computing the first term of x * log(y) - y for large values x and y. Compute this instead as (x - y/log(y)) * log(y). PiperOrigin-RevId: 235268279 --- tensorflow/compiler/xla/client/lib/math.cc | 69 +++++++++++++------ .../compiler/xla/tests/exhaustive_op_test.cc | 20 ++++++ 2 files changed, 68 insertions(+), 21 deletions(-) diff --git a/tensorflow/compiler/xla/client/lib/math.cc b/tensorflow/compiler/xla/client/lib/math.cc index 5ac1615b42..adcaac9861 100644 --- a/tensorflow/compiler/xla/client/lib/math.cc +++ b/tensorflow/compiler/xla/client/lib/math.cc @@ -27,21 +27,24 @@ limitations under the License. namespace xla { -// Returns operation(operand), except if `operand` is BF16, first converts it to -// F32, and then converts the result down to BF16. -static XlaOp DoWithBF16Upcast(XlaOp operand, - const std::function& operation) { +// Returns operation(operand), except if `operand` is one of the types in +// upcast_types, in which case first converts it to F32, and then converts the +// result down to the original type. +static XlaOp DoWithUpcastToF32(XlaOp operand, + absl::Span upcast_types, + const std::function& operation) { auto& b = *operand.builder(); return b.ReportErrorOrReturn([&]() -> StatusOr { TF_ASSIGN_OR_RETURN(auto shape, b.GetShape(operand)); - bool is_bf16 = shape.element_type() == BF16; + PrimitiveType elem_ty = shape.element_type(); + bool needs_upcast = absl::c_linear_search(upcast_types, elem_ty); - if (is_bf16) { + if (needs_upcast) { operand = ConvertElementType(operand, F32); } XlaOp result = operation(operand); - if (is_bf16) { - result = ConvertElementType(result, BF16); + if (needs_upcast) { + result = ConvertElementType(result, elem_ty); } return result; }); @@ -210,7 +213,7 @@ XlaOp Erfc(XlaOp x) { // // Erf(c)Impl don't have enough precision when run with bf16 intermediates // (not surprising!), so upcast to f32 in this case. - return DoWithBF16Upcast(x, [](XlaOp x) { + return DoWithUpcastToF32(x, {BF16}, [](XlaOp x) { return Select(Gt(Abs(x), ScalarLike(x, 1)), ErfcImpl(x), ScalarLike(x, 1) - ErfImpl(x)); }); @@ -227,7 +230,7 @@ XlaOp Erf(XlaOp x) { // // Erf(c)Impl don't have enough precision when run with bf16 intermediates // (not surprising!), so upcast to f32 in this case. - return DoWithBF16Upcast(x, [](XlaOp x) { + return DoWithUpcastToF32(x, {BF16}, [](XlaOp x) { return Select(Lt(Abs(x), ScalarLike(x, 1)), ErfImpl(x), ScalarLike(x, 1) - ErfcImpl(x)); }); @@ -297,10 +300,7 @@ static constexpr std::array kLanczosCoefficients = { // t(z) = z + kLanczosGamma + 1/2 // A(z) = kBaseLanczosCoeff + sigma(k = 1, n, kLanczosCoefficients[i] / (z + k)) XlaOp Lgamma(XlaOp input) { - auto& b = *input.builder(); - return b.ReportErrorOrReturn([&]() -> StatusOr { - TF_RETURN_IF_ERROR(EnsureOperandIsRealFp("Lgamma", input)); - + auto do_it = [](XlaOp input) { XlaOp one_half = ScalarLike(input, 0.5); XlaOp one = ScalarLike(input, 1); @@ -336,7 +336,16 @@ XlaOp Lgamma(XlaOp input) { XlaOp log_t = log_lanczos_gamma_plus_one_half + Log1p(z / lanczos_gamma_plus_one_half); - XlaOp log_y = log_sqrt_two_pi + (z + one_half) * log_t - t + Log(x); + // Compute the final result (modulo reflection). t(z) may be large, and we + // need to be careful not to overflow to infinity in the first term of + // + // (z + 1/2) * log(t(z)) - t(z). + // + // Therefore we compute this as + // + // (z + 1/2 - t(z) / log(t(z))) * log(t(z)). + // + XlaOp log_y = log_sqrt_two_pi + (z + one_half - t / log_t) * log_t + Log(x); // Compute the reflected value, used when x < 0.5: // @@ -351,18 +360,27 @@ XlaOp Lgamma(XlaOp input) { // important. // // Because abs(sin(pi * x)) has period 1, we can equivalently use - // abs(sin(pi * frac(x))) = sin(pi * frac(x)), where frac(x) is the - // fractional part of x. This is more numerically accurate: It doesn't - // overflow to inf like pi * x can, and if x is an integer, it evaluates to - // 0 exactly, which is significant because we then take the log of this - // value, and log(0) is inf. + // abs(sin(pi * frac(x))), where frac(x) is the fractional part of x. This + // is more numerically accurate: It doesn't overflow to inf like pi * x can, + // and if x is an integer, it evaluates to 0 exactly, which is significant + // because we then take the log of this value, and log(0) is inf. // // We don't have a frac(x) primitive in XLA and computing it is tricky, but // because abs(sin(pi * x)) = abs(sin(pi * abs(x))), it's good enough for // our purposes to use abs(frac(x)) = abs(x) - floor(abs(x)). // + // Furthermore, pi * abs(frac(x)) loses precision when abs(frac(x)) is close + // to 1. To remedy this, we can use the fact that sin(pi * x) in the domain + // [0, 1] is symmetric across the line Y=0.5. + // XlaOp abs_input = Abs(input); - XlaOp reflection_denom = Log(Sin(pi * (abs_input - Floor(abs_input)))); + XlaOp abs_frac_input = abs_input - Floor(abs_input); + // Convert values of abs_frac_input > 0.5 to (1 - frac_input) to improve + // precision of pi * abs_frac_input for values of abs_frac_input close to 1. + XlaOp reduced_frac_input = + Select(Gt(abs_frac_input, ScalarLike(abs_frac_input, 0.5)), + ScalarLike(abs_frac_input, 1) - abs_frac_input, abs_frac_input); + XlaOp reflection_denom = Log(Sin(pi * reduced_frac_input)); // Avoid computing -inf - inf, which is nan. If reflection_denom is +/-inf, // then it "wins" and the result is +/-inf. @@ -376,6 +394,15 @@ XlaOp Lgamma(XlaOp input) { return Select(Or(IsFinite(input), // is finite, or Not(Or(Lt(input, one), Ge(input, one)))), // is nan result, inf_bcast); + }; + + auto& b = *input.builder(); + return b.ReportErrorOrReturn([&]() -> StatusOr { + TF_RETURN_IF_ERROR(EnsureOperandIsRealFp("Lgamma", input)); + // F16 and BF16 don't provide sufficient precision for intermediate results + // here (although it's better than you might expect!), so do the + // computations in F32. + return DoWithUpcastToF32(input, {BF16, F16}, do_it); }); } diff --git a/tensorflow/compiler/xla/tests/exhaustive_op_test.cc b/tensorflow/compiler/xla/tests/exhaustive_op_test.cc index 2100d2acd3..c405f5580e 100644 --- a/tensorflow/compiler/xla/tests/exhaustive_op_test.cc +++ b/tensorflow/compiler/xla/tests/exhaustive_op_test.cc @@ -397,6 +397,26 @@ XLA_TEST_P(ExhaustiveOpTest, Sqrt) { XLA_TEST_P(ExhaustiveOpTest, Tanh) { Run(Tanh, std::tanh); } XLA_TEST_P(ExhaustiveOpTest, Erf) { Run(Erf, std::erf); } XLA_TEST_P(ExhaustiveOpTest, Erfc) { Run(Erfc, std::erfc); } +XLA_TEST_P(ExhaustiveOpTest, Lgamma) { + // Our implementation gets within 0.0001 rel error except for ~20 denormal + // inputs on GPU. Anyway 0.001 rel error should be good enough for lgamma. + if (platform_ == "CUDA" && (ty_ == F32 || ty_ == F16)) { + rel_err_ = 0.001; + } + if (platform_ != "Host" && platform_ != "CUDA") { + // TODO(b/123956399): This is a fairly high error, significantly higher than + // we see on CPU/GPU. + rel_err_ = 0.01; + abs_err_ = 0.01; + + // Overflows for to inf for input 4.08500343e+36 (0x7c44af8e). + if (ty_ == F32) { + known_incorrect_begin_ = 0x7c44af8e; + known_incorrect_end_ = 0x7c44af8e + 1; + } + } + Run(Lgamma, std::lgamma); +} std::vector> CreateExhaustiveF32Ranges() { // We break up the 2^32-element space into small'ish chunks to keep peak -- GitLab From 8b11496c44e280319ef4167afddff519923f9ebf Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 22 Feb 2019 15:21:02 -0800 Subject: [PATCH 383/766] Return directly when num_rows is 0 in TopK op. When num_rows is 0, SortPairsDescending() called in the GPU kernel will return an error, even though there is no work to do. PiperOrigin-RevId: 235268833 --- tensorflow/core/kernels/topk_op.cc | 4 ++-- tensorflow/python/kernel_tests/topk_op_test.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/tensorflow/core/kernels/topk_op.cc b/tensorflow/core/kernels/topk_op.cc index 7fdce6cb71..2f6fffed2f 100644 --- a/tensorflow/core/kernels/topk_op.cc +++ b/tensorflow/core/kernels/topk_op.cc @@ -82,8 +82,8 @@ class TopK : public OpKernel { OP_REQUIRES_OK(context, context->allocate_output(1, output_shape, &indices_out)); - // Nothing to do for top-nothing. - if (k == 0) return; + // Nothing to do for top-nothing or over nothing. + if (k == 0 || num_rows == 0) return; auto values = values_out->flat_inner_dims(); auto indices = indices_out->flat_inner_dims(); diff --git a/tensorflow/python/kernel_tests/topk_op_test.py b/tensorflow/python/kernel_tests/topk_op_test.py index 5d46176bce..32ac9a4156 100644 --- a/tensorflow/python/kernel_tests/topk_op_test.py +++ b/tensorflow/python/kernel_tests/topk_op_test.py @@ -182,6 +182,11 @@ class TopKTest(test.TestCase): k = constant_op.constant(3) self._validateTopK(inputs, k, [19, 18, 17], [11, 3, 7]) + def testTop3ZeroRows(self): + inputs = np.zeros([0, 10], dtype=np.float32) + self._validateTopK(inputs, 3, np.zeros([0, 3], dtype=np.float32), + np.zeros([0, 3], dtype=np.int32)) + @test_util.run_deprecated_v1 def testKNegative(self): inputs = [[0.1, 0.2], [0.3, 0.4]] -- GitLab From 932c34f8d59a7967b8ddb56eb00044622518a78e Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 22 Feb 2019 15:25:24 -0800 Subject: [PATCH 384/766] Removes CocoaPods instructions from Swift TFLite library README. CocoaPod is not yet available. PiperOrigin-RevId: 235269544 --- tensorflow/lite/experimental/swift/README.md | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/tensorflow/lite/experimental/swift/README.md b/tensorflow/lite/experimental/swift/README.md index cf7eeac6b4..754ecd6819 100644 --- a/tensorflow/lite/experimental/swift/README.md +++ b/tensorflow/lite/experimental/swift/README.md @@ -59,20 +59,3 @@ script: ```shell generate_xcodeproj.sh --genconfig tensorflow/lite/swift/TensorFlowLite.tulsiproj:TensorFlowLite --outputfolder ~/path/to/generated/TensorFlowLite.xcodeproj ``` - -### CocoaPods - -Add the following to your `Podfile`: - -```ruby -use_frameworks! -pod 'TensorFlowLiteSwift' -``` - -Then, run `pod install`. - -In your Swift files, import the module: - -```swift -import TensorFlowLite -``` -- GitLab From 8be0d24cc9d842fd06e25b628ab0aabbd614b1a2 Mon Sep 17 00:00:00 2001 From: Justin Lebar Date: Fri, 22 Feb 2019 15:35:10 -0800 Subject: [PATCH 385/766] [XLA] Fix edge cases in erfinv implementation. We were not handling +/-1 correctly. Some backends would return the wrong infinity (i.e. -/+inf rather than +/-inf), while others were returning nan. Also clean up the tests a tad. PiperOrigin-RevId: 235271238 --- tensorflow/compiler/xla/client/lib/math.cc | 13 +++- .../xla/client/lib/math_exhaustive_test.cc | 3 - .../compiler/xla/client/lib/math_test.cc | 15 +++- .../compiler/xla/tests/exhaustive_op_test.cc | 77 +++++++++++++++++++ 4 files changed, 103 insertions(+), 5 deletions(-) diff --git a/tensorflow/compiler/xla/client/lib/math.cc b/tensorflow/compiler/xla/client/lib/math.cc index adcaac9861..381da66a71 100644 --- a/tensorflow/compiler/xla/client/lib/math.cc +++ b/tensorflow/compiler/xla/client/lib/math.cc @@ -273,7 +273,18 @@ XlaOp ErfInv(XlaOp x) { for (int i = 1; i < kDegree; ++i) { p = coefficient(i) + p * w; } - return p * x; + + // Result modulo edge cases. + XlaOp result = p * x; + + // Handle edge cases, namely erfinv(+/-1) = +/-inf. (The above computation is + // indeterminate, and can give nan or -/+inf.) + auto& b = *x.builder(); + return b.ReportErrorOrReturn([&]() -> StatusOr { + TF_ASSIGN_OR_RETURN(Shape shape, b.GetShape(x)); + return Select(Eq(Abs(x), ScalarLike(x, 1)), + x * MaxValue(&b, shape.element_type()), result); + }); } namespace { diff --git a/tensorflow/compiler/xla/client/lib/math_exhaustive_test.cc b/tensorflow/compiler/xla/client/lib/math_exhaustive_test.cc index 2d6c8797d1..09a7e295b5 100644 --- a/tensorflow/compiler/xla/client/lib/math_exhaustive_test.cc +++ b/tensorflow/compiler/xla/client/lib/math_exhaustive_test.cc @@ -171,9 +171,6 @@ INSTANTIATE_TEST_CASE_P( ::testing::ValuesIn(std::vector{ Testcase{"square", Square, [](float x) { return x * x; }}, Testcase{"reciprocal", Reciprocal, [](float x) { return 1 / x; }}, - Testcase{"lgamma", Lgamma, std::lgamma} - .set_tolerance(0.1, 0.15) - .set_fewer_infs_ok(), Testcase{"asin", Asin, std::asin}.set_skip_infs(), Testcase{"acos", Acos, std::acos}.set_skip_infs(), Testcase{"atan", Atan, std::atan}, diff --git a/tensorflow/compiler/xla/client/lib/math_test.cc b/tensorflow/compiler/xla/client/lib/math_test.cc index f5ba3e7805..50613ce502 100644 --- a/tensorflow/compiler/xla/client/lib/math_test.cc +++ b/tensorflow/compiler/xla/client/lib/math_test.cc @@ -129,13 +129,25 @@ class MathTypedTest : public MathTest { XlaBuilder b(TestName()); auto x = AddParam(LiteralUtil::CreateR1({-inf}), &b); - ConstantR1(&b, {-inf}); ConcatInDim( &b, {Sqrt(x), Pow(x, ScalarLike(x, 0.5)), Pow(x, ScalarLike(x, 0.3))}, 0); std::vector expected = {nan, inf, inf}; ComputeAndCompareR1(&b, expected, {}, error_spec_); } + + void TestErfEdgeCases() { + SetFastMathDisabled(true); + + XlaBuilder b(TestName()); + auto x = AddParam(LiteralUtil::CreateR1({T{-1}, T{1}, T{0}}), &b); + ErfInv(x); + + const T inf(std::numeric_limits::infinity()); + std::vector expected = {-inf, inf, T{0}}; + + ComputeAndCompareR1(&b, expected, {}, error_spec_); + } }; // TODO(b/123355973): Add bfloat16 to TestTypes once it's working. @@ -154,6 +166,7 @@ XLA_TYPED_TEST(MathTypedTest, IsNegZero) { this->TestIsNegZero(); } XLA_TYPED_TEST(MathTypedTest, SqrtPowInequivalence) { this->TestSqrtPowInequivalence(); } +XLA_TYPED_TEST(MathTypedTest, ErfInvEdgeCases) { this->TestErfEdgeCases(); } // Check that certain ops only support real, floating-point inputs. // diff --git a/tensorflow/compiler/xla/tests/exhaustive_op_test.cc b/tensorflow/compiler/xla/tests/exhaustive_op_test.cc index c405f5580e..e143a1c3d1 100644 --- a/tensorflow/compiler/xla/tests/exhaustive_op_test.cc +++ b/tensorflow/compiler/xla/tests/exhaustive_op_test.cc @@ -27,6 +27,82 @@ namespace { using Eigen::half; +template +T EvaluatePolynomial(T x, const std::array& coeffs) { + T result = 0; + for (T c : coeffs) { + result = result * x + c; + } + return result; +} + +// There's no std::erfinv, so we have to implement it ourselves. This follows +// Wichura 1998, https://www.jstor.org/stable/2347330 which, notably, is a +// different implementation from that in math.cc. +float HostErfInv(float x) { + std::array kPolyA = { + 8.8709406962545514830200e2, 1.1819493347062294404278e4, + 2.3782041382114385731252e4, 1.6235862515167575384252e4, + 4.8548868893843886794648e3, 6.9706266534389598238465e2, + 4.7072688112383978012285e1, 1.1975323115670912564578e0, + }; + std::array kPolyB = { + 5.2264952788528545610e3, 2.8729085735721942674e4, 3.9307895800092710610e4, + 2.1213794301586595867e4, 5.3941960214247511077e3, 6.8718700749205790830e2, + 4.2313330701600911252e1, 1.0000000000000000000e0, + }; + std::array kPolyC = { + 7.74545014278341407640e-4, 2.27238449892691845833e-2, + 2.41780725177450611770e-1, 1.27045825245236838258e0, + 3.64784832476320460504e0, 5.76949722146069140550e0, + 4.63033784615654529590e0, 1.42343711074968357734e0, + }; + std::array kPolyD = { + 1.4859850019840355905497876e-9, 7.7441459065157709165577218e-4, + 2.1494160384252876777097297e-2, 2.0945065210512749128288442e-1, + 9.7547832001787427186894837e-1, 2.3707661626024532365971225e0, + 2.9036514445419946173133295e0, 1.4142135623730950488016887e0, + }; + std::array kPolyE = { + 2.01033439929228813265e-7, 2.71155556874348757815e-5, + 1.24266094738807843860e-3, 2.65321895265761230930e-2, + 2.96560571828504891230e-1, 1.78482653991729133580e0, + 5.46378491116411436990e0, 6.65790464350110377720e0, + }; + std::array kPolyF = { + 2.891024605872965461538222e-15, 2.010321207683943062279931e-7, + 2.611088405080593625138020e-5, 1.112800997078859844711555e-3, + 2.103693768272068968719679e-2, 1.936480946950659106176712e-1, + 8.482908416595164588112026e-1, 1.414213562373095048801689e0, + }; + + if (std::abs(x) > 1 || std::isnan(x)) { + return std::numeric_limits::quiet_NaN(); + } + if (std::abs(x) == 1) { + return std::copysign(std::numeric_limits::infinity(), x); + } + + float unsigned_result = [&] { + float y = std::abs(x); + if (y <= 0.85) { + double r = 0.180625 - 0.25 * y * y; + return (y * EvaluatePolynomial(r, kPolyA)) / + EvaluatePolynomial(r, kPolyB); + } else { + double r = std::sqrt(std::log(2.0) - std::log1p(-y)); + if (r <= 5.0) { + r -= 1.6; + return EvaluatePolynomial(r, kPolyC) / EvaluatePolynomial(r, kPolyD); + } else { + r -= 5; + return EvaluatePolynomial(r, kPolyE) / EvaluatePolynomial(r, kPolyF); + } + } + }(); + return std::copysign(unsigned_result, x); +} + // For f32, f16, and bf16, we need 9, 5, and 4 decimal places of precision to be // guaranteed that we're printing the full number. // @@ -397,6 +473,7 @@ XLA_TEST_P(ExhaustiveOpTest, Sqrt) { XLA_TEST_P(ExhaustiveOpTest, Tanh) { Run(Tanh, std::tanh); } XLA_TEST_P(ExhaustiveOpTest, Erf) { Run(Erf, std::erf); } XLA_TEST_P(ExhaustiveOpTest, Erfc) { Run(Erfc, std::erfc); } +XLA_TEST_P(ExhaustiveOpTest, ErfInv) { Run(ErfInv, HostErfInv); } XLA_TEST_P(ExhaustiveOpTest, Lgamma) { // Our implementation gets within 0.0001 rel error except for ~20 denormal // inputs on GPU. Anyway 0.001 rel error should be good enough for lgamma. -- GitLab From 135fee16857a216e47add40fa106b9f7141891fb Mon Sep 17 00:00:00 2001 From: Skye Wanderman-Milne Date: Fri, 22 Feb 2019 15:35:37 -0800 Subject: [PATCH 386/766] Fix bug in tf.gradients. This is fixing in a bug where we'd incorrectly match up gradients tensors with the corresponding forward-pass tensors due to filtering out captured EagerTensors. This could lead to errors about incompatible gradient shapes or, even worse, the wrong gradient value. PiperOrigin-RevId: 235271326 --- .../kernel_tests/control_flow_ops_py_test.py | 16 ++++++++++ tensorflow/python/ops/gradients_test.py | 5 ++-- tensorflow/python/ops/gradients_util.py | 30 ++++++++++++++----- 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/tensorflow/python/kernel_tests/control_flow_ops_py_test.py b/tensorflow/python/kernel_tests/control_flow_ops_py_test.py index 982ead7e94..20c03178ea 100644 --- a/tensorflow/python/kernel_tests/control_flow_ops_py_test.py +++ b/tensorflow/python/kernel_tests/control_flow_ops_py_test.py @@ -2595,6 +2595,22 @@ class ControlFlowTest(test.TestCase): self.evaluate(variables.global_variables_initializer()) self.assertAllClose(216.0, g[0]) + def testWhileGrad_EagerResourceVariable(self): + with context.eager_mode(): + a = resource_variable_ops.ResourceVariable( + np.ones([2, 2], dtype=np.float32)) + v = constant_op.constant(1.0) + + @eager_function.defun + def fn(): + r = control_flow_ops.while_loop( + lambda i, _: i < 2, + lambda i, x: (i + 1, x * math_ops.reduce_sum(a) * v), + [0, 1.0])[1] + return gradients_impl.gradients(r, [v])[0] + + self.assertEqual(self.evaluate(fn()), 32.) + def testWhileGrad_ResourceVarInFunctionCall(self): @def_function.function diff --git a/tensorflow/python/ops/gradients_test.py b/tensorflow/python/ops/gradients_test.py index 9caffa3ea8..9b24473f57 100644 --- a/tensorflow/python/ops/gradients_test.py +++ b/tensorflow/python/ops/gradients_test.py @@ -587,11 +587,12 @@ class FunctionGradientsTest(test_util.TensorFlowTestCase): def Foo(): x = constant_op.constant(10.0, name="x") y = math_ops.multiply(x, c, name="y") - z = math_ops.multiply(y, 3.0, name="z") + # Regression test for b/122564611. + z = math_ops.multiply(c, y, name="z") g = gradients_impl.gradients(z, x) return g[0] - self.assertEqual(Foo().numpy(), 6.0) + self.assertEqual(Foo().numpy(), 4.0) class StopGradientTest(test_util.TensorFlowTestCase): diff --git a/tensorflow/python/ops/gradients_util.py b/tensorflow/python/ops/gradients_util.py index 64c199ad29..af46101726 100644 --- a/tensorflow/python/ops/gradients_util.py +++ b/tensorflow/python/ops/gradients_util.py @@ -478,14 +478,28 @@ def _MaybeCaptured(t): return t -# TODO(skyewm): plumbing xs through everywhere is ugly, consider making -# _GradientsHelper a class with xs as a member variable. def _NonEagerInputs(op, xs): """Returns the inputs of op, crossing closure boundaries where necessary. Does not return any captured EagerTensors, i.e., the number of tensors returned may be less than than the actual number of inputs. + Args: + op: Operation + xs: list of Tensors we are differentiating w.r.t. + + Returns: + A list of tensors. The tensors may be from multiple Graph/FuncGraphs if op + is in a FuncGraph and has captured inputs. + """ + return [t for t in _Inputs(op, xs) if not isinstance(t, ops.EagerTensor)] + + +# TODO(skyewm): plumbing xs through everywhere is ugly, consider making +# _GradientsHelper a class with xs as a member variable. +def _Inputs(op, xs): + """Returns the inputs of op, crossing closure boundaries where necessary. + Args: op: Operation xs: list of Tensors we are differentiating w.r.t. @@ -504,8 +518,6 @@ def _NonEagerInputs(op, xs): # direct input to op. if t not in xs: t = _MaybeCaptured(t) - # Skip captured eager inputs. - if isinstance(t, ops.EagerTensor): continue inputs.append(t) return inputs else: @@ -736,9 +748,10 @@ def _GradientsHelper(ys, else: # If no grad_fn is defined or none of out_grads is available, # just propagate a list of None backwards. - in_grads = [None] * len(_NonEagerInputs(op, xs)) - for i, (t_in, in_grad) in enumerate(zip(_NonEagerInputs(op, xs), - in_grads)): + in_grads = [None] * len(_Inputs(op, xs)) + # Note: we don't filter out eager inputs here because the inputs need to + # line up with in_grads. + for i, (t_in, in_grad) in enumerate(zip(_Inputs(op, xs), in_grads)): if in_grad is not None: if (isinstance(in_grad, ops.Tensor) and t_in.dtype != dtypes.resource): @@ -751,7 +764,8 @@ def _GradientsHelper(ys, "Original input shape: %s. " "Calculated input gradient shape: %s" % (op.name, i, t_in.shape, in_grad.shape)) - _SetGrad(grads, t_in, in_grad) + if not isinstance(t_in, ops.EagerTensor): + _SetGrad(grads, t_in, in_grad) if loop_state: loop_state.ExitGradWhileContext(op, before=False) -- GitLab From 4af87127490470cb6464546113a3c59d8167f8ae Mon Sep 17 00:00:00 2001 From: Rick Chao Date: Fri, 22 Feb 2019 15:35:52 -0800 Subject: [PATCH 387/766] Internal refactoring. PiperOrigin-RevId: 235271388 --- .../python/multi_worker_test_base.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tensorflow/contrib/distribute/python/multi_worker_test_base.py b/tensorflow/contrib/distribute/python/multi_worker_test_base.py index 7dca13a5b4..d951a8b783 100644 --- a/tensorflow/contrib/distribute/python/multi_worker_test_base.py +++ b/tensorflow/contrib/distribute/python/multi_worker_test_base.py @@ -37,12 +37,16 @@ except ImportError as _error: # pylint: disable=invalid-name from tensorflow.core.protobuf import config_pb2 from tensorflow.core.protobuf import rewriter_config_pb2 from tensorflow.python.client import session +from tensorflow.python.distribute import distribute_coordinator as dc from tensorflow.python.estimator import run_config from tensorflow.python.platform import test from tensorflow.python.platform import tf_logging as logging from tensorflow.python.training import coordinator from tensorflow.python.training import server_lib + +original_run_std_server = dc._run_std_server # pylint: disable=protected-access + ASSIGNED_PORTS = set() lock = threading.Lock() @@ -357,6 +361,22 @@ class MockOsEnv(collections.Mapping): class IndependentWorkerTestBase(test.TestCase): """Testing infra for independent workers.""" + def _make_mock_run_std_server(self): + thread_local = threading.local() + + def _mock_run_std_server(*args, **kwargs): + ret = original_run_std_server(*args, **kwargs) + # Wait for all std servers to be brought up in order to reduce the chance + # of remote sessions taking local ports that have been assigned to std + # servers. Only call this barrier the first time this function is run for + # each thread. + if not getattr(thread_local, 'server_started', False): + self._barrier.wait() + thread_local.server_started = True + return ret + + return _mock_run_std_server + def setUp(self): self._mock_os_env = MockOsEnv() self._mock_context = test.mock.patch.object(os, 'environ', -- GitLab From de7d814b50ebf74bda4fe560afe22d120428b1fd Mon Sep 17 00:00:00 2001 From: Akshay Modi Date: Fri, 22 Feb 2019 15:47:10 -0800 Subject: [PATCH 388/766] Support DT_UINT64 as a direct conversion type PiperOrigin-RevId: 235273319 --- tensorflow/python/eager/tensor_test.py | 8 ++++++ tensorflow/python/lib/core/py_seq_tensor.cc | 29 +++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/tensorflow/python/eager/tensor_test.py b/tensorflow/python/eager/tensor_test.py index 23fb983767..4e6a12b897 100644 --- a/tensorflow/python/eager/tensor_test.py +++ b/tensorflow/python/eager/tensor_test.py @@ -364,6 +364,14 @@ class TFETensorTest(test_util.TensorFlowTestCase): "Provided value.*Requested dtype.*"): _ = ops.convert_to_tensor(1., dtype=dtypes.int32) + def testEagerLargeConstant(self): + self.assertEqual( + constant_op.constant(dtypes.uint64.max, dtype=dtypes.uint64).numpy(), + dtypes.uint64.max) + self.assertEqual( + constant_op.constant(dtypes.uint32.max, dtype=dtypes.uint32).numpy(), + dtypes.uint32.max) + class TFETensorUtilTest(test_util.TensorFlowTestCase): diff --git a/tensorflow/python/lib/core/py_seq_tensor.cc b/tensorflow/python/lib/core/py_seq_tensor.cc index 77fbfd51bb..726701eb8f 100644 --- a/tensorflow/python/lib/core/py_seq_tensor.cc +++ b/tensorflow/python/lib/core/py_seq_tensor.cc @@ -316,6 +316,31 @@ const char* ConvertOneInt64(PyObject* v, int64* out) { DEFINE_HELPER(ConvertInt64, int64, DT_INT64, ConvertOneInt64); +const char* ConvertOneUint64(PyObject* v, uint64* out) { +#if PY_MAJOR_VERSION < 3 + if (TF_PREDICT_TRUE(PyInt_Check(v))) { + *out = PyInt_AsUnsignedLongMask(v); + return nullptr; + } +#endif + if (TF_PREDICT_TRUE(PyLong_Check(v) || IsPyDimension(v))) { + *out = PyLong_AsUnsignedLong(v); + return nullptr; + } + if (PyIsInstance(v, &PyIntegerArrType_Type)) { // NumPy integers +#if PY_MAJOR_VERSION < 3 + Safe_PyObjectPtr as_int = make_safe(PyNumber_Int(v)); +#else + Safe_PyObjectPtr as_int = make_safe(PyNumber_Long(v)); +#endif + return ConvertOneUint64(as_int.get(), out); + } + if (IsPyFloat(v)) return ErrorFoundFloat; + return ErrorMixedTypes; +} + +DEFINE_HELPER(ConvertUint64, uint64, DT_UINT64, ConvertOneUint64); + const char* ConvertOneInt32(PyObject* v, int32* out) { int64 i; #if PY_MAJOR_VERSION < 3 @@ -525,6 +550,10 @@ Status PySeqToTensor(PyObject* obj, PyObject* dtype, Tensor* ret) { if (ConvertInt32(obj, shape, ret) == nullptr) return Status::OK(); break; + case DT_UINT64: + if (ConvertUint64(obj, shape, ret) == nullptr) return Status::OK(); + break; + case DT_COMPLEX128: if (ConvertComplex(obj, shape, ret) == nullptr) return Status::OK(); break; -- GitLab From fa4eb70820dd7ca02d5f26962c958ffde336cb2c Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 22 Feb 2019 15:52:18 -0800 Subject: [PATCH 389/766] acquire lock before accessing global profiler PiperOrigin-RevId: 235274121 --- tensorflow/python/eager/profiler.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tensorflow/python/eager/profiler.py b/tensorflow/python/eager/profiler.py index 8a19057c31..0c8e9796f5 100644 --- a/tensorflow/python/eager/profiler.py +++ b/tensorflow/python/eager/profiler.py @@ -58,9 +58,9 @@ def start(): AssertionError: If another profiling session is running. """ global _profiler - if _profiler is not None: - raise AssertionError('Another profiler is running.') with _profiler_lock: + if _profiler is not None: + raise AssertionError('Another profiler is running.') profiler_context = pywrap_tensorflow.TFE_NewProfilerContext() if context.default_execution_mode == context.EAGER_MODE: pywrap_tensorflow.TFE_ProfilerContextSetEagerContext( @@ -86,15 +86,15 @@ def stop(): """ global _profiler global _run_num - if _profiler is None: - raise AssertionError('Cannot stop profiling. No profiler is running.') - with c_api_util.tf_buffer() as buffer_: - pywrap_tensorflow.TFE_ProfilerSerializeToString( - context.context()._handle, # pylint: disable=protected-access - _profiler, - buffer_) - result = pywrap_tensorflow.TF_GetBuffer(buffer_) with _profiler_lock: + if _profiler is None: + raise AssertionError('Cannot stop profiling. No profiler is running.') + with c_api_util.tf_buffer() as buffer_: + pywrap_tensorflow.TFE_ProfilerSerializeToString( + context.context()._handle, # pylint: disable=protected-access + _profiler, + buffer_) + result = pywrap_tensorflow.TF_GetBuffer(buffer_) pywrap_tensorflow.TFE_DeleteProfiler(_profiler) _profiler = None _run_num += 1 @@ -122,7 +122,7 @@ def start_profiler_server(port): The profiler server will keep the program running even the training finishes. Please shutdown the server with CTRL-C. It can be used in both eager mode and graph mode. The service defined in - tensorflow/contrib/tpu/profiler/tpu_profiler.proto. Please use + tensorflow/core/profiler/profiler_service.proto. Please use tensorflow/contrib/tpu/profiler/capture_tpu_profile to capture tracable file following https://cloud.google.com/tpu/docs/cloud-tpu-tools#capture_trace -- GitLab From 61368b23ac560d158a27e679ac570a9b7ae94e0a Mon Sep 17 00:00:00 2001 From: Justin Lebar Date: Fri, 22 Feb 2019 15:57:41 -0800 Subject: [PATCH 390/766] [XLA] Use IsInf function in implementation of lgamma. PiperOrigin-RevId: 235275004 --- tensorflow/compiler/xla/client/lib/math.cc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tensorflow/compiler/xla/client/lib/math.cc b/tensorflow/compiler/xla/client/lib/math.cc index 381da66a71..273012a0c3 100644 --- a/tensorflow/compiler/xla/client/lib/math.cc +++ b/tensorflow/compiler/xla/client/lib/math.cc @@ -402,9 +402,7 @@ XlaOp Lgamma(XlaOp input) { // lgamma(+/-inf) = +inf. XlaOp inf_bcast = FullLike(input, std::numeric_limits::infinity()); - return Select(Or(IsFinite(input), // is finite, or - Not(Or(Lt(input, one), Ge(input, one)))), // is nan - result, inf_bcast); + return Select(IsInf(input), inf_bcast, result); }; auto& b = *input.builder(); -- GitLab From 9e8c24dfeced9600f067025787cb84463397043f Mon Sep 17 00:00:00 2001 From: Justin Lebar Date: Fri, 22 Feb 2019 16:17:13 -0800 Subject: [PATCH 391/766] [XLA] Improve accuracy of digamma function. This function suffered from the same accuracy problems when computing cot(pi * x) as lgamma. PiperOrigin-RevId: 235278472 --- tensorflow/compiler/xla/client/lib/math.cc | 29 +++++-- .../xla/client/lib/math_exhaustive_test.cc | 6 -- .../compiler/xla/tests/exhaustive_op_test.cc | 85 +++++++++++++++++++ 3 files changed, 108 insertions(+), 12 deletions(-) diff --git a/tensorflow/compiler/xla/client/lib/math.cc b/tensorflow/compiler/xla/client/lib/math.cc index 273012a0c3..20d3c0fc54 100644 --- a/tensorflow/compiler/xla/client/lib/math.cc +++ b/tensorflow/compiler/xla/client/lib/math.cc @@ -423,10 +423,7 @@ XlaOp Lgamma(XlaOp input) { // A(z) = kBaseLanczosCoeff + sigma(k = 1, n, kLanczosCoefficients[i] / (z + k)) // A'(z) = sigma(k = 1, n, kLanczosCoefficients[i] / (z + k) / (z + k)) XlaOp Digamma(XlaOp input) { - auto& b = *input.builder(); - return b.ReportErrorOrReturn([&]() -> StatusOr { - TF_RETURN_IF_ERROR(EnsureOperandIsRealFp("Digamma", input)); - + auto do_it = [](XlaOp input) { XlaOp zero = ScalarLike(input, 0); XlaOp one_half = ScalarLike(input, 0.5); XlaOp one = ScalarLike(input, 1); @@ -464,8 +461,28 @@ XlaOp Digamma(XlaOp input) { Log1p(z / lanczos_gamma_plus_one_half); XlaOp y = log_t + num / denom - lanczos_gamma / t; - XlaOp reflection = y - pi * Cos(pi * input) / Sin(pi * input); - return Select(need_to_reflect, reflection, y); + + // We need to be careful how we compute cot(pi * input) below: For + // near-integral values of `input`, pi * input can lose precision. + // + // Input is already known to be less than 0.5 (otherwise we don't have to + // reflect). We shift values smaller than -0.5 into the range [-.5, .5] to + // increase precision of pi * input and the resulting cotangent. + XlaOp reduced_input = input + Abs(Floor(input + ScalarLike(input, 0.5))); + XlaOp reflection = + y - pi * Cos(pi * reduced_input) / Sin(pi * reduced_input); + XlaOp real_result = Select(need_to_reflect, reflection, y); + + // Digamma has poles at negative integers and zero; return nan for those. + return Select(And(Le(input, zero), Eq(input, Floor(input))), + FullLike(input, std::numeric_limits::quiet_NaN()), + real_result); + }; + + auto& b = *input.builder(); + return b.ReportErrorOrReturn([&]() -> StatusOr { + TF_RETURN_IF_ERROR(EnsureOperandIsRealFp("Digamma", input)); + return DoWithUpcastToF32(input, {BF16, F16}, do_it); }); } diff --git a/tensorflow/compiler/xla/client/lib/math_exhaustive_test.cc b/tensorflow/compiler/xla/client/lib/math_exhaustive_test.cc index 09a7e295b5..7f5add15ba 100644 --- a/tensorflow/compiler/xla/client/lib/math_exhaustive_test.cc +++ b/tensorflow/compiler/xla/client/lib/math_exhaustive_test.cc @@ -151,11 +151,6 @@ XLA_TEST_P(MathExhaustiveTest, DISABLED_ON_INTERPRETER(F16)) { // Testcase{"round_to_even", RoundToEven, // [](float x) { return std::nearbyint(x / 2) * 2; }}, // -// - No equivalent std function to compare with. -// -// Testcase{"erfinv", ErfInv, std::erfinv}, -// Testcase{"digamma", Digamma, std::digamma}, -// // - Needs a special test (function takes two args, and simply computing in f32 // and downcasting to f16 doesn't give the correct answer). // @@ -164,7 +159,6 @@ XLA_TEST_P(MathExhaustiveTest, DISABLED_ON_INTERPRETER(F16)) { // TODO(b/123355973): Test math functions not from math.cc (e.g. log). // TODO(b/123355973): Test bf16 and f32. // TODO(b/123355973): Get rid of skip_infs / skip_neg_zero below if possible. -// TODO(b/123355973): Reduce lgamma error if possible; it is very high. // TODO(b/123355973): Move these into exhaustive_op_test. INSTANTIATE_TEST_CASE_P( MathExhaustiveTest_Instantiation, MathExhaustiveTest, diff --git a/tensorflow/compiler/xla/tests/exhaustive_op_test.cc b/tensorflow/compiler/xla/tests/exhaustive_op_test.cc index e143a1c3d1..9ec53488a2 100644 --- a/tensorflow/compiler/xla/tests/exhaustive_op_test.cc +++ b/tensorflow/compiler/xla/tests/exhaustive_op_test.cc @@ -103,6 +103,62 @@ float HostErfInv(float x) { return std::copysign(unsigned_result, x); } +// Digamma implementation using a polynomial from Cephes. Notably this is a +// different implementation from the one in math.cc. +float HostDigamma(float x) { + // Euler-Mascheroni constant + float kGamma = 0.57721566490153286061; + float kPi = M_PI; + + std::array kPoly = { + -4.16666666666666666667E-3, + 3.96825396825396825397E-3, + -8.33333333333333333333E-3, + 8.33333333333333333333E-2, + }; + + float reflection = 0; + if (x <= 0) { + float floor = std::floor(x); + if (x == floor) { + return std::numeric_limits::quiet_NaN(); + } + // Compute reflection term, pi * cot(pi * x). + reflection = x - floor; + if (reflection == 0.5) { + reflection = 0; + } else { + if (reflection > 0.5) { + reflection = x - (floor + 1.0f); + } + reflection = kPi / std::tan(kPi * reflection); + } + x = 1 - x; + } + + float result = 0; + if (x <= 10 && x == std::floor(x)) { + // Special case for integers <= 10. + for (int i = 1; i < x; ++i) { + result += 1.0f / i; + } + result -= kGamma; + } else { + float w = 0; + for (; x < 10; ++x) { + w += 1.0f / x; + } + if (x < 1e8) { + float z = 1.0f / (x * x); + result = z * EvaluatePolynomial(z, kPoly); + } + result = std::log(x) - 0.5f / x - result - w; + } + + // Compute the final, reflected value. + return result - reflection; +} + // For f32, f16, and bf16, we need 9, 5, and 4 decimal places of precision to be // guaranteed that we're printing the full number. // @@ -474,6 +530,35 @@ XLA_TEST_P(ExhaustiveOpTest, Tanh) { Run(Tanh, std::tanh); } XLA_TEST_P(ExhaustiveOpTest, Erf) { Run(Erf, std::erf); } XLA_TEST_P(ExhaustiveOpTest, Erfc) { Run(Erfc, std::erfc); } XLA_TEST_P(ExhaustiveOpTest, ErfInv) { Run(ErfInv, HostErfInv); } +XLA_TEST_P(ExhaustiveOpTest, Digamma) { + if (platform_ != "Host" && platform_ != "CUDA") { + // TODO(b/123956399): This is a fairly high error, significantly higher than + // we see on CPU/GPU. + rel_err_ = 0.01; + abs_err_ = 0.01; + } + + if (platform_ == "CUDA") { + // On GPU we get a wrong answer for the denormal inputs +/-2.93873588e-39 + // (0x00200000 and 0x80200000). These should return -/+inf (at least + // according to our reference implementation!) but XLA:GPU returns + // -/+3.40282326e+38 (0xff7ffffe and 0x7f7ffffe). + // + // I deem this an acceptable result, as XLA:GPU flushes denormals, and as + // the results we get here are very close to MAX_FLOAT. We just hardcode + // these results, as this is better than ignoring these inputs altogether. + auto host_digamma_with_gpu_ftz_errors = +[](float x) { + if (absl::bit_cast(x) == 0x00200000 || + absl::bit_cast(x) == 0x80200000) { + return std::copysign(std::numeric_limits::max(), -x); + } + return HostDigamma(x); + }; + Run(Digamma, host_digamma_with_gpu_ftz_errors); + } else { + Run(Digamma, HostDigamma); + } +} XLA_TEST_P(ExhaustiveOpTest, Lgamma) { // Our implementation gets within 0.0001 rel error except for ~20 denormal // inputs on GPU. Anyway 0.001 rel error should be good enough for lgamma. -- GitLab From c4c1a2399bdcb135aa989fffcd9eb1e25dd5bf2d Mon Sep 17 00:00:00 2001 From: Mark Daoust Date: Fri, 22 Feb 2019 16:19:10 -0800 Subject: [PATCH 392/766] Make submodules the preferred names, except for modules, they should prefer the root. Prefer `tf.submodule.Symbol` to `tf.Symbol`. When the object is a module, then prefer `tf.submodule` over `tf.package.submodule` so that `tf.submodule.Symbol` finds `tf.submodule`. PiperOrigin-RevId: 235278774 --- tensorflow/tools/docs/doc_generator_visitor.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tensorflow/tools/docs/doc_generator_visitor.py b/tensorflow/tools/docs/doc_generator_visitor.py index a66f3e4493..6157eb1b7f 100644 --- a/tensorflow/tools/docs/doc_generator_visitor.py +++ b/tensorflow/tools/docs/doc_generator_visitor.py @@ -194,10 +194,11 @@ class DocGeneratorVisitor(object): contrib_score = 1 while parts: - parts.pop() container = self._index['.'.join(parts)] if tf_inspect.ismodule(container): break + parts.pop() + module_length = len(parts) if len(parts) == 2: # `tf.submodule.thing` is better than `tf.thing` -- GitLab From fd53853d50028ca744d316537754c92a072cf9f5 Mon Sep 17 00:00:00 2001 From: Justin Lebar Date: Fri, 22 Feb 2019 16:35:39 -0800 Subject: [PATCH 393/766] [XLA] Remove math_exhaustive_test. Now mostly covered by exhaustive_op_test. The big missing gap is the trig functions. But to fix these, we need to carefully define how they're supposed to work over complex inputs, and that's a bigger task than I'm ready to take on at the moment. PiperOrigin-RevId: 235281417 --- tensorflow/compiler/xla/client/lib/BUILD | 16 -- .../xla/client/lib/math_exhaustive_test.cc | 175 ------------------ .../compiler/xla/tests/exhaustive_op_test.cc | 18 +- 3 files changed, 10 insertions(+), 199 deletions(-) delete mode 100644 tensorflow/compiler/xla/client/lib/math_exhaustive_test.cc diff --git a/tensorflow/compiler/xla/client/lib/BUILD b/tensorflow/compiler/xla/client/lib/BUILD index b8c0be4833..f264ec50a2 100644 --- a/tensorflow/compiler/xla/client/lib/BUILD +++ b/tensorflow/compiler/xla/client/lib/BUILD @@ -208,22 +208,6 @@ xla_test( ], ) -xla_test( - name = "math_exhaustive_test", - srcs = ["math_exhaustive_test.cc"], - shard_count = 16, - deps = [ - ":math", - "//tensorflow/compiler/xla:literal_util", - "//tensorflow/compiler/xla:test", - "//tensorflow/compiler/xla:types", - "//tensorflow/compiler/xla:xla_data_proto", - "//tensorflow/compiler/xla/client:xla_builder", - "//tensorflow/compiler/xla/tests:client_library_test_base", - "//tensorflow/compiler/xla/tests:xla_internal_test_main", - ], -) - cc_library( name = "matrix", srcs = ["matrix.cc"], diff --git a/tensorflow/compiler/xla/client/lib/math_exhaustive_test.cc b/tensorflow/compiler/xla/client/lib/math_exhaustive_test.cc deleted file mode 100644 index 7f5add15ba..0000000000 --- a/tensorflow/compiler/xla/client/lib/math_exhaustive_test.cc +++ /dev/null @@ -1,175 +0,0 @@ -/* Copyright 2019 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/lib/math.h" -#include "tensorflow/compiler/xla/client/xla_builder.h" -#include "tensorflow/compiler/xla/literal_util.h" -#include "tensorflow/compiler/xla/test.h" -#include "tensorflow/compiler/xla/tests/client_library_test_base.h" -#include "tensorflow/compiler/xla/tests/test_macros.h" -#include "tensorflow/compiler/xla/types.h" - -namespace xla { -namespace { - -using Eigen::half; - -struct Testcase { - Testcase(string name, const std::function& op, - float (*host_op)(float)) - : name(name), op(op), host_op(host_op) {} - - Testcase& set_tolerance(float abs_err, float rel_err) { - error.abs = abs_err; - error.rel = rel_err; - return *this; - } - - Testcase& set_relaxed_nans() { - error.relaxed_nans = true; - return *this; - } - - Testcase& set_fewer_infs_ok() { - error.fewer_infs_ok = true; - return *this; - } - - Testcase& set_skip_pos_inf() { - skip_pos_inf = true; - return *this; - } - - Testcase& set_skip_neg_inf() { - skip_neg_inf = true; - return *this; - } - - Testcase& set_skip_infs() { - skip_pos_inf = true; - skip_neg_inf = true; - return *this; - } - - Testcase& set_skip_neg_zero() { - skip_neg_zero = true; - return *this; - } - - string name; - std::function op; - float (*host_op)(float); - - ErrorSpec error{0.01, 0.01}; - - // If true, don't test +/-infinity or negative 0. - bool skip_pos_inf = false; - bool skip_neg_inf = false; - bool skip_neg_zero = false; -}; - -void PrintTo(const Testcase& tc, std::ostream* os) { *os << tc.name; } - -class MathExhaustiveTest : public ClientLibraryTestBase, - public ::testing::WithParamInterface { - public: - MathExhaustiveTest() { - // Disable fast-math, otherwise we get the wrong results for e.g. - // sqrt(-inf). - SetFastMathDisabled(true); - } -}; - -// Checks a function's behavior on all fp16 values. -// -// TODO(jlebar): asin and lgamma tests fail on interpreter. -#if !defined(XLA_BACKEND_DOES_NOT_SUPPORT_FLOAT16) -XLA_TEST_P(MathExhaustiveTest, DISABLED_ON_INTERPRETER(F16)) { - const Testcase& tc = GetParam(); - XlaBuilder b(TestName()); - - std::vector input; - for (uint32 i = 0; i < 1 << 16; ++i) { - half h; - h.x = i; - - // If we're not using infinity as an input, use 0 as a placeholder rather - // than simply skipping this element. We do this because when the test - // framework reports an incorrect answer, it tells us which index failed. - // So long as our inputs are a simple list of all possible float16s, we can - // convert an index to a half with e.g. the following Python: - // - // np.frombuffer(array('H', [12345]), dtype=np.float16)[0] - // - // but as soon as our list of inputs has any gaps, this doesn't work. - if (std::isinf(static_cast(h)) && - ((tc.skip_pos_inf && h > half{0}) || - (tc.skip_neg_inf && h < half{0}))) { - h = half{0}; - } - - if (h == half{0} && tc.skip_neg_zero && - std::signbit(static_cast(h))) { - h = half{0}; - } - - input.push_back(h); - } - - std::vector expected_result; - for (const auto& h : input) { - expected_result.push_back( - static_cast(tc.host_op(static_cast(h)))); - } - - XlaOp param = AddParam(LiteralUtil::CreateR1(input), &b); - tc.op(param); - ComputeAndCompareR1(&b, expected_result, {}, tc.error); -} -#endif - -// TODO(b/123355973): The following tests from math.cc are missing. -// -// - Many failures. -// -// Testcase{"acosh", Acosh, std::acosh}.set_relaxed_nans(), -// Testcase{"asinh", Asinh, std::asinh}, -// Testcase{"sinh", Sinh, std::sinh}, -// Testcase{"cosh", Cosh, std::cosh}.set_fewer_infs_ok(), -// Testcase{"round_to_even", RoundToEven, -// [](float x) { return std::nearbyint(x / 2) * 2; }}, -// -// - Needs a special test (function takes two args, and simply computing in f32 -// and downcasting to f16 doesn't give the correct answer). -// -// Testcase{"nextafter", NextAfter, std::nextafter}, -// -// TODO(b/123355973): Test math functions not from math.cc (e.g. log). -// TODO(b/123355973): Test bf16 and f32. -// TODO(b/123355973): Get rid of skip_infs / skip_neg_zero below if possible. -// TODO(b/123355973): Move these into exhaustive_op_test. -INSTANTIATE_TEST_CASE_P( - MathExhaustiveTest_Instantiation, MathExhaustiveTest, - ::testing::ValuesIn(std::vector{ - Testcase{"square", Square, [](float x) { return x * x; }}, - Testcase{"reciprocal", Reciprocal, [](float x) { return 1 / x; }}, - Testcase{"asin", Asin, std::asin}.set_skip_infs(), - Testcase{"acos", Acos, std::acos}.set_skip_infs(), - Testcase{"atan", Atan, std::atan}, - Testcase{"tan", Tan, std::tan}.set_tolerance(0.05, 0.05), - })); - -} // namespace -} // namespace xla diff --git a/tensorflow/compiler/xla/tests/exhaustive_op_test.cc b/tensorflow/compiler/xla/tests/exhaustive_op_test.cc index 9ec53488a2..b409eee87d 100644 --- a/tensorflow/compiler/xla/tests/exhaustive_op_test.cc +++ b/tensorflow/compiler/xla/tests/exhaustive_op_test.cc @@ -162,13 +162,12 @@ float HostDigamma(float x) { // For f32, f16, and bf16, we need 9, 5, and 4 decimal places of precision to be // guaranteed that we're printing the full number. // -// If we have a floating-point number with S significand bits, we need +// (The general formula is, given a floating-point number with S significand +// bits, the number of decimal digits needed to print it to full precision is // -// ceil(1 + S * log_10(2)) ~= ceil(1 + S * 0.30103) +// ceil(1 + S * log_10(2)) ~= ceil(1 + S * 0.30103). // -// decimal digits to be guaranteed that we're printing the full number. For -// F32/F16/BF16 this works out to 9/5/4 digits. See -// https://people.eecs.berkeley.edu/~wkahan/Math128/BinDecBin.pdf +// See https://people.eecs.berkeley.edu/~wkahan/Math128/BinDecBin.pdf.) string StringifyNum(float x) { return absl::StrFormat("%0.9g (0x%08x)", x, absl::bit_cast(x)); } @@ -361,9 +360,9 @@ class ExhaustiveOpTest // Easy case: If `input` is not denormal and !IsClose(expected, actual), // print an error. // - // TODO(jlebar): This doesn't correctly detect f16 and bfloat16 denormals! - // This seems to be OK for now, but at some point we may need to implement - // fpclassify for half and bfloat. + // (This doesn't correctly detect f16 and bfloat16 denormals! This seems + // to be OK for now, but at some point we may need to implement fpclassify + // for half and bfloat.) if (std::fpclassify(input_f32) != FP_SUBNORMAL) { PrintMismatch(&mismatches, [&] { return absl::StrFormat("Mismatch on %s. Expected %s, but got %s.", @@ -526,7 +525,10 @@ XLA_TEST_P(ExhaustiveOpTest, Sqrt) { Run(Sqrt, std::sqrt); } +// TODO(jlebar): Add remaining trig functions. Don't forget Atan2! +// TODO(jlebar): Test trig functions over complex inputs. XLA_TEST_P(ExhaustiveOpTest, Tanh) { Run(Tanh, std::tanh); } + XLA_TEST_P(ExhaustiveOpTest, Erf) { Run(Erf, std::erf); } XLA_TEST_P(ExhaustiveOpTest, Erfc) { Run(Erfc, std::erfc); } XLA_TEST_P(ExhaustiveOpTest, ErfInv) { Run(ErfInv, HostErfInv); } -- GitLab From dfb34a1a69d3024ee808e0c2cf01cb2110811e86 Mon Sep 17 00:00:00 2001 From: Haoyu Zhang Date: Fri, 22 Feb 2019 16:45:33 -0800 Subject: [PATCH 394/766] Pass config proto through session options in eager graph execution. PiperOrigin-RevId: 235282812 --- tensorflow/core/common_runtime/eager/kernel_and_device.cc | 7 +++---- .../common_runtime/process_function_library_runtime.cc | 7 +++++++ tensorflow/core/framework/function.cc | 6 ++++++ tensorflow/core/framework/function.h | 6 ++++++ 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/tensorflow/core/common_runtime/eager/kernel_and_device.cc b/tensorflow/core/common_runtime/eager/kernel_and_device.cc index 8ea45d5dfc..8a06aa87e8 100644 --- a/tensorflow/core/common_runtime/eager/kernel_and_device.cc +++ b/tensorflow/core/common_runtime/eager/kernel_and_device.cc @@ -100,8 +100,7 @@ Status KernelAndDeviceFunc::Init(const NodeDef& ndef, // Android tf library does not include grappler. const auto& config_it = ndef.attr().find("config_proto"); if (it != ndef.attr().end()) { - ConfigProto config_proto; - if (!config_proto.ParseFromString(config_it->second.s())) { + if (!options.config_proto.ParseFromString(config_it->second.s())) { return errors::InvalidArgument( "Failed to parse config_proto attribute as tensorflow::ConfigProto " "proto."); @@ -122,8 +121,8 @@ Status KernelAndDeviceFunc::Init(const NodeDef& ndef, options.optimize_graph_fn = std::bind( grappler::OptimizeGraph, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, - config_proto, function_def->signature().name(), optimization_options, - std::placeholders::_6); + options.config_proto, function_def->signature().name(), + optimization_options, std::placeholders::_6); } #endif options.graph_collector = graph_collector; diff --git a/tensorflow/core/common_runtime/process_function_library_runtime.cc b/tensorflow/core/common_runtime/process_function_library_runtime.cc index 6db1fae77c..37f24f2e43 100644 --- a/tensorflow/core/common_runtime/process_function_library_runtime.cc +++ b/tensorflow/core/common_runtime/process_function_library_runtime.cc @@ -568,6 +568,7 @@ Status ProcessFunctionLibraryRuntime::InstantiateMultiDevice( // TODO(iga): Thread other relevant options from SessionOptions. SessionOptions session_options; session_options.env = flr->env(); + session_options.config = options.config_proto; optimization_options.session_options = &session_options; optimization_options.graph = &graph; optimization_options.flib_def = &data->overlay_lib_; @@ -578,6 +579,12 @@ Status ProcessFunctionLibraryRuntime::InstantiateMultiDevice( OptimizationPassRegistry::PRE_PLACEMENT, optimization_options)); DumpGraph("Before calling Placer", graph.get()); + // TODO(b/124993244): Smartly merge options in nested defuns, and raise + // exceptions/warnings in case where nested function call options are ignored. + // TODO(b/125933502): Currently config proto in function call options is not + // respected by placer, because placer and config proto has different default + // behaviors (allowing soft placement by default, vs. not allowing it). Pass + // config proto with appropriate default values to placer here. Placer placer(graph.get(), &device_set, nullptr, /* No session options */ flr->device() /* Default device */); TF_RETURN_IF_ERROR(placer.Run()); diff --git a/tensorflow/core/framework/function.cc b/tensorflow/core/framework/function.cc index 35d04eb727..bf00ed982a 100644 --- a/tensorflow/core/framework/function.cc +++ b/tensorflow/core/framework/function.cc @@ -917,6 +917,12 @@ string Canonicalize(const string& funcname, AttrSlice attrs, if (!executor_type.empty()) { entries.push_back(strings::StrCat(kExecutorAttr, "=", executor_type)); } + string config_proto_serialized; + options.config_proto.SerializeToString(&config_proto_serialized); + if (!config_proto_serialized.empty()) { + entries.push_back(strings::StrCat( + "_config_proto", "=", str_util::CEscape(config_proto_serialized))); + } std::sort(entries.begin(), entries.end()); return strings::StrCat(funcname, "[", str_util::Join(entries, ","), "]"); } diff --git a/tensorflow/core/framework/function.h b/tensorflow/core/framework/function.h index 4f0d595ed3..c27e077f2f 100644 --- a/tensorflow/core/framework/function.h +++ b/tensorflow/core/framework/function.h @@ -31,6 +31,7 @@ limitations under the License. #include "tensorflow/core/platform/macros.h" #include "tensorflow/core/platform/mutex.h" #include "tensorflow/core/platform/protobuf.h" +#include "tensorflow/core/protobuf/config.pb.h" namespace tensorflow { @@ -564,6 +565,11 @@ class FunctionLibraryRuntime { // surface errors earlier. bool create_kernels_eagerly = false; + // This interface is EXPERIMENTAL and subject to change. + // + // Instantiates the function with the provided config_proto. + ConfigProto config_proto; + // If provided, this optimization function will be invoked before // the placer for multi-device functions. std::function /*ret_node_names*/, -- GitLab From 8c1887bb270063042fe407c18bb7df4c0f3c80c9 Mon Sep 17 00:00:00 2001 From: Justin Lebar Date: Fri, 22 Feb 2019 16:56:06 -0800 Subject: [PATCH 395/766] [XLA] Add exhaustive tests for log1p and expm1. PiperOrigin-RevId: 235284387 --- .../compiler/xla/tests/exhaustive_op_test.cc | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tensorflow/compiler/xla/tests/exhaustive_op_test.cc b/tensorflow/compiler/xla/tests/exhaustive_op_test.cc index b409eee87d..66451a5633 100644 --- a/tensorflow/compiler/xla/tests/exhaustive_op_test.cc +++ b/tensorflow/compiler/xla/tests/exhaustive_op_test.cc @@ -486,6 +486,15 @@ XLA_TEST_P(ExhaustiveOpTest, Log) { Run(Log, std::log); } +XLA_TEST_P(ExhaustiveOpTest, Log1p) { + if (platform_ != "Host" && platform_ != "CUDA" && ty_ == F32) { + abs_err_ = 0.001; + rel_err_ = 0.001; + } + + Run(Log1p, std::log1p); +} + XLA_TEST_P(ExhaustiveOpTest, Exp) { if (platform_ == "Host" && ty_ == F32) { // TODO(b/73142289): The vectorized Exp implementation gives results outside @@ -504,6 +513,25 @@ XLA_TEST_P(ExhaustiveOpTest, Exp) { Run(Exp, std::exp); } +XLA_TEST_P(ExhaustiveOpTest, Expm1) { + // Expm1 has the same erroneous behavior on CPU as Exp. + if (platform_ == "Host" && ty_ == F32) { + // TODO(b/73142289): The vectorized Exp implementation gives results outside + // our error spec in this range. + known_incorrect_begin_ = 1107296256 + 11583654; + known_incorrect_end_ = 1107296256 + 11629080; + } else if (platform_ == "Host" && ty_ == BF16) { + // TODO(jlebar): Is this a rounding error? Why doesn't it occur on XLA:GPU? + // + // Mismatch on 88.5 (0x42b1). + // Expected 2.72491739e+38 (0x7f4d), but got inf (0x7f80). + known_incorrect_begin_ = 0x42b1; + known_incorrect_end_ = 0x42b2; + } + + Run(Expm1, std::expm1); +} + // It feels a little overkill to exhaustively test sqrt and pow(x, 0.5), but // this *did* find a bug, namely that some backends were assuming sqrt(x) == // pow(x, 0.5), but this is not true for x == -inf. -- GitLab From ced4557e76a896523f5df2ae628110ee84813b3f Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 22 Feb 2019 17:05:24 -0800 Subject: [PATCH 396/766] Upgrade gRPC version used in OSS Tensorflow PiperOrigin-RevId: 235285807 --- tensorflow/contrib/cmake/external/grpc.cmake | 2 +- tensorflow/workspace.bzl | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tensorflow/contrib/cmake/external/grpc.cmake b/tensorflow/contrib/cmake/external/grpc.cmake index 30b4e2dbde..594f3ca8e5 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 62688b6a05cc85b47fb77dd408611734253e47e2) +set(GRPC_TAG 5f00cfd3bd667852c23a9e2d52b1e8aed93f0d9e) if(WIN32) # We use unsecure gRPC because boringssl does not build on windows diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl index 091f1371cf..8a79467941 100755 --- a/tensorflow/workspace.bzl +++ b/tensorflow/workspace.bzl @@ -477,12 +477,12 @@ def tf_workspace(path_prefix = "", tf_repo_name = ""): # WARNING: make sure ncteisen@ and vpai@ are cc-ed on any CL to change the below rule tf_http_archive( name = "grpc", - sha256 = "e1e3a9edbfbe4230bee174d4aa45a15c1ec2b203cedb02d20df3e6345d8fa63e", - strip_prefix = "grpc-62688b6a05cc85b47fb77dd408611734253e47e2", + sha256 = "f4b8476bb3a531ee47810e4bc39cdc5d5ec1005424a32cbf02c9da7751e261b7", + strip_prefix = "grpc-5f00cfd3bd667852c23a9e2d52b1e8aed93f0d9e", system_build_file = clean_dep("//third_party/systemlibs:grpc.BUILD"), urls = [ - "https://mirror.bazel.build/github.com/grpc/grpc/archive/62688b6a05cc85b47fb77dd408611734253e47e2.tar.gz", - "https://github.com/grpc/grpc/archive/62688b6a05cc85b47fb77dd408611734253e47e2.tar.gz", + "https://mirror.bazel.build/github.com/grpc/grpc/archive/5f00cfd3bd667852c23a9e2d52b1e8aed93f0d9e.tar.gz", + "https://github.com/grpc/grpc/archive/5f00cfd3bd667852c23a9e2d52b1e8aed93f0d9e.tar.gz", ], ) -- GitLab From 3157d8110d7db6fe3ff7e1b627feccacd3eb3b33 Mon Sep 17 00:00:00 2001 From: Karim Nosir Date: Fri, 22 Feb 2019 18:15:42 -0800 Subject: [PATCH 397/766] Don't create stripped binary for debug builds PiperOrigin-RevId: 235294387 --- tensorflow/lite/build_def.bzl | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tensorflow/lite/build_def.bzl b/tensorflow/lite/build_def.bzl index 4fc3d2e238..1d16d61551 100644 --- a/tensorflow/lite/build_def.bzl +++ b/tensorflow/lite/build_def.bzl @@ -89,20 +89,21 @@ def tflite_jni_linkopts_unstripped(): def tflite_linkopts(): """Defines linker flags to reduce size of TFLite binary.""" return tflite_linkopts_unstripped() + select({ - "//tensorflow:android": [ - "-s", # Omit symbol table. + "//tensorflow:debug": [], + "//conditions:default": [ + "-s", # Omit symbol table, for all non debug builds ], - "//conditions:default": [], }) def tflite_jni_linkopts(): """Defines linker flags to reduce size of TFLite binary with JNI.""" - return tflite_jni_linkopts_unstripped() + select({ - "//tensorflow:android": [ - "-s", # Omit symbol table. - "-latomic", # Required for some uses of ISO C++11 in x86. + return tflite_jni_linkopts_unstripped() + [ + "-latomic", # Required for some uses of ISO C++11 in x86.] + ] + select({ + "//tensorflow:debug": [], + "//conditions:default": [ + "-s", # Omit symbol table, for all non debug builds ], - "//conditions:default": [], }) def tflite_jni_binary( -- GitLab From 8af58fde99e490f74de4a2b6633b7c59413a9d22 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 22 Feb 2019 18:16:35 -0800 Subject: [PATCH 398/766] Configuration for TPU embedding hot ID optimization. PiperOrigin-RevId: 235294450 --- .../tpu/optimization_parameters.proto | 41 ++++++++++++++++++- .../tpu/tpu_embedding_configuration.proto | 4 +- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/tensorflow/core/protobuf/tpu/optimization_parameters.proto b/tensorflow/core/protobuf/tpu/optimization_parameters.proto index 752f0dbc9c..7d3c105eec 100644 --- a/tensorflow/core/protobuf/tpu/optimization_parameters.proto +++ b/tensorflow/core/protobuf/tpu/optimization_parameters.proto @@ -175,7 +175,7 @@ message ProximalAdagradParameters { // them using the actual optimization algorithm). The extra message is to wrap // the enum for scoping. message GradientAccumulationStatus { - // Defaults to ENABLED. + // if UNSPECIFIED (default), gradient accumulation is ENABLED. enum Status { UNSPECIFIED = 0; ENABLED = 1; @@ -183,6 +183,41 @@ message GradientAccumulationStatus { } }; +// Configuration proto for hot ID optimization. This is an experimental feature +// that is currently disabled (by default). +message HotIdOptimizerConfiguration { + // Whether to enable or disable hot ID optimization. + // If UNSPECIFIED (default), hot ID optimization is DISABLED. + enum Status { + UNSPECIFIED = 0; + ENABLED = 1; + DISABLED = 2; + } + Status status = 1; + + // The following fields are never expected to be set by the TF model. However, + // a TF model could set them if it chooses to. If the fields are not set, + // meaningful default values will be chosen by the TPU software. + + // Frequency above which an embedding ID is classified as hot. The valid + // range for the frequency is [0.0, 1.0]. The frequency of an embedding ID is + // defined as the ratio of the number of lookups for that ID to the total + // number of lookups for the embedding table. + float frequency_threshold = 2; + + // The maximum number of hot IDs for the embedding table. If greater than + // max_id_count hot IDs exist for the table, the IDs with the highest + // frequencies are chosen. + int32 max_id_count = 3; + + // The maximum number of slots reserved in HBM (across the entire TPU system) + // for storing the replicas of hot IDs for the embedding table. In future, the + // number of replicas for a particular hot ID could be adjusted based on its + // frequency. The max_slot_count value captures the total number of replicas + // across all hot IDs for the table. + int32 max_slot_count = 4; +} + message OptimizationParameters { // Learning rate used for updating the embedding layer parameters. LearningRate learning_rate = 13; @@ -209,6 +244,10 @@ message OptimizationParameters { // apply them using the actual optimization algorithm). GradientAccumulationStatus.Status gradient_accumulation_status = 17; + // Configuration proto for hot ID optimization. This is an experimental + // feature that is currently disabled (by default). + HotIdOptimizerConfiguration hot_id_optimizer_configuration = 18; + // Optimization algorithm parameters; which field is selected determines which // algorithm to use. oneof parameters { diff --git a/tensorflow/core/protobuf/tpu/tpu_embedding_configuration.proto b/tensorflow/core/protobuf/tpu/tpu_embedding_configuration.proto index 53280edfa6..22be27795c 100644 --- a/tensorflow/core/protobuf/tpu/tpu_embedding_configuration.proto +++ b/tensorflow/core/protobuf/tpu/tpu_embedding_configuration.proto @@ -50,8 +50,8 @@ message TPUEmbeddingConfiguration { // contiguous manner. In this case, 13 ids are split across 5 hosts as: // [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10], [11, 12]]. // In both the strategies, if the id space does not evenly divide the number - // of hosts, each of the first "table_descriptor.num_ids % num_hosts" hosts - // will be assigned one more id. + // of hosts, each of the first "table_descriptor.vocabulary_size % num_hosts" + // hosts will be assigned one more id. // This partitioning strategy exactly follows that in the embedding_lookup // TensorFlow function at tensorflow/python/ops/embedding_ops.py. enum ShardingStrategy { -- GitLab From 16d954d8c4dc6cca905306bcfa323793afab7a0b Mon Sep 17 00:00:00 2001 From: Guangda Lai Date: Fri, 22 Feb 2019 18:25:31 -0800 Subject: [PATCH 399/766] Use TrtNodeValidator to determine whether an op type is supported, and register all node converters as validators as well. This also fix a bug where it says Floor is supported (since it's in the op list in IsTensorRTCandidate(), and validator_.ValidateNode() returns OK since no validator was registered for it), but actually it's not supported until TRT 5.1. PiperOrigin-RevId: 235295283 --- .../tf2tensorrt/convert/convert_graph.cc | 90 +----------- .../tf2tensorrt/convert/convert_nodes.cc | 137 ++++++++++-------- .../tf2tensorrt/convert/convert_nodes.h | 3 + .../tf2tensorrt/convert/convert_nodes_test.cc | 48 ++++-- 4 files changed, 119 insertions(+), 159 deletions(-) diff --git a/tensorflow/compiler/tf2tensorrt/convert/convert_graph.cc b/tensorflow/compiler/tf2tensorrt/convert/convert_graph.cc index 1f3cae3fda..3970e74d28 100644 --- a/tensorflow/compiler/tf2tensorrt/convert/convert_graph.cc +++ b/tensorflow/compiler/tf2tensorrt/convert/convert_graph.cc @@ -87,94 +87,6 @@ TrtCandidateSelector::TrtCandidateSelector( : graph_properties_(graph_properties), precision_mode_(precision_mode) {} Status TrtCandidateSelector::IsTensorRTCandidate(const tensorflow::Node* node) { - // TODO(laigd): move this set to TrtNodeValidator where it should belong. - // LINT.IfChange - static const auto* candidate_ops = new std::set{ - "Abs", - "Acos", - "Acosh", - "Add", - "Asin", - "Asinh", - "Atan", - "Atanh", - "AvgPool", - "BatchMatMul", - "BiasAdd", - "Ceil", - "ConcatV2", - "Const", - "Conv2D", - "Conv2DBackpropInput", - "Cos", - "Cosh", - "DepthwiseConv2dNative", - "Div", - "Exp", - "ExpandDims", - "Floor", - "FusedBatchNorm", - "FusedBatchNormV2", - "GatherV2", - "Identity", - "LeakyRelu", - "Log", - "MatMul", - "Max", - "Maximum", - "MaxPool", - "Mean", - "Min", - "Minimum", - "Mul", - "Neg", - "Pad", - "Prod", - "RealDiv", - "Reciprocal", - "Relu", - "Relu6", - "Reshape", - "Rsqrt", - "Sigmoid", - "Sin", - "Sinh", - "Slice", - "Snapshot", - "Softmax", - "Sqrt", - "Square", - "Squeeze", - "StridedSlice", - "Sub", - "Sum", - "Tan", - "Tanh", - "TopKV2", - "Transpose", - }; - bool is_supported_op_type = - (candidate_ops->count(node->type_string()) || - PluginFactoryTensorRT::GetInstance()->IsPlugin(node->type_string())); - static const auto* quantize_ops = new std::set{ - "QuantizeAndDequantizeV2", - "QuantizeAndDequantizeV3", - "FakeQuantWithMinMaxVars", - "FakeQuantWithMinMaxArgs", - }; - // In INT8 mode, we will always apply the quantization ranges provided by - // these ops to the relevant tensors. This happens regardless of the value of - // use_calibration. - if (precision_mode_ == TrtPrecisionMode::INT8 && - quantize_ops->count(node->type_string())) { - is_supported_op_type = true; - } - // LINT.ThenChange(//tensorflow/compiler/tf2tensorrt/convert/convert_nodes.cc) - if (!is_supported_op_type) { - return errors::Unimplemented("Op type ", node->type_string(), - " is not supported"); - } - std::vector input_edges; TF_RETURN_IF_ERROR(node->input_edges(&input_edges)); std::vector> input_node_and_ports; @@ -184,7 +96,7 @@ Status TrtCandidateSelector::IsTensorRTCandidate(const tensorflow::Node* node) { input_edge->src_output()); } return validator_.ValidateNode(node->def(), input_node_and_ports, - graph_properties_); + precision_mode_, graph_properties_); } namespace { diff --git a/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.cc b/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.cc index dd28062d3c..03064cbe4c 100644 --- a/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.cc +++ b/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.cc @@ -854,6 +854,13 @@ TRT_ShapedWeights TrtWeightStore::GetTempWeights(tensorflow::DataType type, return weights; } +const std::set* TrtNodeValidator::quantize_ops = new std::set{ + "QuantizeAndDequantizeV2", + "QuantizeAndDequantizeV3", + "FakeQuantWithMinMaxVars", + "FakeQuantWithMinMaxArgs", +}; + TrtNodeValidator::TrtNodeValidator() { RegisterOpValidators(); } Status TrtNodeValidator::ConvertToTensorOrWeights( @@ -899,9 +906,27 @@ Status TrtNodeValidator::ConvertToTensorOrWeights( } Status TrtNodeValidator::ValidateNode( - const tensorflow::NodeDef& node_def, + const NodeDef& node_def, const std::vector>& input_node_and_ports, + const TrtPrecisionMode precision_mode, const grappler::GraphProperties& graph_properties) { + const string& op = node_def.op(); + // It doesn't support validation of plugins. + if (PluginFactoryTensorRT::GetInstance()->IsPlugin(op)) return Status::OK(); + + // In INT8 mode, we will always apply the quantization ranges provided by + // these ops to the relevant tensors. This happens regardless of the value of + // use_calibration. + bool is_supported_op = false; + if (quantize_ops->count(op)) { + is_supported_op = (precision_mode == TrtPrecisionMode::INT8); + } else { + is_supported_op = op_validators_.count(node_def.op()); + } + if (!is_supported_op) { + return errors::Unimplemented("Op type ", op, " is not supported."); + } + // Convert input NodeDef and corresponding output ports to // TRT_TensorOrWeights. std::vector inputs; @@ -918,14 +943,7 @@ Status TrtNodeValidator::ValidateNode( inputs.push_back(tensor_or_weights); } - // Validate the node. - const auto iter = op_validators_.find(node_def.op()); - if (iter == op_validators_.end()) { - // If validator is not registered, it means no validation is needed. - return Status::OK(); - } - - OpConverter validator = iter->second; + OpConverter validator = op_validators_[node_def.op()]; OpConverterParams params( /*arg_converter=*/nullptr, node_def, inputs, /*arg_outputs=*/nullptr, /*arg_validation_only=*/true, &weight_store_); @@ -964,7 +982,7 @@ Status Converter::ConvertNode(const NodeDef& node_def) { TF_RETURN_IF_ERROR(plugin_converter_(¶ms)); } else { if (!op_registry_.count(op)) { - return errors::Unimplemented("No converter registered for op: " + op); + return errors::Unimplemented("No converter registered for op: ", op); } OpConverter op_converter = op_registry_.at(op); TF_RETURN_IF_ERROR(op_converter(¶ms)); @@ -3050,8 +3068,9 @@ tensorflow::Status ConvertIdentity(OpConverterParams* params) { // TODO(tmorris): TRT's Identity layer does not get optimized away as of TRT // 5.0, however once we know that it does it would be nice to use that // instead. + if (params->validation_only) return Status::OK(); params->outputs->push_back(params->inputs.at(0)); - return tensorflow::Status::OK(); + return Status::OK(); } Status ConvertBinary(OpConverterParams* params) { @@ -3237,7 +3256,7 @@ tensorflow::Status ConvertSquare(OpConverterParams* params) { return tensorflow::Status::OK(); } -tensorflow::Status ConvertReduce(OpConverterParams* params) { +Status ConvertReduce(OpConverterParams* params) { const auto& inputs = params->inputs; const auto& node_def = params->node_def; TF_RETURN_IF_ERROR( @@ -3247,16 +3266,14 @@ tensorflow::Status ConvertReduce(OpConverterParams* params) { TRT_ShapedWeights index_list = inputs.at(1).weights(); TFAttrs attrs(node_def); - auto index_type = attrs.get("Tidx"); - // Only expect to handle INT32 as attributes for now - if (index_type != tensorflow::DataType::DT_INT32) { - return tensorflow::errors::Unimplemented("Tidx supports only DT_INT32"); + if (attrs.get("Tidx") != DataType::DT_INT32) { + return errors::Unimplemented("Tidx supports only DT_INT32"); } int axes = 0; if (index_list.count() == 0) { - return tensorflow::errors::InvalidArgument( + return errors::InvalidArgument( "TRT cannot support reduce on all (batch) dimensions, at", node_def.name()); } else { @@ -3266,7 +3283,7 @@ tensorflow::Status ConvertReduce(OpConverterParams* params) { int axis = index_list_data[i]; if (axis < 0) axis += tensor->getDimensions().nbDims + 1; if (axis == 0) { - return tensorflow::errors::InvalidArgument( + return errors::InvalidArgument( "TRT cannot reduce at batch dimension, at", node_def.name()); } axes |= (1 << (axis - 1)); @@ -3285,9 +3302,10 @@ tensorflow::Status ConvertReduce(OpConverterParams* params) { } else if (node_def.op() == "Mean") { reduce_operation = nvinfer1::ReduceOperation::kAVG; } else { - return tensorflow::errors::Unimplemented("Op not supported ", node_def.op(), - " , at ", node_def.name()); + return errors::Unimplemented("Op not supported ", node_def.op(), ", at ", + node_def.name()); } + if (params->validation_only) return Status::OK(); const auto keep_dims = attrs.get("keep_dims"); nvinfer1::ILayer* layer = params->converter->network()->addReduce( @@ -3296,7 +3314,7 @@ tensorflow::Status ConvertReduce(OpConverterParams* params) { TFTRT_RETURN_ERROR_IF_NULLPTR(layer, node_def.name()); params->outputs->push_back(TRT_TensorOrWeights(layer->getOutput(0))); - return tensorflow::Status::OK(); + return Status::OK(); } tensorflow::Status ConvertPad(OpConverterParams* params) { @@ -3711,49 +3729,48 @@ tensorflow::Status ConvertMatMul(OpConverterParams* params) { transpose_b, node_def.name()); } -tensorflow::Status ConvertBatchMatMul(OpConverterParams* params) { +Status ConvertBatchMatMul(OpConverterParams* params) { const auto& inputs = params->inputs; const auto& node_def = params->node_def; // TODO(tmorris): Enable once false is updated to mean either tensor or weight // TF_RETURN_IF_ERROR(CheckInputsWeights(*params, {{"x", false}, {"y", // false}})); if (inputs.size() != 2) { - return tensorflow::errors::InvalidArgument( - node_def.op(), " got ", inputs.size(), " inputs but expected 2, at ", - node_def.name()); + return errors::InvalidArgument(node_def.op(), " got ", inputs.size(), + " inputs but expected 2, at ", + node_def.name()); } TFAttrs attrs(node_def); - tensorflow::DataType tf_dtype = attrs.get("T"); - if (tf_dtype != tensorflow::DataType::DT_FLOAT && - tf_dtype != tensorflow::DataType::DT_HALF) { - return tensorflow::errors::Unimplemented( - "data type is not supported, for node " + node_def.name() + " got " + - tensorflow::DataTypeString(tf_dtype)); + const DataType tf_dtype = attrs.get("T"); + if (tf_dtype != DataType::DT_FLOAT && tf_dtype != DataType::DT_HALF) { + return errors::Unimplemented("data type is not supported, for node ", + node_def.name(), + " got " + DataTypeString(tf_dtype)); } - bool transpose_a = attrs.get("adj_x"); - bool transpose_b = attrs.get("adj_y"); - - auto dims = inputs.at(0).GetTrtDims(); + const bool transpose_a = attrs.get("adj_x"); + const bool transpose_b = attrs.get("adj_y"); + const auto dims = inputs.at(0).GetTrtDims(); if (dims.nbDims == 1) { // NC * CK is only supported through fully connected if (transpose_a == false && inputs.at(0).is_tensor() && inputs.at(1).is_weights()) { return ConvertMatMulHelper(params, inputs.at(0), inputs.at(1).weights(), transpose_b, node_def.name()); } else { - return tensorflow::errors::InvalidArgument( - "Invalid configuration for MatMul, at: " + node_def.name()); + return errors::InvalidArgument("Invalid configuration for MatMul, at: ", + node_def.name()); } } + if (params->validation_only) return Status::OK(); - const nvinfer1::ITensor* tensor_l; - const nvinfer1::ITensor* tensor_r; + // TODO(laigd): avoid duplicating code, and add tests. + // TODO(laigd): should we add the following chesks to validator as well? auto dims_l = inputs.at(0).GetTrtDims(); auto dims_r = inputs.at(1).GetTrtDims(); if (inputs.at(0).is_weights()) { if (inputs.at(0).GetTrtDims().d[0] != 1) { - return tensorflow::errors::InvalidArgument( + return errors::InvalidArgument( "Input 0 as weight assumes broadcast across batch for MatMul, at: " + node_def.name()); } else { @@ -3765,7 +3782,7 @@ tensorflow::Status ConvertBatchMatMul(OpConverterParams* params) { } if (inputs.at(1).is_weights()) { if (inputs.at(1).GetTrtDims().d[0] != 1) { - return tensorflow::errors::InvalidArgument( + return errors::InvalidArgument( "Input 1 as weight assumes broadcast across batch for MatMul, at: " + node_def.name()); } else { @@ -3775,6 +3792,9 @@ tensorflow::Status ConvertBatchMatMul(OpConverterParams* params) { dims_r.nbDims--; } } + + const nvinfer1::ITensor* tensor_l; + const nvinfer1::ITensor* tensor_r; TF_RETURN_IF_ERROR(params->converter->PrepareTensorForShape( inputs.at(0), dims_l, &tensor_l)); TF_RETURN_IF_ERROR(params->converter->PrepareTensorForShape( @@ -3787,10 +3807,10 @@ tensorflow::Status ConvertBatchMatMul(OpConverterParams* params) { TFTRT_RETURN_ERROR_IF_NULLPTR(layer, node_def.name()); nvinfer1::ITensor* output_tensor = layer->getOutput(0); params->outputs->push_back(TRT_TensorOrWeights(output_tensor)); - return tensorflow::Status::OK(); + return Status::OK(); } -tensorflow::Status ConvertSoftmax(OpConverterParams* params) { +Status ConvertSoftmax(OpConverterParams* params) { const auto& inputs = params->inputs; const auto& node_def = params->node_def; TF_RETURN_IF_ERROR(CheckInputsWeights(*params, {{"logits", false}})); @@ -3798,8 +3818,8 @@ tensorflow::Status ConvertSoftmax(OpConverterParams* params) { int nbDims = tensor->getDimensions().nbDims; if (nbDims == 0) { - return tensorflow::errors::InvalidArgument( - "TensorRT Softmax cannot apply on batch dimension, at" + + return errors::InvalidArgument( + "TensorRT Softmax cannot apply on batch dimension, at", node_def.name()); } if (params->validation_only) return Status::OK(); @@ -3814,7 +3834,7 @@ tensorflow::Status ConvertSoftmax(OpConverterParams* params) { // Quantization range for SoftMax is always (0, 1) params->converter->ProvideQuantizationRange(output_tensor, 0.0f, 1.0f); params->outputs->push_back(TRT_TensorOrWeights(output_tensor)); - return tensorflow::Status::OK(); + return Status::OK(); } tensorflow::Status ConvertTopK(OpConverterParams* params) { @@ -3860,7 +3880,6 @@ tensorflow::Status ConvertTopK(OpConverterParams* params) { static void RegisterValidatableOpConverters( std::unordered_map* registration) { - // TODO(laigd): support all op types. (*registration)["BiasAdd"] = ConvertBiasAdd; (*registration)["ConcatV2"] = ConvertConcat; (*registration)["Const"] = ConvertConst; @@ -3882,6 +3901,18 @@ static void RegisterValidatableOpConverters( (*registration)["Transpose"] = ConvertTranspose; (*registration)["TopKV2"] = ConvertTopK; + // TODO(ben,jie): this is a temp hack. + (*registration)["Identity"] = ConvertIdentity; // Identity should be removed + (*registration)["Snapshot"] = ConvertIdentity; // Snapshot should be removed + + (*registration)["Sum"] = ConvertReduce; + (*registration)["Prod"] = ConvertReduce; + (*registration)["Max"] = ConvertReduce; + (*registration)["Min"] = ConvertReduce; + (*registration)["Mean"] = ConvertReduce; + (*registration)["Softmax"] = ConvertSoftmax; + (*registration)["BatchMatMul"] = ConvertBatchMatMul; + for (auto quantization_op_type : {"QuantizeAndDequantizeV2", "QuantizeAndDequantizeV3", "FakeQuantWithMinMaxVars", "FakeQuantWithMinMaxArgs"}) { @@ -3911,18 +3942,6 @@ void TrtNodeValidator::RegisterOpValidators() { void Converter::RegisterOpConverters() { RegisterValidatableOpConverters(&op_registry_); - // TODO(ben,jie): this is a temp hack. - op_registry_["Identity"] = ConvertIdentity; // Identity should be removed - op_registry_["Snapshot"] = ConvertIdentity; // Snapshot should be removed - - op_registry_["Sum"] = ConvertReduce; - op_registry_["Prod"] = ConvertReduce; - op_registry_["Max"] = ConvertReduce; - op_registry_["Min"] = ConvertReduce; - op_registry_["Mean"] = ConvertReduce; - op_registry_["Softmax"] = ConvertSoftmax; - op_registry_["BatchMatMul"] = ConvertBatchMatMul; - plugin_converter_ = ConvertPlugin; } diff --git a/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.h b/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.h index 7b37173090..4babdf93b6 100644 --- a/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.h +++ b/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.h @@ -379,9 +379,12 @@ class TrtNodeValidator { Status ValidateNode( const NodeDef& node_def, const std::vector>& input_node_and_ports, + const TrtPrecisionMode precision_mode, const grappler::GraphProperties& graph_properties); private: + static const std::set* quantize_ops; + void RegisterOpValidators(); // Convert a Const node to a TRT_TensorOrWeights. diff --git a/tensorflow/compiler/tf2tensorrt/convert/convert_nodes_test.cc b/tensorflow/compiler/tf2tensorrt/convert/convert_nodes_test.cc index 45afc76d75..d09a5cf6a1 100644 --- a/tensorflow/compiler/tf2tensorrt/convert/convert_nodes_test.cc +++ b/tensorflow/compiler/tf2tensorrt/convert/convert_nodes_test.cc @@ -389,8 +389,8 @@ TEST(TRT_TensorOrWeights_Test, Basic) { class ValidatorTest : public ::testing::Test { public: - void AddOpValidator(const string& op_name, OpConverter op_validator) { - validator_.op_validators_[op_name] = op_validator; + std::unordered_map& op_validators() { + return validator_.op_validators_; } Status ConvertToTensorOrWeights( @@ -401,10 +401,18 @@ class ValidatorTest : public ::testing::Test { node_def, output_port, graph_properties, tensor_or_weights); } + const std::set* GetQuantizeOps() { return validator_.quantize_ops; } + protected: TrtNodeValidator validator_; }; +TEST_F(ValidatorTest, QuantizeOpsAreRegistered) { + for (const string& quantize_op : *GetQuantizeOps()) { + QCHECK(op_validators().count(quantize_op)); + } +} + TEST_F(ValidatorTest, ConvertToTensorOrWeights) { // Convert Const. { @@ -477,18 +485,30 @@ TEST_F(ValidatorTest, ValidateNode) { }; NodeDef node_def = MakeNodeDef("my_op", "MyOp", {}); - // Validator not registered, validation should pass. - TF_EXPECT_OK(validator_.ValidateNode(node_def, {}, graph_properties)); + // Validator not registered. + ExpectStatus(validator_.ValidateNode(node_def, {}, TrtPrecisionMode::FP32, + graph_properties), + error::UNIMPLEMENTED, "Op type MyOp is not supported."); // Register validator. - AddOpValidator("MyOp", op_converter); - TF_EXPECT_OK(validator_.ValidateNode(node_def, {}, graph_properties)); + op_validators()["MyOp"] = op_converter; + TF_EXPECT_OK(validator_.ValidateNode(node_def, {}, TrtPrecisionMode::FP32, + graph_properties)); EXPECT_EQ(false, start_conversion); // Let the converter return error. should_fail = true; - ExpectStatus(validator_.ValidateNode(node_def, {}, graph_properties), + ExpectStatus(validator_.ValidateNode(node_def, {}, TrtPrecisionMode::FP32, + graph_properties), error::INVALID_ARGUMENT); + + // Test quantization ops, they're only supported in INT8 mode. The success + // case is tested in OpConverterTest.ConvertQuantize. + node_def = MakeNodeDef("my_op", "FakeQuantWithMinMaxArgs", {}); + ExpectStatus(validator_.ValidateNode(node_def, {}, TrtPrecisionMode::FP32, + graph_properties), + error::UNIMPLEMENTED, + "Op type FakeQuantWithMinMaxArgs is not supported."); } class ConverterTest : public ::testing::Test { @@ -1049,7 +1069,7 @@ class OpConverterTest : public ::testing::Test { // Reset the validator and converter. validator_.reset(new TrtNodeValidator); - converter_.reset(new Converter(network_.get(), TrtPrecisionMode::FP32, + converter_.reset(new Converter(network_.get(), precision_mode_to_test_, /*use_calibration=*/false)); // Reset other related artifacts. @@ -1182,9 +1202,10 @@ class OpConverterTest : public ::testing::Test { grappler::GraphProperties graph_properties(item); TF_EXPECT_OK(graph_properties.InferStatically(true)); - ExpectStatus(validator_->ValidateNode(node_def, input_node_and_ports, - graph_properties), - expected_code, expected_msg_substr); + ExpectStatus( + validator_->ValidateNode(node_def, input_node_and_ports, + precision_mode_to_test_, graph_properties), + expected_code, expected_msg_substr); } void RunConversion(const NodeDef& node_def, @@ -1214,6 +1235,10 @@ class OpConverterTest : public ::testing::Test { std::unique_ptr converter_; std::unique_ptr validator_; + protected: + // TODO(laigd): parameterize the test and make the precision mode a parameter. + TrtPrecisionMode precision_mode_to_test_ = TrtPrecisionMode::FP32; + private: Logger logger_; TrtUniquePtrType builder_; @@ -2006,6 +2031,7 @@ TEST_F(OpConverterTest, ConvertBinary) { } TEST_F(OpConverterTest, ConvertQuantize) { + precision_mode_to_test_ = TrtPrecisionMode::INT8; const std::pair op_with_num_inputs[4] = { {"FakeQuantWithMinMaxArgs", 1}, {"FakeQuantWithMinMaxVars", 3}, -- GitLab From d0494a8920186a759cb1135c3d4c7bd59989ed3f Mon Sep 17 00:00:00 2001 From: Smit Hinsu Date: Fri, 22 Feb 2019 20:26:32 -0800 Subject: [PATCH 400/766] Remove redundant namespace specifications from unqualified names in tf2tensorrt No functional changes. PiperOrigin-RevId: 235303926 --- .../tf2tensorrt/convert/convert_graph.cc | 205 +++++++++--------- .../tf2tensorrt/convert/convert_graph.h | 24 +- .../tf2tensorrt/convert/convert_nodes.h | 44 ++-- .../tf2tensorrt/convert/convert_nodes_test.cc | 20 +- .../convert/trt_optimization_pass.cc | 58 +++-- .../convert/trt_optimization_pass.h | 19 +- .../kernels/get_serialized_resource_op.cc | 2 +- .../tf2tensorrt/kernels/trt_engine_op.cc | 90 ++++---- .../tf2tensorrt/plugin/trt_plugin_factory.cc | 8 +- .../tf2tensorrt/plugin/trt_plugin_factory.h | 7 +- .../compiler/tf2tensorrt/segment/segment.cc | 78 +++---- .../compiler/tf2tensorrt/segment/segment.h | 14 +- .../tf2tensorrt/segment/segment_test.cc | 26 +-- .../compiler/tf2tensorrt/utils/test_utils.h | 11 +- .../tf2tensorrt/utils/trt_allocator.cc | 2 +- .../tf2tensorrt/utils/trt_allocator.h | 4 +- .../tf2tensorrt/utils/trt_int8_calibrator.cc | 8 +- .../tf2tensorrt/utils/trt_int8_calibrator.h | 4 +- .../tf2tensorrt/utils/trt_lru_cache.h | 16 +- .../tf2tensorrt/utils/trt_resources.cc | 2 +- .../tf2tensorrt/utils/trt_resources.h | 4 +- 21 files changed, 306 insertions(+), 340 deletions(-) diff --git a/tensorflow/compiler/tf2tensorrt/convert/convert_graph.cc b/tensorflow/compiler/tf2tensorrt/convert/convert_graph.cc index 3970e74d28..9ab21d6a35 100644 --- a/tensorflow/compiler/tf2tensorrt/convert/convert_graph.cc +++ b/tensorflow/compiler/tf2tensorrt/convert/convert_graph.cc @@ -86,7 +86,7 @@ TrtCandidateSelector::TrtCandidateSelector( TrtPrecisionMode precision_mode) : graph_properties_(graph_properties), precision_mode_(precision_mode) {} -Status TrtCandidateSelector::IsTensorRTCandidate(const tensorflow::Node* node) { +Status TrtCandidateSelector::IsTensorRTCandidate(const Node* node) { std::vector input_edges; TF_RETURN_IF_ERROR(node->input_edges(&input_edges)); std::vector> input_node_and_ports; @@ -101,29 +101,27 @@ Status TrtCandidateSelector::IsTensorRTCandidate(const tensorflow::Node* node) { namespace { -tensorflow::Status BuildNodeMap( - const tensorflow::Graph& graph, - std::unordered_map* node_map) { +Status BuildNodeMap(const Graph& graph, + std::unordered_map* node_map) { for (auto* node : graph.op_nodes()) { if (!node_map->insert({node->name(), node}).second) { - return tensorflow::errors::AlreadyExists( - "Node name is not unique in graph: " + node->name()); + return errors::AlreadyExists("Node name is not unique in graph: " + + node->name()); } } - return tensorflow::Status::OK(); + return Status::OK(); } } // namespace -tensorflow::Status ConvertGraphDefToTensorRT( - const tensorflow::GraphDef& graph_def, - const std::vector& output_names, size_t max_batch_size, - size_t max_workspace_size_bytes, tensorflow::GraphDef* new_graph_def, - TrtPrecisionMode precision_mode, int minimum_segment_size, bool is_dyn_op, - int max_cached_engines, std::vector cached_engine_batches, - bool use_calibration) { +Status ConvertGraphDefToTensorRT( + const GraphDef& graph_def, const std::vector& output_names, + size_t max_batch_size, size_t max_workspace_size_bytes, + GraphDef* new_graph_def, TrtPrecisionMode precision_mode, + int minimum_segment_size, bool is_dyn_op, int max_cached_engines, + std::vector cached_engine_batches, bool use_calibration) { // Create GrapplerItem. - tensorflow::grappler::GrapplerItem item; + grappler::GrapplerItem item; item.fetch = output_names; item.graph = graph_def; @@ -137,13 +135,13 @@ tensorflow::Status ConvertGraphDefToTensorRT( // Create single machine cluster. Note that this will create a session and // initialize the gpu devices. const int num_cpu_cores = - tensorflow::grappler::GetNumAvailableLogicalCPUCores(); - const int num_gpus = tensorflow::grappler::GetNumAvailableGPUs(); + grappler::GetNumAvailableLogicalCPUCores(); + const int num_gpus = grappler::GetNumAvailableGPUs(); VLOG(2) << "cpu_cores: " << num_cpu_cores; VLOG(2) << "gpus: " << num_gpus; const int timeout_s = 60 * 10; - std::unique_ptr cluster( - new tensorflow::grappler::SingleMachine( + std::unique_ptr cluster( + new grappler::SingleMachine( timeout_s, num_cpu_cores, num_gpus)); // These settings are the defaults in tensorflow/python/grappler/cluster.py. cluster->DisableDetailedStats(true); @@ -154,18 +152,17 @@ tensorflow::Status ConvertGraphDefToTensorRT( // Create virtual cluster. Grappler requires a virtual cluster with a proper // GPU device in order to calculate flops>0 or fails with FATAL in dbg mode. // We add numbers from a Pascal card here to have flops>0. - tensorflow::DeviceProperties device_properties; + DeviceProperties device_properties; device_properties.set_type("GPU"); device_properties.mutable_environment()->insert({"architecture", "6"}); device_properties.set_num_cores(3584); device_properties.set_frequency(1531); - std::unique_ptr cluster( - new tensorflow::grappler::VirtualCluster( - {{"/GPU:0", device_properties}})); + std::unique_ptr cluster( + new grappler::VirtualCluster({{"/GPU:0", device_properties}})); #endif // Create RewriterConfig. - tensorflow::ConfigProto config_proto; + ConfigProto config_proto; auto& rw_cfg = *config_proto.mutable_graph_options()->mutable_rewrite_options(); // TODO(aaroey): use only const folding and layout for the time being since @@ -191,7 +188,7 @@ tensorflow::Status ConvertGraphDefToTensorRT( parameters["use_calibration"].set_b(use_calibration); // Run optimizer. - tensorflow::grappler::MetaOptimizer meta_opt(nullptr, config_proto); + grappler::MetaOptimizer meta_opt(nullptr, config_proto); TF_RETURN_IF_ERROR(meta_opt.Optimize(cluster.get(), item, new_graph_def)); if (VLOG_IS_ON(5)) { @@ -205,20 +202,18 @@ tensorflow::Status ConvertGraphDefToTensorRT( } struct EdgePtrCompare { - bool operator()(const tensorflow::Edge* lhs, - const tensorflow::Edge* rhs) const { + bool operator()(const Edge* lhs, const Edge* rhs) const { return lhs->id() < rhs->id(); } }; // Function to get subsegment information structure. -tensorflow::Status GetEngineInfo( - const tensorflow::Graph* g, - const tensorflow::grappler::GraphProperties& graph_properties, - const std::set& segment_nodes, - const std::unordered_map& node_map, - const std::vector& reverse_topo_order, - EngineInfo* info) { +Status GetEngineInfo(const Graph* g, + const grappler::GraphProperties& graph_properties, + const std::set& segment_nodes, + const std::unordered_map& node_map, + const std::vector& reverse_topo_order, + EngineInfo* info) { std::vector subgraph_nodes; // Topologically sorted nodes. std::set added_const_nodes; // Used to prevent double insertion. std::set segment_devices; @@ -265,8 +260,8 @@ tensorflow::Status GetEngineInfo( // Create input connections. Sort edges first to make determnistic since // in_edges is a set of pointers. - std::vector in_edges(node->in_edges().begin(), - node->in_edges().end()); + std::vector in_edges(node->in_edges().begin(), + node->in_edges().end()); std::sort(in_edges.begin(), in_edges.end(), EdgePtrCompare()); for (const auto edge : in_edges) { auto input_node = edge->src(); @@ -317,8 +312,8 @@ tensorflow::Status GetEngineInfo( } // Create output connections. Sort edges first to make determnistic since // out_edges is a set of pointers. - std::vector out_edges(node->out_edges().begin(), - node->out_edges().end()); + std::vector out_edges(node->out_edges().begin(), + node->out_edges().end()); std::sort(out_edges.begin(), out_edges.end(), EdgePtrCompare()); for (const auto edge : out_edges) { auto output_node = edge->dst(); @@ -377,7 +372,7 @@ void UpdateToEngineNode(const std::vector& infos, const size_t my_engine_id, const std::vector& engine_nodes, const bool is_input_edge, const string& node_name, - tensorflow::Node** node, int* port) { + Node** node, int* port) { for (size_t t = 0; t < infos.size(); ++t) { if (t == my_engine_id) { continue; @@ -414,20 +409,20 @@ void UpdateToEngineNode(const std::vector& infos, // one). Connect to the pre-existing engine node instead. // 3. In this way, we ensure the graph is topologically sort-able after each // invocation of CreateTRTNode(). -tensorflow::Status CreateTRTNode(const std::vector& infos, int pos, - int max_batch_size, tensorflow::Graph* graph, - nvinfer1::IGpuAllocator* alloc, - std::vector* engine_nodes) { +Status CreateTRTNode(const std::vector& infos, int pos, + int max_batch_size, Graph* graph, + nvinfer1::IGpuAllocator* alloc, + std::vector* engine_nodes) { const auto& info = infos.at(pos); TRT_RETURN_IF_TEST_VALUE(StrCat(info.engine_name, ":CreateTRTNode"), "fail"); - std::vector output_shape_protos; - std::vector input_shape_protos; - std::vector input_shapes; - std::vector inputs; - std::vector input_nodes; - std::vector control_input_nodes; + std::vector output_shape_protos; + std::vector input_shape_protos; + std::vector input_shapes; + std::vector inputs; + std::vector input_nodes; + std::vector control_input_nodes; std::unordered_set control_input_names; - std::vector out_types; + std::vector out_types; VLOG(1) << "Processing " << info.engine_name; // Collect needed info for creating the engine node in the graph @@ -439,8 +434,8 @@ tensorflow::Status CreateTRTNode(const std::vector& infos, int pos, if (!conn.is_input_edge) continue; // Rewrire control input if it's not found in original graph. - tensorflow::Node* input_node = graph->FindNodeId(conn.outside_id); - int port = tensorflow::Graph::kControlSlot; + Node* input_node = graph->FindNodeId(conn.outside_id); + int port = Graph::kControlSlot; if (!input_node) { UpdateToEngineNode(infos, pos, *engine_nodes, /*is_input_edge=*/true, conn.outside_node_name, &input_node, &port); @@ -456,7 +451,7 @@ tensorflow::Status CreateTRTNode(const std::vector& infos, int pos, // Data edges if (!conn.is_input_edge) { // Set the shapes and data types of output edge. - tensorflow::TensorShapeProto out_shape; + TensorShapeProto out_shape; // shape of the output node inside segment conn.inside_shape.AsProto(&out_shape); if (output_shape_protos.size() <= conn.port_number) { @@ -467,7 +462,7 @@ tensorflow::Status CreateTRTNode(const std::vector& infos, int pos, out_types.at(conn.port_number) = conn.connection_type; } else { // Set the shapes and data types of input edge. - tensorflow::TensorShapeProto in_shape; + TensorShapeProto in_shape; conn.outside_shape.AsProto(&in_shape); if (input_shape_protos.size() <= conn.port_number) { input_shape_protos.resize(conn.port_number + 1); @@ -480,7 +475,7 @@ tensorflow::Status CreateTRTNode(const std::vector& infos, int pos, if (info.engine_type == EngineInfo::EngineType::TRTStatic) { for (int i = 1; i < conn.outside_shape.dims(); i++) { if (conn.outside_shape.dim_size(i) <= 0) { - return tensorflow::errors::Internal( + return errors::Internal( "Input shapes must be fully defined when in static mode. " "Please try is_dynamic_op=True (shape was ", conn.outside_shape.DebugString(), ")"); @@ -489,7 +484,7 @@ tensorflow::Status CreateTRTNode(const std::vector& infos, int pos, } // Rewrire data input if it's not found in original graph. - tensorflow::Node* input_node = graph->FindNodeId(conn.outside_id); + Node* input_node = graph->FindNodeId(conn.outside_id); int port = conn.outside_port; if (!input_node) { UpdateToEngineNode(infos, pos, *engine_nodes, /*is_input_edge=*/true, @@ -512,7 +507,7 @@ tensorflow::Status CreateTRTNode(const std::vector& infos, int pos, // avoid crash later. Constant folding should've folded the ops that make up // these segments. if (inputs.empty()) { - return tensorflow::errors::Internal( + return errors::Internal( "Segment has no inputs (possible " "constfold failure)"); } @@ -550,7 +545,7 @@ tensorflow::Status CreateTRTNode(const std::vector& infos, int pos, string prec_string; TF_RETURN_IF_ERROR(TrtPrecisionModeToName(info.precision_mode, &prec_string)); - tensorflow::NodeDefBuilder node_builder(info.engine_name, "TRTEngineOp"); + NodeDefBuilder node_builder(info.engine_name, "TRTEngineOp"); if (!info.device.empty()) node_builder.Device(info.device); if (VLOG_IS_ON(1)) { string ins = StrCat(info.engine_name, " inputs= "); @@ -568,8 +563,8 @@ tensorflow::Status CreateTRTNode(const std::vector& infos, int pos, !info.cached_engine_batches.empty()) { LOG(WARNING) << "Cached engine batches are ignored for static engines"; } - tensorflow::NodeDef trt_node; - tensorflow::Status status = + NodeDef trt_node; + Status status = node_builder.Attr("input_shapes", input_shape_protos) .Attr("output_shapes", output_shape_protos) .Attr("static_engine", @@ -594,7 +589,7 @@ tensorflow::Status CreateTRTNode(const std::vector& infos, int pos, // here, this segment will be skipped // TODO(aaroey): let it return proper error status for the following logic // instead of checking fail. - tensorflow::Node* engine_node = graph->AddNode(trt_node, &status); + Node* engine_node = graph->AddNode(trt_node, &status); (*engine_nodes)[pos] = engine_node; if (!status.ok()) { LOG(ERROR) << "Adding node failed " << status; @@ -621,7 +616,7 @@ tensorflow::Status CreateTRTNode(const std::vector& infos, int pos, if (conn.is_input_edge) { continue; } - tensorflow::Node* output_node = graph->FindNodeId(conn.outside_id); + Node* output_node = graph->FindNodeId(conn.outside_id); int port = conn.outside_port; if (!output_node) { UpdateToEngineNode(infos, pos, *engine_nodes, /*is_input_edge=*/false, @@ -644,20 +639,19 @@ tensorflow::Status CreateTRTNode(const std::vector& infos, int pos, } // Function to construct a funcdef from the segment and add it to the graph. -tensorflow::Status RegisterSegmentFunctionToFunctionLibrary( - tensorflow::Graph* graph, const tensorflow::GraphDef& segment, - const string& engine_name) { - tensorflow::Graph sgraph(graph->flib_def()); - tensorflow::GraphConstructorOptions gcopts; - TF_RETURN_IF_ERROR( - tensorflow::ConvertGraphDefToGraph(gcopts, segment, &sgraph)); - std::map io_nodes; +Status RegisterSegmentFunctionToFunctionLibrary(Graph* graph, + const GraphDef& segment, + const string& engine_name) { + Graph sgraph(graph->flib_def()); + GraphConstructorOptions gcopts; + TF_RETURN_IF_ERROR(ConvertGraphDefToGraph(gcopts, segment, &sgraph)); + std::map io_nodes; int num_inputs = 0; for (auto n : sgraph.op_nodes()) { - if (tensorflow::str_util::StartsWith(n->name(), kInputPHName)) { + if (str_util::StartsWith(n->name(), kInputPHName)) { num_inputs++; io_nodes.insert({n->name(), n}); - } else if (tensorflow::str_util::StartsWith(n->name(), kOutputPHName)) { + } else if (str_util::StartsWith(n->name(), kOutputPHName)) { io_nodes.insert({n->name(), n}); } } @@ -665,14 +659,14 @@ tensorflow::Status RegisterSegmentFunctionToFunctionLibrary( for (int i = 0; i < num_inputs; ++i) { auto name = StrCat(kInputPHName, i); auto node = io_nodes[name]; - tensorflow::NodeDef nd; - tensorflow::NodeDefBuilder node_builder( - StrCat(name, "_Arg"), tensorflow::FunctionLibraryDefinition::kArgOp); + NodeDef nd; + NodeDefBuilder node_builder(StrCat(name, "_Arg"), + FunctionLibraryDefinition::kArgOp); VLOG(1) << "Adding " << StrCat(name, "_Arg"); TF_RETURN_IF_ERROR(node_builder.Attr("T", node->output_type(0)) .Attr("index", i) .Finalize(&nd)); - tensorflow::Status s; + Status s; auto node_arg = sgraph.AddNode(nd, &s); if (!s.ok()) { LOG(ERROR) << "Couldn't add _Arg node for " << name; @@ -692,15 +686,14 @@ tensorflow::Status RegisterSegmentFunctionToFunctionLibrary( for (int i = 0; i < io_nodes.size() - num_inputs; ++i) { auto name = StrCat(kOutputPHName, i); auto node = io_nodes[name]; - tensorflow::NodeDef nd; - tensorflow::NodeDefBuilder node_builder( - StrCat(name, "_Ret"), tensorflow::FunctionLibraryDefinition::kRetOp); + NodeDef nd; + NodeDefBuilder node_builder(StrCat(name, "_Ret"), + FunctionLibraryDefinition::kRetOp); auto edge = *(node->in_edges().begin()); - tensorflow::NodeDefBuilder::NodeOut nout( - edge->src()->name(), edge->src_output(), - edge->src()->output_type(edge->src_output())); + NodeDefBuilder::NodeOut nout(edge->src()->name(), edge->src_output(), + edge->src()->output_type(edge->src_output())); VLOG(1) << " input " << nout.node << ":" << nout.index - << " dtype=" << tensorflow::DataTypeString(nout.data_type); + << " dtype=" << DataTypeString(nout.data_type); // nvcc complains that Input() is // ambiguous, so do not use Input({nout}). node_builder.Input(nout); @@ -710,7 +703,7 @@ tensorflow::Status RegisterSegmentFunctionToFunctionLibrary( if (VLOG_IS_ON(3)) { VLOG(3) << nd.DebugString(); } - tensorflow::Status s; + Status s; auto node_ret = sgraph.AddNode(nd, &s); if (!s.ok()) { LOG(ERROR) << "Couldn't add _Ret node for " << name; @@ -726,9 +719,9 @@ tensorflow::Status RegisterSegmentFunctionToFunctionLibrary( } sgraph.RemoveNode(node); } - tensorflow::FunctionDefLibrary fdeflib; + FunctionDefLibrary fdeflib; auto native_segment = fdeflib.add_function(); - TF_RETURN_IF_ERROR(tensorflow::GraphToFunctionDef( + TF_RETURN_IF_ERROR(GraphToFunctionDef( sgraph, StrCat(engine_name, "_native_segment"), native_segment)); // Set kIntsonDeviceAttr to true so that all TRTEngineOp outputs are always on // a GPU device as expected. Otherwise, some of the tensors of type DT_INT32 @@ -742,13 +735,13 @@ tensorflow::Status RegisterSegmentFunctionToFunctionLibrary( } VLOG(1) << "Adding funcdef to graphlib"; TF_RETURN_IF_ERROR(graph->AddFunctionLibrary(fdeflib)); - return tensorflow::Status::OK(); + return Status::OK(); } -std::pair GetDeviceAndAllocator( - const ConversionParams& params, const EngineInfo& engine) { +std::pair GetDeviceAndAllocator(const ConversionParams& params, + const EngineInfo& engine) { int cuda_device_id = -1; - tensorflow::Allocator* dev_allocator = nullptr; + Allocator* dev_allocator = nullptr; if (params.cluster == nullptr || params.cluster->GetDeviceSet() == nullptr || engine.device.empty()) { // If device is not set, use the first found GPU device for the conversion. @@ -776,7 +769,7 @@ std::pair GetDeviceAndAllocator( // Use the device requested by the engine. auto device_set = params.cluster->GetDeviceSet(); - std::vector devices; + std::vector devices; DeviceNameUtils::ParsedName parsed_name; if (DeviceNameUtils::ParseFullName(engine.device, &parsed_name) && parsed_name.has_id) { @@ -790,7 +783,7 @@ std::pair GetDeviceAndAllocator( StrAppend(&msg, ". Will get the allocator from first one."); LOG(WARNING) << msg; } - tensorflow::AllocatorAttributes alloc_attr; + AllocatorAttributes alloc_attr; cuda_device_id = devices[0]->tensorflow_gpu_device_info()->gpu_id; dev_allocator = devices[0]->GetAllocator(alloc_attr); VLOG(1) << "Using allocator " << dev_allocator->Name() @@ -804,25 +797,25 @@ std::pair GetDeviceAndAllocator( // Entry function from optimization pass. // TODO(aaeory): parameter should use pointer type. -tensorflow::Status ConvertAfterShapes(ConversionParams& params) { +Status ConvertAfterShapes(ConversionParams& params) { // Convert graphdef to graph. - tensorflow::FunctionLibraryDefinition flib(tensorflow::OpRegistry::Global(), - params.input_graph_def->library()); - tensorflow::Graph graph(flib); - TF_RETURN_IF_ERROR(tensorflow::ConvertGraphDefToGraph( - tensorflow::GraphConstructorOptions(), *params.input_graph_def, &graph)); + FunctionLibraryDefinition flib(OpRegistry::Global(), + params.input_graph_def->library()); + Graph graph(flib); + TF_RETURN_IF_ERROR(ConvertGraphDefToGraph(GraphConstructorOptions(), + *params.input_graph_def, &graph)); // Segment the graph into subgraphs that can be converted to TensorRT - tensorflow::tensorrt::segment::SegmentOptions segment_options; + segment::SegmentOptions segment_options; // TODO(ben,jie,sami): exclude output nodes (DISCUSS IT) for (auto node : *(params.output_names)) { segment_options.exclude_node_list.insert(node); } segment_options.minimum_segment_size = params.minimum_segment_size; - tensorflow::tensorrt::segment::SegmentNodesVector initial_segments; + segment::SegmentNodesVector initial_segments; TrtCandidateSelector candidate_selector(*params.graph_properties, params.precision_mode); - TF_RETURN_IF_ERROR(tensorrt::segment::SegmentGraph( + TF_RETURN_IF_ERROR(segment::SegmentGraph( &graph, std::bind(&TrtCandidateSelector::IsTensorRTCandidate, &candidate_selector, std::placeholders::_1), @@ -834,16 +827,16 @@ tensorflow::Status ConvertAfterShapes(ConversionParams& params) { << initial_segments.size(); // Get the EngineInfo for each segment. - std::unordered_map node_map; + std::unordered_map node_map; TF_RETURN_IF_ERROR(BuildNodeMap(graph, &node_map)); float total_num_nodes_in_segments = 0.; std::vector engine_segments; engine_segments.reserve(initial_segments.size()); - std::vector reverse_topo_order; - tensorflow::GetPostOrder(graph, &reverse_topo_order); + std::vector reverse_topo_order; + GetPostOrder(graph, &reverse_topo_order); size_t total_engine_bytes_size = 0; std::vector engine_bytes_size; - tensorflow::tensorrt::segment::SegmentNodesVector converted_segments; + segment::SegmentNodesVector converted_segments; converted_segments.reserve(initial_segments.size()); for (size_t t = 0; t < initial_segments.size(); t++) { auto& curr_segment = initial_segments.at(t); @@ -956,7 +949,7 @@ tensorflow::Status ConvertAfterShapes(ConversionParams& params) { cudaSetDevice(old_cuda_device); graph.ToGraphDef(params.output_graph_def); VLOG(1) << "Returning from conversion"; - return tensorflow::Status::OK(); + return Status::OK(); } } // namespace convert diff --git a/tensorflow/compiler/tf2tensorrt/convert/convert_graph.h b/tensorflow/compiler/tf2tensorrt/convert/convert_graph.h index 80f68d36a3..f4ae3f8811 100644 --- a/tensorflow/compiler/tf2tensorrt/convert/convert_graph.h +++ b/tensorflow/compiler/tf2tensorrt/convert/convert_graph.h @@ -40,7 +40,7 @@ class TrtCandidateSelector { // Returns OK iff 'node' is a TF-TRT conversion candidate, which will be added // to TRT subgraph and later converted into TRT engine. - Status IsTensorRTCandidate(const tensorflow::Node* node); + Status IsTensorRTCandidate(const Node* node); private: // The TF-TRT node converter used to verify whether individual node is @@ -69,15 +69,15 @@ struct ConversionParams { fixed_input_size(true), use_calibration(true), max_cached_engines(1) {} - const tensorflow::GraphDef* input_graph_def; + const GraphDef* input_graph_def; const std::vector* output_names; size_t max_batch_size; size_t max_workspace_size_bytes; - tensorflow::GraphDef* output_graph_def; + GraphDef* output_graph_def; TrtPrecisionMode precision_mode; int minimum_segment_size; - const tensorflow::grappler::GraphProperties* graph_properties; - const tensorflow::grappler::Cluster* cluster; + const grappler::GraphProperties* graph_properties; + const grappler::Cluster* cluster; bool is_dyn_op; // Whether to create engine on conversion or execution time bool fixed_input_size; // Assume non-batch ranks of input tensors are fixed int max_cached_engines; // maximum number of cached engines @@ -89,17 +89,17 @@ struct ConversionParams { // optimization targets inference run with max batch size. // - max_workspace_size_bytes: The upper bound of memory allowance for engine // building. -tensorflow::Status ConvertGraphDefToTensorRT( - const tensorflow::GraphDef& graph_def, - const std::vector& output_names, size_t max_batch_size, - size_t max_workspace_size_bytes, tensorflow::GraphDef* new_graph_def, +Status ConvertGraphDefToTensorRT( + const GraphDef& graph_def, const std::vector& output_names, + size_t max_batch_size, size_t max_workspace_size_bytes, + GraphDef* new_graph_def, TrtPrecisionMode precision_mode = TrtPrecisionMode::FP32, int minimum_segment_size = 3, bool is_dyn_op = false, int max_cached_engines = 1, std::vector cached_engine_batches = {}, bool use_calibration = true); // Method to call from optimization pass -tensorflow::Status ConvertAfterShapes(ConversionParams& params); +Status ConvertAfterShapes(ConversionParams& params); // Return compile time TensorRT library version information. std::vector GetLinkedTensorRTVersion(); @@ -108,8 +108,8 @@ std::vector GetLinkedTensorRTVersion(); std::vector GetLoadedTensorRTVersion(); // Helper method for the conversion, expose for testing. -std::pair GetDeviceAndAllocator( - const ConversionParams& params, const EngineInfo& engine); +std::pair GetDeviceAndAllocator(const ConversionParams& params, + const EngineInfo& engine); } // namespace convert } // namespace tensorrt diff --git a/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.h b/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.h index 4babdf93b6..35b44cfeca 100644 --- a/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.h +++ b/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.h @@ -74,14 +74,14 @@ struct EngineConnection { const string outside_node_name; const int outside_id; const int outside_port; - tensorflow::PartialTensorShape outside_shape; // Only set for input edge. + PartialTensorShape outside_shape; // Only set for input edge. const string inside_node_name; const int inside_id; const int inside_port; - tensorflow::PartialTensorShape inside_shape; // Only set for output edge. + PartialTensorShape inside_shape; // Only set for output edge. - tensorflow::DataType connection_type; + DataType connection_type; const bool is_input_edge; // The port number of the TRT node connected with this edge. @@ -97,7 +97,7 @@ struct EngineInfo { string engine_name; string device; - tensorflow::GraphDef segment_graph_def; + GraphDef segment_graph_def; // Non-control input connections inside this vector are sorted in a way such // that, the segment nodes connecting to them are topological sorted. @@ -125,12 +125,11 @@ struct EngineInfo { // sorted in topological order. // // TODO(aaroey): add tests to validate these properties. -tensorflow::Status ConvertSegmentToGraphDef( - const tensorflow::Graph* graph, - const tensorflow::grappler::GraphProperties& graph_properties, +Status ConvertSegmentToGraphDef( + const Graph* graph, const grappler::GraphProperties& graph_properties, const std::vector& subgraph_nodes, - std::vector* connections, - tensorflow::GraphDef* segment_def, string* common_scope); + std::vector* connections, GraphDef* segment_def, + string* common_scope); // Converts given subgraph to a TRT engine saved in 'engine'. Returns ok iff // 'builder' successfully build the engine. If the result is not ok, 'engine' @@ -140,12 +139,11 @@ tensorflow::Status ConvertSegmentToGraphDef( // - convert_successfully: indicates whether the converson to TensorRT network // is successful. This is different than successfully building the engine: // building can still fail afterwards. -tensorflow::Status ConvertGraphDefToEngine( - const tensorflow::GraphDef& gdef, TrtPrecisionMode precision_mode, - int max_batch_size, size_t max_workspace_size_bytes, - const std::vector& input_shapes, - Logger* logger, nvinfer1::IGpuAllocator* allocator, - TRTInt8Calibrator* calibrator, +Status ConvertGraphDefToEngine( + const GraphDef& gdef, TrtPrecisionMode precision_mode, int max_batch_size, + size_t max_workspace_size_bytes, + const std::vector& input_shapes, Logger* logger, + nvinfer1::IGpuAllocator* allocator, TRTInt8Calibrator* calibrator, TrtUniquePtrType* engine, bool use_calibration, bool* convert_successfully); @@ -155,7 +153,7 @@ class OutputEdgeValidator { public: // Return true if the specified edge is eligible to be an output edge of the // TRT segment. - bool operator()(const tensorflow::Edge* out_edge) const; + bool operator()(const Edge* out_edge) const; }; string DebugString(const nvinfer1::DimensionType type); @@ -203,7 +201,7 @@ class TRT_ShapedWeights { // TODO(aaroey): make these private. nvinfer1::Dims shape_; // Note: shape.type[] is not used. - tensorflow::DataType type_; + DataType type_; private: // This constructor is only used by TrtWeightStore, which creates the @@ -229,8 +227,7 @@ class TRT_ShapedWeights { class TrtWeightStore { public: // Get a TRT_ShapedWeights with 'type' and 'dims'. - TRT_ShapedWeights GetTempWeights(tensorflow::DataType type, - const nvinfer1::Dims& dims); + TRT_ShapedWeights GetTempWeights(DataType type, const nvinfer1::Dims& dims); // Get a TRT_ShapedWeights with the same data type and dimensions as // 'weights'. @@ -341,8 +338,7 @@ class Converter; // Parameters for each op converter. struct OpConverterParams { - OpConverterParams(Converter* arg_converter, - const tensorflow::NodeDef& arg_node_def, + OpConverterParams(Converter* arg_converter, const NodeDef& arg_node_def, const std::vector& arg_inputs, std::vector* arg_outputs, bool arg_validation_only, TrtWeightStore* arg_weight_store) @@ -354,7 +350,7 @@ struct OpConverterParams { weight_store(arg_weight_store) {} Converter* converter; - const tensorflow::NodeDef& node_def; + const NodeDef& node_def; const std::vector& inputs; std::vector* outputs; const bool validation_only; @@ -437,7 +433,7 @@ class Converter { // function/subgraph. // Convert the node to TRT network. - Status ConvertNode(const tensorflow::NodeDef& node_def); + Status ConvertNode(const NodeDef& node_def); // Add input tensor to the TRT network with given 'name', 'dtype', 'dims' and // 'batch_size'. @@ -518,7 +514,7 @@ class Converter { Status GetTensorOrWeights(const string& name, TRT_TensorOrWeights* output); // Get the inputs of 'node_def' from trt_tensors_. - Status GetInputs(const tensorflow::NodeDef& node_def, + Status GetInputs(const NodeDef& node_def, std::vector* inputs) const; void RegisterOpConverters(); diff --git a/tensorflow/compiler/tf2tensorrt/convert/convert_nodes_test.cc b/tensorflow/compiler/tf2tensorrt/convert/convert_nodes_test.cc index d09a5cf6a1..7899d6b199 100644 --- a/tensorflow/compiler/tf2tensorrt/convert/convert_nodes_test.cc +++ b/tensorflow/compiler/tf2tensorrt/convert/convert_nodes_test.cc @@ -123,7 +123,7 @@ template NodeDef MakeConstNodeDef(const string& name, const std::vector& vals, const TensorShape& shape) { Scope s = Scope::NewRootScope(); - Tensor t = ::tensorflow::test::AsTensor(vals, shape); + Tensor t = test::AsTensor(vals, shape); auto const_op = ops::Const(s.WithOpName(name), t); return const_op.node()->def(); } @@ -968,7 +968,7 @@ class ConvertGraphDefToEngineTest : public ::testing::Test { Status RunConvertGraphDefToEngine(Scope* s) { GraphDef gdef; TF_EXPECT_OK(s->ToGraphDef(&gdef)); - std::vector input_shapes; + std::vector input_shapes; int batch_size = -1; for (const NodeDef& node : gdef.node()) { absl::string_view node_name(node.name()); @@ -1323,34 +1323,34 @@ void TestConvertConst(OpConverterTest* test) { reset_and_test(t, false, {}, {}); } { - Tensor t = ::tensorflow::test::AsScalar(12); + Tensor t = test::AsScalar(12); reset_and_test(t, false, {1}, {12}); reset_and_test(t, true, {1}, {12}); } { - Tensor t = ::tensorflow::test::AsTensor({1, 2}); + Tensor t = test::AsTensor({1, 2}); reset_and_test(t, false, {2}, {1, 2}); reset_and_test(t, true, {2}, {1, 2}); } { - Tensor t = ::tensorflow::test::AsTensor({1, 2, 3, 4, 5, 6}, - TensorShape({2, 3})); + Tensor t = + test::AsTensor({1, 2, 3, 4, 5, 6}, TensorShape({2, 3})); reset_and_test(t, false, {2, 3}, {1, 2, 3, 4, 5, 6}); reset_and_test(t, true, {2, 3}, {1, 2, 3, 4, 5, 6}); } { // Set all tensor elements to the same value. Such tensors are encoded // using a single element list in tensor proto. - Tensor t = ::tensorflow::test::AsTensor({1, 1, 1, 1, 1, 1}, - TensorShape({2, 3})); + Tensor t = + test::AsTensor({1, 1, 1, 1, 1, 1}, TensorShape({2, 3})); reset_and_test(t, false, {2, 3}, {1, 1, 1, 1, 1, 1}); reset_and_test(t, true, {2, 3}, {1, 1, 1, 1, 1, 1}); } { // Set trailing tensor elements to the same value. Such tensors are // encoded by truncating all equal elements except the first one. - Tensor t = ::tensorflow::test::AsTensor({2, 2, 1, 1, 1, 1}, - TensorShape({2, 3})); + Tensor t = + test::AsTensor({2, 2, 1, 1, 1, 1}, TensorShape({2, 3})); reset_and_test(t, false, {2, 3}, {2, 2, 1, 1, 1, 1}); reset_and_test(t, true, {2, 3}, {2, 2, 1, 1, 1, 1}); } diff --git a/tensorflow/compiler/tf2tensorrt/convert/trt_optimization_pass.cc b/tensorflow/compiler/tf2tensorrt/convert/trt_optimization_pass.cc index 0eedfcacb4..a4b2fad155 100644 --- a/tensorflow/compiler/tf2tensorrt/convert/trt_optimization_pass.cc +++ b/tensorflow/compiler/tf2tensorrt/convert/trt_optimization_pass.cc @@ -34,13 +34,13 @@ namespace convert { // TODO(sami): Remove VLOG messages once the code matures using absl::StrAppend; using absl::StrCat; -using tensorflow::str_util::Uppercase; +using str_util::Uppercase; -tensorflow::Status TRTOptimizationPass::Init( - const tensorflow::RewriterConfig_CustomGraphOptimizer* config) { +Status TRTOptimizationPass::Init( + const RewriterConfig_CustomGraphOptimizer* config) { VLOG(1) << "Called INIT for " << name_ << " with config = " << config; if (config == nullptr) { - return tensorflow::Status::OK(); + return Status::OK(); } const auto params = config->parameter_map(); if (params.count("minimum_segment_size")) { @@ -72,12 +72,11 @@ tensorflow::Status TRTOptimizationPass::Init( if (params.count("use_calibration")) { use_calibration_ = params.at("use_calibration").b(); } - return tensorflow::Status::OK(); + return Status::OK(); } -void TRTOptimizationPass::PrintDebugInfo( - tensorflow::grappler::Cluster* cluster, - const tensorflow::grappler::GrapplerItem& item) { +void TRTOptimizationPass::PrintDebugInfo(grappler::Cluster* cluster, + const grappler::GrapplerItem& item) { LOG(INFO) << "Cluster = " << cluster; string offset(" "); string offset2 = StrCat(offset, offset); @@ -95,7 +94,7 @@ void TRTOptimizationPass::PrintDebugInfo( } std::unordered_map peak_mem; auto status = cluster->GetPeakMemoryUsage(&peak_mem); - if (status == tensorflow::Status::OK()) { + if (status == Status::OK()) { LOG(INFO) << offset << "Peak Memory Usage :"; for (auto s : peak_mem) { LOG(INFO) << offset2 << s.first << " = " << s.second; @@ -177,9 +176,9 @@ void TRTOptimizationPass::PrintDebugInfo( } } -tensorflow::Status TRTOptimizationPass::Optimize( - tensorflow::grappler::Cluster* cluster, - const tensorflow::grappler::GrapplerItem& item, GraphDef* optimized_graph) { +Status TRTOptimizationPass::Optimize(grappler::Cluster* cluster, + const grappler::GrapplerItem& item, + GraphDef* optimized_graph) { VLOG(1) << "Called TRTOptimization Pass " << name_; // This is a hack to workaround optimizer issue. MetaOptimizer calls // optimization passes on function objects as well, we should not modify @@ -190,7 +189,7 @@ tensorflow::Status TRTOptimizationPass::Optimize( << " is probably called on funcdef! This optimizer must *NOT* " "be called on function objects."; *optimized_graph = item.graph; - return tensorflow::Status::OK(); + return Status::OK(); } if (VLOG_IS_ON(3)) { LOG(INFO) << CurrentStackTrace(); @@ -223,9 +222,9 @@ tensorflow::Status TRTOptimizationPass::Optimize( << " adjusting maximum batch size to match input batch size"; } } - tensorflow::grappler::GraphProperties static_graph_properties(item); + grappler::GraphProperties static_graph_properties(item); TF_RETURN_IF_ERROR(static_graph_properties.InferStatically(true)); - tensorflow::tensorrt::convert::ConversionParams cp; + ConversionParams cp; if (use_calibration_ && precision_mode_ != TrtPrecisionMode::INT8) { VLOG(1) << "Calibration with FP32 or FP16 is not implemented. " @@ -263,27 +262,23 @@ tensorflow::Status TRTOptimizationPass::Optimize( cp.cached_engine_batches = batches_; cp.max_cached_engines = max_cached_batches_; cp.use_calibration = use_calibration_; - auto status = tensorflow::tensorrt::convert::ConvertAfterShapes(cp); + auto status = ConvertAfterShapes(cp); VLOG(1) << "Returning from " << name_; return status; } -void TRTOptimizationPass::Feedback( - tensorflow::grappler::Cluster* cluster, - const tensorflow::grappler::GrapplerItem& item, - const GraphDef& optimized_graph, double result) {} - -} // namespace convert -} // namespace tensorrt -} // namespace tensorflow +void TRTOptimizationPass::Feedback(grappler::Cluster* cluster, + const grappler::GrapplerItem& item, + const GraphDef& optimized_graph, + double result) {} class VerboseCustomGraphOptimizerRegistrar - : public tensorflow::grappler::CustomGraphOptimizerRegistrar { + : public grappler::CustomGraphOptimizerRegistrar { public: VerboseCustomGraphOptimizerRegistrar( - const tensorflow::grappler::CustomGraphOptimizerRegistry::Creator& cr, - const tensorflow::string& name) - : tensorflow::grappler::CustomGraphOptimizerRegistrar(cr, name) { + const grappler::CustomGraphOptimizerRegistry::Creator& cr, + const string& name) + : grappler::CustomGraphOptimizerRegistrar(cr, name) { VLOG(1) << "Constructing a CustomOptimizationPass registration object for " << name; } @@ -293,10 +288,13 @@ static VerboseCustomGraphOptimizerRegistrar TRTOptimizationPass_Registrar( []() { VLOG(1) << "Instantiating CustomOptimizationPass object TensorRTOptimizer"; - return new tensorflow::tensorrt::convert::TRTOptimizationPass( - "TensorRTOptimizer"); + return new TRTOptimizationPass("TensorRTOptimizer"); }, ("TensorRTOptimizer")); +} // namespace convert +} // namespace tensorrt +} // namespace tensorflow + #endif #endif diff --git a/tensorflow/compiler/tf2tensorrt/convert/trt_optimization_pass.h b/tensorflow/compiler/tf2tensorrt/convert/trt_optimization_pass.h index b2aed2a37a..a305167100 100644 --- a/tensorflow/compiler/tf2tensorrt/convert/trt_optimization_pass.h +++ b/tensorflow/compiler/tf2tensorrt/convert/trt_optimization_pass.h @@ -30,7 +30,7 @@ namespace tensorflow { namespace tensorrt { namespace convert { -class TRTOptimizationPass : public tensorflow::grappler::CustomGraphOptimizer { +class TRTOptimizationPass : public grappler::CustomGraphOptimizer { public: TRTOptimizationPass(const string& name = "TRTOptimizationPass") : name_(name), @@ -46,19 +46,18 @@ class TRTOptimizationPass : public tensorflow::grappler::CustomGraphOptimizer { string name() const override { return name_; }; - tensorflow::Status Init(const tensorflow::RewriterConfig_CustomGraphOptimizer* - config = nullptr) override; + Status Init( + const RewriterConfig_CustomGraphOptimizer* config = nullptr) override; - tensorflow::Status Optimize(tensorflow::grappler::Cluster* cluster, - const tensorflow::grappler::GrapplerItem& item, - GraphDef* optimized_graph) override; + Status Optimize(grappler::Cluster* cluster, + const grappler::GrapplerItem& item, + GraphDef* optimized_graph) override; - void Feedback(tensorflow::grappler::Cluster* cluster, - const tensorflow::grappler::GrapplerItem& item, + void Feedback(grappler::Cluster* cluster, const grappler::GrapplerItem& item, const GraphDef& optimized_graph, double result) override; - void PrintDebugInfo(tensorflow::grappler::Cluster* cluster, - const tensorflow::grappler::GrapplerItem& item); + void PrintDebugInfo(grappler::Cluster* cluster, + const grappler::GrapplerItem& item); private: const string name_; diff --git a/tensorflow/compiler/tf2tensorrt/kernels/get_serialized_resource_op.cc b/tensorflow/compiler/tf2tensorrt/kernels/get_serialized_resource_op.cc index 81406b6e30..e252f9111d 100644 --- a/tensorflow/compiler/tf2tensorrt/kernels/get_serialized_resource_op.cc +++ b/tensorflow/compiler/tf2tensorrt/kernels/get_serialized_resource_op.cc @@ -46,7 +46,7 @@ class GetSerializedResourceOp : public OpKernel { SerializableResourceBase* resource = nullptr; OP_REQUIRES_OK(context, context->resource_manager()->Lookup( container, resource_name, &resource)); - ::tensorflow::core::ScopedUnref sc(resource); + core::ScopedUnref sc(resource); // Serialize the resource as output. string serialized_resource; diff --git a/tensorflow/compiler/tf2tensorrt/kernels/trt_engine_op.cc b/tensorflow/compiler/tf2tensorrt/kernels/trt_engine_op.cc index f6d387c59c..07d6e5db9b 100644 --- a/tensorflow/compiler/tf2tensorrt/kernels/trt_engine_op.cc +++ b/tensorflow/compiler/tf2tensorrt/kernels/trt_engine_op.cc @@ -53,7 +53,7 @@ using ::nvinfer1::IRuntime; // A helper class to call done() when destructed for asynchronous execution. // Helps simultaneous execution of native and TRT engines. -class AsyncHelper : public tensorflow::core::RefCounted { +class AsyncHelper : public core::RefCounted { public: AsyncHelper(AsyncOpKernel::DoneCallback done) { done_ = done; } ~AsyncHelper() override { done_(); } @@ -139,37 +139,36 @@ class TRTEngineOp : public AsyncOpKernel { bool use_calibration_; }; -#define TYPECASE(dt, X, Y) \ - case dt: { \ - return (void*)X->flat::Type>().data(); \ +#define TYPECASE(dt, X, Y) \ + case dt: { \ + return (void*)X->flat::Type>().data(); \ } void* GetTensorAddress(const Tensor* tensor_ptr) { auto tensor_type = tensor_ptr->dtype(); switch (tensor_type) { - TYPECASE(tensorflow::DT_FLOAT, tensor_ptr, dest_ptr); - TYPECASE(tensorflow::DT_HALF, tensor_ptr, dest_ptr); - TYPECASE(tensorflow::DT_INT8, tensor_ptr, dest_ptr); + TYPECASE(DT_FLOAT, tensor_ptr, dest_ptr); + TYPECASE(DT_HALF, tensor_ptr, dest_ptr); + TYPECASE(DT_INT8, tensor_ptr, dest_ptr); default: { - LOG(ERROR) << "Unsupported Data type " - << tensorflow::DataTypeString(tensor_type); + LOG(ERROR) << "Unsupported Data type " << DataTypeString(tensor_type); return nullptr; } } } -tensorflow::Status TRTEngineOp::ConstructFunctionHandle(OpKernelContext* ctx) { +Status TRTEngineOp::ConstructFunctionHandle(OpKernelContext* ctx) { VLOG(1) << "Constructing function handle"; auto lib = ctx->function_library(); if (lib == nullptr) { - return tensorflow::errors::Internal("Context function library is null"); + return errors::Internal("Context function library is null"); } auto fdef = lib->GetFunctionLibraryDefinition()->Find(funcdef_name_); if (fdef == nullptr) { - return tensorflow::errors::Internal("Native FunctionDef ", funcdef_name_, - " can't be found in function library"); + return errors::Internal("Native FunctionDef ", funcdef_name_, + " can't be found in function library"); } - tensorflow::FunctionLibraryRuntime::InstantiateOptions inst_ops; + FunctionLibraryRuntime::InstantiateOptions inst_ops; inst_ops.overlay_lib = nullptr; inst_ops.state_handle = ""; inst_ops.target = ctx->device()->name(); @@ -194,8 +193,8 @@ TRTEngineOp::TRTEngineOp(OpKernelConstruction* context) if (!static_engine_) { if (!segment_graph_.ParseFromString(serialized_segment_)) { LOG(ERROR) << "Parsing segment graph failed!"; - context->SetStatus(tensorflow::errors::InvalidArgument( - "Failed to parse segment graphdef!")); + context->SetStatus( + errors::InvalidArgument("Failed to parse segment graphdef!")); return; } serialized_segment_.resize(0); @@ -220,7 +219,7 @@ TRTEngineOp::TRTEngineOp(OpKernelConstruction* context) calibrator_.reset(new TRTInt8Calibrator(calibration_data)); calibration_data.resize(0); } - native_func_ = tensorflow::kInvalidHandle; + native_func_ = kInvalidHandle; OP_REQUIRES_OK(context, context->GetAttr("max_cached_engines_count", &max_cached_engines_)); OP_REQUIRES_OK(context, context->GetAttr("cached_engine_batches", @@ -239,7 +238,7 @@ void TRTEngineOp::ExecuteNativeSegment(OpKernelContext* ctx, AsyncHelper* helper) { std::vector inputs; std::vector* outputs = new std::vector(); - if (native_func_ == tensorflow::kInvalidHandle) { + if (native_func_ == kInvalidHandle) { auto status = ConstructFunctionHandle(ctx); if (!status.ok()) { LOG(ERROR) << "Couldn't construct function handle " << funcdef_name_; @@ -248,7 +247,7 @@ void TRTEngineOp::ExecuteNativeSegment(OpKernelContext* ctx, } } auto lib = ctx->function_library(); - tensorflow::FunctionLibraryRuntime::Options opts; + FunctionLibraryRuntime::Options opts; opts.step_id = ctx->step_id(); opts.rendezvous = ctx->rendezvous(); opts.cancellation_manager = ctx->cancellation_manager(); @@ -260,8 +259,8 @@ void TRTEngineOp::ExecuteNativeSegment(OpKernelContext* ctx, helper->Ref(); // Increment count for calculating native graph VLOG(1) << "Executing native segment: " << name(); lib->Run(opts, native_func_, inputs, outputs, - [this, ctx, outputs, helper](const tensorflow::Status& s) { - tensorflow::core::ScopedUnref sc(helper); + [this, ctx, outputs, helper](const Status& s) { + core::ScopedUnref sc(helper); if (!s.ok()) { LOG(ERROR) << "Failed to execute native segment " << this->name() << ": " << s; @@ -282,18 +281,17 @@ void TRTEngineOp::ExecuteCalibration(OpKernelContext* ctx, AsyncHelper* helper) { VLOG(1) << "Executing TRT calibration: " << name(); helper->Ref(); - tensorflow::core::ScopedUnref sc(helper); + core::ScopedUnref sc(helper); auto res_mgr = ctx->resource_manager(); TRTCalibrationResource* calib_res = nullptr; - OP_REQUIRES_OK( - ctx, - res_mgr->LookupOrCreate( - "TF_TRT_Calibration", name(), - reinterpret_cast(&calib_res), - {[ctx, this](SerializableResourceBase** cr) -> tensorflow::Status { - return this->AllocateCalibrationResources(ctx, cr); - }})); - tensorflow::core::ScopedUnref calib_sc(calib_res); + OP_REQUIRES_OK(ctx, + res_mgr->LookupOrCreate( + "TF_TRT_Calibration", name(), + reinterpret_cast(&calib_res), + {[ctx, this](SerializableResourceBase** cr) -> Status { + return this->AllocateCalibrationResources(ctx, cr); + }})); + core::ScopedUnref calib_sc(calib_res); int num_inputs = ctx->num_inputs(); // Pass input data to calibrator std::unordered_map input_data; @@ -301,7 +299,7 @@ void TRTEngineOp::ExecuteCalibration(OpKernelContext* ctx, const Tensor& t = ctx->input(i); void* data_address = GetTensorAddress(&t); if (data_address == nullptr) { - ctx->SetStatus(tensorflow::errors::InvalidArgument( + ctx->SetStatus(errors::InvalidArgument( "Unsupported data type encountered in input ", i)); return; } @@ -357,13 +355,13 @@ bool TRTEngineOp::GetCompatibleCachedEngine( void TRTEngineOp::ComputeAsync(OpKernelContext* ctx, AsyncOpKernel::DoneCallback done) { auto helper = new AsyncHelper(done); - tensorflow::core::ScopedUnref sc(helper); + core::ScopedUnref sc(helper); if (calibration_mode_) { ExecuteCalibration(ctx, helper); return; } // Get shapes of inputs to engine. - std::vector input_shapes; + std::vector input_shapes; input_shapes.reserve(ctx->num_inputs()); for (int i = 0; i < ctx->num_inputs(); ++i) { input_shapes.push_back(ctx->input(i).shape()); @@ -492,7 +490,7 @@ bool TRTEngineOp::ExecuteTrtEngine(OpKernelContext* ctx, // nvinfer1::IExecutionContext::enqueue is not thread safe and we need a mutex // for it. - tensorflow::mutex_lock lock(engine_context->mu); + mutex_lock lock(engine_context->mu); // TODO(jie): trt enqueue does not return error auto ret = engine_context->execution_context->enqueue(num_batch, &buffers[0], *stream, nullptr); @@ -508,7 +506,7 @@ bool TRTEngineOp::ExecuteTrtEngine(OpKernelContext* ctx, EngineContext* TRTEngineOp::GetEngine( const std::vector& input_shapes, OpKernelContext* ctx) { static EngineContext empty_context; - tensorflow::mutex_lock lock(engine_mutex_); + mutex_lock lock(engine_mutex_); // TODO(tmorris): using first input to get batch size - is this reliable? const int batch_size = input_shapes[0].dim_size(0); @@ -516,7 +514,7 @@ EngineContext* TRTEngineOp::GetEngine( TRTEngineCacheResource* cache_res = nullptr; auto status = ctx->resource_manager()->LookupOrCreate( "TRTEngineCache", funcdef_name_, &cache_res, - {[this, ctx](TRTEngineCacheResource** cr) -> tensorflow::Status { + {[this, ctx](TRTEngineCacheResource** cr) -> Status { *cr = new TRTEngineCacheResource(ctx, this->max_cached_engines_); return Status::OK(); }}); @@ -524,7 +522,7 @@ EngineContext* TRTEngineOp::GetEngine( ctx->SetStatus(status); return &empty_context; } - tensorflow::core::ScopedUnref sc(cache_res); + core::ScopedUnref sc(cache_res); auto& cache = cache_res->cache_; auto allocator = cache_res->allocator_.get(); if (allocator == nullptr) { @@ -576,7 +574,7 @@ EngineContext* TRTEngineOp::GetEngine( // Handle the dynamic engine case. // See if there is a compatible engine cached. The batch size should be <= the // cached batch size. - std::vector engine_input_shapes; + std::vector engine_input_shapes; const bool matched_successfully = GetCompatibleCachedEngine(input_shapes, &engine_input_shapes); // If matched, use that engine. Otherwise, we will look in cache for that @@ -630,12 +628,12 @@ EngineContext* TRTEngineOp::GetEngine( return cache.at(engine_input_shapes).get(); } -tensorflow::Status TRTEngineOp::AllocateCalibrationResources( +Status TRTEngineOp::AllocateCalibrationResources( OpKernelContext* ctx, SerializableResourceBase** cr) { auto cres = new TRTCalibrationResource(); *cr = cres; // Get the allocator. - auto alloc = ctx->device()->GetAllocator(tensorflow::AllocatorAttributes()); + auto alloc = ctx->device()->GetAllocator(AllocatorAttributes()); if (!alloc) { LOG(WARNING) << "Can't get device allocator will not be able to " "allocate memory from TensorFlow memory pool"; @@ -646,12 +644,12 @@ tensorflow::Status TRTEngineOp::AllocateCalibrationResources( // Get the input shapes. const int batch_size = ctx->input(0).dim_size(0); const int num_inputs = ctx->num_inputs(); - std::vector shapes; + std::vector shapes; cres->device_tensors_.resize(num_inputs); VLOG(1) << " Constructing calibrator"; for (int i = 0; i < num_inputs; i++) { // allocate workspace on device for inputs - const tensorflow::Tensor& t = ctx->input(i); + const Tensor& t = ctx->input(i); shapes.emplace_back(t.shape()); Tensor* device_tensor; TF_RETURN_IF_ERROR(ctx->allocate_persistent( @@ -659,7 +657,7 @@ tensorflow::Status TRTEngineOp::AllocateCalibrationResources( CHECK_EQ(t.TotalBytes(), device_tensor->TotalBytes()); void* device_address = GetTensorAddress(device_tensor); if (device_address == nullptr) { - return tensorflow::errors::InvalidArgument( + return errors::InvalidArgument( "Unsupported data type encountered in input ", i); } cres->device_buffers_.emplace( @@ -674,7 +672,7 @@ tensorflow::Status TRTEngineOp::AllocateCalibrationResources( ctx->device()->tensorflow_gpu_device_info()->gpu_id; if (platform_gpu_id < 0) { LOG(ERROR) << "Can't get gpu_device_info from context->device()"; - return tensorflow::errors::InvalidArgument( + return errors::InvalidArgument( "Context->device doesn't contain device info!"); } const int64 workspace_size_bytes = workspace_size_; @@ -709,7 +707,7 @@ tensorflow::Status TRTEngineOp::AllocateCalibrationResources( VLOG(1) << "Calibration loop terminated " << label; })); VLOG(1) << "initialized calibrator resource"; - return tensorflow::Status::OK(); + return Status::OK(); } REGISTER_KERNEL_BUILDER(Name("TRTEngineOp").Device(DEVICE_GPU), TRTEngineOp); diff --git a/tensorflow/compiler/tf2tensorrt/plugin/trt_plugin_factory.cc b/tensorflow/compiler/tf2tensorrt/plugin/trt_plugin_factory.cc index 871fb1210b..dd73d15029 100644 --- a/tensorflow/compiler/tf2tensorrt/plugin/trt_plugin_factory.cc +++ b/tensorflow/compiler/tf2tensorrt/plugin/trt_plugin_factory.cc @@ -33,7 +33,7 @@ PluginTensorRT* PluginFactoryTensorRT::createPlugin(const char* layer_name, return nullptr; } - tensorflow::mutex_lock lock(instance_m_); + mutex_lock lock(instance_m_); auto plugin_ptr = plugin_registry_[encoded_op_name].first(serial_data, serial_length); owned_plugins_.emplace_back(plugin_ptr); @@ -44,7 +44,7 @@ PluginTensorRT* PluginFactoryTensorRT::createPlugin(const char* layer_name, PluginTensorRT* PluginFactoryTensorRT::CreatePlugin(const string& op_name) { if (!IsPlugin(op_name)) return nullptr; - tensorflow::mutex_lock lock(instance_m_); + mutex_lock lock(instance_m_); auto plugin_ptr = plugin_registry_[op_name].second(); owned_plugins_.emplace_back(plugin_ptr); @@ -56,7 +56,7 @@ bool PluginFactoryTensorRT::RegisterPlugin( PluginConstructFunc construct_func) { if (IsPlugin(op_name)) return false; - tensorflow::mutex_lock lock(instance_m_); + mutex_lock lock(instance_m_); auto ret = plugin_registry_.emplace( op_name, std::make_pair(deserialize_func, construct_func)); @@ -64,7 +64,7 @@ bool PluginFactoryTensorRT::RegisterPlugin( } void PluginFactoryTensorRT::DestroyPlugins() { - tensorflow::mutex_lock lock(instance_m_); + mutex_lock lock(instance_m_); owned_plugins_.clear(); } diff --git a/tensorflow/compiler/tf2tensorrt/plugin/trt_plugin_factory.h b/tensorflow/compiler/tf2tensorrt/plugin/trt_plugin_factory.h index 9aa99a40b8..cce4f52d9f 100644 --- a/tensorflow/compiler/tf2tensorrt/plugin/trt_plugin_factory.h +++ b/tensorflow/compiler/tf2tensorrt/plugin/trt_plugin_factory.h @@ -69,7 +69,7 @@ class PluginFactoryTensorRT : public nvinfer1::IPluginFactory { // TODO(jie): Owned plugin should be associated with different sessions; // should really hand ownership of plugins to resource management; std::vector> owned_plugins_; - tensorflow::mutex instance_m_; + mutex instance_m_; }; class TrtPluginRegistrar { @@ -89,9 +89,8 @@ class TrtPluginRegistrar { construct_func) \ REGISTER_TRT_PLUGIN_UNIQ(ctr, name, deserialize_func, construct_func) #define REGISTER_TRT_PLUGIN_UNIQ(ctr, name, deserialize_func, construct_func) \ - static ::tensorflow::tensorrt::TrtPluginRegistrar trt_plugin_registrar##ctr \ - TF_ATTRIBUTE_UNUSED = ::tensorflow::tensorrt::TrtPluginRegistrar( \ - name, deserialize_func, construct_func) + static TrtPluginRegistrar trt_plugin_registrar##ctr TF_ATTRIBUTE_UNUSED = \ + TrtPluginRegistrar(name, deserialize_func, construct_func) } // namespace tensorrt } // namespace tensorflow diff --git a/tensorflow/compiler/tf2tensorrt/segment/segment.cc b/tensorflow/compiler/tf2tensorrt/segment/segment.cc index b3031b69ec..9cab9d7012 100644 --- a/tensorflow/compiler/tf2tensorrt/segment/segment.cc +++ b/tensorflow/compiler/tf2tensorrt/segment/segment.cc @@ -39,7 +39,7 @@ namespace segment { using absl::StrAppend; using absl::StrCat; -// A simple graph representation to mirror tensorflow::Graph. This structure +// A simple graph representation to mirror Graph. This structure // helps saving memory since segmenter modifies the graph in place, preventing // the need to create a copy of the graph. It is composed of edges and nodes. // Nodes keep pointers to original TF nodes. @@ -75,7 +75,7 @@ class SimpleEdge { class SimpleNode { public: - SimpleNode(const tensorflow::Node* node, const int id); + SimpleNode(const Node* node, const int id); const std::vector& in_edges() const { return in_edges_; } const std::vector& out_edges() const { return out_edges_; } @@ -99,11 +99,11 @@ class SimpleNode { } const string& name() const { return node_->name(); } - const tensorflow::Node* tf_node() const { return node_; } + const Node* tf_node() const { return node_; } int id() const { return id_; } private: - const tensorflow::Node* node_; + const Node* node_; std::vector in_edges_; std::vector out_edges_; int id_; @@ -113,7 +113,7 @@ class SimpleNode { class SimpleGraph { public: - explicit SimpleGraph(const tensorflow::Graph* g); + explicit SimpleGraph(const Graph* g); ~SimpleGraph(); void AddControlEdge(SimpleNode* src, SimpleNode* dst); @@ -126,15 +126,11 @@ class SimpleGraph { return nodes_[node_id]; } int num_node_ids() const { return nodes_.size(); } - const SimpleNode* source_node() const { - return nodes_[tensorflow::Graph::kSourceId]; - } - const SimpleNode* sink_node() const { - return nodes_[tensorflow::Graph::kSinkId]; - } + const SimpleNode* source_node() const { return nodes_[Graph::kSourceId]; } + const SimpleNode* sink_node() const { return nodes_[Graph::kSinkId]; } private: - const tensorflow::Graph* g_; + const Graph* g_; std::vector nodes_; std::vector edges_; // free_edge_ids_ and free_node_ids_ contain freed indices. @@ -142,15 +138,14 @@ class SimpleGraph { std::set free_node_ids_; }; -SimpleNode::SimpleNode(const tensorflow::Node* node, const int id) - : node_(node), id_(id) { +SimpleNode::SimpleNode(const Node* node, const int id) : node_(node), id_(id) { if (node_) { in_edges_.reserve(node_->in_edges().size()); out_edges_.reserve(node_->out_edges().size()); } } -SimpleGraph::SimpleGraph(const tensorflow::Graph* g) : g_(g) { +SimpleGraph::SimpleGraph(const Graph* g) : g_(g) { int n_nodes = g_->num_node_ids(); nodes_.resize(n_nodes, nullptr); nodes_[g->kSourceId] = new SimpleNode(g->source_node(), g->kSourceId); @@ -194,8 +189,8 @@ void SimpleGraph::AddEdge(SimpleNode* src, int out_port, SimpleNode* dst, } else { edges_.push_back(nullptr); } - bool is_control = (out_port == tensorflow::Graph::kControlSlot); - is_control |= (in_port == tensorflow::Graph::kControlSlot); + bool is_control = (out_port == Graph::kControlSlot); + is_control |= (in_port == Graph::kControlSlot); auto edge = new SimpleEdge(i, src, out_port, dst, in_port, is_control); edges_[i] = edge; src->out_edges_.push_back(edge); @@ -203,8 +198,7 @@ void SimpleGraph::AddEdge(SimpleNode* src, int out_port, SimpleNode* dst, } void SimpleGraph::AddControlEdge(SimpleNode* src, SimpleNode* dst) { - AddEdge(src, tensorflow::Graph::kControlSlot, dst, - tensorflow::Graph::kControlSlot); + AddEdge(src, Graph::kControlSlot, dst, Graph::kControlSlot); } void SimpleGraph::RemoveEdge(const SimpleEdge* edge) { @@ -241,15 +235,14 @@ struct SimpleEdgePtrCompare { }; struct NodePtrCompare { - bool operator()(const tensorflow::Node* lhs, - const tensorflow::Node* rhs) const { + bool operator()(const Node* lhs, const Node* rhs) const { return lhs->name() < rhs->name(); } }; namespace { -// Copied from TF ReverseDFS, which only works for tensorflow::Graph. +// Copied from TF ReverseDFS, which only works for Graph. void StableDFS(const SimpleGraph& g, bool reverse, const std::vector& start, const std::function& enter, @@ -371,8 +364,7 @@ void ContractEdge(SimpleEdge* edge, SimpleGraph* graph, if (in_edge->src() != src) { SimpleEdge* e = const_cast(in_edge); if (e->src() == graph->source_node()) { - graph->AddEdge(e->src(), e->src_output(), src, - tensorflow::Graph::kControlSlot); + graph->AddEdge(e->src(), e->src_output(), src, Graph::kControlSlot); } else { graph->AddEdge(e->src(), e->src_output(), src, 0 /* input index */); } @@ -391,8 +383,7 @@ void ContractEdge(SimpleEdge* edge, SimpleGraph* graph, 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()); + graph->AddEdge(src, Graph::kControlSlot, e->dst(), e->dst_input()); } else { graph->AddEdge(src, 0 /* output index */, e->dst(), e->dst_input()); } @@ -410,12 +401,12 @@ void ContractEdge(SimpleEdge* edge, SimpleGraph* graph, } } -tensorflow::Status SegmentGraph( - const tensorflow::Graph* tf_graph, - const std::function& candidate_fn, - const std::function& input_candidate_fn, - const std::function& output_candidate_fn, - const SegmentOptions& options, SegmentNodesVector* segments) { +Status SegmentGraph(const Graph* tf_graph, + const std::function& candidate_fn, + const std::function& input_candidate_fn, + const std::function& output_candidate_fn, + const SegmentOptions& options, + SegmentNodesVector* segments) { // Steps: // 1. run the segmentation algorithm to find all the segments, which uses // candidate_fn to determine the candidates segment nodes; @@ -552,7 +543,7 @@ tensorflow::Status SegmentGraph( // A map from the segment identifier (currently the name of the root node of // the segment tree) to the segment nodes set. - std::map> sg_map; + std::map> sg_map; // A map from the segment identifier (currently the name of the root node of // the segment tree) to the device names that the nodes in the segment are @@ -588,17 +579,16 @@ tensorflow::Status SegmentGraph( // --------------------------------- Step 2 --------------------------------- // Remove ineligible input/output nodes. for (auto& itr : sg_map) { - std::set& segment_nodes = - itr.second; + std::set& segment_nodes = itr.second; VLOG(1) << "Segment original size: " << segment_nodes.size(); while (true) { - std::deque in_nodes_que, out_nodes_que; + std::deque in_nodes_que, out_nodes_que; // Find an input node that is not eligible and add it to the queue. // Nodes that has no incoming edges should not be treated as "input", // as there are really no inputs to them. Similar for output nodes. for (auto node : segment_nodes) { bool added = false; - for (const tensorflow::Edge* edge : node->in_edges()) { + for (const Edge* edge : node->in_edges()) { if (!edge->IsControlEdge() && !edge->src()->IsSource() && !segment_nodes.count(edge->src())) { // 'node' is an input node. if (!input_candidate_fn(edge)) { @@ -609,7 +599,7 @@ tensorflow::Status SegmentGraph( } } if (added) continue; // Only adding the node once to either queue. - for (const tensorflow::Edge* edge : node->out_edges()) { + for (const Edge* edge : node->out_edges()) { if (!edge->dst()->IsSink() && !edge->IsControlEdge() && !segment_nodes.count(edge->dst())) { // 'node' is an output node. if (!output_candidate_fn(edge)) { @@ -637,13 +627,11 @@ tensorflow::Status SegmentGraph( // remove all their inputs, and for non-const output nodes remove all // their outputs. In this way, for common cases the number of removed // nodes should be minimum. - auto remove_nodes = [&segment_nodes]( - bool is_input_nodes, - std::deque* que) { + auto remove_nodes = [&segment_nodes](bool is_input_nodes, + std::deque* que) { // Run a BFS on the queue to find all the input/output nodes. - std::set visited; - std::set logged(que->begin(), - que->end()); + std::set visited; + std::set logged(que->begin(), que->end()); while (!que->empty()) { auto node = que->front(); que->pop_front(); @@ -722,7 +710,7 @@ tensorflow::Status SegmentGraph( VLOG(1) << "Devices " << s; } } - return tensorflow::Status::OK(); + return Status::OK(); } } // namespace segment diff --git a/tensorflow/compiler/tf2tensorrt/segment/segment.h b/tensorflow/compiler/tf2tensorrt/segment/segment.h index 9622ddd593..e31f1a989d 100644 --- a/tensorflow/compiler/tf2tensorrt/segment/segment.h +++ b/tensorflow/compiler/tf2tensorrt/segment/segment.h @@ -44,19 +44,19 @@ struct SegmentOptions { // Get the subgraphs of a graph that can be handled by TensorRT. // -// @param graph tensorflow::Graph of the network +// @param graph Graph of the network // @param candidate_fn A function that returns OK for a Node* if // that node can be handled by TensorRT. // @param segments Returns the TensorRT segments/subgraphs. Each entry // in the vector describes a subgraph by giving a set of the names of // all the NodeDefs in that subgraph. // @return the status. -tensorflow::Status SegmentGraph( - const tensorflow::Graph* tf_graph, - const std::function& candidate_fn, - const std::function& input_candidate_fn, - const std::function& output_candidate_fn, - const SegmentOptions& options, SegmentNodesVector* segments); +Status SegmentGraph(const Graph* tf_graph, + const std::function& candidate_fn, + const std::function& input_candidate_fn, + const std::function& output_candidate_fn, + const SegmentOptions& options, + SegmentNodesVector* segments); } // namespace segment } // namespace tensorrt diff --git a/tensorflow/compiler/tf2tensorrt/segment/segment_test.cc b/tensorflow/compiler/tf2tensorrt/segment/segment_test.cc index e11ad27197..84b690ecba 100644 --- a/tensorflow/compiler/tf2tensorrt/segment/segment_test.cc +++ b/tensorflow/compiler/tf2tensorrt/segment/segment_test.cc @@ -33,13 +33,12 @@ namespace tensorflow { namespace tensorrt { namespace segment { namespace test { -namespace ops = ::tensorflow::ops; class SegmentTest : public ::testing::Test { protected: - std::function MakeCandidateFn( + std::function MakeCandidateFn( const std::set& node_names) { - return [node_names](const tensorflow::Node* node) -> Status { + return [node_names](const Node* node) -> Status { if (node_names.find(node->name()) != node_names.end()) { return Status::OK(); } @@ -47,22 +46,21 @@ class SegmentTest : public ::testing::Test { }; } - std::function MakeInputEdgeCandidateFn( + std::function MakeInputEdgeCandidateFn( const std::set& node_names) { - return [node_names](const tensorflow::Edge* in_edge) -> bool { + return [node_names](const Edge* in_edge) -> bool { return node_names.find(in_edge->dst()->name()) != node_names.end(); }; } - std::function MakeOutputEdgeCandidateFn( + std::function MakeOutputEdgeCandidateFn( const std::set& node_names) { - return [node_names](const tensorflow::Edge* out_edge) -> bool { + return [node_names](const Edge* out_edge) -> bool { return node_names.find(out_edge->src()->name()) != node_names.end(); }; } - void RunTest(const tensorflow::Graph* graph, - const std::set& candidates, + void RunTest(const Graph* graph, const std::set& candidates, const std::set& input_candidates, const std::set& output_candidates, const std::vector>& expected_segments) { @@ -106,7 +104,7 @@ std::set operator-(const std::set& lhs, const string& rhs) { TEST_F(SegmentTest, Empty) { Scope s = Scope::NewRootScope(); - tensorflow::Graph g(OpRegistry::Global()); + Graph g(OpRegistry::Global()); TF_EXPECT_OK(s.ToGraph(&g)); // Expect no segments/subgraphs. RunTest(&g, {}, {}, {}, {}); @@ -129,7 +127,7 @@ TEST_F(SegmentTest, Simple) { auto add2 = ops::Add(s.WithOpName("add2"), add0, add1); auto add3 = ops::Add(s.WithOpName("add3"), add0, add2); auto add4 = ops::Add(s.WithOpName("add4"), add2, add2); - tensorflow::Graph g(OpRegistry::Global()); + Graph g(OpRegistry::Global()); TF_EXPECT_OK(s.ToGraph(&g)); // All Add operations are candidates, and we expect all of them to be @@ -176,7 +174,7 @@ TEST_F(SegmentTest, AvoidCycle) { auto add2 = ops::Add(s.WithOpName("add2"), add0, add1); auto add3 = ops::Add(s.WithOpName("add3"), add0, add2); auto add4 = ops::Add(s.WithOpName("add4"), add2, add2); - tensorflow::Graph g(OpRegistry::Global()); + Graph g(OpRegistry::Global()); TF_EXPECT_OK(s.ToGraph(&g)); // add2 is not a TRT candidate so there should be no segments generated. @@ -207,7 +205,7 @@ TEST_F(SegmentTest, Multiple) { auto add3 = ops::Add(s.WithOpName("add3"), add0, add2); auto add4 = ops::Add(s.WithOpName("add4"), add2, add5); auto add6 = ops::Add(s.WithOpName("add6"), add5, add8); - tensorflow::Graph g(OpRegistry::Global()); + Graph g(OpRegistry::Global()); TF_EXPECT_OK(s.ToGraph(&g)); const std::set all_adds = {"add0", "add1", "add2", "add3", "add4", @@ -254,7 +252,7 @@ TEST_F(SegmentTest, BigIfElse) { auto add5 = ops::Add(s.WithOpName("add5"), add4, add4); auto add6 = ops::Add(s.WithOpName("add6"), add5, add5); auto add7 = ops::Add(s.WithOpName("add7"), add3, add6); - tensorflow::Graph g(OpRegistry::Global()); + Graph g(OpRegistry::Global()); TF_EXPECT_OK(s.ToGraph(&g)); // Make add2 not a TRT candidate, and we expect 2 segments. diff --git a/tensorflow/compiler/tf2tensorrt/utils/test_utils.h b/tensorflow/compiler/tf2tensorrt/utils/test_utils.h index d85875991b..361eb01059 100644 --- a/tensorflow/compiler/tf2tensorrt/utils/test_utils.h +++ b/tensorflow/compiler/tf2tensorrt/utils/test_utils.h @@ -28,12 +28,11 @@ void ClearTestValues(const string& pattern); void AddTestValue(const string& label, const string& value); string GetTestValue(const string& label); -#define TRT_RETURN_IF_TEST_VALUE(label, value_to_return) \ - do { \ - if (::tensorflow::tensorrt::test::GetTestValue(label) == \ - value_to_return) { \ - return errors::Internal("Injected manually"); \ - } \ +#define TRT_RETURN_IF_TEST_VALUE(label, value_to_return) \ + do { \ + if (test::GetTestValue(label) == value_to_return) { \ + return errors::Internal("Injected manually"); \ + } \ } while (0) } // namespace test diff --git a/tensorflow/compiler/tf2tensorrt/utils/trt_allocator.cc b/tensorflow/compiler/tf2tensorrt/utils/trt_allocator.cc index 1636cdc30c..1e9ab1202f 100644 --- a/tensorflow/compiler/tf2tensorrt/utils/trt_allocator.cc +++ b/tensorflow/compiler/tf2tensorrt/utils/trt_allocator.cc @@ -94,7 +94,7 @@ void* TRTDeviceAllocator::allocate(uint64_t size, uint64_t alignment, return mem; } -TRTDeviceAllocator::TRTDeviceAllocator(tensorflow::Allocator* allocator) +TRTDeviceAllocator::TRTDeviceAllocator(Allocator* allocator) : allocator_(allocator) { VLOG(1) << "Using " << allocator->Name() << " allocator from TensorFlow"; } diff --git a/tensorflow/compiler/tf2tensorrt/utils/trt_allocator.h b/tensorflow/compiler/tf2tensorrt/utils/trt_allocator.h index 59ffb42bad..8ec06d7456 100644 --- a/tensorflow/compiler/tf2tensorrt/utils/trt_allocator.h +++ b/tensorflow/compiler/tf2tensorrt/utils/trt_allocator.h @@ -59,7 +59,7 @@ class TRTCudaAllocator : public TRTBaseAllocator { class TRTDeviceAllocator : public TRTBaseAllocator { // Allocator implementation wrapping TF device allocators. public: - TRTDeviceAllocator(tensorflow::Allocator* allocator); + TRTDeviceAllocator(Allocator* allocator); // TODO(aaroey): base class doesn't have a virtual destructor, work with // Nvidia to fix it. @@ -70,7 +70,7 @@ class TRTDeviceAllocator : public TRTBaseAllocator { void free(void* memory) override; private: - tensorflow::Allocator* allocator_; + Allocator* allocator_; // supporting alignment from allocation request requires a map to free; std::unordered_map mem_map_; diff --git a/tensorflow/compiler/tf2tensorrt/utils/trt_int8_calibrator.cc b/tensorflow/compiler/tf2tensorrt/utils/trt_int8_calibrator.cc index 5213fced1e..33a5c719ba 100644 --- a/tensorflow/compiler/tf2tensorrt/utils/trt_int8_calibrator.cc +++ b/tensorflow/compiler/tf2tensorrt/utils/trt_int8_calibrator.cc @@ -50,7 +50,7 @@ TRTInt8Calibrator::TRTInt8Calibrator(const string& calib_data) bool TRTInt8Calibrator::setBatch(const std::unordered_map& data, const cudaStream_t stream) { - tensorflow::mutex_lock lock(cond_mtx_); + mutex_lock lock(cond_mtx_); // Wait while the queue is full or calibration is running. while ((calib_running_ || batch_is_set_) && !done_) cond_.wait(lock); @@ -87,7 +87,7 @@ bool TRTInt8Calibrator::setBatch(const std::unordered_map& data, bool TRTInt8Calibrator::getBatch(void** bindings, const char** names, int num_bindings) { - tensorflow::mutex_lock lock(cond_mtx_); + mutex_lock lock(cond_mtx_); // Notify finish of last round of calibration. calib_running_ = false; cond_.notify_all(); @@ -111,7 +111,7 @@ bool TRTInt8Calibrator::getBatch(void** bindings, const char** names, } void TRTInt8Calibrator::waitAndSetDone() { - tensorflow::mutex_lock lock(cond_mtx_); + mutex_lock lock(cond_mtx_); // Wait while the queue is full or calibration is running, so we don't miss // the last batch. while ((calib_running_ || batch_is_set_) && !done_) cond_.wait(lock); @@ -128,7 +128,7 @@ const void* TRTInt8Calibrator::readCalibrationCache(std::size_t& length) { } void TRTInt8Calibrator::setDone() { - tensorflow::mutex_lock lock(cond_mtx_); + mutex_lock lock(cond_mtx_); done_ = true; cond_.notify_all(); } diff --git a/tensorflow/compiler/tf2tensorrt/utils/trt_int8_calibrator.h b/tensorflow/compiler/tf2tensorrt/utils/trt_int8_calibrator.h index aa70b07f8d..d34e244f6c 100644 --- a/tensorflow/compiler/tf2tensorrt/utils/trt_int8_calibrator.h +++ b/tensorflow/compiler/tf2tensorrt/utils/trt_int8_calibrator.h @@ -78,10 +78,10 @@ struct TRTInt8Calibrator : public nvinfer1::IInt8EntropyCalibrator { const int batch_size_; // mutex for condition_variable - tensorflow::mutex cond_mtx_; + mutex cond_mtx_; // condition variable to implement producer-consumer queue for calibration - tensorflow::condition_variable cond_; + condition_variable cond_; // Is calibration finished? bool done_; diff --git a/tensorflow/compiler/tf2tensorrt/utils/trt_lru_cache.h b/tensorflow/compiler/tf2tensorrt/utils/trt_lru_cache.h index 09c47b36b0..8ece326446 100644 --- a/tensorflow/compiler/tf2tensorrt/utils/trt_lru_cache.h +++ b/tensorflow/compiler/tf2tensorrt/utils/trt_lru_cache.h @@ -100,26 +100,24 @@ class LRUCache { } // Creates n free positions in cache - tensorflow::Status DiscardOld(size_t n = 0) { + Status DiscardOld(size_t n = 0) { if (n > capacity_) { - return tensorflow::errors::Internal( - "Insufficient capacity in cache (capacity = ", capacity_, - ", requested ", n, ")"); + return errors::Internal("Insufficient capacity in cache (capacity = ", + capacity_, ", requested ", n, ")"); } while (objects_.size() > (capacity_ - n)) { key_type discard_key = keys_.back(); keys_.pop_back(); objects_.erase(discard_key); } - return tensorflow::Status::OK(); + return Status::OK(); } }; // Define a hash function for vector because it is used as the key // for the engine cache. struct VectorTensorShapeHasher { - std::size_t operator()( - const std::vector& key) const { + std::size_t operator()(const std::vector& key) const { return std::hash()(TensorShapeUtils::ShapeListString(key)); } }; @@ -141,12 +139,12 @@ struct EngineContext { GUARDED_BY(mu); }; -class TRTEngineCacheResource : public tensorflow::ResourceBase { +class TRTEngineCacheResource : public ResourceBase { public: TRTEngineCacheResource(OpKernelContext* ctx, size_t capacity) : cache_(capacity) { auto device = ctx->device(); - auto alloc = device->GetAllocator(tensorflow::AllocatorAttributes()); + auto alloc = device->GetAllocator(AllocatorAttributes()); if (!alloc) { LOG(ERROR) << "Can't find device allocator for gpu device " << device->name(); diff --git a/tensorflow/compiler/tf2tensorrt/utils/trt_resources.cc b/tensorflow/compiler/tf2tensorrt/utils/trt_resources.cc index 2e553079b1..534e59f06b 100644 --- a/tensorflow/compiler/tf2tensorrt/utils/trt_resources.cc +++ b/tensorflow/compiler/tf2tensorrt/utils/trt_resources.cc @@ -49,7 +49,7 @@ Status TRTCalibrationResource::SerializeToString(string* serialized) { thr_->join(); *serialized = calibrator_->getCalibrationTableAsString(); if (serialized->empty()) { - return tensorflow::errors::Unknown("Calibration table is empty."); + return errors::Unknown("Calibration table is empty."); } return Status::OK(); } diff --git a/tensorflow/compiler/tf2tensorrt/utils/trt_resources.h b/tensorflow/compiler/tf2tensorrt/utils/trt_resources.h index 5e8d4b3b73..abfed2c181 100644 --- a/tensorflow/compiler/tf2tensorrt/utils/trt_resources.h +++ b/tensorflow/compiler/tf2tensorrt/utils/trt_resources.h @@ -37,7 +37,7 @@ limitations under the License. namespace tensorflow { namespace tensorrt { -class SerializableResourceBase : public tensorflow::ResourceBase { +class SerializableResourceBase : public ResourceBase { public: virtual Status SerializeToString(string* serialized) = 0; }; @@ -60,7 +60,7 @@ class TRTCalibrationResource : public SerializableResourceBase { TrtUniquePtrType builder_; TrtUniquePtrType engine_; std::unique_ptr allocator_; - tensorflow::tensorrt::Logger logger_; + Logger logger_; // TODO(sami): Use threadpool threads! std::unique_ptr thr_; }; -- GitLab From 2ae06ca49145bfee2e5e98c64ae5cbe064a58a33 Mon Sep 17 00:00:00 2001 From: Lu Wang Date: Fri, 22 Feb 2019 22:04:22 -0800 Subject: [PATCH 401/766] Implement Reverse_Sequence. PiperOrigin-RevId: 235309643 --- tensorflow/lite/build_def.bzl | 1 + tensorflow/lite/builtin_ops.h | 1 + tensorflow/lite/c/builtin_op_data.h | 5 + .../lite/core/api/flatbuffer_conversions.cc | 11 + .../writer/option_writer_generator.cc | 2 + tensorflow/lite/g3doc/tf_ops_compatibility.md | 19 ++ tensorflow/lite/kernels/BUILD | 13 ++ .../internal/reference/reference_ops.h | 79 +++++++ tensorflow/lite/kernels/register.cc | 2 + tensorflow/lite/kernels/reverse_sequence.cc | 158 +++++++++++++ .../lite/kernels/reverse_sequence_test.cc | 211 ++++++++++++++++++ tensorflow/lite/nnapi_delegate.cc | 1 + tensorflow/lite/schema/schema.fbs | 6 + tensorflow/lite/schema/schema_generated.h | 156 ++++++++++++- tensorflow/lite/testing/generate_examples.py | 47 ++++ tensorflow/lite/toco/export_tensorflow.cc | 27 ++- .../propagate_fixed_sizes.cc | 1 + tensorflow/lite/toco/import_tensorflow.cc | 22 ++ tensorflow/lite/toco/model.h | 16 +- tensorflow/lite/toco/tflite/operator.cc | 28 +++ tensorflow/lite/toco/tflite/operator_test.cc | 11 + tensorflow/lite/toco/tooling_util.cc | 1 + 22 files changed, 806 insertions(+), 12 deletions(-) create mode 100644 tensorflow/lite/kernels/reverse_sequence.cc create mode 100644 tensorflow/lite/kernels/reverse_sequence_test.cc diff --git a/tensorflow/lite/build_def.bzl b/tensorflow/lite/build_def.bzl index 1d16d61551..80c0c83e5a 100644 --- a/tensorflow/lite/build_def.bzl +++ b/tensorflow/lite/build_def.bzl @@ -299,6 +299,7 @@ def generated_test_models(): "reshape", "resize_bilinear", "resolve_constant_strided_slice", + "reverse_sequence", "reverse_v2", "rsqrt", "shape", diff --git a/tensorflow/lite/builtin_ops.h b/tensorflow/lite/builtin_ops.h index e7fb59282c..7b4efdf4a3 100644 --- a/tensorflow/lite/builtin_ops.h +++ b/tensorflow/lite/builtin_ops.h @@ -137,6 +137,7 @@ typedef enum { kTfLiteBuiltinWhere = 109, kTfLiteBuiltinRank = 110, kTfLiteBuiltinElu = 111, + kTfLiteBuiltinReverseSequence = 112, } TfLiteBuiltinOperator; #ifdef __cplusplus diff --git a/tensorflow/lite/c/builtin_op_data.h b/tensorflow/lite/c/builtin_op_data.h index 40fea17fef..5d1c92d36f 100644 --- a/tensorflow/lite/c/builtin_op_data.h +++ b/tensorflow/lite/c/builtin_op_data.h @@ -368,6 +368,11 @@ typedef struct { TfLiteType index_out_type; } TfLiteUniqueParams; +typedef struct { + int seq_dim; + int batch_dim; +} TfLiteReverseSequenceParams; + #ifdef __cplusplus } // extern "C" #endif // __cplusplus diff --git a/tensorflow/lite/core/api/flatbuffer_conversions.cc b/tensorflow/lite/core/api/flatbuffer_conversions.cc index 7ca0fc2deb..2ba64f51d9 100644 --- a/tensorflow/lite/core/api/flatbuffer_conversions.cc +++ b/tensorflow/lite/core/api/flatbuffer_conversions.cc @@ -672,6 +672,17 @@ TfLiteStatus ParseOpData(const Operator* op, BuiltinOperator op_type, *builtin_data = reinterpret_cast(params); break; } + case BuiltinOperator_REVERSE_SEQUENCE: { + TfLiteReverseSequenceParams* params = + allocator->AllocatePOD(); + if (auto* reverse_seq_params = + op->builtin_options_as_ReverseSequenceOptions()) { + params->seq_dim = reverse_seq_params->seq_dim(); + params->batch_dim = reverse_seq_params->batch_dim(); + } + *builtin_data = reinterpret_cast(params); + break; + } // Below are the ops with no builtin_data strcture. case BuiltinOperator_ABS: diff --git a/tensorflow/lite/experimental/writer/option_writer_generator.cc b/tensorflow/lite/experimental/writer/option_writer_generator.cc index b425a75325..a5c5dc8709 100644 --- a/tensorflow/lite/experimental/writer/option_writer_generator.cc +++ b/tensorflow/lite/experimental/writer/option_writer_generator.cc @@ -75,6 +75,7 @@ static const char* param_structs[] = {"TfLiteAddParams", "TfLiteUnidirectionalSequenceLSTMParams", "TfLiteUniqueParams", "TfLiteUnpackParams", + "TfLiteReverseSequenceParams", nullptr}; } // namespace @@ -180,6 +181,7 @@ class OpOptionData { op_to_option_["SQRT"] = ""; op_to_option_["RSQRT"] = ""; op_to_option_["ELU"] = ""; + op_to_option_["REVERSE_SEQUENCE"] = ""; // TODO(aselle): These are undesirable hacks. Consider changing C structs option_to_struct_["Pool2DOptions"] = "TfLitePoolParams"; diff --git a/tensorflow/lite/g3doc/tf_ops_compatibility.md b/tensorflow/lite/g3doc/tf_ops_compatibility.md index ff8ddabdf4..a0cb9281db 100644 --- a/tensorflow/lite/g3doc/tf_ops_compatibility.md +++ b/tensorflow/lite/g3doc/tf_ops_compatibility.md @@ -822,6 +822,25 @@ Outputs { } ``` +**REVERSE_SEQUENCE** + +``` +Inputs { + 0: a tensor + 1: a 1-D tensor which specifies the length of sequence to be reversed in each + dim +} +Outputs { + 0: a tensor with the same shape as the input tensor +} +Options { + seq_dim: a 0-D int tensor (scalar). The dimension which is partially + reversed. + batch_dim: a 0-D int tensor (scalar). Defaults to 0. The dimension along + which reversal is performed. +} +``` + **SHAPE** ``` diff --git a/tensorflow/lite/kernels/BUILD b/tensorflow/lite/kernels/BUILD index c24b6ede63..4841c636ca 100644 --- a/tensorflow/lite/kernels/BUILD +++ b/tensorflow/lite/kernels/BUILD @@ -205,6 +205,7 @@ cc_library( "resize_bilinear.cc", "resize_nearest_neighbor.cc", "reverse.cc", + "reverse_sequence.cc", "select.cc", "shape.cc", "skip_gram.cc", @@ -1374,3 +1375,15 @@ tf_cc_test( "@com_google_googletest//:gtest", ], ) + +tf_cc_test( + name = "reverse_sequence_test", + size = "small", + srcs = ["reverse_sequence_test.cc"], + deps = [ + ":builtin_ops", + "//tensorflow/lite:framework", + "//tensorflow/lite/kernels:test_util", + "@com_google_googletest//:gtest", + ], +) diff --git a/tensorflow/lite/kernels/internal/reference/reference_ops.h b/tensorflow/lite/kernels/internal/reference/reference_ops.h index d2f5d987b9..60ed828bb5 100644 --- a/tensorflow/lite/kernels/internal/reference/reference_ops.h +++ b/tensorflow/lite/kernels/internal/reference/reference_ops.h @@ -4721,6 +4721,85 @@ void Reverse(int axis, const RuntimeShape& input_shape, } } +template +void ReverseSequence(const TS* seq_lengths, const int seq_dim, + const int batch_dim, const RuntimeShape& input_shape, + const Scalar* input_data, const RuntimeShape& output_shape, + Scalar* output_data) { + gemmlowp::ScopedProfilingLabel label("ReverseSequence"); + + int outer_size = 1; + int outer_dim = std::min(batch_dim, seq_dim); + int medium_dim = std::max(batch_dim, seq_dim); + for (int i = 0; i < outer_dim; ++i) { + outer_size *= input_shape.Dims(i); + } + + int medium_size = 1; + for (int i = outer_dim + 1; i < medium_dim; ++i) { + medium_size *= input_shape.Dims(i); + } + + int copy_size = 1; + for (int i = medium_dim + 1; i < input_shape.DimensionsCount(); ++i) { + copy_size *= input_shape.Dims(i); + } + + const int dims_at_outer_dim = input_shape.Dims(outer_dim); + const int dims_at_medium_dim = input_shape.Dims(medium_dim); + + Scalar* output_ptr; + if (batch_dim > seq_dim) { + for (int i = 0; i < outer_size; ++i) { + for (int j = 0; j < dims_at_outer_dim; ++j) { + const int in_pos_base = (i * dims_at_outer_dim + j) * medium_size; + for (int p = 0; p < medium_size; ++p) { + for (int q = 0; q < dims_at_medium_dim; ++q) { + const int in_pos = + ((in_pos_base + p) * dims_at_medium_dim + q) * copy_size; + const Scalar* in_ptr = input_data + in_pos; + int sl = seq_lengths[q] - 1; + if (j > sl) { + output_ptr = output_data + in_pos; + } else { + const int out_pos_base = + (i * dims_at_outer_dim + sl - j) * medium_size; + const int out_pos = + ((out_pos_base + p) * dims_at_medium_dim + q) * copy_size; + output_ptr = output_data + out_pos; + } + memcpy(output_ptr, in_ptr, copy_size * sizeof(Scalar)); + } + } + } + } + } else if (batch_dim < seq_dim) { + for (int i = 0; i < outer_size; ++i) { + for (int j = 0; j < dims_at_outer_dim; ++j) { + const int in_pos_base = (i * dims_at_outer_dim + j) * medium_size; + int sl = seq_lengths[j] - 1; + const int out_pos_base = (i * dims_at_outer_dim + j) * medium_size; + for (int p = 0; p < medium_size; ++p) { + for (int q = 0; q < dims_at_medium_dim; ++q) { + const int in_pos = + ((in_pos_base + p) * dims_at_medium_dim + q) * copy_size; + const Scalar* in_ptr = input_data + in_pos; + if (q > sl) { + output_ptr = output_data + in_pos; + } else { + const int out_pos = + ((out_pos_base + p) * dims_at_medium_dim + sl - q) * + copy_size; + output_ptr = output_data + out_pos; + } + memcpy(output_ptr, in_ptr, copy_size * sizeof(Scalar)); + } + } + } + } + } +} + } // namespace reference_ops } // namespace tflite diff --git a/tensorflow/lite/kernels/register.cc b/tensorflow/lite/kernels/register.cc index 2a4654a8a3..d66bbf1bfb 100644 --- a/tensorflow/lite/kernels/register.cc +++ b/tensorflow/lite/kernels/register.cc @@ -138,6 +138,7 @@ TfLiteRegistration* Register_ADD_N(); TfLiteRegistration* Register_GATHER_ND(); TfLiteRegistration* Register_WHERE(); TfLiteRegistration* Register_ELU(); +TfLiteRegistration* Register_REVERSE_SEQUENCE(); TfLiteStatus UnsupportedTensorFlowOp(TfLiteContext* context, TfLiteNode* node) { context->ReportError( @@ -368,6 +369,7 @@ BuiltinOpResolver::BuiltinOpResolver() { AddBuiltin(BuiltinOperator_GATHER_ND, Register_GATHER_ND()); AddBuiltin(BuiltinOperator_WHERE, Register_WHERE()); AddBuiltin(BuiltinOperator_ELU, Register_ELU()); + AddBuiltin(BuiltinOperator_REVERSE_SEQUENCE, Register_REVERSE_SEQUENCE()); // TODO(andrewharp, ahentz): Move these somewhere more appropriate so that // custom ops aren't always included by default. diff --git a/tensorflow/lite/kernels/reverse_sequence.cc b/tensorflow/lite/kernels/reverse_sequence.cc new file mode 100644 index 0000000000..2b698bdb16 --- /dev/null +++ b/tensorflow/lite/kernels/reverse_sequence.cc @@ -0,0 +1,158 @@ +/* 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/lite/c/builtin_op_data.h" +#include "tensorflow/lite/c/c_api_internal.h" +#include "tensorflow/lite/kernels/internal/reference/reference_ops.h" +#include "tensorflow/lite/kernels/internal/tensor.h" +#include "tensorflow/lite/kernels/kernel_util.h" + +namespace tflite { +namespace ops { +namespace builtin { +namespace reverse_sequence { +namespace { + +constexpr int kInputTensor = 0; +constexpr int kSeqLengthsTensor = 1; +constexpr int kOutputTensor = 0; + +TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { + TF_LITE_ENSURE_EQ(context, NumInputs(node), 2); + TF_LITE_ENSURE_EQ(context, NumOutputs(node), 1); + + const TfLiteTensor* input = GetInput(context, node, kInputTensor); + const TfLiteTensor* seq_lengths = GetInput(context, node, kSeqLengthsTensor); + TF_LITE_ENSURE_EQ(context, NumDimensions(seq_lengths), 1); + + if (input->type != kTfLiteInt32 && input->type != kTfLiteFloat32 && + input->type != kTfLiteUInt8 && input->type != kTfLiteInt16 && + input->type != kTfLiteInt64) { + context->ReportError(context, + "Type '%s' is not supported by reverse_sequence.", + TfLiteTypeGetName(input->type)); + return kTfLiteError; + } + + if (seq_lengths->type != kTfLiteInt32 && seq_lengths->type != kTfLiteInt64) { + context->ReportError( + context, "Seq_lengths type '%s' is not supported by reverse_sequence.", + TfLiteTypeGetName(seq_lengths->type)); + return kTfLiteError; + } + + TfLiteTensor* output = GetOutput(context, node, kOutputTensor); + TfLiteIntArray* output_shape = TfLiteIntArrayCopy(input->dims); + TF_LITE_ENSURE_EQ(context, output->type, input->type); + + return context->ResizeTensor(context, output, output_shape); +} + +template +TfLiteStatus ReverseSequenceImpl(TfLiteContext* context, TfLiteNode* node) { + const TfLiteTensor* input = GetInput(context, node, kInputTensor); + const TfLiteTensor* seq_lengths_tensor = + GetInput(context, node, kSeqLengthsTensor); + const TS* seq_lengths = GetTensorData(seq_lengths_tensor); + + auto* params = + reinterpret_cast(node->builtin_data); + int seq_dim = params->seq_dim; + int batch_dim = params->batch_dim; + + TF_LITE_ENSURE(context, seq_dim >= 0); + TF_LITE_ENSURE(context, batch_dim >= 0); + TF_LITE_ENSURE(context, seq_dim != batch_dim); + TF_LITE_ENSURE(context, seq_dim < NumDimensions(input)); + TF_LITE_ENSURE(context, batch_dim < NumDimensions(input)); + TF_LITE_ENSURE_EQ(context, SizeOfDimension(seq_lengths_tensor, 0), + SizeOfDimension(input, batch_dim)); + for (int i = 0; i < NumDimensions(seq_lengths_tensor); ++i) { + TF_LITE_ENSURE(context, seq_lengths[i] <= SizeOfDimension(input, seq_dim)); + } + + TfLiteTensor* output = GetOutput(context, node, kOutputTensor); + + reference_ops::ReverseSequence( + seq_lengths, seq_dim, batch_dim, GetTensorShape(input), + GetTensorData(input), GetTensorShape(output), + GetTensorData(output)); + + return kTfLiteOk; +} + +template +TfLiteStatus ReverseSequenceHelper(TfLiteContext* context, TfLiteNode* node) { + const TfLiteTensor* seq_lengths_tensor = + GetInput(context, node, kSeqLengthsTensor); + switch (seq_lengths_tensor->type) { + case kTfLiteInt32: { + return ReverseSequenceImpl(context, node); + } + case kTfLiteInt64: { + return ReverseSequenceImpl(context, node); + } + default: { + context->ReportError( + context, + "Seq_lengths type '%s' is not supported by reverse_sequence.", + TfLiteTypeGetName(seq_lengths_tensor->type)); + return kTfLiteError; + } + } + return kTfLiteOk; +} + +TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { + TfLiteTensor* output = GetOutput(context, node, kOutputTensor); + + switch (output->type) { + case kTfLiteFloat32: { + return ReverseSequenceHelper(context, node); + } + case kTfLiteUInt8: { + return ReverseSequenceHelper(context, node); + } + case kTfLiteInt16: { + return ReverseSequenceHelper(context, node); + } + case kTfLiteInt32: { + return ReverseSequenceHelper(context, node); + } + case kTfLiteInt64: { + return ReverseSequenceHelper(context, node); + } + default: { + context->ReportError(context, + "Type '%s' is not supported by reverse_sequence.", + TfLiteTypeGetName(output->type)); + return kTfLiteError; + } + } + return kTfLiteOk; +} // namespace + +} // namespace +} // namespace reverse_sequence + +TfLiteRegistration* Register_REVERSE_SEQUENCE() { + static TfLiteRegistration r = {nullptr, nullptr, reverse_sequence::Prepare, + reverse_sequence::Eval}; + return &r; +} + +} // namespace builtin +} // namespace ops +} // namespace tflite diff --git a/tensorflow/lite/kernels/reverse_sequence_test.cc b/tensorflow/lite/kernels/reverse_sequence_test.cc new file mode 100644 index 0000000000..e81f138029 --- /dev/null +++ b/tensorflow/lite/kernels/reverse_sequence_test.cc @@ -0,0 +1,211 @@ +/* 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/lite/interpreter.h" +#include "tensorflow/lite/kernels/register.h" +#include "tensorflow/lite/kernels/test_util.h" +#include "tensorflow/lite/model.h" + +namespace tflite { +namespace { + +using ::testing::ElementsAre; +using ::testing::ElementsAreArray; + +template +class ReverseSequenceOpModel : public SingleOpModel { + public: + ReverseSequenceOpModel(const TensorData& input, const TensorData& seq_lengths, + int seq_dim, int batch_dim) { + input_ = AddInput(input); + seq_lengths_ = AddInput(seq_lengths); + + output_ = AddOutput({input.type, {}}); + + SetBuiltinOp( + BuiltinOperator_REVERSE_SEQUENCE, BuiltinOptions_ReverseSequenceOptions, + CreateReverseSequenceOptions(builder_, seq_dim, batch_dim).Union()); + BuildInterpreter({GetShape(input_)}); + } + + int input() { return input_; } + int seq_lengths() { return seq_lengths_; } + + std::vector GetOutput() { return ExtractVector(output_); } + std::vector GetOutputShape() { return GetTensorShape(output_); } + + private: + int input_; + int seq_lengths_; + int output_; +}; + +// float32 tests +TEST(ReverseSequenceOpTest, FloatSeqDimIsGreater) { + ReverseSequenceOpModel model({TensorType_FLOAT32, {4, 3, 2}}, + {TensorType_INT32, {4}}, 1, 0); + model.PopulateTensor(model.input(), + {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}); + model.PopulateTensor(model.seq_lengths(), {3, 2, 3, 3}); + model.Invoke(); + EXPECT_THAT(model.GetOutputShape(), ElementsAre(4, 3, 2)); + EXPECT_THAT( + model.GetOutput(), + ElementsAreArray({5, 6, 3, 4, 1, 2, 9, 10, 7, 8, 11, 12, + 17, 18, 15, 16, 13, 14, 23, 24, 21, 22, 19, 20})); +} + +TEST(ReverseSequenceOpTest, FloatBatchDimIsGreater) { + ReverseSequenceOpModel model({TensorType_FLOAT32, {4, 3, 2}}, + {TensorType_INT32, {2}}, 0, 2); + model.PopulateTensor(model.input(), + {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}); + model.PopulateTensor(model.seq_lengths(), {3, 4}); + model.Invoke(); + EXPECT_THAT(model.GetOutputShape(), ElementsAre(4, 3, 2)); + EXPECT_THAT(model.GetOutput(), + ElementsAreArray({13, 20, 15, 22, 17, 24, 7, 14, 9, 16, 11, 18, 1, + 8, 3, 10, 5, 12, 19, 2, 21, 4, 23, 6})); +} + +// int32 tests +TEST(ReverseSequenceOpTest, Int32SeqDimIsGreater) { + ReverseSequenceOpModel model({TensorType_INT32, {4, 3, 2}}, + {TensorType_INT32, {4}}, 1, 0); + model.PopulateTensor( + model.input(), {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}); + model.PopulateTensor(model.seq_lengths(), {3, 2, 3, 3}); + model.Invoke(); + EXPECT_THAT(model.GetOutputShape(), ElementsAre(4, 3, 2)); + EXPECT_THAT( + model.GetOutput(), + ElementsAreArray({5, 6, 3, 4, 1, 2, 9, 10, 7, 8, 11, 12, + 17, 18, 15, 16, 13, 14, 23, 24, 21, 22, 19, 20})); +} + +TEST(ReverseSequenceOpTest, Int32BatchDimIsGreater) { + ReverseSequenceOpModel model({TensorType_INT32, {4, 3, 2}}, + {TensorType_INT32, {2}}, 0, 2); + model.PopulateTensor( + model.input(), {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}); + model.PopulateTensor(model.seq_lengths(), {3, 4}); + model.Invoke(); + EXPECT_THAT(model.GetOutputShape(), ElementsAre(4, 3, 2)); + EXPECT_THAT(model.GetOutput(), + ElementsAreArray({13, 20, 15, 22, 17, 24, 7, 14, 9, 16, 11, 18, 1, + 8, 3, 10, 5, 12, 19, 2, 21, 4, 23, 6})); +} + +// int64 tests +TEST(ReverseSequenceOpTest, Int64SeqDimIsGreater) { + ReverseSequenceOpModel model({TensorType_INT64, {4, 3, 2}}, + {TensorType_INT32, {4}}, 1, 0); + model.PopulateTensor( + model.input(), {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}); + model.PopulateTensor(model.seq_lengths(), {3, 2, 3, 3}); + model.Invoke(); + EXPECT_THAT(model.GetOutputShape(), ElementsAre(4, 3, 2)); + EXPECT_THAT( + model.GetOutput(), + ElementsAreArray({5, 6, 3, 4, 1, 2, 9, 10, 7, 8, 11, 12, + 17, 18, 15, 16, 13, 14, 23, 24, 21, 22, 19, 20})); +} + +TEST(ReverseSequenceOpTest, Int64BatchDimIsGreater) { + ReverseSequenceOpModel model({TensorType_INT64, {4, 3, 2}}, + {TensorType_INT32, {2}}, 0, 2); + model.PopulateTensor( + model.input(), {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}); + model.PopulateTensor(model.seq_lengths(), {3, 4}); + model.Invoke(); + EXPECT_THAT(model.GetOutputShape(), ElementsAre(4, 3, 2)); + EXPECT_THAT(model.GetOutput(), + ElementsAreArray({13, 20, 15, 22, 17, 24, 7, 14, 9, 16, 11, 18, 1, + 8, 3, 10, 5, 12, 19, 2, 21, 4, 23, 6})); +} + +// uint8 tests +TEST(ReverseSequenceOpTest, Uint8SeqDimIsGreater) { + ReverseSequenceOpModel model({TensorType_UINT8, {4, 3, 2}}, + {TensorType_INT32, {4}}, 1, 0); + model.PopulateTensor( + model.input(), {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}); + model.PopulateTensor(model.seq_lengths(), {3, 2, 3, 3}); + model.Invoke(); + EXPECT_THAT(model.GetOutputShape(), ElementsAre(4, 3, 2)); + EXPECT_THAT( + model.GetOutput(), + ElementsAreArray({5, 6, 3, 4, 1, 2, 9, 10, 7, 8, 11, 12, + 17, 18, 15, 16, 13, 14, 23, 24, 21, 22, 19, 20})); +} + +TEST(ReverseSequenceOpTest, Uint8BatchDimIsGreater) { + ReverseSequenceOpModel model({TensorType_UINT8, {4, 3, 2}}, + {TensorType_INT32, {2}}, 0, 2); + model.PopulateTensor( + model.input(), {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}); + model.PopulateTensor(model.seq_lengths(), {3, 4}); + model.Invoke(); + EXPECT_THAT(model.GetOutputShape(), ElementsAre(4, 3, 2)); + EXPECT_THAT(model.GetOutput(), + ElementsAreArray({13, 20, 15, 22, 17, 24, 7, 14, 9, 16, 11, 18, 1, + 8, 3, 10, 5, 12, 19, 2, 21, 4, 23, 6})); +} + +// int16 tests +TEST(ReverseSequenceOpTest, Int16SeqDimIsGreater) { + ReverseSequenceOpModel model({TensorType_INT16, {4, 3, 2}}, + {TensorType_INT32, {4}}, 1, 0); + model.PopulateTensor( + model.input(), {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}); + model.PopulateTensor(model.seq_lengths(), {3, 2, 3, 3}); + model.Invoke(); + EXPECT_THAT(model.GetOutputShape(), ElementsAre(4, 3, 2)); + EXPECT_THAT( + model.GetOutput(), + ElementsAreArray({5, 6, 3, 4, 1, 2, 9, 10, 7, 8, 11, 12, + 17, 18, 15, 16, 13, 14, 23, 24, 21, 22, 19, 20})); +} + +TEST(ReverseSequenceOpTest, Int16BatchDimIsGreater) { + ReverseSequenceOpModel model({TensorType_INT16, {4, 3, 2}}, + {TensorType_INT32, {2}}, 0, 2); + model.PopulateTensor( + model.input(), {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}); + model.PopulateTensor(model.seq_lengths(), {3, 4}); + model.Invoke(); + EXPECT_THAT(model.GetOutputShape(), ElementsAre(4, 3, 2)); + EXPECT_THAT(model.GetOutput(), + ElementsAreArray({13, 20, 15, 22, 17, 24, 7, 14, 9, 16, 11, 18, 1, + 8, 3, 10, 5, 12, 19, 2, 21, 4, 23, 6})); +} + +} // namespace +} // namespace tflite + +int main(int argc, char** argv) { + ::tflite::LogToStderr(); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tensorflow/lite/nnapi_delegate.cc b/tensorflow/lite/nnapi_delegate.cc index d0605ef6b3..443651b991 100644 --- a/tensorflow/lite/nnapi_delegate.cc +++ b/tensorflow/lite/nnapi_delegate.cc @@ -670,6 +670,7 @@ TfLiteStatus AddOpsAndParams( case tflite::BuiltinOperator_WHERE: case tflite::BuiltinOperator_RANK: case tflite::BuiltinOperator_ELU: + case tflite::BuiltinOperator_REVERSE_SEQUENCE: logError("Op code %d is currently not delegated to NNAPI", builtin); return kTfLiteError; break; diff --git a/tensorflow/lite/schema/schema.fbs b/tensorflow/lite/schema/schema.fbs index 69a0a0aceb..c6c61a602a 100644 --- a/tensorflow/lite/schema/schema.fbs +++ b/tensorflow/lite/schema/schema.fbs @@ -225,6 +225,7 @@ enum BuiltinOperator : byte { WHERE = 109, RANK = 110, ELU = 111, + REVERSE_SEQUENCE = 112, } // Options for the builtin operators. @@ -315,6 +316,7 @@ union BuiltinOptions { CosOptions, WhereOptions, RankOptions, + ReverseSequenceOptions, } enum Padding : byte { SAME, VALID } @@ -750,6 +752,10 @@ table GatherNdOptions { table WhereOptions { } +table ReverseSequenceOptions { + seq_dim:int; + batch_dim:int = 0; +} // 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/lite/schema/schema_generated.h b/tensorflow/lite/schema/schema_generated.h index ad9ae192db..2a55698a61 100755 --- a/tensorflow/lite/schema/schema_generated.h +++ b/tensorflow/lite/schema/schema_generated.h @@ -289,6 +289,9 @@ struct GatherNdOptionsT; struct WhereOptions; struct WhereOptionsT; +struct ReverseSequenceOptions; +struct ReverseSequenceOptionsT; + struct OperatorCode; struct OperatorCodeT; @@ -550,11 +553,12 @@ enum BuiltinOperator { BuiltinOperator_WHERE = 109, BuiltinOperator_RANK = 110, BuiltinOperator_ELU = 111, + BuiltinOperator_REVERSE_SEQUENCE = 112, BuiltinOperator_MIN = BuiltinOperator_ADD, - BuiltinOperator_MAX = BuiltinOperator_ELU + BuiltinOperator_MAX = BuiltinOperator_REVERSE_SEQUENCE }; -inline const BuiltinOperator (&EnumValuesBuiltinOperator())[111] { +inline const BuiltinOperator (&EnumValuesBuiltinOperator())[112] { static const BuiltinOperator values[] = { BuiltinOperator_ADD, BuiltinOperator_AVERAGE_POOL_2D, @@ -666,7 +670,8 @@ inline const BuiltinOperator (&EnumValuesBuiltinOperator())[111] { BuiltinOperator_COS, BuiltinOperator_WHERE, BuiltinOperator_RANK, - BuiltinOperator_ELU + BuiltinOperator_ELU, + BuiltinOperator_REVERSE_SEQUENCE }; return values; } @@ -785,6 +790,7 @@ inline const char * const *EnumNamesBuiltinOperator() { "WHERE", "RANK", "ELU", + "REVERSE_SEQUENCE", nullptr }; return names; @@ -883,11 +889,12 @@ enum BuiltinOptions { BuiltinOptions_CosOptions = 84, BuiltinOptions_WhereOptions = 85, BuiltinOptions_RankOptions = 86, + BuiltinOptions_ReverseSequenceOptions = 87, BuiltinOptions_MIN = BuiltinOptions_NONE, - BuiltinOptions_MAX = BuiltinOptions_RankOptions + BuiltinOptions_MAX = BuiltinOptions_ReverseSequenceOptions }; -inline const BuiltinOptions (&EnumValuesBuiltinOptions())[87] { +inline const BuiltinOptions (&EnumValuesBuiltinOptions())[88] { static const BuiltinOptions values[] = { BuiltinOptions_NONE, BuiltinOptions_Conv2DOptions, @@ -975,7 +982,8 @@ inline const BuiltinOptions (&EnumValuesBuiltinOptions())[87] { BuiltinOptions_GatherNdOptions, BuiltinOptions_CosOptions, BuiltinOptions_WhereOptions, - BuiltinOptions_RankOptions + BuiltinOptions_RankOptions, + BuiltinOptions_ReverseSequenceOptions }; return values; } @@ -1069,6 +1077,7 @@ inline const char * const *EnumNamesBuiltinOptions() { "CosOptions", "WhereOptions", "RankOptions", + "ReverseSequenceOptions", nullptr }; return names; @@ -1427,6 +1436,10 @@ template<> struct BuiltinOptionsTraits { static const BuiltinOptions enum_value = BuiltinOptions_RankOptions; }; +template<> struct BuiltinOptionsTraits { + static const BuiltinOptions enum_value = BuiltinOptions_ReverseSequenceOptions; +}; + struct BuiltinOptionsUnion { BuiltinOptions type; void *value; @@ -2146,6 +2159,14 @@ struct BuiltinOptionsUnion { return type == BuiltinOptions_RankOptions ? reinterpret_cast(value) : nullptr; } + ReverseSequenceOptionsT *AsReverseSequenceOptions() { + return type == BuiltinOptions_ReverseSequenceOptions ? + reinterpret_cast(value) : nullptr; + } + const ReverseSequenceOptionsT *AsReverseSequenceOptions() const { + return type == BuiltinOptions_ReverseSequenceOptions ? + reinterpret_cast(value) : nullptr; + } }; bool VerifyBuiltinOptions(flatbuffers::Verifier &verifier, const void *obj, BuiltinOptions type); @@ -7482,6 +7503,72 @@ inline flatbuffers::Offset CreateWhereOptions( flatbuffers::Offset CreateWhereOptions(flatbuffers::FlatBufferBuilder &_fbb, const WhereOptionsT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); +struct ReverseSequenceOptionsT : public flatbuffers::NativeTable { + typedef ReverseSequenceOptions TableType; + int32_t seq_dim; + int32_t batch_dim; + ReverseSequenceOptionsT() + : seq_dim(0), + batch_dim(0) { + } +}; + +struct ReverseSequenceOptions FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { + typedef ReverseSequenceOptionsT NativeTableType; + enum { + VT_SEQ_DIM = 4, + VT_BATCH_DIM = 6 + }; + int32_t seq_dim() const { + return GetField(VT_SEQ_DIM, 0); + } + int32_t batch_dim() const { + return GetField(VT_BATCH_DIM, 0); + } + bool Verify(flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyField(verifier, VT_SEQ_DIM) && + VerifyField(verifier, VT_BATCH_DIM) && + verifier.EndTable(); + } + ReverseSequenceOptionsT *UnPack(const flatbuffers::resolver_function_t *_resolver = nullptr) const; + void UnPackTo(ReverseSequenceOptionsT *_o, const flatbuffers::resolver_function_t *_resolver = nullptr) const; + static flatbuffers::Offset Pack(flatbuffers::FlatBufferBuilder &_fbb, const ReverseSequenceOptionsT* _o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); +}; + +struct ReverseSequenceOptionsBuilder { + flatbuffers::FlatBufferBuilder &fbb_; + flatbuffers::uoffset_t start_; + void add_seq_dim(int32_t seq_dim) { + fbb_.AddElement(ReverseSequenceOptions::VT_SEQ_DIM, seq_dim, 0); + } + void add_batch_dim(int32_t batch_dim) { + fbb_.AddElement(ReverseSequenceOptions::VT_BATCH_DIM, batch_dim, 0); + } + explicit ReverseSequenceOptionsBuilder(flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ReverseSequenceOptionsBuilder &operator=(const ReverseSequenceOptionsBuilder &); + flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = flatbuffers::Offset(end); + return o; + } +}; + +inline flatbuffers::Offset CreateReverseSequenceOptions( + flatbuffers::FlatBufferBuilder &_fbb, + int32_t seq_dim = 0, + int32_t batch_dim = 0) { + ReverseSequenceOptionsBuilder builder_(_fbb); + builder_.add_batch_dim(batch_dim); + builder_.add_seq_dim(seq_dim); + return builder_.Finish(); +} + +flatbuffers::Offset CreateReverseSequenceOptions(flatbuffers::FlatBufferBuilder &_fbb, const ReverseSequenceOptionsT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); + struct OperatorCodeT : public flatbuffers::NativeTable { typedef OperatorCode TableType; BuiltinOperator builtin_code; @@ -7873,6 +7960,9 @@ struct Operator FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { const RankOptions *builtin_options_as_RankOptions() const { return builtin_options_type() == BuiltinOptions_RankOptions ? static_cast(builtin_options()) : nullptr; } + const ReverseSequenceOptions *builtin_options_as_ReverseSequenceOptions() const { + return builtin_options_type() == BuiltinOptions_ReverseSequenceOptions ? static_cast(builtin_options()) : nullptr; + } const flatbuffers::Vector *custom_options() const { return GetPointer *>(VT_CUSTOM_OPTIONS); } @@ -8248,6 +8338,10 @@ template<> inline const RankOptions *Operator::builtin_options_as() return builtin_options_as_RankOptions(); } +template<> inline const ReverseSequenceOptions *Operator::builtin_options_as() const { + return builtin_options_as_ReverseSequenceOptions(); +} + struct OperatorBuilder { flatbuffers::FlatBufferBuilder &fbb_; flatbuffers::uoffset_t start_; @@ -11033,6 +11127,35 @@ inline flatbuffers::Offset CreateWhereOptions(flatbuffers::FlatBuf _fbb); } +inline ReverseSequenceOptionsT *ReverseSequenceOptions::UnPack(const flatbuffers::resolver_function_t *_resolver) const { + auto _o = new ReverseSequenceOptionsT(); + UnPackTo(_o, _resolver); + return _o; +} + +inline void ReverseSequenceOptions::UnPackTo(ReverseSequenceOptionsT *_o, const flatbuffers::resolver_function_t *_resolver) const { + (void)_o; + (void)_resolver; + { auto _e = seq_dim(); _o->seq_dim = _e; }; + { auto _e = batch_dim(); _o->batch_dim = _e; }; +} + +inline flatbuffers::Offset ReverseSequenceOptions::Pack(flatbuffers::FlatBufferBuilder &_fbb, const ReverseSequenceOptionsT* _o, const flatbuffers::rehasher_function_t *_rehasher) { + return CreateReverseSequenceOptions(_fbb, _o, _rehasher); +} + +inline flatbuffers::Offset CreateReverseSequenceOptions(flatbuffers::FlatBufferBuilder &_fbb, const ReverseSequenceOptionsT *_o, const flatbuffers::rehasher_function_t *_rehasher) { + (void)_rehasher; + (void)_o; + struct _VectorArgs { flatbuffers::FlatBufferBuilder *__fbb; const ReverseSequenceOptionsT* __o; const flatbuffers::rehasher_function_t *__rehasher; } _va = { &_fbb, _o, _rehasher}; (void)_va; + auto _seq_dim = _o->seq_dim; + auto _batch_dim = _o->batch_dim; + return tflite::CreateReverseSequenceOptions( + _fbb, + _seq_dim, + _batch_dim); +} + inline OperatorCodeT *OperatorCode::UnPack(const flatbuffers::resolver_function_t *_resolver) const { auto _o = new OperatorCodeT(); UnPackTo(_o, _resolver); @@ -11635,6 +11758,10 @@ inline bool VerifyBuiltinOptions(flatbuffers::Verifier &verifier, const void *ob auto ptr = reinterpret_cast(obj); return verifier.VerifyTable(ptr); } + case BuiltinOptions_ReverseSequenceOptions: { + auto ptr = reinterpret_cast(obj); + return verifier.VerifyTable(ptr); + } default: return false; } } @@ -11997,6 +12124,10 @@ inline void *BuiltinOptionsUnion::UnPack(const void *obj, BuiltinOptions type, c auto ptr = reinterpret_cast(obj); return ptr->UnPack(resolver); } + case BuiltinOptions_ReverseSequenceOptions: { + auto ptr = reinterpret_cast(obj); + return ptr->UnPack(resolver); + } default: return nullptr; } } @@ -12347,6 +12478,10 @@ inline flatbuffers::Offset BuiltinOptionsUnion::Pack(flatbuffers::FlatBuff auto ptr = reinterpret_cast(value); return CreateRankOptions(_fbb, ptr, _rehasher).Union(); } + case BuiltinOptions_ReverseSequenceOptions: { + auto ptr = reinterpret_cast(value); + return CreateReverseSequenceOptions(_fbb, ptr, _rehasher).Union(); + } default: return 0; } } @@ -12697,6 +12832,10 @@ inline BuiltinOptionsUnion::BuiltinOptionsUnion(const BuiltinOptionsUnion &u) FL value = new RankOptionsT(*reinterpret_cast(u.value)); break; } + case BuiltinOptions_ReverseSequenceOptions: { + value = new ReverseSequenceOptionsT(*reinterpret_cast(u.value)); + break; + } default: break; } @@ -13134,6 +13273,11 @@ inline void BuiltinOptionsUnion::Reset() { delete ptr; break; } + case BuiltinOptions_ReverseSequenceOptions: { + auto ptr = reinterpret_cast(value); + delete ptr; + break; + } default: break; } value = nullptr; diff --git a/tensorflow/lite/testing/generate_examples.py b/tensorflow/lite/testing/generate_examples.py index 20c23017c5..6e40f5ee0a 100644 --- a/tensorflow/lite/testing/generate_examples.py +++ b/tensorflow/lite/testing/generate_examples.py @@ -4278,6 +4278,53 @@ def make_reverse_v2_tests(zip_path): make_zip_of_tests(zip_path, test_parameters, build_graph, build_inputs) +def make_reverse_sequence_tests(zip_path): + """Make a set of tests to do reverse_sequence.""" + + test_parameters = [ + { + "input_dtype": [tf.float32, tf.int32, tf.int64], + "input_shape": [[8, 4, 5, 5, 6], [4, 4, 3, 5]], + "seq_lengths": [[2, 2, 2, 2], [2, 1, 1, 0]], + "seq_axis": [0, 3], + "batch_axis": [1] + }, + { + "input_dtype": [tf.float32], + "input_shape": [[2, 4, 5, 5, 6]], + "seq_lengths": [[2, 1]], + "seq_axis": [2], + "batch_axis": [0] + }, + { + "input_dtype": [tf.float32], + "input_shape": [[4, 2]], + "seq_lengths": [[3, 1]], + "seq_axis": [0], + "batch_axis": [1] + }] + + def build_graph(parameters): + input_value = tf.placeholder( + dtype=parameters["input_dtype"], + name="input", + shape=parameters["input_shape"]) + outs = tf.reverse_sequence( + input_value, + seq_lengths=parameters["seq_lengths"], + batch_axis=parameters["batch_axis"], + seq_axis=parameters["seq_axis"]) + return [input_value], [outs] + + def build_inputs(parameters, sess, inputs, outputs): + input_value = create_tensor_data(parameters["input_dtype"], + parameters["input_shape"]) + return [input_value], sess.run( + outputs, feed_dict=dict(zip(inputs, [input_value]))) + + make_zip_of_tests(zip_path, test_parameters, build_graph, build_inputs) + + # Toco binary path provided by the generate rule. bin_path = None diff --git a/tensorflow/lite/toco/export_tensorflow.cc b/tensorflow/lite/toco/export_tensorflow.cc index c2952c7dd1..d426a69067 100644 --- a/tensorflow/lite/toco/export_tensorflow.cc +++ b/tensorflow/lite/toco/export_tensorflow.cc @@ -22,11 +22,6 @@ limitations under the License. #include "google/protobuf/text_format.h" #include "absl/memory/memory.h" #include "absl/strings/string_view.h" -#include "tensorflow/lite/toco/model.h" -#include "tensorflow/lite/toco/model_flags.pb.h" -#include "tensorflow/lite/toco/runtime/types.h" -#include "tensorflow/lite/toco/tensorflow_util.h" -#include "tensorflow/lite/toco/tooling_util.h" #include "tensorflow/core/framework/attr_value.pb.h" #include "tensorflow/core/framework/graph.pb.h" #include "tensorflow/core/framework/node_def.pb.h" @@ -34,6 +29,11 @@ limitations under the License. #include "tensorflow/core/framework/tensor_shape.pb.h" #include "tensorflow/core/framework/types.pb.h" #include "tensorflow/core/platform/logging.h" +#include "tensorflow/lite/toco/model.h" +#include "tensorflow/lite/toco/model_flags.pb.h" +#include "tensorflow/lite/toco/runtime/types.h" +#include "tensorflow/lite/toco/tensorflow_util.h" +#include "tensorflow/lite/toco/tooling_util.h" using tensorflow::DT_BOOL; using tensorflow::DT_COMPLEX64; @@ -2077,6 +2077,19 @@ void ConvertReverseV2Operator(const Model& model, (*reverse_v2_op->mutable_attr())["T"].set_type(data_type); } +void ConvertReverseSequenceOperator(const Model& model, + const ReverseSequenceOperator& src_op, + GraphDef* tensorflow_graph) { + tensorflow::NodeDef* reverse_seq_op = tensorflow_graph->add_node(); + reverse_seq_op->set_op("ReverseSequence"); + reverse_seq_op->set_name(src_op.outputs[0]); + CHECK_EQ(src_op.inputs.size(), 2); + *reverse_seq_op->add_input() = src_op.inputs[0]; + *reverse_seq_op->add_input() = src_op.inputs[1]; + (*reverse_seq_op->mutable_attr())["seq_dim"].set_i(src_op.seq_dim); + (*reverse_seq_op->mutable_attr())["batch_dim"].set_i(src_op.batch_dim); +} + void ConvertOperator(const Model& model, const Operator& src_op, GraphDef* tensorflow_graph) { if (src_op.fused_activation_function != FusedActivationFunctionType::kNone) { @@ -2361,6 +2374,10 @@ void ConvertOperator(const Model& model, const Operator& src_op, ConvertReverseV2Operator(model, static_cast(src_op), "Reverse_V2", tensorflow_graph); + } else if (src_op.type == OperatorType::kReverseSequence) { + ConvertReverseSequenceOperator( + model, static_cast(src_op), + tensorflow_graph); } else { LOG(FATAL) << "Unhandled operator type " << OperatorTypeName(src_op.type); } diff --git a/tensorflow/lite/toco/graph_transformations/propagate_fixed_sizes.cc b/tensorflow/lite/toco/graph_transformations/propagate_fixed_sizes.cc index 1ea4b29883..99e67c7ce5 100644 --- a/tensorflow/lite/toco/graph_transformations/propagate_fixed_sizes.cc +++ b/tensorflow/lite/toco/graph_transformations/propagate_fixed_sizes.cc @@ -2096,6 +2096,7 @@ void ProcessUniqueOperator(Model* model, UniqueOperator* op) { case OperatorType::kLogicalOr: case OperatorType::kZerosLike: case OperatorType::kReverseV2: + case OperatorType::kReverseSequence: ProcessSimpleOperator(model, op, 0); break; case OperatorType::kGather: diff --git a/tensorflow/lite/toco/import_tensorflow.cc b/tensorflow/lite/toco/import_tensorflow.cc index db7c5e6fd5..7ef04b07c5 100644 --- a/tensorflow/lite/toco/import_tensorflow.cc +++ b/tensorflow/lite/toco/import_tensorflow.cc @@ -2025,6 +2025,27 @@ tensorflow::Status ConvertShapeOperator( return tensorflow::Status::OK(); } +tensorflow::Status ConvertReverseSequenceOperator( + const NodeDef& node, const TensorFlowImportFlags& tf_import_flags, + Model* model) { + CHECK_EQ(node.op(), "ReverseSequence"); + TF_QCHECK_OK(CheckInputsCount(node, tf_import_flags, 2)); + auto op = absl::make_unique(); + if (HasAttr(node, "seq_dim")) { + op->seq_dim = GetIntAttr(node, "seq_dim"); + } + // In tf.reverse_sequence, batch_dim defaults to 0. + op->batch_dim = + HasAttr(node, "batch_dim") ? GetIntAttr(node, "batch_dim") : 0; + const int num_inputs = GetInputsCount(node, tf_import_flags); + for (int i = 0; i < num_inputs; ++i) { + op->inputs.push_back(node.input(i)); + } + op->outputs.push_back(node.name()); + model->operators.push_back(std::move(op)); + return tensorflow::Status::OK(); +} + void StripCaretFromArrayNames(Model* model) { for (auto& op : model->operators) { for (auto& input : op->inputs) { @@ -2480,6 +2501,7 @@ ConverterMapType GetTensorFlowNodeConverterMap() { {"Reshape", ConvertSimpleOperator}, {"ResizeBilinear", ConvertResizeBilinearOperator}, {"ResizeNearestNeighbor", ConvertResizeNearestNeighborOperator}, + {"ReverseSequence", ConvertReverseSequenceOperator}, {"ReverseV2", ConvertSimpleOperator}, {"Rsqrt", ConvertSimpleOperator}, {"Select", ConvertSimpleOperator}, diff --git a/tensorflow/lite/toco/model.h b/tensorflow/lite/toco/model.h index d9909c11fa..46f70c9e37 100644 --- a/tensorflow/lite/toco/model.h +++ b/tensorflow/lite/toco/model.h @@ -167,7 +167,8 @@ enum class OperatorType : uint8 { kBidirectionalSequenceRnn, kGatherNd, kWhere, - kElu + kElu, + kReverseSequence }; // Helper to deal with TensorFlow arrays using a different ordering of @@ -2031,6 +2032,19 @@ struct MirrorPadOperator : Operator { MirrorPadMode mode; }; +// ReverseSequence operator: +// +// Inputs: +// Inputs[0]: required: the input array. +// Inputs[1]: required: the lengths of the elements to be reversed. +// +// TensorFlow equivalent: tf.reverse_sequence. +struct ReverseSequenceOperator : Operator { + ReverseSequenceOperator() : Operator(OperatorType::kReverseSequence) {} + int seq_dim; + int batch_dim = 0; +}; + // Unique Operator: // // Inputs: diff --git a/tensorflow/lite/toco/tflite/operator.cc b/tensorflow/lite/toco/tflite/operator.cc index 6c83ef6288..a441fef861 100644 --- a/tensorflow/lite/toco/tflite/operator.cc +++ b/tensorflow/lite/toco/tflite/operator.cc @@ -2191,6 +2191,31 @@ class Dequantize } }; +class ReverseSequence + : public BuiltinOperator { + public: + using BuiltinOperator::BuiltinOperator; + + flatbuffers::Offset WriteOptions( + const TocoOperator& op, + flatbuffers::FlatBufferBuilder* builder) const override { + return ::tflite::CreateReverseSequenceOptions(*builder, op.seq_dim, + op.batch_dim); + } + + void ReadOptions(const TfLiteOptions& options, + TocoOperator* op) const override { + op->seq_dim = options.seq_dim(); + op->batch_dim = options.batch_dim(); + } + + int GetVersion(const OperatorSignature& op_signature) const override { + return 1; + } +}; + class Equal : public SimpleOperator { public: explicit Equal() : SimpleOperator("EQUAL", OperatorType::kEqual) {} @@ -2434,6 +2459,9 @@ std::vector> BuildOperatorList( OperatorType::kUnidirectionalSequenceRnn)); ops.push_back( MakeUnique(::tflite::BuiltinOperator_WHERE, OperatorType::kWhere)); + ops.push_back( + MakeUnique(::tflite::BuiltinOperator_REVERSE_SEQUENCE, + OperatorType::kReverseSequence)); // Custom Operators. ops.push_back( diff --git a/tensorflow/lite/toco/tflite/operator_test.cc b/tensorflow/lite/toco/tflite/operator_test.cc index ac3b847779..8cba2753db 100644 --- a/tensorflow/lite/toco/tflite/operator_test.cc +++ b/tensorflow/lite/toco/tflite/operator_test.cc @@ -732,6 +732,17 @@ TEST_F(OperatorTest, BuiltinUnique) { EXPECT_EQ(output_toco_op->idx_out_type, op.idx_out_type); } +TEST_F(OperatorTest, BuiltinReverseSequence) { + ReverseSequenceOperator op; + op.seq_dim = 3; + op.batch_dim = 1; + std::unique_ptr output_toco_op = + SerializeAndDeserialize( + GetOperator("REVERSE_SEQUENCE", OperatorType::kReverseSequence), op); + EXPECT_EQ(op.seq_dim, output_toco_op->seq_dim); + EXPECT_EQ(op.batch_dim, output_toco_op->batch_dim); +} + // Test version for a simple Op with 2 versions and the input type controls the // version. template diff --git a/tensorflow/lite/toco/tooling_util.cc b/tensorflow/lite/toco/tooling_util.cc index 4177335638..82a86d7fc8 100644 --- a/tensorflow/lite/toco/tooling_util.cc +++ b/tensorflow/lite/toco/tooling_util.cc @@ -426,6 +426,7 @@ const char* OperatorTypeName(OperatorType type) { HANDLE_OPERATORTYPENAME_CASE(ReverseV2) HANDLE_OPERATORTYPENAME_CASE(Cos) HANDLE_OPERATORTYPENAME_CASE(Where) + HANDLE_OPERATORTYPENAME_CASE(ReverseSequence) default: LOG(FATAL) << "Unhandled op type"; #undef HANDLE_OPERATORTYPENAME_CASE -- GitLab From 6bcdd92fa68c05f98e66d7d1bcf4c41de6698345 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Sat, 23 Feb 2019 14:31:10 +0800 Subject: [PATCH 402/766] Refactor ConcatenateDatasetOpTest and ZipDatasetOpTest --- .../data/concatenate_dataset_op_test.cc | 65 +-- .../core/kernels/data/dataset_test_base.cc | 1 - .../core/kernels/data/zip_dataset_op_test.cc | 411 +++++------------- 3 files changed, 123 insertions(+), 354 deletions(-) diff --git a/tensorflow/core/kernels/data/concatenate_dataset_op_test.cc b/tensorflow/core/kernels/data/concatenate_dataset_op_test.cc index 38d5acb580..e0a0f7174a 100644 --- a/tensorflow/core/kernels/data/concatenate_dataset_op_test.cc +++ b/tensorflow/core/kernels/data/concatenate_dataset_op_test.cc @@ -51,7 +51,7 @@ class ConcatenateDatasetOpTest : public DatasetOpsTestBase { node_def_ = test::function::NDef( kNodeName, kOpName, {"input_dataset", "another_dataset"}, {{"output_types", output_tyeps}, {"output_shapes", output_shapes}}); - TF_CHECK_OK(CreateOpKernel(node_def_, op_kernel)); + TF_RETURN_IF_ERROR(CreateOpKernel(node_def_, op_kernel)); return Status::OK(); } @@ -124,11 +124,13 @@ struct TestParam { /*expected_cardinality*/ 4, /*breakpoints*/ {0, 2, 5}}}; -struct ConcatenateDatasetOpTestHelper : ConcatenateDatasetOpTest { +class ConcatenateDatasetOpTestHelper : public ConcatenateDatasetOpTest { + public: ~ConcatenateDatasetOpTestHelper() { if (dataset) dataset->Unref(); } + protected: Status CreateDatasetFromTestCase(const TestParam &test_case) { std::vector tensor_slice_dataset_tensors; TF_RETURN_IF_ERROR(CreateTensorSliceDatasetTensors( @@ -163,10 +165,11 @@ struct ConcatenateDatasetOpTestHelper : ConcatenateDatasetOpTest { std::unique_ptr iterator; }; -struct DatasetGetNextTest : public ConcatenateDatasetOpTestHelper, - ::testing::WithParamInterface {}; +class ParameterizedDatasetTest + : public ConcatenateDatasetOpTestHelper, + public ::testing::WithParamInterface {}; -TEST_P(DatasetGetNextTest, GetNext) { +TEST_P(ParameterizedDatasetTest, GetNext) { int thread_num = 2, cpu_num = 2; TF_ASSERT_OK(InitThreadPool(thread_num)); TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); @@ -180,7 +183,7 @@ TEST_P(DatasetGetNextTest, GetNext) { TF_EXPECT_OK( iterator->GetNext(iterator_ctx.get(), &out_tensors, &end_of_sequence)); if (!end_of_sequence) { - for (auto &tensor : out_tensors) { + for (const auto &tensor : out_tensors) { EXPECT_NE(expected_outputs_it, test_case.expected_outputs.end()); TF_EXPECT_OK(ExpectEqual(tensor, *expected_outputs_it)); expected_outputs_it++; @@ -190,9 +193,6 @@ TEST_P(DatasetGetNextTest, GetNext) { EXPECT_EQ(expected_outputs_it, test_case.expected_outputs.end()); } -INSTANTIATE_TEST_CASE_P(ConcatenateDatasetOpTest, DatasetGetNextTest, - ::testing::ValuesIn(TestCases)); - TEST_F(ConcatenateDatasetOpTestHelper, DifferentDtypes) { int thread_num = 2, cpu_num = 2; TF_ASSERT_OK(InitThreadPool(thread_num)); @@ -221,10 +221,7 @@ TEST_F(ConcatenateDatasetOpTestHelper, DatasetName) { EXPECT_EQ(dataset->name(), kOpName); } -struct DatasetOutputDtypesTest : ConcatenateDatasetOpTestHelper, - ::testing::WithParamInterface {}; - -TEST_P(DatasetOutputDtypesTest, DatasetOutputDtypes) { +TEST_P(ParameterizedDatasetTest, DatasetOutputDtypes) { int thread_num = 2, cpu_num = 2; TF_ASSERT_OK(InitThreadPool(thread_num)); TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); @@ -234,13 +231,7 @@ TEST_P(DatasetOutputDtypesTest, DatasetOutputDtypes) { test_case.expected_output_dtypes)); } -INSTANTIATE_TEST_CASE_P(ConcatenateDatasetOpTest, DatasetOutputDtypesTest, - ::testing::ValuesIn(TestCases)); - -struct DatasetOutputShapesTest : ConcatenateDatasetOpTestHelper, - ::testing::WithParamInterface {}; - -TEST_P(DatasetOutputShapesTest, DatasetOutputShapes) { +TEST_P(ParameterizedDatasetTest, DatasetOutputShapes) { int thread_num = 2, cpu_num = 2; TF_ASSERT_OK(InitThreadPool(thread_num)); TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); @@ -250,13 +241,7 @@ TEST_P(DatasetOutputShapesTest, DatasetOutputShapes) { test_case.expected_output_shapes)); } -INSTANTIATE_TEST_CASE_P(ConcatenateDatasetOpTest, DatasetOutputShapesTest, - ::testing::ValuesIn(TestCases)); - -struct DatasetCardinalityTest : ConcatenateDatasetOpTestHelper, - ::testing::WithParamInterface {}; - -TEST_P(DatasetCardinalityTest, Cardinality) { +TEST_P(ParameterizedDatasetTest, Cardinality) { int thread_num = 2, cpu_num = 2; TF_ASSERT_OK(InitThreadPool(thread_num)); TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); @@ -266,9 +251,6 @@ TEST_P(DatasetCardinalityTest, Cardinality) { EXPECT_EQ(dataset->Cardinality(), GetParam().expected_cardinality); } -INSTANTIATE_TEST_CASE_P(ConcatenateDatasetOpTest, DatasetCardinalityTest, - ::testing::ValuesIn(TestCases)); - TEST_F(ConcatenateDatasetOpTestHelper, DatasetSave) { int thread_num = 2, cpu_num = 2; TF_ASSERT_OK(InitThreadPool(thread_num)); @@ -283,10 +265,7 @@ TEST_F(ConcatenateDatasetOpTestHelper, DatasetSave) { TF_ASSERT_OK(writer.Flush()); } -struct IteratorOutputDtypesTest : ConcatenateDatasetOpTestHelper, - ::testing::WithParamInterface {}; - -TEST_P(IteratorOutputDtypesTest, IteratorOutputDtypes) { +TEST_P(ParameterizedDatasetTest, IteratorOutputDtypes) { int thread_num = 2, cpu_num = 2; TF_ASSERT_OK(InitThreadPool(thread_num)); TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); @@ -296,13 +275,7 @@ TEST_P(IteratorOutputDtypesTest, IteratorOutputDtypes) { test_case.expected_output_dtypes)); } -INSTANTIATE_TEST_CASE_P(ConcatenateDatasetOpTest, IteratorOutputDtypesTest, - ::testing::ValuesIn(TestCases)); - -struct IteratorOutputShapesTest : ConcatenateDatasetOpTestHelper, - ::testing::WithParamInterface {}; - -TEST_P(IteratorOutputShapesTest, IteratorOutputShapes) { +TEST_P(ParameterizedDatasetTest, IteratorOutputShapes) { int thread_num = 2, cpu_num = 2; TF_ASSERT_OK(InitThreadPool(thread_num)); TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); @@ -312,9 +285,6 @@ TEST_P(IteratorOutputShapesTest, IteratorOutputShapes) { test_case.expected_output_shapes)); } -INSTANTIATE_TEST_CASE_P(ConcatenateDatasetOpTest, IteratorOutputShapesTest, - ::testing::ValuesIn(TestCases)); - TEST_F(ConcatenateDatasetOpTestHelper, IteratorOutputPrefix) { int thread_num = 2, cpu_num = 2; TF_ASSERT_OK(InitThreadPool(thread_num)); @@ -323,10 +293,7 @@ TEST_F(ConcatenateDatasetOpTestHelper, IteratorOutputPrefix) { EXPECT_EQ(iterator->prefix(), "Iterator::Concatenate"); } -struct IteratorRoundtripTest : ConcatenateDatasetOpTestHelper, - ::testing::WithParamInterface {}; - -TEST_P(IteratorRoundtripTest, Roundtrip) { +TEST_P(ParameterizedDatasetTest, Roundtrip) { int thread_num = 2, cpu_num = 2; TF_ASSERT_OK(InitThreadPool(thread_num)); TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); @@ -371,7 +338,7 @@ TEST_P(IteratorRoundtripTest, Roundtrip) { } } -INSTANTIATE_TEST_CASE_P(ConcatenateDatasetOpTest, IteratorRoundtripTest, +INSTANTIATE_TEST_CASE_P(ConcatenateDatasetOpTest, ParameterizedDatasetTest, ::testing::ValuesIn(TestCases)); } // namespace } // namespace data diff --git a/tensorflow/core/kernels/data/dataset_test_base.cc b/tensorflow/core/kernels/data/dataset_test_base.cc index f5939deab1..3e56c3576a 100644 --- a/tensorflow/core/kernels/data/dataset_test_base.cc +++ b/tensorflow/core/kernels/data/dataset_test_base.cc @@ -86,7 +86,6 @@ Status DatasetOpsTestBase::CreateTensorSliceDataset( return Status::OK(); } ->>>>>>> Add CreateTensorSliceDataset[Kernel] functions to the test base class Status DatasetOpsTestBase::CreateOpKernel( const NodeDef& node_def, std::unique_ptr* op_kernel) { Status status; diff --git a/tensorflow/core/kernels/data/zip_dataset_op_test.cc b/tensorflow/core/kernels/data/zip_dataset_op_test.cc index 87ebd00035..a97b1ad821 100644 --- a/tensorflow/core/kernels/data/zip_dataset_op_test.cc +++ b/tensorflow/core/kernels/data/zip_dataset_op_test.cc @@ -30,7 +30,8 @@ struct RangeDatasetParam { class ZipDatasetOpTest : public DatasetOpsTestBase { protected: - // Creates `RangeDataset` variant tensors + // Creates `RangeDataset` variant tensors from the input vector of + // `RangeDatasetParam`. Status CreateRangeDatasetTensors(const std::vector ¶ms, std::vector *const dataset_tensors) { for (int i = 0; i < params.size(); ++i) { @@ -53,6 +54,7 @@ class ZipDatasetOpTest : public DatasetOpsTestBase { std::vector input_datasets; input_datasets.reserve(n); for (int i = 0; i < n; ++i) { + // Create the placeholder names for the input components of `ZipDataset`. input_datasets.emplace_back(strings::StrCat("input_dataset_", i)); } node_def_ = test::function::NDef( @@ -77,252 +79,166 @@ class ZipDatasetOpTest : public DatasetOpsTestBase { }; struct TestParam { - std::vector testcases; + std::vector input_range_dataset_params; std::vector expected_outputs; std::vector breakpoints; } TestCases[] = { - // Test the input datasets with same number of outputs. - {{RangeDatasetParam{0, 3, 1}, RangeDatasetParam{10, 13, 1}}, + // Test case 1: the input datasets with same number of outputs. + {/*input_range_dataset_params*/ + {RangeDatasetParam{0, 3, 1}, RangeDatasetParam{10, 13, 1}}, + /*expected_outputs*/ {DatasetOpsTestBase::CreateTensor(TensorShape{}, {0}), DatasetOpsTestBase::CreateTensor(TensorShape{}, {10}), DatasetOpsTestBase::CreateTensor(TensorShape{}, {1}), DatasetOpsTestBase::CreateTensor(TensorShape{}, {11}), DatasetOpsTestBase::CreateTensor(TensorShape{}, {2}), DatasetOpsTestBase::CreateTensor(TensorShape{}, {12})}, - {0, 1, 4}}, - // Test the input datasets with different number of outputs. - {{RangeDatasetParam{0, 3, 1}, RangeDatasetParam{10, 15, 1}}, + /*breakpoints*/ {0, 1, 4}}, + // Test case 2: the input datasets with different number of outputs. + {/*input_range_dataset_params*/ + {RangeDatasetParam{0, 3, 1}, RangeDatasetParam{10, 15, 1}}, + /*expected_outputs*/ {DatasetOpsTestBase::CreateTensor(TensorShape{}, {0}), DatasetOpsTestBase::CreateTensor(TensorShape{}, {10}), DatasetOpsTestBase::CreateTensor(TensorShape{}, {1}), DatasetOpsTestBase::CreateTensor(TensorShape{}, {11}), DatasetOpsTestBase::CreateTensor(TensorShape{}, {2}), DatasetOpsTestBase::CreateTensor(TensorShape{}, {12})}, - {0, 1, 4}}}; + /*breakpoints*/ {0, 1, 4}}}; -struct DatasetGetNextTest : ZipDatasetOpTest, - ::testing::WithParamInterface {}; - -TEST_P(DatasetGetNextTest, GetNext) { - int thread_num = 2, cpu_num = 2; - TF_ASSERT_OK(InitThreadPool(thread_num)); - TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); +class ZipDatasetOpTestHelper : public ZipDatasetOpTest { + public: + ~ZipDatasetOpTestHelper() { + if (dataset) dataset->Unref(); + } - std::vector testcases = GetParam().testcases; - std::vector expected_outputs = GetParam().expected_outputs; - auto expected_outputs_it = expected_outputs.begin(); + protected: + Status CreateDatasetFromTestCase(const TestParam &test_case) { + std::vector range_dataset_tensors; + range_dataset_tensors.reserve(test_case.input_range_dataset_params.size()); + TF_RETURN_IF_ERROR(CreateRangeDatasetTensors( + test_case.input_range_dataset_params, &range_dataset_tensors)); + gtl::InlinedVector inputs; + inputs.reserve(range_dataset_tensors.size()); + for (auto &tensor : range_dataset_tensors) { + inputs.emplace_back(&tensor); + } + int num_tensors_per_slice = test_case.input_range_dataset_params.size(); + TF_RETURN_IF_ERROR(CreateZipDatasetKernel( + {DT_INT64}, {{num_tensors_per_slice}}, inputs.size(), &dataset_kernel)); + TF_RETURN_IF_ERROR(CreateZipDatasetContext(dataset_kernel.get(), &inputs, + &dataset_kernel_ctx)); + TF_RETURN_IF_ERROR(CreateDataset(dataset_kernel.get(), + dataset_kernel_ctx.get(), &dataset)); + return Status::OK(); + } - std::vector range_dataset_tensors; - TF_ASSERT_OK(CreateRangeDatasetTensors(testcases, &range_dataset_tensors)); - gtl::InlinedVector inputs; - for (auto &tensor : range_dataset_tensors) { - inputs.emplace_back(&tensor); + Status CreateIteratorFromTestCase(const TestParam &test_case) { + TF_RETURN_IF_ERROR(CreateDatasetFromTestCase(test_case)); + TF_RETURN_IF_ERROR( + CreateIteratorContext(dataset_kernel_ctx.get(), &iterator_ctx)); + TF_RETURN_IF_ERROR( + dataset->MakeIterator(iterator_ctx.get(), "Iterator", &iterator)); + return Status::OK(); } - int num_tensors_per_slice = testcases.size(); + std::unique_ptr dataset_kernel; - TF_ASSERT_OK(CreateZipDatasetKernel({DT_INT64}, {{num_tensors_per_slice}}, - inputs.size(), &dataset_kernel)); std::unique_ptr dataset_kernel_ctx; - TF_ASSERT_OK(CreateZipDatasetContext(dataset_kernel.get(), &inputs, - &dataset_kernel_ctx)); - DatasetBase *dataset; - TF_ASSERT_OK( - CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); - core::ScopedUnref scoped_unref(dataset); - + DatasetBase *dataset = nullptr; // owned by this class. std::unique_ptr iterator_ctx; - TF_ASSERT_OK(CreateIteratorContext(dataset_kernel_ctx.get(), &iterator_ctx)); std::unique_ptr iterator; - TF_ASSERT_OK( - dataset->MakeIterator(iterator_ctx.get(), "Iterator", &iterator)); +}; + +class ParameterizedDatasetTest + : public ZipDatasetOpTestHelper, + public ::testing::WithParamInterface {}; + +TEST_P(ParameterizedDatasetTest, GetNext) { + int thread_num = 2, cpu_num = 2; + TF_ASSERT_OK(InitThreadPool(thread_num)); + TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); + const TestParam &test_case = GetParam(); + TF_ASSERT_OK(CreateIteratorFromTestCase(test_case)); + auto expected_outputs_it = test_case.expected_outputs.begin(); bool end_of_sequence = false; std::vector out_tensors; while (!end_of_sequence) { TF_EXPECT_OK( iterator->GetNext(iterator_ctx.get(), &out_tensors, &end_of_sequence)); if (!end_of_sequence) { - EXPECT_NE(expected_outputs_it, expected_outputs.end()); - for (int i = 0; i < num_tensors_per_slice; ++i) { - TF_EXPECT_OK(ExpectEqual(out_tensors[i], *expected_outputs_it)); + for (const auto &tensor : out_tensors) { + EXPECT_NE(expected_outputs_it, test_case.expected_outputs.end()); + TF_EXPECT_OK(ExpectEqual(tensor, *expected_outputs_it)); expected_outputs_it++; } } } - EXPECT_EQ(expected_outputs_it, expected_outputs.end()); + EXPECT_EQ(expected_outputs_it, test_case.expected_outputs.end()); } -INSTANTIATE_TEST_CASE_P(ZipDatasetOpTest, DatasetGetNextTest, - ::testing::ValuesIn(TestCases)); - -TEST_F(ZipDatasetOpTest, DatasetName) { +TEST_F(ZipDatasetOpTestHelper, DatasetName) { int thread_num = 2, cpu_num = 2; TF_ASSERT_OK(InitThreadPool(thread_num)); TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); - - std::vector testcases = TestCases[0].testcases; - std::vector range_dataset_tensors; - TF_ASSERT_OK(CreateRangeDatasetTensors(testcases, &range_dataset_tensors)); - gtl::InlinedVector inputs; - for (auto &tensor : range_dataset_tensors) { - inputs.emplace_back(&tensor); - } - - std::unique_ptr dataset_kernel; - TF_ASSERT_OK(CreateZipDatasetKernel({DT_INT64}, {{2}}, inputs.size(), - &dataset_kernel)); - std::unique_ptr dataset_kernel_ctx; - TF_ASSERT_OK(CreateZipDatasetContext(dataset_kernel.get(), &inputs, - &dataset_kernel_ctx)); - DatasetBase *dataset; - TF_ASSERT_OK( - CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); - core::ScopedUnref scoped_unref(dataset); + TF_ASSERT_OK(CreateDatasetFromTestCase(TestCases[0])); EXPECT_EQ(dataset->name(), kOpName); } -struct DatasetOutputDtypesTest : ZipDatasetOpTest, - ::testing::WithParamInterface {}; - -TEST_P(DatasetOutputDtypesTest, DatasetOutputDtypes) { +TEST_P(ParameterizedDatasetTest, DatasetOutputDtypes) { int thread_num = 2, cpu_num = 2; TF_ASSERT_OK(InitThreadPool(thread_num)); TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); - - std::vector testcases = GetParam().testcases; - std::vector expected_outputs = GetParam().expected_outputs; - - std::vector range_dataset_tensors; - TF_ASSERT_OK(CreateRangeDatasetTensors(testcases, &range_dataset_tensors)); - gtl::InlinedVector inputs; - for (auto &tensor : range_dataset_tensors) { - inputs.emplace_back(&tensor); - } - int num_tensors_per_slice = testcases.size(); - std::unique_ptr dataset_kernel; - TF_ASSERT_OK(CreateZipDatasetKernel({DT_INT64}, {{num_tensors_per_slice}}, - inputs.size(), &dataset_kernel)); - std::unique_ptr dataset_kernel_ctx; - TF_ASSERT_OK(CreateZipDatasetContext(dataset_kernel.get(), &inputs, - &dataset_kernel_ctx)); - DatasetBase *dataset; - TF_ASSERT_OK( - CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); - core::ScopedUnref scoped_unref(dataset); + const TestParam &test_case = GetParam(); + int num_tensors_per_slice = test_case.input_range_dataset_params.size(); + TF_ASSERT_OK(CreateDatasetFromTestCase(test_case)); DataTypeVector expected_output_dtypes; + expected_output_dtypes.reserve(num_tensors_per_slice); for (int i = 0; i < num_tensors_per_slice; ++i) { - expected_output_dtypes.emplace_back(expected_outputs[i].dtype()); + expected_output_dtypes.emplace_back(test_case.expected_outputs[i].dtype()); } TF_EXPECT_OK( VerifyTypesMatch(dataset->output_dtypes(), expected_output_dtypes)); } -INSTANTIATE_TEST_CASE_P(ZipDatasetOpTest, DatasetOutputDtypesTest, - ::testing::ValuesIn(TestCases)); - -struct DatasetOutputShapesTest : ZipDatasetOpTest, - ::testing::WithParamInterface {}; - -TEST_P(DatasetOutputShapesTest, DatasetOutputShapes) { +TEST_P(ParameterizedDatasetTest, DatasetOutputShapes) { int thread_num = 2, cpu_num = 2; TF_ASSERT_OK(InitThreadPool(thread_num)); TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); - - std::vector testcases = GetParam().testcases; - std::vector expected_outputs = GetParam().expected_outputs; - - std::vector range_dataset_tensors; - TF_ASSERT_OK(CreateRangeDatasetTensors(testcases, &range_dataset_tensors)); - gtl::InlinedVector inputs; - for (auto &tensor : range_dataset_tensors) { - inputs.emplace_back(&tensor); - } - int num_tensors_per_slice = testcases.size(); - std::unique_ptr dataset_kernel; - TF_ASSERT_OK(CreateZipDatasetKernel({DT_INT64}, {{num_tensors_per_slice}}, - inputs.size(), &dataset_kernel)); - std::unique_ptr dataset_kernel_ctx; - TF_ASSERT_OK(CreateZipDatasetContext(dataset_kernel.get(), &inputs, - &dataset_kernel_ctx)); - DatasetBase *dataset; - TF_ASSERT_OK( - CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); - core::ScopedUnref scoped_unref(dataset); + const TestParam &test_case = GetParam(); + int num_tensors_per_slice = test_case.input_range_dataset_params.size(); + TF_ASSERT_OK(CreateDatasetFromTestCase(test_case)); std::vector expected_output_shapes; + expected_output_shapes.reserve(num_tensors_per_slice); for (int i = 0; i < num_tensors_per_slice; ++i) { - expected_output_shapes.emplace_back(expected_outputs[i].shape()); + expected_output_shapes.emplace_back(test_case.expected_outputs[i].shape()); } TF_EXPECT_OK( VerifyShapesCompatible(dataset->output_shapes(), expected_output_shapes)); } -INSTANTIATE_TEST_CASE_P(ZipDatasetOpTest, DatasetOutputShapesTest, - ::testing::ValuesIn(TestCases)); - -struct DatasetCardinalityTest : ZipDatasetOpTest, - ::testing::WithParamInterface {}; - -TEST_P(DatasetCardinalityTest, Cardinality) { +TEST_P(ParameterizedDatasetTest, Cardinality) { int thread_num = 2, cpu_num = 2; TF_ASSERT_OK(InitThreadPool(thread_num)); TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); - - std::vector testcases = GetParam().testcases; - std::vector expected_outputs = GetParam().expected_outputs; - - std::vector range_dataset_tensors; - TF_ASSERT_OK(CreateRangeDatasetTensors(testcases, &range_dataset_tensors)); - gtl::InlinedVector inputs; - for (auto &tensor : range_dataset_tensors) { - inputs.emplace_back(&tensor); - } - int num_tensors_per_slice = testcases.size(); - std::unique_ptr dataset_kernel; - TF_ASSERT_OK(CreateZipDatasetKernel({DT_INT64}, {{num_tensors_per_slice}}, - inputs.size(), &dataset_kernel)); - std::unique_ptr dataset_kernel_ctx; - TF_ASSERT_OK(CreateZipDatasetContext(dataset_kernel.get(), &inputs, - &dataset_kernel_ctx)); - DatasetBase *dataset; - TF_ASSERT_OK( - CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); - core::ScopedUnref scoped_unref(dataset); + const TestParam &test_case = GetParam(); + int num_tensors_per_slice = test_case.input_range_dataset_params.size(); + TF_ASSERT_OK(CreateDatasetFromTestCase(test_case)); EXPECT_EQ(dataset->Cardinality(), - expected_outputs.size() / num_tensors_per_slice); + test_case.expected_outputs.size() / num_tensors_per_slice); } -INSTANTIATE_TEST_CASE_P(ZipDatasetOpTest, DatasetCardinalityTest, - ::testing::ValuesIn(TestCases)); - -TEST_F(ZipDatasetOpTest, DatasetSave) { +TEST_F(ZipDatasetOpTestHelper, DatasetSave) { int thread_num = 2, cpu_num = 2; TF_ASSERT_OK(InitThreadPool(thread_num)); TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); - - std::vector testcases = TestCases[0].testcases; - std::vector range_dataset_tensors; - TF_ASSERT_OK(CreateRangeDatasetTensors(testcases, &range_dataset_tensors)); - gtl::InlinedVector inputs; - for (auto &tensor : range_dataset_tensors) { - inputs.emplace_back(&tensor); - } - - std::unique_ptr dataset_kernel; - TF_ASSERT_OK(CreateZipDatasetKernel({DT_INT64}, {{2}}, inputs.size(), - &dataset_kernel)); - std::unique_ptr dataset_kernel_ctx; - TF_ASSERT_OK(CreateZipDatasetContext(dataset_kernel.get(), &inputs, - &dataset_kernel_ctx)); - DatasetBase *dataset; - TF_ASSERT_OK( - CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); - core::ScopedUnref scoped_unref(dataset); + TF_ASSERT_OK(CreateDatasetFromTestCase(TestCases[0])); std::unique_ptr serialization_ctx; TF_ASSERT_OK(CreateSerializationContext(&serialization_ctx)); @@ -332,170 +248,57 @@ TEST_F(ZipDatasetOpTest, DatasetSave) { TF_ASSERT_OK(writer.Flush()); } -struct IteratorOutputDtypesTest : ZipDatasetOpTest, - ::testing::WithParamInterface {}; - -TEST_P(IteratorOutputDtypesTest, IteratorOutputDtypes) { +TEST_P(ParameterizedDatasetTest, IteratorOutputDtypes) { int thread_num = 2, cpu_num = 2; TF_ASSERT_OK(InitThreadPool(thread_num)); TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); - - std::vector testcases = GetParam().testcases; - std::vector expected_outputs = GetParam().expected_outputs; - - std::vector range_dataset_tensors; - TF_ASSERT_OK(CreateRangeDatasetTensors(testcases, &range_dataset_tensors)); - gtl::InlinedVector inputs; - for (auto &tensor : range_dataset_tensors) { - inputs.emplace_back(&tensor); - } - int num_tensors_per_slice = testcases.size(); - std::unique_ptr dataset_kernel; - TF_ASSERT_OK(CreateZipDatasetKernel({DT_INT64}, {{num_tensors_per_slice}}, - inputs.size(), &dataset_kernel)); - std::unique_ptr dataset_kernel_ctx; - TF_ASSERT_OK(CreateZipDatasetContext(dataset_kernel.get(), &inputs, - &dataset_kernel_ctx)); - DatasetBase *dataset; - TF_ASSERT_OK( - CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); - core::ScopedUnref scoped_unref(dataset); - - std::unique_ptr iterator_ctx; - TF_ASSERT_OK(CreateIteratorContext(dataset_kernel_ctx.get(), &iterator_ctx)); - std::unique_ptr iterator; - TF_ASSERT_OK( - dataset->MakeIterator(iterator_ctx.get(), "Iterator", &iterator)); + const TestParam &test_case = GetParam(); + int num_tensors_per_slice = test_case.input_range_dataset_params.size(); + TF_ASSERT_OK(CreateIteratorFromTestCase(test_case)); DataTypeVector expected_output_dtypes; + expected_output_dtypes.reserve(num_tensors_per_slice); for (int i = 0; i < num_tensors_per_slice; ++i) { - expected_output_dtypes.emplace_back(expected_outputs[i].dtype()); + expected_output_dtypes.emplace_back(test_case.expected_outputs[i].dtype()); } TF_EXPECT_OK( VerifyTypesMatch(iterator->output_dtypes(), expected_output_dtypes)); } -INSTANTIATE_TEST_CASE_P(ZipDatasetOpTest, IteratorOutputDtypesTest, - ::testing::ValuesIn(TestCases)); - -struct IteratorOutputShapesTest : ZipDatasetOpTest, - ::testing::WithParamInterface {}; - -TEST_P(IteratorOutputShapesTest, IteratorOutputShapes) { +TEST_P(ParameterizedDatasetTest, IteratorOutputShapes) { int thread_num = 2, cpu_num = 2; TF_ASSERT_OK(InitThreadPool(thread_num)); TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); - - std::vector testcases = GetParam().testcases; - std::vector expected_outputs = GetParam().expected_outputs; - - std::vector range_dataset_tensors; - TF_ASSERT_OK(CreateRangeDatasetTensors(testcases, &range_dataset_tensors)); - gtl::InlinedVector inputs; - for (auto &tensor : range_dataset_tensors) { - inputs.emplace_back(&tensor); - } - int num_tensors_per_slice = testcases.size(); - std::unique_ptr dataset_kernel; - TF_ASSERT_OK(CreateZipDatasetKernel({DT_INT64}, {{num_tensors_per_slice}}, - inputs.size(), &dataset_kernel)); - std::unique_ptr dataset_kernel_ctx; - TF_ASSERT_OK(CreateZipDatasetContext(dataset_kernel.get(), &inputs, - &dataset_kernel_ctx)); - DatasetBase *dataset; - TF_ASSERT_OK( - CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); - core::ScopedUnref scoped_unref(dataset); - - std::unique_ptr iterator_ctx; - TF_ASSERT_OK(CreateIteratorContext(dataset_kernel_ctx.get(), &iterator_ctx)); - std::unique_ptr iterator; - TF_ASSERT_OK( - dataset->MakeIterator(iterator_ctx.get(), "Iterator", &iterator)); + const TestParam &test_case = GetParam(); + int num_tensors_per_slice = test_case.input_range_dataset_params.size(); + TF_ASSERT_OK(CreateIteratorFromTestCase(test_case)); std::vector expected_output_shapes; expected_output_shapes.reserve(num_tensors_per_slice); for (int i = 0; i < num_tensors_per_slice; ++i) { - expected_output_shapes.emplace_back(expected_outputs[i].shape()); + expected_output_shapes.emplace_back(test_case.expected_outputs[i].shape()); } TF_EXPECT_OK(VerifyShapesCompatible(iterator->output_shapes(), expected_output_shapes)); } -INSTANTIATE_TEST_CASE_P(ZipDatasetOpTest, IteratorOutputShapesTest, - ::testing::ValuesIn(TestCases)); - -TEST_F(ZipDatasetOpTest, IteratorOutputPrefix) { +TEST_F(ZipDatasetOpTestHelper, IteratorOutputPrefix) { int thread_num = 2, cpu_num = 2; TF_ASSERT_OK(InitThreadPool(thread_num)); TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); - - std::vector testcases( - {RangeDatasetParam{0, 10, 1}, RangeDatasetParam{10, 20, 1}}); - std::vector range_dataset_tensors; - TF_ASSERT_OK(CreateRangeDatasetTensors(testcases, &range_dataset_tensors)); - gtl::InlinedVector inputs; - for (auto &tensor : range_dataset_tensors) { - inputs.emplace_back(&tensor); - } - - std::unique_ptr dataset_kernel; - TF_ASSERT_OK(CreateZipDatasetKernel({DT_INT64}, {{2}}, inputs.size(), - &dataset_kernel)); - std::unique_ptr dataset_kernel_ctx; - TF_ASSERT_OK(CreateZipDatasetContext(dataset_kernel.get(), &inputs, - &dataset_kernel_ctx)); - DatasetBase *dataset; - TF_ASSERT_OK( - CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); - core::ScopedUnref scoped_unref(dataset); - - std::unique_ptr iterator_ctx; - TF_ASSERT_OK(CreateIteratorContext(dataset_kernel_ctx.get(), &iterator_ctx)); - std::unique_ptr iterator; - TF_ASSERT_OK( - dataset->MakeIterator(iterator_ctx.get(), "Iterator", &iterator)); + TF_ASSERT_OK(CreateIteratorFromTestCase(TestCases[0])); EXPECT_EQ(iterator->prefix(), "Iterator::Zip"); } -struct IteratorRoundtripTest : ZipDatasetOpTest, - ::testing::WithParamInterface {}; - -TEST_P(IteratorRoundtripTest, Roundtrip) { +TEST_P(ParameterizedDatasetTest, Roundtrip) { int thread_num = 2, cpu_num = 2; TF_ASSERT_OK(InitThreadPool(thread_num)); TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num)); - - std::vector testcases = GetParam().testcases; - std::vector expected_outputs = GetParam().expected_outputs; - auto expected_outputs_it = expected_outputs.begin(); - std::vector breakpoints = GetParam().breakpoints; - - std::vector range_dataset_tensors; - TF_ASSERT_OK(CreateRangeDatasetTensors(testcases, &range_dataset_tensors)); - gtl::InlinedVector inputs; - for (auto &tensor : range_dataset_tensors) { - inputs.emplace_back(&tensor); - } - int num_tensors_per_slice = testcases.size(); - std::unique_ptr dataset_kernel; - TF_ASSERT_OK(CreateZipDatasetKernel({DT_INT64}, {{num_tensors_per_slice}}, - inputs.size(), &dataset_kernel)); - std::unique_ptr dataset_kernel_ctx; - TF_ASSERT_OK(CreateZipDatasetContext(dataset_kernel.get(), &inputs, - &dataset_kernel_ctx)); - DatasetBase *dataset; - TF_ASSERT_OK( - CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); - core::ScopedUnref scoped_unref(dataset); - - std::unique_ptr iterator_ctx; - TF_ASSERT_OK(CreateIteratorContext(dataset_kernel_ctx.get(), &iterator_ctx)); - std::unique_ptr iterator; - TF_ASSERT_OK( - dataset->MakeIterator(iterator_ctx.get(), "Iterator", &iterator)); + const TestParam &test_case = GetParam(); + auto expected_outputs_it = test_case.expected_outputs.begin(); + TF_ASSERT_OK(CreateIteratorFromTestCase(test_case)); std::unique_ptr serialization_ctx; TF_ASSERT_OK(CreateSerializationContext(&serialization_ctx)); @@ -503,7 +306,7 @@ TEST_P(IteratorRoundtripTest, Roundtrip) { bool end_of_sequence = false; std::vector out_tensors; int cur_iteration = 0; - for (int breakpoint : breakpoints) { + for (int breakpoint : test_case.breakpoints) { VariantTensorData data; VariantTensorDataWriter writer(&data); TF_EXPECT_OK(iterator->Save(serialization_ctx.get(), &writer)); @@ -516,7 +319,7 @@ TEST_P(IteratorRoundtripTest, Roundtrip) { &end_of_sequence)); if (!end_of_sequence) { for (auto &tensor : out_tensors) { - EXPECT_NE(expected_outputs_it, expected_outputs.end()); + EXPECT_NE(expected_outputs_it, test_case.expected_outputs.end()); TF_EXPECT_OK(ExpectEqual(tensor, *expected_outputs_it)); expected_outputs_it++; } @@ -526,14 +329,14 @@ TEST_P(IteratorRoundtripTest, Roundtrip) { if (breakpoint >= dataset->Cardinality()) { EXPECT_TRUE(end_of_sequence); - EXPECT_EQ(expected_outputs_it, expected_outputs.end()); + EXPECT_EQ(expected_outputs_it, test_case.expected_outputs.end()); } else { EXPECT_FALSE(end_of_sequence); } } } -INSTANTIATE_TEST_CASE_P(ZipDatasetOpTest, IteratorRoundtripTest, +INSTANTIATE_TEST_CASE_P(ZipDatasetOpTest, ParameterizedDatasetTest, ::testing::ValuesIn(TestCases)); } // namespace -- GitLab From 13910e7b426defd0dfc9435ff818f2bfea67d6a7 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Sat, 23 Feb 2019 01:02:36 -0800 Subject: [PATCH 403/766] compat: Update forward compatibility horizon to 2019-02-23 PiperOrigin-RevId: 235320364 --- tensorflow/python/compat/compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/compat/compat.py b/tensorflow/python/compat/compat.py index abb1ab7f75..04d546b09d 100644 --- a/tensorflow/python/compat/compat.py +++ b/tensorflow/python/compat/compat.py @@ -27,7 +27,7 @@ import datetime from tensorflow.python.util import tf_contextlib from tensorflow.python.util.tf_export import tf_export -_FORWARD_COMPATIBILITY_HORIZON = datetime.date(2019, 2, 22) +_FORWARD_COMPATIBILITY_HORIZON = datetime.date(2019, 2, 23) @tf_export("compat.forward_compatible") -- GitLab From 20dd116e71f861cd1540b85c4ab1c000ac5aeadb Mon Sep 17 00:00:00 2001 From: Martin Wicke Date: Sat, 23 Feb 2019 01:03:54 -0800 Subject: [PATCH 404/766] Add TensorFlow license to c/jni tarballs. Fixes #21913. PiperOrigin-RevId: 235320635 --- tensorflow/tools/lib_package/BUILD | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tensorflow/tools/lib_package/BUILD b/tensorflow/tools/lib_package/BUILD index 86bd510792..0c81ebeefb 100644 --- a/tensorflow/tools/lib_package/BUILD +++ b/tensorflow/tools/lib_package/BUILD @@ -38,6 +38,7 @@ pkg_tar( extension = "tar.gz", files = [ "include/tensorflow/jni/LICENSE", + "//:LICENSE", "//tensorflow/java:libtensorflow_jni", ], # Mark as "manual" till @@ -99,7 +100,10 @@ pkg_tar( pkg_tar( name = "clicenses", - files = [":include/tensorflow/c/LICENSE"], + files = [ + ":include/tensorflow/c/LICENSE", + "//:LICENSE", + ], package_dir = "include/tensorflow/c", # Mark as "manual" till # https://github.com/bazelbuild/bazel/issues/2352 -- GitLab From 7ef266852ddb8ffe3e7d5dd5747c6f58f0e02008 Mon Sep 17 00:00:00 2001 From: Thomas O'Malley Date: Sat, 23 Feb 2019 01:43:07 -0800 Subject: [PATCH 405/766] Add `batch_size` to allowed kwargs for backwards compatibility. PiperOrigin-RevId: 235323179 --- tensorflow/python/keras/callbacks.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tensorflow/python/keras/callbacks.py b/tensorflow/python/keras/callbacks.py index f6975b0a19..49c3971c19 100644 --- a/tensorflow/python/keras/callbacks.py +++ b/tensorflow/python/keras/callbacks.py @@ -1194,10 +1194,14 @@ class TensorBoard(Callback): if kwargs.get('embeddings_freq', False): logging.warning('Embeddings will be ignored in TensorFlow 2.0 ' 'for the `TensorBoard` Callback.') + if kwargs.get('batch_size', False): + logging.warning('`batch_size` is no longer needed in the ' + '`TensorBoard` Callback and will be ignored ' + 'in TensorFlow 2.0.') unrecognized_kwargs = set(kwargs.keys()) - { 'write_grads', 'embeddings_freq', 'embeddings_layer_names', - 'embeddings_metadata', 'embeddings_data' + 'embeddings_metadata', 'embeddings_data', 'batch_size' } # Only allow kwargs that were supported in V1. -- GitLab From 14523a55c81b5f92e6f732edca18cdb85fda2b0c Mon Sep 17 00:00:00 2001 From: Lasse Espeholt Date: Sat, 23 Feb 2019 07:26:35 -0800 Subject: [PATCH 406/766] Updated documentation, reference to deleted TensorQueue. PiperOrigin-RevId: 235340188 --- tensorflow/core/framework/op_kernel.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/core/framework/op_kernel.h b/tensorflow/core/framework/op_kernel.h index ff0b44650c..5b8521ba70 100644 --- a/tensorflow/core/framework/op_kernel.h +++ b/tensorflow/core/framework/op_kernel.h @@ -1138,7 +1138,7 @@ class OpKernelContext { // Cancellation. // - // EXPERIMENTAL. See the implementation in tensorflow::TensorQueue for an + // EXPERIMENTAL. See the implementation in tensorflow::FIFOQueue for an // example of how to use this API. CancellationManager* cancellation_manager() const { return params_->cancellation_manager; -- GitLab From ead20181fcad8c83dad08f366959d2e639401ada Mon Sep 17 00:00:00 2001 From: Mark Daoust Date: Sat, 23 Feb 2019 07:55:11 -0800 Subject: [PATCH 407/766] Prefer relative paths within the g3doc directory. --- tensorflow/lite/g3doc/devguide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/g3doc/devguide.md b/tensorflow/lite/g3doc/devguide.md index 6395c92da1..b198767fd7 100644 --- a/tensorflow/lite/g3doc/devguide.md +++ b/tensorflow/lite/g3doc/devguide.md @@ -250,7 +250,7 @@ tflite_quantized_model=converter.convert() open(“quantized_model.tflite”, “wb”).write(tflite_quantized_model) ``` -Read the full documentation [here](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/g3doc/performance/post_training_quantization.md) and see a tutorial [here](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/tutorials/post_training_quant.ipynb). +Read the full documentation [here](performance/post_training_quantization.md) and see a tutorial [here](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/tutorials/post_training_quant.ipynb). ### GPU Run on GPU GPUs are designed to have high throughput for massively -- GitLab From ef29c4accb19c2122c4a45c2061536cd98d65272 Mon Sep 17 00:00:00 2001 From: Sergii Khomenko Date: Sat, 23 Feb 2019 21:40:48 +0100 Subject: [PATCH 408/766] Update contributing.md to point from to addons Since we are migrating out of contrib with TensorFlow 2.0 --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b978f89f9e..08403f1089 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -70,7 +70,7 @@ TensorFlow coding style. contribution must be compared against the cost of maintaining the feature. * Full new features (e.g., a new op implementing a cutting-edge algorithm) typically will live in - [tensorflow/contrib](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib) + [tensorflow/addons](https://github.com/tensorflow/addons) to get some airtime before decision is made regarding whether they are to be migrated to the core. -- GitLab From 3b2a1a8698e6025b002bc169778202ee18f3b6fb Mon Sep 17 00:00:00 2001 From: Sergii Khomenko Date: Sat, 23 Feb 2019 22:19:03 +0100 Subject: [PATCH 409/766] Define ipynb in build file --- tensorflow/tools/compatibility/BUILD | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tensorflow/tools/compatibility/BUILD b/tensorflow/tools/compatibility/BUILD index 74ef9ec98b..83f696b514 100644 --- a/tensorflow/tools/compatibility/BUILD +++ b/tensorflow/tools/compatibility/BUILD @@ -8,6 +8,12 @@ licenses(["notice"]) # Apache 2.0 package(default_visibility = ["//tensorflow:internal"]) +py_library( + name = "ipynb", + srcs = ["ipynb.py"], + srcs_version = "PY2AND3", +) + py_library( name = "ast_edits", srcs = ["ast_edits.py"], @@ -87,6 +93,7 @@ py_binary( srcs_version = "PY2AND3", deps = [ ":ast_edits", + ":ipynb", ":tf_upgrade_v2_lib", ], ) -- GitLab From c9ee938279c833041d06fb71f8ab6b0dcd4db782 Mon Sep 17 00:00:00 2001 From: Sergii Khomenko Date: Sat, 23 Feb 2019 22:56:30 +0100 Subject: [PATCH 410/766] Satisfy do_check_futures_test with future imports --- tensorflow/tools/compatibility/ipynb.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tensorflow/tools/compatibility/ipynb.py b/tensorflow/tools/compatibility/ipynb.py index 633a0d523e..5b140c29f0 100644 --- a/tensorflow/tools/compatibility/ipynb.py +++ b/tensorflow/tools/compatibility/ipynb.py @@ -15,6 +15,8 @@ """A module to support operation on ipynb files""" +from __future__ import absolute_import +from __future__ import division from __future__ import print_function import collections -- GitLab From 4afd534131b31c68f22994adadb0ee5ef032cbbc Mon Sep 17 00:00:00 2001 From: Sergii Khomenko Date: Sat, 23 Feb 2019 23:40:09 +0100 Subject: [PATCH 411/766] Fix identation for pylint --- tensorflow/tools/compatibility/ipynb.py | 16 ++++++++-------- .../tools/compatibility/tf_upgrade_v2_main.py | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tensorflow/tools/compatibility/ipynb.py b/tensorflow/tools/compatibility/ipynb.py index 5b140c29f0..ca7fb39917 100644 --- a/tensorflow/tools/compatibility/ipynb.py +++ b/tensorflow/tools/compatibility/ipynb.py @@ -38,15 +38,15 @@ def process_file(in_filename, out_filename, upgrader): with tempfile.NamedTemporaryFile("w", delete=False) as temp_file: processed_file, new_file_content, log, process_errors = ( - upgrader.update_string_pasta("\n".join(raw_lines), in_filename)) + upgrader.update_string_pasta("\n".join(raw_lines), in_filename)) if temp_file and processed_file: new_notebook = _update_notebook( - notebook, raw_code, new_file_content.split("\n")) + notebook, raw_code, new_file_content.split("\n")) json.dump(new_notebook, temp_file) else: raise SyntaxError( - "Was not able to process the file: \n%s\n" % ''.join(log)) + "Was not able to process the file: \n%s\n" % ''.join(log)) files_processed = processed_file report_text = upgrader._format_log(log, in_filename, out_filename) @@ -88,8 +88,8 @@ def _get_code(input_file): # sometimes a line would start with `\n` and content after # that's the hack for this raw_code.append( - CodeLine(cell_index, - code_line.rstrip().replace('\n', "###==="))) + CodeLine(cell_index, + code_line.rstrip().replace('\n', "###==="))) cell_index += 1 @@ -112,13 +112,13 @@ def _update_notebook(original_notebook, original_raw_lines, updated_code_lines): continue applicable_lines = [ - idx for idx, code_line in enumerate(original_raw_lines) - if code_line.cell_number == code_cell_idx] + idx for idx, code_line in enumerate(original_raw_lines) + if code_line.cell_number == code_cell_idx] new_code = [updated_code_lines[idx] for idx in applicable_lines] cell['source'] = "\n".join(new_code).replace( - '###!!!', '').replace("###===", "\n") + '###!!!', '').replace("###===", "\n") code_cell_idx += 1 return new_notebook diff --git a/tensorflow/tools/compatibility/tf_upgrade_v2_main.py b/tensorflow/tools/compatibility/tf_upgrade_v2_main.py index cd0f204ae9..48eb4fbea6 100644 --- a/tensorflow/tools/compatibility/tf_upgrade_v2_main.py +++ b/tensorflow/tools/compatibility/tf_upgrade_v2_main.py @@ -37,7 +37,7 @@ def process_file(in_filename, out_filename, upgrader): ipynb.process_file(in_filename, out_filename, upgrader) else: raise NotImplementedError( - "Currently converter only supports python or ipynb") + "Currently converter only supports python or ipynb") return files_processed, report_text, errors -- GitLab From 424f6c9f51769bf4fb52bdb25613c1cd716b4b7e Mon Sep 17 00:00:00 2001 From: Sergii Khomenko Date: Sun, 24 Feb 2019 01:01:59 +0100 Subject: [PATCH 412/766] Fix last identation error --- tensorflow/tools/compatibility/ipynb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/tools/compatibility/ipynb.py b/tensorflow/tools/compatibility/ipynb.py index ca7fb39917..19c7fc9d66 100644 --- a/tensorflow/tools/compatibility/ipynb.py +++ b/tensorflow/tools/compatibility/ipynb.py @@ -118,7 +118,7 @@ def _update_notebook(original_notebook, original_raw_lines, updated_code_lines): new_code = [updated_code_lines[idx] for idx in applicable_lines] cell['source'] = "\n".join(new_code).replace( - '###!!!', '').replace("###===", "\n") + '###!!!', '').replace("###===", "\n") code_cell_idx += 1 return new_notebook -- GitLab From 6aa83398ab03bfae822f36772757097bcb98b6ed Mon Sep 17 00:00:00 2001 From: Justin Lebar Date: Sat, 23 Feb 2019 16:51:42 -0800 Subject: [PATCH 413/766] [SE] Fix function signature of stream_executor::CalculateDimensionality. PiperOrigin-RevId: 235369844 --- tensorflow/stream_executor/device_description.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/stream_executor/device_description.cc b/tensorflow/stream_executor/device_description.cc index aaf5f6d6cd..3247665485 100644 --- a/tensorflow/stream_executor/device_description.cc +++ b/tensorflow/stream_executor/device_description.cc @@ -148,8 +148,8 @@ uint64 DivideCeil(uint64 x, uint64 y) { } void CalculateDimensionality(const DeviceDescription &device_description, - uint64 element_count, uint64 *threads_per_block, - uint64 *block_count) { + int64 element_count, int64 *threads_per_block, + int64 *block_count) { *threads_per_block = device_description.threads_per_block_limit(); *block_count = port::MathUtil::CeilOfRatio(element_count, *threads_per_block); if (*block_count == 1) { -- GitLab From 781fb01584a92bf82a9335061c723f02bfbc445c Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Sun, 24 Feb 2019 01:03:02 -0800 Subject: [PATCH 414/766] compat: Update forward compatibility horizon to 2019-02-24 PiperOrigin-RevId: 235395188 --- tensorflow/python/compat/compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/compat/compat.py b/tensorflow/python/compat/compat.py index 04d546b09d..5e886dc3e8 100644 --- a/tensorflow/python/compat/compat.py +++ b/tensorflow/python/compat/compat.py @@ -27,7 +27,7 @@ import datetime from tensorflow.python.util import tf_contextlib from tensorflow.python.util.tf_export import tf_export -_FORWARD_COMPATIBILITY_HORIZON = datetime.date(2019, 2, 23) +_FORWARD_COMPATIBILITY_HORIZON = datetime.date(2019, 2, 24) @tf_export("compat.forward_compatible") -- GitLab From 177297a99e3060e3e419029773b4ff24948a4141 Mon Sep 17 00:00:00 2001 From: Amit <30853054+amitsrivastava78@users.noreply.github.com> Date: Sun, 24 Feb 2019 15:06:49 +0530 Subject: [PATCH 415/766] Updated custom_operators.md Typo error fixed --- tensorflow/lite/g3doc/custom_operators.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/g3doc/custom_operators.md b/tensorflow/lite/g3doc/custom_operators.md index 2d80668f37..38ca1c9f4c 100644 --- a/tensorflow/lite/g3doc/custom_operators.md +++ b/tensorflow/lite/g3doc/custom_operators.md @@ -141,7 +141,7 @@ When `tflite_convert` converts a TensorFlow graph into TFLite format, it makes some assumption about custom operations that might be not correct. In this case, the generated graph may not execute. -It is possible to add aditional information about your custom op output to TF +It is possible to add additional information about your custom op output to TF graph before it is converted. The following attributes are supported: - **_output_quantized** a boolean attribute, true if the operation outputs are -- GitLab From 6f01d30a9585663b35394749c644502da48ec6ef Mon Sep 17 00:00:00 2001 From: Amit <30853054+amitsrivastava78@users.noreply.github.com> Date: Sun, 24 Feb 2019 15:15:50 +0530 Subject: [PATCH 416/766] Updated profile_buffer.h Fixed the warning in the file --- tensorflow/lite/profiling/profile_buffer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/profiling/profile_buffer.h b/tensorflow/lite/profiling/profile_buffer.h index 9aa9e41131..2202df2dbe 100644 --- a/tensorflow/lite/profiling/profile_buffer.h +++ b/tensorflow/lite/profiling/profile_buffer.h @@ -128,7 +128,7 @@ class ProfileBuffer { // Returns the profile event at the given index. If the index is invalid a // nullptr is returned. The return event may get overwritten if more events // are added to buffer. - const struct ProfileEvent* const At(int index) const { + const struct ProfileEvent* const At(size_t index) const { size_t size = Size(); if (index >= size) { return nullptr; -- GitLab From 812ba6322060bdbca4c394b811777d4afee59eda Mon Sep 17 00:00:00 2001 From: Amit <30853054+amitsrivastava78@users.noreply.github.com> Date: Sun, 24 Feb 2019 15:23:02 +0530 Subject: [PATCH 417/766] Updated profile_buffer_test.cc Fixed the warning in the file --- tensorflow/lite/profiling/profile_buffer_test.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/lite/profiling/profile_buffer_test.cc b/tensorflow/lite/profiling/profile_buffer_test.cc index 6642a15884..92973302e7 100644 --- a/tensorflow/lite/profiling/profile_buffer_test.cc +++ b/tensorflow/lite/profiling/profile_buffer_test.cc @@ -27,7 +27,7 @@ namespace { std::vector GetProfileEvents(const ProfileBuffer& buffer) { std::vector events; - for (auto i = 0; i < buffer.Size(); i++) { + for (size_t i = 0; i < buffer.Size(); i++) { events.push_back(buffer.At(i)); } return events; @@ -69,7 +69,7 @@ TEST(ProfileBufferTest, OverFlow) { EXPECT_EQ(expected_size, buffer.Size()); } EXPECT_EQ(max_size, buffer.Size()); - for (int j = 0; j < buffer.Size(); ++j) { + for (size_t j = 0; j < buffer.Size(); ++j) { auto event = buffer.At(j); EXPECT_EQ(eventNames[j % 4], event->tag); EXPECT_EQ(ProfileEvent::EventType::DEFAULT, event->event_type); -- GitLab From f84ef7b162cc4ed22a53fe01eeb4c532041e15f3 Mon Sep 17 00:00:00 2001 From: Amit <30853054+amitsrivastava78@users.noreply.github.com> Date: Sun, 24 Feb 2019 16:01:05 +0530 Subject: [PATCH 418/766] Updated tf_ops_compatibility.md Fixed typo error --- tensorflow/lite/g3doc/tf_ops_compatibility.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/g3doc/tf_ops_compatibility.md b/tensorflow/lite/g3doc/tf_ops_compatibility.md index a0cb9281db..1065c93a8b 100644 --- a/tensorflow/lite/g3doc/tf_ops_compatibility.md +++ b/tensorflow/lite/g3doc/tf_ops_compatibility.md @@ -803,7 +803,7 @@ Inputs { 1: a 1D tensor with 2 elements } Outputs { - 0: A tensor of type `tensor 0` resized according to `tensor 1` heigh/width values + 0: A tensor of type `tensor 0` resized according to `tensor 1` height/width values using nearest neighbors interpolation. } Options { -- GitLab From e4327587c6e0b6d2da8afc9aba6d464980c08fff Mon Sep 17 00:00:00 2001 From: Amit <30853054+amitsrivastava78@users.noreply.github.com> Date: Sun, 24 Feb 2019 16:08:37 +0530 Subject: [PATCH 419/766] Updated model_test.cc Fixed Typo error --- tensorflow/lite/model_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/model_test.cc b/tensorflow/lite/model_test.cc index 67d2380be0..d58dbf4d45 100644 --- a/tensorflow/lite/model_test.cc +++ b/tensorflow/lite/model_test.cc @@ -116,7 +116,7 @@ TEST(BasicFlatBufferModel, TestModelWithoutNullRegistrations) { ASSERT_EQ(interpreter, nullptr); } -// Make sure model is read to interpreter propelrly +// Make sure model is read to interpreter properly TEST(BasicFlatBufferModel, TestModelInInterpreter) { auto model = FlatBufferModel::BuildFromFile( "tensorflow/lite/testdata/test_model.bin"); -- GitLab From 7b5a2944c9f654b151af36a021410bed4c5108b3 Mon Sep 17 00:00:00 2001 From: Amit <30853054+amitsrivastava78@users.noreply.github.com> Date: Sun, 24 Feb 2019 16:48:37 +0530 Subject: [PATCH 420/766] Updated hierarchical_tree_broadcaster.cc Fixed uninitialised warning --- tensorflow/core/common_runtime/hierarchical_tree_broadcaster.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/core/common_runtime/hierarchical_tree_broadcaster.cc b/tensorflow/core/common_runtime/hierarchical_tree_broadcaster.cc index eae34997d9..a7dc9466bb 100644 --- a/tensorflow/core/common_runtime/hierarchical_tree_broadcaster.cc +++ b/tensorflow/core/common_runtime/hierarchical_tree_broadcaster.cc @@ -62,7 +62,7 @@ int HierarchicalTreeBroadcaster::GetDeviceTask( int device_rank, const std::vector& dev_per_task) { int num_tasks = static_cast(dev_per_task.size()); int task_lo = 0; - int task_hi; + int task_hi = -1; for (int ti = 0; ti < num_tasks; ti++) { task_hi = task_lo + dev_per_task[ti]; if (task_lo <= device_rank && device_rank < task_hi) return ti; -- GitLab From 1721fa8be7d7751e1d18cc6ea5508464fc51c890 Mon Sep 17 00:00:00 2001 From: Dan Moldovan Date: Sun, 24 Feb 2019 06:03:17 -0800 Subject: [PATCH 421/766] Automated rollback of commit e42a66b26372845ea41aea6c12bc3e4f09f1efc5 PiperOrigin-RevId: 235411715 --- tensorflow/python/data/ops/dataset_ops.py | 3 --- .../data/ops/multi_device_iterator_ops.py | 25 +++++-------------- tensorflow/python/eager/def_function.py | 2 +- tensorflow/python/framework/func_graph.py | 3 +-- tensorflow/python/util/tf_decorator.py | 14 +---------- 5 files changed, 9 insertions(+), 38 deletions(-) diff --git a/tensorflow/python/data/ops/dataset_ops.py b/tensorflow/python/data/ops/dataset_ops.py index 3f50738f17..91a07a5534 100644 --- a/tensorflow/python/data/ops/dataset_ops.py +++ b/tensorflow/python/data/ops/dataset_ops.py @@ -2366,8 +2366,6 @@ class StructuredFunctionWrapper(object): else: defun_kwargs.update({"func_name": func_name}) - # TODO(b/124254153): Enable autograph once the overhead is low enough. - # TODO(mdan): Make sure autograph recurses into _wrapper_helper when on. @eager_function.defun_with_attributes( input_signature=[ tensor_spec.TensorSpec(input_shape, input_type) # pylint: disable=g-complex-comprehension @@ -2375,7 +2373,6 @@ class StructuredFunctionWrapper(object): self._input_structure._flat_shapes, self._input_structure._flat_types) ], - autograph=False, attributes=defun_kwargs) def wrapper_fn(*args): # pylint: disable=missing-docstring ret = _wrapper_helper(*args) diff --git a/tensorflow/python/data/ops/multi_device_iterator_ops.py b/tensorflow/python/data/ops/multi_device_iterator_ops.py index f253f8bf42..36e1e311d3 100644 --- a/tensorflow/python/data/ops/multi_device_iterator_ops.py +++ b/tensorflow/python/data/ops/multi_device_iterator_ops.py @@ -42,15 +42,13 @@ class _PerDeviceGenerator(dataset_ops.DatasetV2): gen_dataset_ops.multi_device_iterator_to_string_handle( multi_device_iterator_resource)) - # TODO(b/124254153): Enable autograph once the overhead is low enough. - @function.defun(autograph=False) # Pure graph code. + @function.defun() def _init_func(): return multi_device_iterator_string_handle init_func_concrete = _init_func._get_concrete_function_internal() # pylint: disable=protected-access - # TODO(b/124254153): Enable autograph once the overhead is low enough. - @function.defun(autograph=False) # Pure graph code. + @function.defun() def _remote_init_func(): return functional_ops.remote_call( target=source_device, @@ -61,10 +59,7 @@ class _PerDeviceGenerator(dataset_ops.DatasetV2): self._init_func = _remote_init_func._get_concrete_function_internal() # pylint: disable=protected-access self._init_captured_args = self._init_func.captured_inputs - # TODO(b/124254153): Enable autograph once the overhead is low enough. - @function.defun( - input_signature=[tensor_spec.TensorSpec([], dtypes.string)], - autograph=False) # Pure graph code. + @function.defun(input_signature=[tensor_spec.TensorSpec([], dtypes.string)]) def _next_func(string_handle): # pylint: disable=protected-access multi_device_iterator = ( @@ -81,11 +76,9 @@ class _PerDeviceGenerator(dataset_ops.DatasetV2): next_func_concrete = _next_func._get_concrete_function_internal() # pylint: disable=protected-access - # TODO(b/124254153): Enable autograph once the overhead is low enough. @function.defun_with_attributes( input_signature=[tensor_spec.TensorSpec([], dtypes.string)], - attributes={"experimental_ints_on_device": True}, - autograph=False) # Pure graph code. + attributes={"experimental_ints_on_device": True}) def _remote_next_func(string_handle): return functional_ops.remote_call( target=source_device, @@ -101,19 +94,13 @@ class _PerDeviceGenerator(dataset_ops.DatasetV2): if arg == incarnation_id: self._incarnation_id_index = i - # TODO(b/124254153): Enable autograph once the overhead is low enough. - @function.defun( - input_signature=[tensor_spec.TensorSpec([], dtypes.string)], - autograph=False) # Pure graph code. + @function.defun(input_signature=[tensor_spec.TensorSpec([], dtypes.string)]) def _finalize_func(unused_string_handle): return array_ops.constant(0, dtypes.int64) finalize_func_concrete = _finalize_func._get_concrete_function_internal() # pylint: disable=protected-access - # TODO(b/124254153): Enable autograph once the overhead is low enough. - @function.defun( - input_signature=[tensor_spec.TensorSpec([], dtypes.string)], - autograph=False) # Pure graph code. + @function.defun(input_signature=[tensor_spec.TensorSpec([], dtypes.string)]) def _remote_finalize_func(string_handle): return functional_ops.remote_call( target=source_device, diff --git a/tensorflow/python/eager/def_function.py b/tensorflow/python/eager/def_function.py index 330b2e0a76..1d54973487 100644 --- a/tensorflow/python/eager/def_function.py +++ b/tensorflow/python/eager/def_function.py @@ -514,7 +514,7 @@ class Function(object): """Make and call a `ConcreteFunction` which initializes variables.""" # Note: using defun here avoids an infinite recursion. - @function_lib.defun(autograph=False) # Pure graph code. + @function_lib.defun def initialize_variables(): for v, init in initializer_map.items(): with ops.init_scope(): diff --git a/tensorflow/python/framework/func_graph.py b/tensorflow/python/framework/func_graph.py index aee8d4c658..9097a8dd1f 100644 --- a/tensorflow/python/framework/func_graph.py +++ b/tensorflow/python/framework/func_graph.py @@ -633,8 +633,7 @@ def func_graph_from_py_func(name, # Wrapping around a decorator allows checks like tf_inspect.getargspec # to be accurate. converted_func = tf_decorator.make_decorator(original_func, wrapper) - python_func = tf_decorator.rewrap(python_func, original_func, - converted_func) + tf_decorator.rewrap(python_func, original_func, converted_func) func_outputs = python_func(*func_args, **func_kwargs) diff --git a/tensorflow/python/util/tf_decorator.py b/tensorflow/python/util/tf_decorator.py index 033d35b2fe..6c71f70c4e 100644 --- a/tensorflow/python/util/tf_decorator.py +++ b/tensorflow/python/util/tf_decorator.py @@ -138,10 +138,6 @@ def rewrap(decorator_func, previous_target, new_target): decorator_func: Callable returned by `wrap`. previous_target: Callable that needs to be replaced. new_target: Callable to replace previous_target with. - - Returns: - The updated decorator. If decorator_func is not a tf_decorator, new_target - is returned. """ # Because the process mutates the decorator, we only need to alter the # innermost function that wraps previous_target. @@ -149,20 +145,14 @@ def rewrap(decorator_func, previous_target, new_target): innermost_decorator = None target = None while hasattr(cur, '_tf_decorator'): - assert cur is not None innermost_decorator = cur target = getattr(cur, '_tf_decorator') if target.decorated_target is previous_target: break cur = target.decorated_target - # If decorator_func is not a decorator, new_target replaces it directly. if innermost_decorator is None: - # Consistency check. The caller should always pass the result of - # tf_decorator.unwrap as previous_target. If decorator_func is not a - # decorator, that will have returned decorator_func itself. - assert decorator_func is previous_target - return new_target + return target.decorated_target = new_target @@ -178,8 +168,6 @@ def rewrap(decorator_func, previous_target, new_target): else: innermost_decorator.__wrapped__ = new_target - return decorator_func - def unwrap(maybe_tf_decorator): """Unwraps an object into a list of TFDecorators and a final target. -- GitLab From 38227f2fba6b420fc69046ec28d97be7c5cd457e Mon Sep 17 00:00:00 2001 From: Paige Bailey Date: Sun, 24 Feb 2019 08:14:42 -0800 Subject: [PATCH 422/766] Reformatted example variables, fixed typo. --- tensorflow/core/ops/math_ops.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tensorflow/core/ops/math_ops.cc b/tensorflow/core/ops/math_ops.cc index 3cab5419ab..1c2e9d40ae 100644 --- a/tensorflow/core/ops/math_ops.cc +++ b/tensorflow/core/ops/math_ops.cc @@ -406,10 +406,10 @@ REGISTER_OP("_MklAdd") "complex128, string}") .SetShapeFn(shape_inference::BroadcastBinaryOpShapeFn) .Doc(R"doc( -Returns x + y element-wise. +Returns `x` + `y` element-wise. -*NOTE*: `Add` supports broadcasting. `AddN` does not. More about broadcasting -[here](http://docs.scipy.org/doc/numpy/user/basics.broadcasting.html) +*NOTE*: `tf.math.add` supports broadcasting. `tf.math.add_n` does not. More about broadcasting +[here](http://docs.scipy.org/doc/numpy/user/basics.broadcasting.html). )doc"); REGISTER_OP("Sub").BINARY_MORE().SetShapeFn( -- GitLab From 3c2033678feb046caede91832045ac8bacb2f95a Mon Sep 17 00:00:00 2001 From: Guangda Lai Date: Sun, 24 Feb 2019 08:21:15 -0800 Subject: [PATCH 423/766] Reenable building with TRT in docker. PiperOrigin-RevId: 235418348 --- .../dockerfiles/dockerfiles/devel-gpu-jupyter.Dockerfile | 6 ++++-- .../tools/dockerfiles/dockerfiles/devel-gpu.Dockerfile | 6 ++++-- .../ppc64le/devel-gpu-ppc64le-jupyter.Dockerfile | 6 ++++-- .../dockerfiles/ppc64le/devel-gpu-ppc64le.Dockerfile | 6 ++++-- .../partials/ubuntu/devel-nvidia.partial.Dockerfile | 6 ++++-- 5 files changed, 20 insertions(+), 10 deletions(-) diff --git a/tensorflow/tools/dockerfiles/dockerfiles/devel-gpu-jupyter.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/devel-gpu-jupyter.Dockerfile index 24309e3ba0..d0d544f8ed 100644 --- a/tensorflow/tools/dockerfiles/dockerfiles/devel-gpu-jupyter.Dockerfile +++ b/tensorflow/tools/dockerfiles/dockerfiles/devel-gpu-jupyter.Dockerfile @@ -64,7 +64,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ RUN [[ "${ARCH}" = "ppc64le" ]] || { apt-get update && \ apt-get install nvinfer-runtime-trt-repo-ubuntu1604-5.0.2-ga-cuda${CUDA} \ && apt-get update \ - && apt-get install -y --no-install-recommends libnvinfer5=5.0.2-1+cuda${CUDA} \ + && apt-get install -y --no-install-recommends \ + libnvinfer5=5.0.2-1+cuda${CUDA} \ + libnvinfer-dev=5.0.2-1+cuda${CUDA} \ && apt-get clean \ && rm -rf /var/lib/apt/lists/*; } @@ -72,7 +74,7 @@ RUN [[ "${ARCH}" = "ppc64le" ]] || { apt-get update && \ ENV CI_BUILD_PYTHON python ENV LD_LIBRARY_PATH /usr/local/cuda/extras/CUPTI/lib64:$LD_LIBRARY_PATH ENV TF_NEED_CUDA 1 -ENV TF_NEED_TENSORRT 0 +ENV TF_NEED_TENSORRT 1 ENV TF_CUDA_COMPUTE_CAPABILITIES=3.5,5.2,6.0,6.1,7.0 ENV TF_CUDA_VERSION=${CUDA} ENV TF_CUDNN_VERSION=${CUDNN_MAJOR_VERSION} diff --git a/tensorflow/tools/dockerfiles/dockerfiles/devel-gpu.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/devel-gpu.Dockerfile index 6bc4e32efb..e36851fa25 100644 --- a/tensorflow/tools/dockerfiles/dockerfiles/devel-gpu.Dockerfile +++ b/tensorflow/tools/dockerfiles/dockerfiles/devel-gpu.Dockerfile @@ -64,7 +64,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ RUN [[ "${ARCH}" = "ppc64le" ]] || { apt-get update && \ apt-get install nvinfer-runtime-trt-repo-ubuntu1604-5.0.2-ga-cuda${CUDA} \ && apt-get update \ - && apt-get install -y --no-install-recommends libnvinfer5=5.0.2-1+cuda${CUDA} \ + && apt-get install -y --no-install-recommends \ + libnvinfer5=5.0.2-1+cuda${CUDA} \ + libnvinfer-dev=5.0.2-1+cuda${CUDA} \ && apt-get clean \ && rm -rf /var/lib/apt/lists/*; } @@ -72,7 +74,7 @@ RUN [[ "${ARCH}" = "ppc64le" ]] || { apt-get update && \ ENV CI_BUILD_PYTHON python ENV LD_LIBRARY_PATH /usr/local/cuda/extras/CUPTI/lib64:$LD_LIBRARY_PATH ENV TF_NEED_CUDA 1 -ENV TF_NEED_TENSORRT 0 +ENV TF_NEED_TENSORRT 1 ENV TF_CUDA_COMPUTE_CAPABILITIES=3.5,5.2,6.0,6.1,7.0 ENV TF_CUDA_VERSION=${CUDA} ENV TF_CUDNN_VERSION=${CUDNN_MAJOR_VERSION} diff --git a/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-gpu-ppc64le-jupyter.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-gpu-ppc64le-jupyter.Dockerfile index c508a0f73f..6935dd1986 100644 --- a/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-gpu-ppc64le-jupyter.Dockerfile +++ b/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-gpu-ppc64le-jupyter.Dockerfile @@ -64,7 +64,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ RUN [[ "${ARCH}" = "ppc64le" ]] || { apt-get update && \ apt-get install nvinfer-runtime-trt-repo-ubuntu1604-5.0.2-ga-cuda${CUDA} \ && apt-get update \ - && apt-get install -y --no-install-recommends libnvinfer5=5.0.2-1+cuda${CUDA} \ + && apt-get install -y --no-install-recommends \ + libnvinfer5=5.0.2-1+cuda${CUDA} \ + libnvinfer-dev=5.0.2-1+cuda${CUDA} \ && apt-get clean \ && rm -rf /var/lib/apt/lists/*; } @@ -72,7 +74,7 @@ RUN [[ "${ARCH}" = "ppc64le" ]] || { apt-get update && \ ENV CI_BUILD_PYTHON python ENV LD_LIBRARY_PATH /usr/local/cuda/extras/CUPTI/lib64:$LD_LIBRARY_PATH ENV TF_NEED_CUDA 1 -ENV TF_NEED_TENSORRT 0 +ENV TF_NEED_TENSORRT 1 ENV TF_CUDA_COMPUTE_CAPABILITIES=3.5,5.2,6.0,6.1,7.0 ENV TF_CUDA_VERSION=${CUDA} ENV TF_CUDNN_VERSION=${CUDNN_MAJOR_VERSION} diff --git a/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-gpu-ppc64le.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-gpu-ppc64le.Dockerfile index f910cb2e91..42eca62ccb 100644 --- a/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-gpu-ppc64le.Dockerfile +++ b/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-gpu-ppc64le.Dockerfile @@ -64,7 +64,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ RUN [[ "${ARCH}" = "ppc64le" ]] || { apt-get update && \ apt-get install nvinfer-runtime-trt-repo-ubuntu1604-5.0.2-ga-cuda${CUDA} \ && apt-get update \ - && apt-get install -y --no-install-recommends libnvinfer5=5.0.2-1+cuda${CUDA} \ + && apt-get install -y --no-install-recommends \ + libnvinfer5=5.0.2-1+cuda${CUDA} \ + libnvinfer-dev=5.0.2-1+cuda${CUDA} \ && apt-get clean \ && rm -rf /var/lib/apt/lists/*; } @@ -72,7 +74,7 @@ RUN [[ "${ARCH}" = "ppc64le" ]] || { apt-get update && \ ENV CI_BUILD_PYTHON python ENV LD_LIBRARY_PATH /usr/local/cuda/extras/CUPTI/lib64:$LD_LIBRARY_PATH ENV TF_NEED_CUDA 1 -ENV TF_NEED_TENSORRT 0 +ENV TF_NEED_TENSORRT 1 ENV TF_CUDA_COMPUTE_CAPABILITIES=3.5,5.2,6.0,6.1,7.0 ENV TF_CUDA_VERSION=${CUDA} ENV TF_CUDNN_VERSION=${CUDNN_MAJOR_VERSION} diff --git a/tensorflow/tools/dockerfiles/partials/ubuntu/devel-nvidia.partial.Dockerfile b/tensorflow/tools/dockerfiles/partials/ubuntu/devel-nvidia.partial.Dockerfile index cf3e38b8c8..e02eb053f0 100644 --- a/tensorflow/tools/dockerfiles/partials/ubuntu/devel-nvidia.partial.Dockerfile +++ b/tensorflow/tools/dockerfiles/partials/ubuntu/devel-nvidia.partial.Dockerfile @@ -41,7 +41,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ RUN [[ "${ARCH}" = "ppc64le" ]] || { apt-get update && \ apt-get install nvinfer-runtime-trt-repo-ubuntu1604-5.0.2-ga-cuda${CUDA} \ && apt-get update \ - && apt-get install -y --no-install-recommends libnvinfer5=5.0.2-1+cuda${CUDA} \ + && apt-get install -y --no-install-recommends \ + libnvinfer5=5.0.2-1+cuda${CUDA} \ + libnvinfer-dev=5.0.2-1+cuda${CUDA} \ && apt-get clean \ && rm -rf /var/lib/apt/lists/*; } @@ -49,7 +51,7 @@ RUN [[ "${ARCH}" = "ppc64le" ]] || { apt-get update && \ ENV CI_BUILD_PYTHON python ENV LD_LIBRARY_PATH /usr/local/cuda/extras/CUPTI/lib64:$LD_LIBRARY_PATH ENV TF_NEED_CUDA 1 -ENV TF_NEED_TENSORRT 0 +ENV TF_NEED_TENSORRT 1 ENV TF_CUDA_COMPUTE_CAPABILITIES=3.5,5.2,6.0,6.1,7.0 ENV TF_CUDA_VERSION=${CUDA} ENV TF_CUDNN_VERSION=${CUDNN_MAJOR_VERSION} -- GitLab From 2e88f70ff56f19add121330c7b9c61fbb81e67a5 Mon Sep 17 00:00:00 2001 From: aweers <32593524+aweers@users.noreply.github.com> Date: Sun, 24 Feb 2019 17:36:05 +0100 Subject: [PATCH 424/766] Added ```python for usage examples --- tensorflow/python/eager/backprop.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tensorflow/python/eager/backprop.py b/tensorflow/python/eager/backprop.py index 694b05c1ee..801e3d6fa5 100644 --- a/tensorflow/python/eager/backprop.py +++ b/tensorflow/python/eager/backprop.py @@ -973,13 +973,15 @@ class GradientTape(object): definition of a Jacobian. Example usage: - + + ```python with tf.GradientTape() as g: x = tf.constant([1.0, 2.0]) g.watch(x) y = x * x jacobian = g.jacobian(y, x) # jacobian value is [[2., 0.], [0., 4.]] + ``` Args: target: Tensor to be differentiated. @@ -1082,12 +1084,14 @@ class GradientTape(object): result in the jacobian computation given the independence assumption. Example usage: + ```python with tf.GradientTape() as g: x = tf.constant([[1, 2], [3, 4]], dtype=tf.float32) g.watch(x) y = x * x batch_jacobian = g.batch_jacobian(y, x) # batch_jacobian is [[[2, 0], [0, 4]], [[6, 0], [0, 8]]] + ``` Args: target: A tensor with rank 2 or higher and with shape [b, y1, ..., y_n]. -- GitLab From 2e88b6a29b39c7088a44cef018442b8413b7ba30 Mon Sep 17 00:00:00 2001 From: Albin Joy Date: Sun, 24 Feb 2019 22:21:54 +0530 Subject: [PATCH 425/766] Reworked on the comments --- tensorflow/core/grappler/optimizers/layout_optimizer.cc | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tensorflow/core/grappler/optimizers/layout_optimizer.cc b/tensorflow/core/grappler/optimizers/layout_optimizer.cc index 05a8293284..e9d622afbf 100644 --- a/tensorflow/core/grappler/optimizers/layout_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/layout_optimizer.cc @@ -2048,11 +2048,10 @@ class DataLayoutOptimizer : GraphProcessor { // only needs to be performed if at least one node in the previous pass is // expanded. if (graph_->node_size() > node_size_original) { - // Creating Const nodes holding the permutation used by added Transposes - // of nodes and ensure both are in the same frame. - NodeDef* n = AddNodePermNHWCToNCHW(); - n = AddNodePermNCHWToNHWC(); - n = n; // Avoid compilation warning + // Create Const nodes holding the permutation used by added Transposes of + // nodes not in a frame. + AddNodePermNHWCToNCHW(); + AddNodePermNCHWToNHWC(); std::set ops_format_agnostic = GetOpsFormatAgnostic(); for (int i = 0; i < graph_->node_size(); i++) { if (ops_format_agnostic.find(graph_->node(i).op()) != -- GitLab From 7f1fad24cf085a2ec9f3b3e46b12bf3584a51d04 Mon Sep 17 00:00:00 2001 From: Deven Desai Date: Fri, 1 Feb 2019 21:38:02 +0000 Subject: [PATCH 426/766] adding support for ROCm MIOpen (dnn) plugin --- tensorflow/stream_executor/dnn.h | 214 + tensorflow/stream_executor/rocm/BUILD | 79 +- tensorflow/stream_executor/rocm/rocm_dnn.cc | 4520 +++++++++++++++++++ tensorflow/stream_executor/rocm/rocm_dnn.h | 776 ++++ 4 files changed, 5549 insertions(+), 40 deletions(-) create mode 100644 tensorflow/stream_executor/rocm/rocm_dnn.cc create mode 100644 tensorflow/stream_executor/rocm/rocm_dnn.h diff --git a/tensorflow/stream_executor/dnn.h b/tensorflow/stream_executor/dnn.h index 24c29486d0..f8dba1fde1 100644 --- a/tensorflow/stream_executor/dnn.h +++ b/tensorflow/stream_executor/dnn.h @@ -2322,6 +2322,220 @@ class DnnSupport { return false; } + // Enqueues a fused convolution+bias+activation operation onto the stream. + // + // Arguments (all borrowed): + // + // stream: borrowed pointer to the stream that the 'fusion' operation should + // be enqueued onto. + // + // conv_input_descriptor: dimensions of the convolution input layer. + // conv_input_data: device memory which contains the convolution input. + // + // filter_descriptor: dimensions of the convolution filter. + // filter_data: device memory which contains the convolution filter weights. + // + // convolution_descriptor: stride of the convolution filter. + // + // bias_descriptor: dimensions of the bias layer + // biases: device memory region containing biases to add to the convolution + // output + // + // activation_mode: Type of activation to perform. + // + // output_descriptor: dimensions of the output layer. + // output_data: device memory region in which to place the fusion result. + // + // output_profile_result: the output profile result for this call. + // The profiling is only enabled when this is not nullptr. + // + virtual bool DoFusedConvolutionBiasActivation( + Stream* stream, const dnn::BatchDescriptor& conv_input_descriptor, + const DeviceMemory& conv_input_data, + const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::BatchDescriptor& bias_descriptor, + const DeviceMemory& bias_data, dnn::ActivationMode activation_mode, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory* output_data, + dnn::ProfileResult* output_profile_result) { + return false; + } + + // Enqueues a fused batchnorm+activation (inference) operation onto the + // stream. + // + // Arguments (all borrowed): + // + // stream: borrowed pointer to the stream that the 'fusion' operation should + // be enqueued onto. + // + // x_descriptor: dimensions of the batchnorm input layer. + // x_data: device memory which contains the batchnorm input. + // + // scale_offset_mean_variance_descriptor: + // dimensions of the scale/offset/mean/variance tensor. + // scale_data: device memory which contains the scale input. + // offset_data: device memory which contains the offset input. + // mean_data: device memory which contains the mean input. + // variance_data: device memory which contains the variance input. + // epsilon : the epsilon value to use in batchnorm calculation + // + // activation_mode: Type of activation to perform. + // + // y_data: device memory region in which to place the fusion result. + // + // output_profile_result: the output profile result for this call. + // The profiling is only enabled when this is not nullptr. + // + virtual bool DoFusedBatchNormActivationInference( + Stream* stream, const dnn::BatchDescriptor& x_descriptor, + const DeviceMemory& x_data, + const dnn::BatchDescriptor& scale_offset_mean_variance_descriptor, + const DeviceMemory& scale_data, + const DeviceMemory& offset_data, + const DeviceMemory& mean_data, + const DeviceMemory& variance_data, double epsilon, + dnn::ActivationMode activation_mode, DeviceMemory* y_data, + dnn::ProfileResult* output_profile_result) { + return false; + } + + virtual bool DoFusedBatchNormActivationInference( + Stream* stream, const dnn::BatchDescriptor& x_descriptor, + const DeviceMemory& x_data, + const dnn::BatchDescriptor& scale_offset_mean_variance_descriptor, + const DeviceMemory& scale_data, + const DeviceMemory& offset_data, + const DeviceMemory& mean_data, + const DeviceMemory& variance_data, double epsilon, + dnn::ActivationMode activation_mode, DeviceMemory* y_data, + dnn::ProfileResult* output_profile_result) { + return false; + } + + // Enqueues a fused batchnorm+activation (training-fwd) operation onto the + // stream. + // + // Arguments (all borrowed): + // + // stream: borrowed pointer to the stream that the 'fusion' operation should + // be enqueued onto. + // + // x_descriptor: dimensions of the batchnorm input layer. + // x_data: device memory which contains the batchnorm input. + // + // scale_offset_mean_variance_descriptor: + // dimensions of the scale/offset/mean/variance tensor. + // scale_data: device memory which contains the scale input. + // offset_data: device memory which contains the offset input. + // epsilon : the epsilon value to use in batchnorm calculation + // + // activation_mode: Type of activation to perform. + // + // y_data: device memory region in which to place the fusion result. + // batch_mean_data: device memory in which to place the batch mean output. + // batch_var_data: device memory in which to place the batch variance output. + // saved_mean_data: device memory in which to save the mean for bwd pass. + // saved_var_data: device memory in which to save the variance for bwd pass. + // + // output_profile_result: the output profile result for this call. + // The profiling is only enabled when this is not nullptr. + // + virtual bool DoFusedBatchNormActivationForward( + Stream* stream, const dnn::BatchDescriptor& x_descriptor, + const DeviceMemory& x_data, + const dnn::BatchDescriptor& scale_offset_mean_variance_descriptor, + const DeviceMemory& scale_data, + const DeviceMemory& offset_data, double epsilon, + dnn::ActivationMode activation_mode, DeviceMemory* y_data, + DeviceMemory* batch_mean_data, DeviceMemory* batch_var_data, + DeviceMemory* saved_mean_data, DeviceMemory* saved_var_data, + dnn::ProfileResult* output_profile_result) { + return false; + } + + virtual bool DoFusedBatchNormActivationForward( + Stream* stream, const dnn::BatchDescriptor& x_descriptor, + const DeviceMemory& x_data, + const dnn::BatchDescriptor& scale_offset_mean_variance_descriptor, + const DeviceMemory& scale_data, + const DeviceMemory& offset_data, double epsilon, + dnn::ActivationMode activation_mode, DeviceMemory* y_data, + DeviceMemory* batch_mean_data, DeviceMemory* batch_var_data, + DeviceMemory* saved_mean_data, DeviceMemory* saved_var_data, + dnn::ProfileResult* output_profile_result) { + return false; + } + + // Enqueues a fused batchnorm+activation (training-bwd) operation onto the + // stream. + // + // Arguments (all borrowed): + // + // stream: borrowed pointer to the stream that the 'fusion' operation should + // be enqueued onto. + // + // y_act_backprop_descriptor: dimensions of the backprop input from the + // previous layer. y_act_backprop_data: device memory which contains the + // backprop input. + // + // y_act_data: device memory which contains the actv-fwd output data. + // + // activation_mode: actv-fwd type. + // + // scale_offset_mean_variance_descriptor: + // dimensions of the scale/offset/mean/variance tensor. + // scale_data: device memory which contains the scale input. + // offset_data: device memory which contains the offset input. + // saved_mean_data: device memory which contains the saved mean from fwd + // pass. saved_var_data: device memory which contains the saved variance from + // fwd pass. + // + // x_bn_backprop_data: device memory region in which to place the backprop + // data from this layer scale_backprop_data: device memory in which to place + // the scale backprop output. offset_backprop_data: device memory in which to + // place the offset backprop output. + // + // output_profile_result: the output profile result for this call. + // The profiling is only enabled when this is not nullptr. + // + virtual bool DoFusedBatchNormActivationBackward( + Stream* stream, const dnn::BatchDescriptor& y_act_backprop_descriptor, + const DeviceMemory& y_act_backprop_data, + const DeviceMemory& y_act_data, + dnn::ActivationMode activation_mode, const DeviceMemory& x_bn_data, + const dnn::BatchDescriptor& scale_offset_mean_variance_descriptor, + const DeviceMemory& scale_data, + const DeviceMemory& offset_data, + const DeviceMemory& saved_mean_data, + const DeviceMemory& saved_var_data, + DeviceMemory* x_bn_backprop_data, + DeviceMemory* scale_backprop_data, + DeviceMemory* offset_backprop_data, + dnn::ProfileResult* output_profile_result) { + return false; + } + + virtual bool DoFusedBatchNormActivationBackward( + Stream* stream, const dnn::BatchDescriptor& y_act_backprop_descriptor, + const DeviceMemory& y_act_backprop_data, + const DeviceMemory& y_act_data, + dnn::ActivationMode activation_mode, + const DeviceMemory& x_bn_data, + const dnn::BatchDescriptor& scale_offset_mean_variance_descriptor, + const DeviceMemory& scale_data, + const DeviceMemory& offset_data, + const DeviceMemory& saved_mean_data, + const DeviceMemory& saved_var_data, + DeviceMemory* x_bn_backprop_data, + DeviceMemory* scale_backprop_data, + DeviceMemory* offset_backprop_data, + dnn::ProfileResult* output_profile_result) { + return false; + } + protected: // Returns whether status is 'ok', and potentially logs the error. static bool IsStatusOk(const port::Status& status, bool report_error); diff --git a/tensorflow/stream_executor/rocm/BUILD b/tensorflow/stream_executor/rocm/BUILD index 5190b551f8..cb39690ad1 100644 --- a/tensorflow/stream_executor/rocm/BUILD +++ b/tensorflow/stream_executor/rocm/BUILD @@ -198,44 +198,44 @@ cc_library( alwayslink = True, ) -# FIXME: enable in future PRs -#cc_library( -# name = "miopen_plugin", -# srcs = ["rocm_dnn.cc"], -# hdrs = [], -# copts = [ -# # STREAM_EXECUTOR_CUDNN_WRAP would fail on Clang with the default -# # setting of template depth 256 -# "-ftemplate-depth-512", -# ], -# visibility = ["//visibility:public"], -# deps = [ -# ":rocm_diagnostics", -# ":rocm_driver", -# ":rocm_gpu_executor", -# ":rocm_platform_id", -# "//third_party/eigen3", -# "//tensorflow/core:lib", -# "//tensorflow/core:lib_internal", -# "//tensorflow/core:logger", -# "//tensorflow/stream_executor:dnn", -# "//tensorflow/stream_executor:event", -# "//tensorflow/stream_executor:logging_proto_cc", -# "//tensorflow/stream_executor:plugin_registry", -# "//tensorflow/stream_executor:scratch_allocator", -# "//tensorflow/stream_executor:stream_executor_pimpl_header", -# "//tensorflow/stream_executor:temporary_device_memory", -# "//tenosrflow/stream_executor/gpu:gpu_activation_header", -# "//tenosrflow/stream_executor/gpu:gpu_stream_header", -# "//tenosrflow/stream_executor/gpu:gpu_timer_header", -# "//tensorflow/stream_executor/lib", -# "//tensorflow/stream_executor/platform", -# "//tensorflow/stream_executor/platform:dso_loader", -# "@com_google_absl//absl/strings", -# "@local_config_rocm//rocm:rocm_headers", -# ] + tf_additional_miopen_plugin_deps() + if_static(["@local_config_rocm//rocm:miopen"]), -# alwayslink = True, -#) +cc_library( + name = "miopen_plugin", + srcs = if_rocm_is_configured(["rocm_dnn.cc"]), + hdrs = if_rocm_is_configured(["rocm_dnn.h"]), + copts = [ + # STREAM_EXECUTOR_CUDNN_WRAP would fail on Clang with the default + # setting of template depth 256 + "-ftemplate-depth-512", + ], + visibility = ["//visibility:public"], + deps = if_rocm_is_configured([ + ":rocm_diagnostics", + ":rocm_driver", + ":rocm_gpu_executor", + ":rocm_platform_id", + "//third_party/eigen3", + "//tensorflow/core:lib", + "//tensorflow/core:lib_internal", + "//tensorflow/stream_executor:dnn", + "//tensorflow/stream_executor:event", + "//tensorflow/stream_executor:logging_proto_cc", + "//tensorflow/stream_executor:plugin_registry", + "//tensorflow/stream_executor:scratch_allocator", + "//tensorflow/stream_executor:stream_executor_pimpl_header", + "//tensorflow/stream_executor:temporary_device_memory", + "//tensorflow/stream_executor/gpu:gpu_activation_header", + "//tensorflow/stream_executor/gpu:gpu_stream_header", + "//tensorflow/stream_executor/gpu:gpu_timer_header", + "//tensorflow/stream_executor/lib", + "//tensorflow/stream_executor/platform", + "//tensorflow/stream_executor/platform:dso_loader", + "@com_google_absl//absl/strings", + "@local_config_rocm//rocm:rocm_headers", + ] + if_static([ + "@local_config_rocm//rocm:miopen", + ])), + alwayslink = True, +) cc_library( name = "rocrand_plugin", @@ -267,8 +267,7 @@ cc_library( copts = tf_copts(), visibility = ["//visibility:public"], deps = if_rocm_is_configured([ - # FIXME: enable in future PRs - #":miopen_plugin", + ":miopen_plugin", ":rocfft_plugin", ":rocblas_plugin", ":rocrand_plugin", diff --git a/tensorflow/stream_executor/rocm/rocm_dnn.cc b/tensorflow/stream_executor/rocm/rocm_dnn.cc new file mode 100644 index 0000000000..96b38039a6 --- /dev/null +++ b/tensorflow/stream_executor/rocm/rocm_dnn.cc @@ -0,0 +1,4520 @@ +/* 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/stream_executor/rocm/rocm_dnn.h" + +#include +#include + +#include "absl/strings/str_cat.h" +#include "rocm/include/miopen/miopen.h" +#include "tensorflow/core/lib/hash/hash.h" +#include "tensorflow/stream_executor/dnn.h" +#include "tensorflow/stream_executor/gpu/gpu_activation.h" +#include "tensorflow/stream_executor/gpu/gpu_driver.h" +#include "tensorflow/stream_executor/gpu/gpu_executor.h" +#include "tensorflow/stream_executor/gpu/gpu_stream.h" +#include "tensorflow/stream_executor/gpu/gpu_timer.h" +#include "tensorflow/stream_executor/lib/env.h" +#include "tensorflow/stream_executor/lib/error.h" +#include "tensorflow/stream_executor/lib/initialize.h" +#include "tensorflow/stream_executor/lib/threadpool.h" +#include "tensorflow/stream_executor/platform/dso_loader.h" +#include "tensorflow/stream_executor/platform/logging.h" +#include "tensorflow/stream_executor/plugin_registry.h" +#include "tensorflow/stream_executor/rocm/rocm_diagnostics.h" +#include "tensorflow/stream_executor/rocm/rocm_platform_id.h" +#include "tensorflow/stream_executor/scratch_allocator.h" +#include "tensorflow/stream_executor/stream.h" +#include "tensorflow/stream_executor/stream_executor_pimpl.h" +#include "third_party/eigen3/Eigen/Core" + +namespace { + +// Converts (via narrowing) a type T value to a type U, and checks that the +// value has no value change due to the conversion. +template +NarrowT CheckedNarrowing(const WideT& wide) { + NarrowT narrow = wide; + CHECK_EQ(narrow, wide) + << "checked narrowing failed; values not equal post-conversion"; + return narrow; +} + +} // namespace + +namespace stream_executor { + +using dnn::BatchDescriptor; +using dnn::ConvolutionDescriptor; +using dnn::FilterDescriptor; +using dnn::NormalizeDescriptor; +using dnn::PoolingDescriptor; + +namespace gpu { + +PLUGIN_REGISTRY_DEFINE_PLUGIN_ID(kMIOpenPlugin); + +string ToString(miopenStatus_t status) { + switch (status) { + case miopenStatusSuccess: + return "miopenStatusSuccess"; + case miopenStatusNotInitialized: + return "miopenStatusNotInitialized"; + case miopenStatusAllocFailed: + return "miopenStatusAllocFailed"; + case miopenStatusBadParm: + return "miopenStatusBadParm"; + case miopenStatusInternalError: + return "miopenStatusInternalError"; + case miopenStatusInvalidValue: + return "miopenStatusInvalidValue"; + case miopenStatusNotImplemented: + return "miopenStatusNotImplemented"; + case miopenStatusUnknownError: + return "miopenStatusUnknownError"; + default: + return absl::StrCat("(status), + ">"); + } +} + +// RAII wrapper for all calls to MIOpen with a MIOpen handle argument. +// +// See MIOpenAccess::GetHandle() for details. +class MIOpenHandle { + public: + // Takes ownership of the executor context and the lock to access MIOpen + // using handle. + MIOpenHandle(gpu::ScopedActivateExecutorContext context, mutex_lock lock, + miopenHandle_t handle) + : context_(std::move(context)), lock_(std::move(lock)), handle_(handle) {} + + // Returns MIOpen handle. To be passed directly to MIOpen APIs, don't keep + // a copy. + miopenHandle_t handle() const { return handle_; } + + private: + gpu::ScopedActivateExecutorContext context_; + mutex_lock lock_; + miopenHandle_t handle_; // Not owned. +}; + +namespace wrap { + +#ifdef PLATFORM_GOOGLE + +#define STREAM_EXECUTOR_MIOPEN_WRAP(__name) \ + struct WrapperShim__##__name { \ + template \ + miopenStatus_t operator()(Args... args) { \ + miopenStatus_t retval = ::__name(args...); \ + return retval; \ + } \ + } __name; + +#else + +#define STREAM_EXECUTOR_MIOPEN_WRAP(__name) \ + struct DynLoadShim__##__name { \ + static const char* kName; \ + using FuncPtrT = std::add_pointer::type; \ + static void* GetDsoHandle() { \ + auto s = internal::CachedDsoLoader::GetMiopenDsoHandle(); \ + return s.ValueOrDie(); \ + } \ + static FuncPtrT LoadOrDie() { \ + void* f; \ + auto s = port::Env::Default()->GetSymbolFromLibrary(GetDsoHandle(), \ + kName, &f); \ + CHECK(s.ok()) << "could not find " << kName \ + << " in miopen DSO; dlerror: " << s.error_message(); \ + return reinterpret_cast(f); \ + } \ + static FuncPtrT DynLoad() { \ + static FuncPtrT f = LoadOrDie(); \ + return f; \ + } \ + template \ + miopenStatus_t operator()(Args... args) { \ + return DynLoad()(args...); \ + } \ + } __name; \ + const char* DynLoadShim__##__name::kName = #__name; + +#endif + +// clang-format off +#define MIOPEN_DNN_ROUTINE_EACH(__macro) \ + __macro(miopenBatchNormalizationBackward) \ + __macro(miopenBatchNormalizationForwardInference) \ + __macro(miopenBatchNormalizationForwardTraining) \ + __macro(miopenGetConvolutionForwardOutputDim) \ + __macro(miopenFindConvolutionForwardAlgorithm) \ + __macro(miopenCreateTensorDescriptor) \ + __macro(miopenDestroyTensorDescriptor) \ + __macro(miopenSet2dPoolingDescriptor) \ + __macro(miopenSetLRNDescriptor) \ + __macro(miopenLRNGetWorkSpaceSize) \ + __macro(miopenCreateConvolutionDescriptor) \ + __macro(miopenCreatePoolingDescriptor) \ + __macro(miopenDestroyPoolingDescriptor) \ + __macro(miopenCreateLRNDescriptor) \ + __macro(miopenDestroyLRNDescriptor) \ + __macro(miopenDestroyConvolutionDescriptor) \ + __macro(miopenCreateWithStream) \ + __macro(miopenDestroy) \ + __macro(miopenSetStream) \ + __macro(miopenSetAllocator) \ + __macro(miopenActivationForward) \ + __macro(miopenConvolutionForward) \ + __macro(miopenConvolutionBackwardBias) \ + __macro(miopenConvolutionForwardGetWorkSpaceSize) \ + __macro(miopenInitConvolutionDescriptor) \ + __macro(miopenGetConvolutionDescriptor) \ + __macro(miopenSetConvolutionGroupCount) \ + __macro(miopenSet4dTensorDescriptor) \ + __macro(miopenGetTensorDescriptor) \ + __macro(miopenSetTensorDescriptor) \ + __macro(miopenGetTensorDescriptorSize) \ + __macro(miopenPoolingForward) \ + __macro(miopenPoolingGetWorkSpaceSize) \ + __macro(miopenPoolingBackward) \ + __macro(miopenLRNForward) \ + __macro(miopenLRNBackward) \ + __macro(miopenOpTensor) \ + __macro(miopenConvolutionBackwardData) \ + __macro(miopenConvolutionBackwardWeights) \ + __macro(miopenConvolutionBackwardWeightsGetWorkSpaceSize)\ + __macro(miopenFindConvolutionBackwardDataAlgorithm) \ + __macro(miopenFindConvolutionBackwardWeightsAlgorithm) \ + __macro(miopenConvolutionBackwardDataGetWorkSpaceSize) \ + __macro(miopenCreateRNNDescriptor) \ + __macro(miopenSetRNNDescriptor) \ + __macro(miopenDestroyRNNDescriptor) \ + __macro(miopenGetRNNParamsSize) \ + __macro(miopenGetRNNLayerParam) \ + __macro(miopenGetRNNLayerBias) \ + __macro(miopenGetRNNWorkspaceSize) \ + __macro(miopenGetRNNTrainingReserveSize) \ + __macro(miopenRNNForwardInference) \ + __macro(miopenRNNForwardTraining) \ + __macro(miopenRNNBackwardData) \ + __macro(miopenRNNBackwardWeights) \ + __macro(miopenGetRNNLayerParamOffset) \ + __macro(miopenGetRNNLayerParamSize) \ + __macro(miopenGetRNNLayerBiasOffset) \ + __macro(miopenGetRNNLayerBiasSize) \ + __macro(miopenGetRNNParamsDescriptor) \ + __macro(miopenCreateActivationDescriptor) \ + __macro(miopenSetActivationDescriptor) \ + __macro(miopenGetActivationDescriptor) \ + __macro(miopenDestroyActivationDescriptor) \ + __macro(miopenCreateFusionPlan) \ + __macro(miopenCreateOpConvForward) \ + __macro(miopenCreateOpBiasForward) \ + __macro(miopenCreateOpActivationForward) \ + __macro(miopenCreateOpActivationBackward) \ + __macro(miopenCreateOpBatchNormInference) \ + __macro(miopenCreateOpBatchNormForward) \ + __macro(miopenCreateOpBatchNormBackward) \ + __macro(miopenCompileFusionPlan) \ + __macro(miopenFusionPlanGetOp) \ + __macro(miopenCreateOperatorArgs) \ + __macro(miopenSetOpArgsConvForward) \ + __macro(miopenSetOpArgsBiasForward) \ + __macro(miopenSetOpArgsActivForward) \ + __macro(miopenSetOpArgsActivBackward) \ + __macro(miopenSetOpArgsBatchNormInference) \ + __macro(miopenSetOpArgsBatchNormForward) \ + __macro(miopenSetOpArgsBatchNormBackward) \ + __macro(miopenExecuteFusionPlan) \ + __macro(miopenDestroyOperatorArgs) \ + __macro(miopenDestroyFusionPlan) + +// clang-format on + +MIOPEN_DNN_ROUTINE_EACH(STREAM_EXECUTOR_MIOPEN_WRAP) + +#undef MIOPEN_DNN_ROUTINE_EACH + +} // namespace wrap + +namespace { + +// These routines should ideally be provided as an MIOpen API. +// They are called for *every* _ROCMmFusedOp*::Compute call, and they need to be +// efficient! Instead of calculating the hash value by quering the MIOpen Get* +// APIs for the descriptor components, it would be a lot more efficient if, +// MIOpen calculated the hash value when creating the descriptor, stored it on +// the descriptor datastructure, and provided an API routine to query it. + +const int kMaxMIOpenTensorSize = 5; + +uint64 GetHashValue(miopenTensorDescriptor_t tensor_desc) { + miopenDataType_t datatype = miopenFloat; + int dims[kMaxMIOpenTensorSize] = {0}; + int strides[kMaxMIOpenTensorSize] = {0}; + wrap::miopenGetTensorDescriptor(tensor_desc, &datatype, dims, strides); + + uint64 hash_value = tensorflow::hash()(datatype); + for (int dim : dims) + hash_value = + tensorflow::Hash64Combine(hash_value, tensorflow::hash()(dim)); + for (int stride : strides) + hash_value = + tensorflow::Hash64Combine(hash_value, tensorflow::hash()(stride)); + + return hash_value; +} + +uint64 GetHashValue(miopenConvolutionDescriptor_t conv_desc) { + miopenConvolutionMode_t c_mode = miopenConvolution; + int pad_h = 0, pad_w = 0, u = 0, v = 0, dilation_h = 0, dilation_w = 0; + wrap::miopenGetConvolutionDescriptor(conv_desc, &c_mode, &pad_h, &pad_w, &u, + &v, &dilation_h, &dilation_w); + + uint64 hash_value = tensorflow::hash()(c_mode); + hash_value = + tensorflow::Hash64Combine(hash_value, tensorflow::hash()(pad_h)); + hash_value = + tensorflow::Hash64Combine(hash_value, tensorflow::hash()(pad_w)); + hash_value = + tensorflow::Hash64Combine(hash_value, tensorflow::hash()(u)); + hash_value = + tensorflow::Hash64Combine(hash_value, tensorflow::hash()(v)); + hash_value = tensorflow::Hash64Combine(hash_value, + tensorflow::hash()(dilation_h)); + hash_value = tensorflow::Hash64Combine(hash_value, + tensorflow::hash()(dilation_w)); + + return hash_value; +} + +// Class to implement a cache of compiled fusion plans. +class CachedFusionPlans { + public: + // Check if we already have a fusion_plan corresponding to the given hash + // value. + // If we do, then + // return true (+ the cached fusion plan via given pointer) + // Else + // create a new fusion plan descriptor, + // associate it with the given hash value in the cache + // return false (+ newly created fusion plan via given pointer) + static bool FindOrCreate(uint64 hash, + miopenFusionPlanDescriptor_t* fusion_plan, + miopenFusionDirection_t fusion_direction, + miopenTensorDescriptor_t input_descriptor) { + mutex_lock lock{cached_plans_mutex}; + + bool found_cached_plan = false; + + auto it = cached_plans.find(hash); + if (it != cached_plans.end()) { + *fusion_plan = it->second; + found_cached_plan = true; + } else { + auto status = wrap::miopenCreateFusionPlan(fusion_plan, fusion_direction, + input_descriptor); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "call to miopenCreateFusionPlan failed: " + << ToString(status); + } else { + cached_plans[hash] = *fusion_plan; + } + } + + return found_cached_plan; + } + + // Need to figure out the right place to call this routine. + static void Clear() { + mutex_lock lock{cached_plans_mutex}; + + for (auto it : cached_plans) { + auto status = wrap::miopenDestroyFusionPlan(it.second); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "call to miopenDestroyFusionPlan failed: " + << ToString(status); + } + } + + cached_plans.clear(); + + unsupported_plans.clear(); + } + + // Is the Fusion plan corresponding to this hash unsupported. + static bool IsUnsupportedFusionPlan(uint64 hash) { + mutex_lock lock{cached_plans_mutex}; + return unsupported_plans.count(hash) > 0; + } + + // Mark the given hash value as corresponding to an unsupported fusion plan. + static void MarkFusionPlanUnsupported(uint64 hash) { + mutex_lock lock{cached_plans_mutex}; + unsupported_plans.insert(hash); + } + + private: + // Mutex to guard access to all data within this class. + static mutex cached_plans_mutex; + + // Map of hash-value to MIOpen Fusion plan descriptors. + // Need to be able share this across more than one stream and hence static. + static std::map cached_plans; + + // Set of hash-values that correspond to MIOpen Fusion plans that will fail + // compile and hence are not supported. + static std::set unsupported_plans; +}; + +mutex CachedFusionPlans::cached_plans_mutex; +std::map CachedFusionPlans::cached_plans; +std::set CachedFusionPlans::unsupported_plans; + +miopenHandle_t ToHandle(void* opaque_handle) { + return static_cast(opaque_handle); +} + +miopenConvFwdAlgorithm_t ToConvForwardAlgo(dnn::AlgorithmDesc algorithm) { + miopenConvFwdAlgorithm_t algo = miopenConvFwdAlgorithm_t(algorithm.algo_id()); + switch (algo) { + case miopenConvolutionFwdAlgoGEMM: + case miopenConvolutionFwdAlgoDirect: + case miopenConvolutionFwdAlgoFFT: + case miopenConvolutionFwdAlgoWinograd: + return algo; + default: + LOG(FATAL) << "Unsupported MIOpen convolution forward algorithm: " + << algorithm.algo_id(); + } +} + +miopenConvBwdDataAlgorithm_t ToConvBackwardDataAlgo( + dnn::AlgorithmDesc algorithm) { + miopenConvBwdDataAlgorithm_t algo = + miopenConvBwdDataAlgorithm_t(algorithm.algo_id()); + switch (algo) { + case miopenConvolutionBwdDataAlgoGEMM: + case miopenConvolutionBwdDataAlgoDirect: + case miopenConvolutionBwdDataAlgoFFT: + case miopenConvolutionBwdDataAlgoWinograd: + return algo; + default: + LOG(FATAL) + << "Unsupported MIOpen convolution backward algorithm for data: " + << algorithm.algo_id(); + } +} + +miopenConvBwdWeightsAlgorithm_t ToConvBackwardFilterAlgo( + dnn::AlgorithmDesc algorithm) { + miopenConvBwdWeightsAlgorithm_t algo = + miopenConvBwdWeightsAlgorithm_t(algorithm.algo_id()); + switch (algo) { + case miopenConvolutionBwdWeightsAlgoGEMM: + case miopenConvolutionBwdWeightsAlgoDirect: + return algo; + default: + LOG(FATAL) + << "Unsupported MIOpen convolution backward algorithm for filter: " + << algorithm.algo_id(); + } +} + +} // namespace + +// Wraps a MIOpen handle and provides access to it through miopenHandle_t +// instances, which also locks a mutex, acquires the ROCm context, and sets +// the stream that MIOpen should use to enqueue any work. +// +// Note: MIOpenSupport::miopen_ should be the only instantiation of this class. +class MIOpenAccess { + public: + // Takes ownership of the handle. + explicit MIOpenAccess(miopenHandle_t handle) : handle_(handle) {} + + ~MIOpenAccess() { + mutex_lock lock(mutex_); + wrap::miopenDestroy(handle_); + } + + // Creates a MIOpenHandle instance for stream. + // + // MIOpen API calls using the same handle instance need to be serialized + // across threads. This is guaranteed by MIOpenHandle instances locking the + // mutex owned by this class. + // + // Most MIOpen APIs taking a handle perform work on a HIP stream. The + // MIOpenHandle instance acquires the executor's ROCm context and sets MIOpen + // to use the provided stream. + // + // The stream argument may be null, which translates to the null stream. + // The null stream synchronizes with all other streams and it is + // therefore a bad idea (performance wise) to call any MIOpen APIs that + // enqueue work in the stream. + MIOpenHandle GetHandle(GpuExecutor* executor, Stream* stream) { + mutex_lock lock(mutex_); + gpu::ScopedActivateExecutorContext context(executor); + hipStream_t hip_stream = stream ? AsGpuStreamValue(stream) : nullptr; + auto status = wrap::miopenSetStream(handle_, hip_stream); + CHECK_EQ(status, miopenStatusSuccess) << "Failed to set MIOpen stream."; + return MIOpenHandle(std::move(context), std::move(lock), handle_); + } + + private: + // Guards the enqueueing of MIOpen operations via the handle_ below. + mutex mutex_; + + // MIOpen library handle. + miopenHandle_t handle_ GUARDED_BY(mutex_); // Owned. +}; + +MIOpenSupport::MIOpenSupport(GpuExecutor* parent) : parent_(parent) {} + +port::Status MIOpenSupport::Init() { + ScopedActivateExecutorContext context(parent_); + miopenHandle_t miopen_handle = nullptr; + auto status = wrap::miopenCreateWithStream( + reinterpret_cast(&miopen_handle), (hipStream_t)(0)); + if (status == miopenStatusSuccess) { + miopen_.reset(new MIOpenAccess(miopen_handle)); + return port::Status::OK(); + } + + CHECK_EQ(miopen_handle, nullptr); + LOG(ERROR) << "could not create miopen handle: " << ToString(status); + if (status == miopenStatusNotInitialized) { + auto result = rocm::Diagnostician::FindKernelDriverVersion(); + if (!result.ok()) { + LOG(ERROR) << "error retrieving driver version: " + << rocm::DriverVersionStatusToString(result); + } else { + const auto& version = result.ValueOrDie(); + LOG(INFO) << "possibly insufficient driver version: " + << rocm::DriverVersionToString(version); + } + } + + return port::Status{port::error::INTERNAL, + absl::StrCat("miopen library could not create a handle: ", + ToString(status))}; +} + +port::StatusOr +MIOpenSupport::GetVersion() { + // ROCM TODO: retrieve MIOpen version with its API + return perftools::gputools::dnn::VersionInfo(1, 3, 0); +} + +// Turns a BatchDescriptor structure into a miopen tensor handle within a scope. +class ScopedTensorDescriptor { + public: + ScopedTensorDescriptor(const BatchDescriptor& batch_descriptor, + miopenDataType_t elem_type) + : handle_(nullptr) { + auto status = wrap::miopenCreateTensorDescriptor(&handle_); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "could not create miopen tensor descriptor: " + << ToString(status); + } + + switch (batch_descriptor.layout()) { + case dnn::DataLayout::kBatchYXDepth: + case dnn::DataLayout::kBatchDepthYX: { + const int nd = batch_descriptor.ndims() + 2; + if (nd != 4) { + LOG(FATAL) << "miopen only supports 4D tensors, dim=" << nd + << " not allowed"; + } + + // MIOpen requires the strides and dims to be ordered as BDYX. + std::vector strides64 = + batch_descriptor.full_strides(dnn::DataLayout::kBatchDepthYX); + std::vector dims64 = + batch_descriptor.full_dims(dnn::DataLayout::kBatchDepthYX); + + // MIOpen requires arrays of ints. + std::vector strides(nd); + std::vector dims(nd); + std::transform(strides64.cbegin(), strides64.cend(), strides.begin(), + &CheckedNarrowing); + std::transform(dims64.cbegin(), dims64.cend(), dims.begin(), + &CheckedNarrowing); + status = wrap::miopenSet4dTensorDescriptor(handle_, elem_type, dims[0], + dims[1], dims[2], dims[3]); + + if (status != miopenStatusSuccess) { + LOG(FATAL) << "could not convert BatchDescriptor " + << batch_descriptor.ToString() + << " to miopen tensor descriptor: " << ToString(status); + } + } break; + default: + LOG(FATAL) << "Unsupported tensor format " + << DataLayoutString(batch_descriptor.layout()); + break; + } + } + + ~ScopedTensorDescriptor() { + auto status = wrap::miopenDestroyTensorDescriptor(handle_); + if (status != miopenStatusSuccess) { + LOG(ERROR) << "could not destroy miopen tensor descriptor: " + << ToString(status); + } + } + + miopenTensorDescriptor_t handle() const { return handle_; } + + private: + miopenTensorDescriptor_t handle_; // Owned. + + SE_DISALLOW_COPY_AND_ASSIGN(ScopedTensorDescriptor); +}; + +// Turns a FilterDescriptor structure into a miopen filter handle within a +// scope. +class ScopedFilterDescriptor { + public: + ScopedFilterDescriptor(const FilterDescriptor& filter_descriptor, + const BatchDescriptor& batch_descriptor, + miopenDataType_t elem_type) + : handle_(nullptr) { + auto status = wrap::miopenCreateTensorDescriptor(&handle_); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "could not create miopen filter descriptor: " + << ToString(status); + } + + const int nd = batch_descriptor.ndims() + 2; + + if (nd != 4) { + LOG(FATAL) << "miopen only supports 4D filters, dim=" << nd + << "not allowed" << ToString(status); + } + + std::vector dims(2 + filter_descriptor.ndims()); + dims[0] = filter_descriptor.output_feature_map_count(); + dims[1] = filter_descriptor.input_feature_map_count(); + const auto& spatial_dims = filter_descriptor.input_filter_dims(); + std::copy(spatial_dims.begin(), spatial_dims.end(), dims.begin() + 2); + + status = wrap::miopenSet4dTensorDescriptor(handle_, elem_type, dims[0], + dims[1], dims[2], dims[3]); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "could not set miopen filter descriptor: " + << ToString(status); + } + } + + ~ScopedFilterDescriptor() { + auto status = wrap::miopenDestroyTensorDescriptor(handle_); + if (status != miopenStatusSuccess) { + LOG(ERROR) << "could not destroy miopen filter descriptor: " + << ToString(status); + } + } + + miopenTensorDescriptor_t handle() const { return handle_; } + + private: + // miopen filter descriptor this object creates. Owned. + miopenTensorDescriptor_t handle_; + + SE_DISALLOW_COPY_AND_ASSIGN(ScopedFilterDescriptor); +}; + +// Turns a ConvolutionDescriptor structure into a miopen convolution handle +// within a scope. +class ScopedConvolutionDescriptor { + public: + ScopedConvolutionDescriptor( + const ConvolutionDescriptor& convolution_descriptor, + miopenDataType_t data_type) + : handle_(nullptr) { + auto status = wrap::miopenCreateConvolutionDescriptor(&handle_); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "could not create miopen convolution descriptor: " + << ToString(status); + } + const auto& strides64 = convolution_descriptor.strides(); + const auto& padding64 = convolution_descriptor.padding(); + if (convolution_descriptor.pad_alignment() == + dnn::PadAlignment::kTensorFlowPadding) { + LOG(ERROR) << "TensorFlow padding alignment is not supported."; + } + + // MIOpen requires arrays of ints. + std::vector strides(convolution_descriptor.ndims()); + std::vector padding(convolution_descriptor.ndims()); + std::transform(strides64.cbegin(), strides64.cend(), strides.begin(), + &CheckedNarrowing); + std::transform(padding64.cbegin(), padding64.cend(), padding.begin(), + &CheckedNarrowing); + std::vector upscale(convolution_descriptor.ndims(), 1); + + status = wrap::miopenInitConvolutionDescriptor( + handle_, miopenConvolution, padding[0], padding[1], strides[0], + strides[1], upscale[0], upscale[1]); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "could not set miopen convolution descriptor: " + << ToString(status); + } + + VLOG(2) << "Requesting grouped convolution: " + << convolution_descriptor.group_count(); + status = wrap::miopenSetConvolutionGroupCount( + handle_, convolution_descriptor.group_count()); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "could not set miopen convolution group count: " + << ToString(status); + } + } + ~ScopedConvolutionDescriptor() { + auto status = wrap::miopenDestroyConvolutionDescriptor(handle_); + if (status != miopenStatusSuccess) { + LOG(ERROR) << "could not destroy miopen convolution descriptor: " + << ToString(status); + } + } + + miopenConvolutionDescriptor_t handle() const { return handle_; } + + private: + miopenConvolutionDescriptor_t handle_; // Owned. + + SE_DISALLOW_COPY_AND_ASSIGN(ScopedConvolutionDescriptor); +}; + +// Turns a PoolingDescriptor structure into a miopen pooling descriptor handle +// within a scope. +class ScopedPoolingDescriptor { + public: + ScopedPoolingDescriptor(const PoolingDescriptor& pooling_descriptor) + : handle_(nullptr) { + auto status = wrap::miopenCreatePoolingDescriptor(&handle_); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "could not create miopen pooling descriptor: " + << ToString(status); + } + + absl::Span strides64 = pooling_descriptor.strides(); + absl::Span padding64 = pooling_descriptor.padding(); + absl::Span shape64 = pooling_descriptor.window(); + + const int nd = pooling_descriptor.ndims(); + std::vector shape(nd); + std::vector padding(nd); + std::vector strides(nd); + std::transform(strides64.cbegin(), strides64.cend(), strides.begin(), + &CheckedNarrowing); + std::transform(padding64.cbegin(), padding64.cend(), padding.begin(), + &CheckedNarrowing); + std::transform(shape64.cbegin(), shape64.cend(), shape.begin(), + &CheckedNarrowing); + + if (nd != 2) { + LOG(FATAL) << "miopen requires pooling dimensions be 2" + << ToString(status); + } + + status = wrap::miopenSet2dPoolingDescriptor( + handle_, + (pooling_descriptor.mode() == dnn::PoolingMode::kMaximum + ? miopenPoolingMax + : miopenPoolingAverage), + shape[0], shape[1], padding[0], padding[1], strides[0], strides[1]); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "could not set miopen pooling descriptor: " + << ToString(status); + } + } + ~ScopedPoolingDescriptor() { + auto status = wrap::miopenDestroyPoolingDescriptor(handle_); + if (status != miopenStatusSuccess) { + LOG(ERROR) << "could not destroy miopen pooling descriptor: " + << ToString(status); + } + } + + miopenPoolingDescriptor_t handle() const { return handle_; } + + private: + miopenPoolingDescriptor_t handle_; // Owned. + + SE_DISALLOW_COPY_AND_ASSIGN(ScopedPoolingDescriptor); +}; + +// Turns a NormalizeDescriptor structure into a miopen LRN descriptor handle. +class ScopedNormalizeDescriptor { + public: + ScopedNormalizeDescriptor(const NormalizeDescriptor& normalize_descriptor) + : handle_(nullptr) { + auto status = wrap::miopenCreateLRNDescriptor(&handle_); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "could not create miopen LRN descriptor: " + << ToString(status); + } + + // The range specifies that the indices in the closed range + // [i - range, i + range] should be included in the normalization for index + // i. The lrnN value is the total number of elements in the range, so + // lrnN = 2*range + 1. + unsigned lrn_N = 2 * normalize_descriptor.range() + 1; + + // Note that SE defines the normalization operation as + // + // U_i = V_i / ((bias + alpha * (sum_j V_j^2)) ^ beta) + // + // but MIOpen defines it as + // + // U_i = V_i / ((bias + (alpha / n) * (sum_j V_j^2)) ^ beta) + // + // i.e. there is a factor of n difference between the meaning of the alphas + // in the two contexts. The MIOpen alpha is n times the SE alpha. + double lrn_alpha = lrn_N * normalize_descriptor.alpha(); + + double lrn_beta = normalize_descriptor.beta(); + double lrn_k = normalize_descriptor.bias(); + status = wrap::miopenSetLRNDescriptor(handle_, miopenLRNCrossChannel, lrn_N, + lrn_alpha, lrn_beta, lrn_k); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "could not set miopen LRN descriptor: " << ToString(status); + } + } + + ~ScopedNormalizeDescriptor() { + auto status = wrap::miopenDestroyLRNDescriptor(handle_); + if (status != miopenStatusSuccess) { + LOG(ERROR) << "could not destroy miopen LRN descriptor: " + << ToString(status); + } + } + + miopenLRNDescriptor_t handle() const { return handle_; } + + private: + miopenLRNDescriptor_t handle_; // Owned. + + SE_DISALLOW_COPY_AND_ASSIGN(ScopedNormalizeDescriptor); +}; + +// Turns a activation mode into a miopen activation mode descriptor with a scope +// around it +class ScopedActivationDescriptor { + public: + ScopedActivationDescriptor(dnn::ActivationMode activation_mode) + : handle_(nullptr), + miopen_activation_mode_(miopenActivationPASTHRU), + alpha_(0.0), + beta_(0.0), + gamma_(0.0) { + auto status = wrap::miopenCreateActivationDescriptor(&handle_); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "call to miopenCreateActivationDescriptor failed: " + << ToString(status); + } else { + switch (activation_mode) { + case dnn::ActivationMode::kNone: + miopen_activation_mode_ = miopenActivationPASTHRU; + break; + + case dnn::ActivationMode::kSigmoid: + miopen_activation_mode_ = miopenActivationLOGISTIC; + break; + + case dnn::ActivationMode::kRelu: + miopen_activation_mode_ = miopenActivationRELU; + break; + + case dnn::ActivationMode::kRelu6: + miopen_activation_mode_ = miopenActivationRELU; + alpha_ = 6.0; + break; + + case dnn::ActivationMode::kTanh: + miopen_activation_mode_ = miopenActivationTANH; + break; + + default: + LOG(FATAL) << "Activation mode (" + << dnn::ActivationModeString(activation_mode) + << ") not yet implemented"; + break; + } + + status = wrap::miopenSetActivationDescriptor( + handle_, miopen_activation_mode_, alpha_, beta_, gamma_); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "call to miopenSetActivationDescriptor failed: " + << ToString(status); + } + } + } + + ~ScopedActivationDescriptor() { + auto status = wrap::miopenDestroyActivationDescriptor(handle_); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "call to miopenDestroyActivationDescriptor failed: " + << ToString(status); + } + } + + miopenActivationDescriptor_t handle() const { return handle_; } + + uint64 GetHashValue() { + uint64 hash_value = tensorflow::hash()(miopen_activation_mode_); + hash_value = tensorflow::Hash64Combine(hash_value, + tensorflow::hash()(alpha_)); + hash_value = tensorflow::Hash64Combine(hash_value, + tensorflow::hash()(beta_)); + hash_value = tensorflow::Hash64Combine(hash_value, + tensorflow::hash()(gamma_)); + + return hash_value; + } + + private: + miopenActivationDescriptor_t handle_; // Owned. + + SE_DISALLOW_COPY_AND_ASSIGN(ScopedActivationDescriptor); + + public: + // caching these values here to avoid calling miopenGetActivationDescriptor + // to do the same. miopenGetActivationDescriptor gets called twice during each + // call to execute a fusion plan (that involves the activation op)...once call + // during calculating hashvalue for the fusion op, and another before calling + // SetOpArgs for the activation op + miopenActivationMode_t miopen_activation_mode_; + double alpha_; + double beta_; + double gamma_; +}; + +// base class for all fusion plan implementations to derive from +class ScopedFusionPlanBase { + public: + ScopedFusionPlanBase(miopenHandle_t miopen_handle, + const miopenFusionDirection_t fuse_direction, + const miopenTensorDescriptor_t input_descriptor) + : miopen_handle_(miopen_handle), + fusion_plan_(nullptr), + fusion_args_(nullptr), + fusion_plan_compiled_(false) { + auto status = wrap::miopenCreateOperatorArgs(&fusion_args_); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "call to miopenCreateOperatorArgs failed: " + << ToString(status); + } + } + + virtual ~ScopedFusionPlanBase() { + auto status = wrap::miopenDestroyOperatorArgs(fusion_args_); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "call to miopenDestroyoperatorArgs failed: " + << ToString(status); + } + } + + miopenStatus_t Execute(miopenTensorDescriptor_t input_descriptor, + const void* input_data, + miopenTensorDescriptor_t output_descriptor, + void* output_data) { + auto status = wrap::miopenExecuteFusionPlan( + miopen_handle_, fusion_plan_, input_descriptor, input_data, + output_descriptor, output_data, fusion_args_); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "call to miopenExecuteFusionPlan failed: " + << ToString(status); + } + + return status; + } + + bool CompilationSucceeded() { return fusion_plan_compiled_; } + + protected: + miopenStatus_t SetConvolutionArgs(const int op_idx, const float* alpha, + const float* beta, const void* data) { + miopenFusionOpDescriptor_t conv_op; + auto status = wrap::miopenFusionPlanGetOp(fusion_plan_, op_idx, &conv_op); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "call to miopenFusionPlanGetOp failed: " + << ToString(status); + } + + status = wrap::miopenSetOpArgsConvForward(fusion_args_, conv_op, alpha, + beta, data); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "call to miopenSetOpArgsConvForward failed: " + << ToString(status); + } + return status; + } + + miopenStatus_t SetBiasArgs(const int op_idx, const float* alpha, + const float* beta, const void* data) { + miopenFusionOpDescriptor_t bias_op; + auto status = wrap::miopenFusionPlanGetOp(fusion_plan_, op_idx, &bias_op); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "call to miopenFusionPlanGetOp failed: " + << ToString(status); + } + + status = wrap::miopenSetOpArgsBiasForward(fusion_args_, bias_op, alpha, + beta, data); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "call to miopenSetOpArgsBiasForward failed: " + << ToString(status); + } + return status; + } + + miopenStatus_t SetBatchNormInferenceArgs(const int op_idx, const float* alpha, + const float* beta, const void* scale, + const void* offset, const void* mean, + const void* variance, + double epsilon) { + miopenFusionOpDescriptor_t batchnorm_op; + auto status = + wrap::miopenFusionPlanGetOp(fusion_plan_, op_idx, &batchnorm_op); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "call to miopenFusionPlanGetOp failed: " + << ToString(status); + } + + status = wrap::miopenSetOpArgsBatchNormInference(fusion_args_, batchnorm_op, + alpha, beta, scale, offset, + mean, variance, epsilon); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "call to miopenSetOpArgsBatchNormInference failed: " + << ToString(status); + } + return status; + } + + miopenStatus_t SetBatchNormForwardArgs(const int op_idx, const float* alpha, + const float* beta, const void* scale, + const void* offset, void* running_mean, + void* running_variance, void* saved_mean, + void* saved_inv_variance, + double epsilon) { + miopenFusionOpDescriptor_t batchnorm_op; + auto status = + wrap::miopenFusionPlanGetOp(fusion_plan_, op_idx, &batchnorm_op); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "call to miopenFusionPlanGetOp failed: " + << ToString(status); + } + + double exp_avg_factor = 1.0; + + status = wrap::miopenSetOpArgsBatchNormForward( + fusion_args_, batchnorm_op, alpha, beta, scale, offset, saved_mean, + saved_inv_variance, running_mean, running_variance, exp_avg_factor, epsilon); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "call to miopenSetOpArgsBatchNormForward failed: " + << ToString(status); + } + return status; + } + + miopenStatus_t SetBatchNormBackwardArgs(const int op_idx, const float* alpha, + const float* beta, const void* x, + const void* scale, const void* offset, + void* scale_grad, void* offset_grad, + const void* saved_mean, + const void* saved_inv_variance) { + miopenFusionOpDescriptor_t batchnorm_op; + auto status = + wrap::miopenFusionPlanGetOp(fusion_plan_, op_idx, &batchnorm_op); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "call to miopenFusionPlanGetOp failed: " + << ToString(status); + } + + status = wrap::miopenSetOpArgsBatchNormBackward( + fusion_args_, batchnorm_op, alpha, beta, x, scale, offset, scale_grad, + offset_grad, saved_mean, saved_inv_variance); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "call to miopenSetOpArgsBatchNormBackward failed: " + << ToString(status); + } + return status; + } + + miopenStatus_t SetActivationForwardArgs(const int op_idx, const float* alpha, + const float* beta, double activ_alpha, + double activ_beta, + double activ_gamma) { + miopenFusionOpDescriptor_t actv_op; + auto status = wrap::miopenFusionPlanGetOp(fusion_plan_, op_idx, &actv_op); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "call to miopenFusionPlanGetOp failed: " + << ToString(status); + } + + status = + wrap::miopenSetOpArgsActivForward(fusion_args_, actv_op, alpha, beta, + activ_alpha, activ_beta, activ_gamma); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "call to miopenSetOpArgsActivForward failed: " + << ToString(status); + } + return status; + } + + miopenStatus_t SetActivationBackwardArgs(const int op_idx, const float* alpha, + const float* beta, const void* y, + double activ_alpha, + double activ_beta, + double activ_gamma) { + miopenFusionOpDescriptor_t actv_op; + auto status = wrap::miopenFusionPlanGetOp(fusion_plan_, op_idx, &actv_op); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "call to miopenFusionPlanGetOp failed: " + << ToString(status); + } + + status = wrap::miopenSetOpArgsActivBackward(fusion_args_, actv_op, alpha, + beta, y, nullptr, activ_alpha, + activ_beta, activ_gamma); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "call to miopenSetOpArgsActivBackward failed: " + << ToString(status); + } + return status; + } + + miopenHandle_t miopen_handle_; + miopenFusionPlanDescriptor_t fusion_plan_; + miopenOperatorArgs_t fusion_args_; // Owned. + bool fusion_plan_compiled_; + + SE_DISALLOW_COPY_AND_ASSIGN(ScopedFusionPlanBase); +}; + +// class to represent the Convolution+Bias+Activation fusion plan +class ScopedFusionPlanConvolutionBiasActivation : public ScopedFusionPlanBase { + public: + ScopedFusionPlanConvolutionBiasActivation( + miopenHandle_t miopen_handle, miopenTensorDescriptor_t input_descriptor, + miopenTensorDescriptor_t filter_descriptor, + miopenConvolutionDescriptor_t conv_descriptor, + miopenTensorDescriptor_t bias_descriptor, + ScopedActivationDescriptor& activation_descriptor) + : ScopedFusionPlanBase(miopen_handle, miopenVerticalFusion, + input_descriptor) { + uint64 hash = GetFusionOpHashValue(miopen_handle, input_descriptor, + filter_descriptor, conv_descriptor, + bias_descriptor, activation_descriptor); + + bool is_compiled = CachedFusionPlans::FindOrCreate( + hash, &fusion_plan_, miopenVerticalFusion, input_descriptor); + if (!is_compiled) { + miopenFusionOpDescriptor_t conv_op; + auto status = wrap::miopenCreateOpConvForward( + fusion_plan_, &conv_op, conv_descriptor, filter_descriptor); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "call to miopenCreateOpConvForward failed: " + << ToString(status); + } + + miopenFusionOpDescriptor_t bias_op; + status = wrap::miopenCreateOpBiasForward(fusion_plan_, &bias_op, + bias_descriptor); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "call to miopenCreateOpBiasForward failed: " + << ToString(status); + } + + miopenFusionOpDescriptor_t actv_op; + status = wrap::miopenCreateOpActivationForward( + fusion_plan_, &actv_op, + activation_descriptor.miopen_activation_mode_); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "call to miopenCreateOpActivationForward failed: " + << ToString(status); + } + + status = wrap::miopenCompileFusionPlan(miopen_handle_, fusion_plan_); + if (status != miopenStatusSuccess) { + VLOG(2) << "call to miopenCompileFusionPlan (CBA) failed: " + << ToString(status); + + CachedFusionPlans::MarkFusionPlanUnsupported(hash); + } else { + VLOG(2) << "Fusion Plan compile succedded (CBA) "; + fusion_plan_compiled_ = true; + } + } else { + // fusion plan was already compiled...check whether it failed to compile + fusion_plan_compiled_ = !CachedFusionPlans::IsUnsupportedFusionPlan(hash); + } + } + + miopenStatus_t SetConvolutionArgs(const void* filter_data) { + float alpha = 1.0; + float beta = 0.0; + return ScopedFusionPlanBase::SetConvolutionArgs(k_conv_op_idx, &alpha, + &beta, filter_data); + } + + miopenStatus_t SetBiasArgs(const void* bias_data) { + float alpha = 1.0; + float beta = 0.0; + return ScopedFusionPlanBase::SetBiasArgs(k_bias_op_idx, &alpha, &beta, + bias_data); + } + + miopenStatus_t SetActivationForwardArgs( + ScopedActivationDescriptor& activation_descriptor) { + float alpha = 1.0; + float beta = 0.0; + + return ScopedFusionPlanBase::SetActivationForwardArgs( + k_actv_op_idx, &alpha, &beta, activation_descriptor.alpha_, + activation_descriptor.beta_, activation_descriptor.gamma_); + } + + uint64 GetFusionOpHashValue( + miopenHandle_t miopen_handle, miopenTensorDescriptor_t input_descriptor, + miopenTensorDescriptor_t filter_descriptor, + miopenConvolutionDescriptor_t conv_descriptor, + miopenTensorDescriptor_t bias_descriptor, + ScopedActivationDescriptor& activation_descriptor) { + uint64 hash_value = tensorflow::Hash64("ConvolutionBiasActivation"); + + hash_value = tensorflow::Hash64Combine( + hash_value, tensorflow::hash()(miopen_handle)); + + hash_value = + tensorflow::Hash64Combine(hash_value, GetHashValue(input_descriptor)); + hash_value = + tensorflow::Hash64Combine(hash_value, GetHashValue(filter_descriptor)); + hash_value = + tensorflow::Hash64Combine(hash_value, GetHashValue(conv_descriptor)); + hash_value = + tensorflow::Hash64Combine(hash_value, GetHashValue(bias_descriptor)); + hash_value = tensorflow::Hash64Combine( + hash_value, activation_descriptor.GetHashValue()); + return hash_value; + } + + private: + const int k_conv_op_idx = 0; + const int k_bias_op_idx = 1; + const int k_actv_op_idx = 2; + + SE_DISALLOW_COPY_AND_ASSIGN(ScopedFusionPlanConvolutionBiasActivation); +}; + +// class to represent the BatchNorm+Activation (inference) fusion plan +class ScopedFusionPlanBatchNormActivationInference + : public ScopedFusionPlanBase { + public: + ScopedFusionPlanBatchNormActivationInference( + miopenHandle_t miopen_handle, miopenTensorDescriptor_t input_descriptor, + miopenTensorDescriptor_t scale_offset_mean_variance_descriptor, + ScopedActivationDescriptor& activation_descriptor) + : ScopedFusionPlanBase(miopen_handle, miopenVerticalFusion, + input_descriptor) { + uint64 hash = GetFusionOpHashValue(miopen_handle, input_descriptor, + scale_offset_mean_variance_descriptor, + activation_descriptor); + + bool is_compiled = CachedFusionPlans::FindOrCreate( + hash, &fusion_plan_, miopenVerticalFusion, input_descriptor); + + if (!is_compiled) { + miopenFusionOpDescriptor_t batchnorm_op; + auto status = wrap::miopenCreateOpBatchNormInference( + fusion_plan_, &batchnorm_op, miopenBNSpatial, + scale_offset_mean_variance_descriptor); + + if (status != miopenStatusSuccess) { + LOG(FATAL) << "call to miopenCreateOpBatchNormInference failed: " + << ToString(status); + } + + miopenFusionOpDescriptor_t actv_op; + status = wrap::miopenCreateOpActivationForward( + fusion_plan_, &actv_op, + activation_descriptor.miopen_activation_mode_); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "call to miopenCreateOpActivationForward failed: " + << ToString(status); + } + + status = wrap::miopenCompileFusionPlan(miopen_handle_, fusion_plan_); + if (status != miopenStatusSuccess) { + VLOG(2) << "call to miopenCompileFusionPlan (BnA inference) failed: " + << ToString(status); + + CachedFusionPlans::MarkFusionPlanUnsupported(hash); + } else { + VLOG(2) << "Fusion Plan compile succedded (BnA inference) "; + fusion_plan_compiled_ = true; + } + } else { + // fusion plan was already compiled...check whether it failed to compile + fusion_plan_compiled_ = !CachedFusionPlans::IsUnsupportedFusionPlan(hash); + } + } + + miopenStatus_t SetBatchNormInferenceArgs(const void* scale, + const void* offset, const void* mean, + const void* variance, + double epsilon) { + float alpha = 1.0; + float beta = 0.0; + return ScopedFusionPlanBase::SetBatchNormInferenceArgs( + k_batchnorm_op_idx, &alpha, &beta, scale, offset, mean, variance, + epsilon); + } + + miopenStatus_t SetActivationForwardArgs( + ScopedActivationDescriptor& activation_descriptor) { + float alpha = 1.0; + float beta = 0.0; + + return ScopedFusionPlanBase::SetActivationForwardArgs( + k_actv_op_idx, &alpha, &beta, activation_descriptor.alpha_, + activation_descriptor.beta_, activation_descriptor.gamma_); + } + + uint64 GetFusionOpHashValue( + miopenHandle_t miopen_handle, miopenTensorDescriptor_t input_descriptor, + miopenTensorDescriptor_t scale_offset_mean_variance_descriptor, + ScopedActivationDescriptor& activation_descriptor) { + uint64 hash_value = tensorflow::Hash64("BatchNormActivationInference"); + + hash_value = tensorflow::Hash64Combine( + hash_value, tensorflow::hash()(miopen_handle)); + + hash_value = + tensorflow::Hash64Combine(hash_value, GetHashValue(input_descriptor)); + + hash_value = tensorflow::Hash64Combine( + hash_value, GetHashValue(scale_offset_mean_variance_descriptor)); + + hash_value = tensorflow::Hash64Combine( + hash_value, activation_descriptor.GetHashValue()); + return hash_value; + } + + private: + const int k_batchnorm_op_idx = 0; + const int k_actv_op_idx = 1; + + SE_DISALLOW_COPY_AND_ASSIGN(ScopedFusionPlanBatchNormActivationInference); +}; + +// class to represent the BatchNorm+Activation (training-forward) fusion plan +class ScopedFusionPlanBatchNormActivationForward : public ScopedFusionPlanBase { + public: + ScopedFusionPlanBatchNormActivationForward( + miopenHandle_t miopen_handle, miopenTensorDescriptor_t input_descriptor, + miopenTensorDescriptor_t scale_offset_mean_variance_descriptor, + ScopedActivationDescriptor& activation_descriptor) + : ScopedFusionPlanBase(miopen_handle, miopenVerticalFusion, + input_descriptor) { + uint64 hash = GetFusionOpHashValue(miopen_handle, input_descriptor, + scale_offset_mean_variance_descriptor, + activation_descriptor); + + bool is_compiled = CachedFusionPlans::FindOrCreate( + hash, &fusion_plan_, miopenVerticalFusion, input_descriptor); + + if (!is_compiled) { + miopenFusionOpDescriptor_t batchnorm_op; + auto status = wrap::miopenCreateOpBatchNormForward( + fusion_plan_, &batchnorm_op, miopenBNSpatial, + true /* runningMeanVariance */); + + if (status != miopenStatusSuccess) { + LOG(FATAL) << "call to miopenCreateOpBatchNormForward failed: " + << ToString(status); + } + + miopenFusionOpDescriptor_t actv_op; + status = wrap::miopenCreateOpActivationForward( + fusion_plan_, &actv_op, + activation_descriptor.miopen_activation_mode_); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "call to miopenCreateOpActivationForward failed: " + << ToString(status); + } + + status = wrap::miopenCompileFusionPlan(miopen_handle_, fusion_plan_); + if (status != miopenStatusSuccess) { + VLOG(2) << "call to miopenCompileFusionPlan (BnA forward) failed: " + << ToString(status); + + CachedFusionPlans::MarkFusionPlanUnsupported(hash); + } else { + VLOG(2) << "Fusion Plan compile succedded (BnA forward) "; + fusion_plan_compiled_ = true; + } + } else { + // fusion plan was already compiled...check whether it failed to compile + fusion_plan_compiled_ = !CachedFusionPlans::IsUnsupportedFusionPlan(hash); + } + } + + miopenStatus_t SetBatchNormForwardArgs(const void* scale, const void* offset, + void* batch_mean, void* batch_var, + void* saved_mean, void* saved_var, + double epsilon) { + float alpha = 1.0; + float beta = 0.0; + return ScopedFusionPlanBase::SetBatchNormForwardArgs( + k_batchnorm_op_idx, &alpha, &beta, scale, offset, batch_mean, batch_var, + saved_mean, saved_var, epsilon); + } + + miopenStatus_t SetActivationForwardArgs( + ScopedActivationDescriptor& activation_descriptor) { + float alpha = 1.0; + float beta = 0.0; + + return ScopedFusionPlanBase::SetActivationForwardArgs( + k_actv_op_idx, &alpha, &beta, activation_descriptor.alpha_, + activation_descriptor.beta_, activation_descriptor.gamma_); + } + + uint64 GetFusionOpHashValue( + miopenHandle_t miopen_handle, miopenTensorDescriptor_t input_descriptor, + miopenTensorDescriptor_t scale_offset_mean_variance_descriptor, + ScopedActivationDescriptor& activation_descriptor) { + uint64 hash_value = tensorflow::Hash64("BatchNormActivationForward"); + + hash_value = tensorflow::Hash64Combine( + hash_value, tensorflow::hash()(miopen_handle)); + + hash_value = + tensorflow::Hash64Combine(hash_value, GetHashValue(input_descriptor)); + + hash_value = tensorflow::Hash64Combine( + hash_value, GetHashValue(scale_offset_mean_variance_descriptor)); + + hash_value = tensorflow::Hash64Combine( + hash_value, activation_descriptor.GetHashValue()); + return hash_value; + } + + private: + const int k_batchnorm_op_idx = 0; + const int k_actv_op_idx = 1; + + SE_DISALLOW_COPY_AND_ASSIGN(ScopedFusionPlanBatchNormActivationForward); +}; + +// class to represent the BatchNorm+Activation (training-backward) fusion plan +class ScopedFusionPlanBatchNormActivationBackward + : public ScopedFusionPlanBase { + public: + ScopedFusionPlanBatchNormActivationBackward( + miopenHandle_t miopen_handle, miopenTensorDescriptor_t input_descriptor, + miopenTensorDescriptor_t scale_offset_mean_variance_descriptor, + ScopedActivationDescriptor& activation_descriptor) + : ScopedFusionPlanBase(miopen_handle, miopenVerticalFusion, + input_descriptor) { + uint64 hash = GetFusionOpHashValue(miopen_handle, input_descriptor, + scale_offset_mean_variance_descriptor, + activation_descriptor); + + bool is_compiled = CachedFusionPlans::FindOrCreate( + hash, &fusion_plan_, miopenVerticalFusion, input_descriptor); + + if (!is_compiled) { + miopenFusionOpDescriptor_t batchnorm_op; + auto status = wrap::miopenCreateOpBatchNormBackward( + fusion_plan_, &batchnorm_op, miopenBNSpatial); + + if (status != miopenStatusSuccess) { + LOG(FATAL) << "call to miopenCreateOpBatchNormBackward failed: " + << ToString(status); + } + + miopenFusionOpDescriptor_t actv_op; + status = wrap::miopenCreateOpActivationBackward( + fusion_plan_, &actv_op, + activation_descriptor.miopen_activation_mode_); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "call to miopenCreateOpActivationBackward failed: " + << ToString(status); + } + + status = wrap::miopenCompileFusionPlan(miopen_handle_, fusion_plan_); + if (status != miopenStatusSuccess) { + VLOG(2) << "call to miopenCompileFusionPlan (BnA backward) failed: " + << ToString(status); + + CachedFusionPlans::MarkFusionPlanUnsupported(hash); + } else { + VLOG(2) << "Fusion Plan compile succedded (BnA backward) "; + fusion_plan_compiled_ = true; + } + } else { + // fusion plan was already compiled...check whether it failed to compile + fusion_plan_compiled_ = !CachedFusionPlans::IsUnsupportedFusionPlan(hash); + } + } + + miopenStatus_t SetBatchNormBackwardArgs(const void* x, const void* scale, + const void* offset, + const void* saved_mean, + const void* saved_var, + void* scale_grad, void* offset_grad) { + float alpha = 1.0; + float beta = 0.0; + + return ScopedFusionPlanBase::SetBatchNormBackwardArgs( + k_batchnorm_op_idx, &alpha, &beta, x, scale, offset, scale_grad, + offset_grad, saved_mean, saved_var); + } + + miopenStatus_t SetActivationBackwardArgs( + ScopedActivationDescriptor& activation_descriptor, const void* y) { + float alpha = 1.0; + float beta = 0.0; + + return ScopedFusionPlanBase::SetActivationBackwardArgs( + k_actv_op_idx, &alpha, &beta, y, activation_descriptor.alpha_, + activation_descriptor.beta_, activation_descriptor.gamma_); + } + + uint64 GetFusionOpHashValue( + miopenHandle_t miopen_handle, miopenTensorDescriptor_t input_descriptor, + miopenTensorDescriptor_t scale_offset_mean_variance_descriptor, + ScopedActivationDescriptor& activation_descriptor) { + uint64 hash_value = tensorflow::Hash64("BatchNormActivationBackward"); + + hash_value = tensorflow::Hash64Combine( + hash_value, tensorflow::hash()(miopen_handle)); + + hash_value = + tensorflow::Hash64Combine(hash_value, GetHashValue(input_descriptor)); + + hash_value = tensorflow::Hash64Combine( + hash_value, GetHashValue(scale_offset_mean_variance_descriptor)); + + hash_value = tensorflow::Hash64Combine( + hash_value, activation_descriptor.GetHashValue()); + return hash_value; + } + + private: + const int k_batchnorm_op_idx = 0; + const int k_actv_op_idx = 1; + + SE_DISALLOW_COPY_AND_ASSIGN(ScopedFusionPlanBatchNormActivationBackward); +}; + +namespace { +miopenDataType_t ToMIOpenDataType( + dnn::DataType data_type, + dnn::DataLayout data_layout = dnn::DataLayout::kBatchDepthYX) { + switch (data_type) { + case dnn::DataType::kFloat: + return miopenFloat; + case dnn::DataType::kHalf: + return miopenHalf; + case dnn::DataType::kDouble: + default: + LOG(FATAL) << "Invalid DNN data type: " << static_cast(data_type); + } +} + +miopenDataType_t ToMIOpenDataType(dnn::DataType data_type, + dnn::FilterLayout filter_layout) { + return ToMIOpenDataType(data_type); +} + +miopenRNNInputMode_t ToMIOpenRnnInputMode(dnn::RnnInputMode input_mode) { + switch (input_mode) { + case dnn::RnnInputMode::kRnnLinearSkip: + return miopenRNNlinear; + case dnn::RnnInputMode::kRnnSkipInput: + return miopenRNNskip; + default: + LOG(FATAL) << "Invalid RNN input mode: " << static_cast(input_mode); + } +} + +miopenRNNDirectionMode_t ToMIOpenRnnDirectionMode( + dnn::RnnDirectionMode direction_mode) { + switch (direction_mode) { + case dnn::RnnDirectionMode::kRnnUnidirectional: + return miopenRNNunidirection; + case dnn::RnnDirectionMode::kRnnBidirectional: + return miopenRNNbidirection; + default: + LOG(FATAL) << "Invalid RNN direction mode: " + << static_cast(direction_mode); + } +} + +miopenRNNMode_t ToMIOpenRnnMode(dnn::RnnMode rnn_mode) { + switch (rnn_mode) { + case dnn::RnnMode::kRnnRelu: + return miopenRNNRELU; + case dnn::RnnMode::kRnnTanh: + return miopenRNNTANH; + case dnn::RnnMode::kRnnLstm: + return miopenLSTM; + case dnn::RnnMode::kRnnGru: + return miopenGRU; + default: + LOG(FATAL) << "Invalid RNN Mode: " << static_cast(rnn_mode); + } +} + +int MIOpenDataTypeToByteSize(miopenDataType_t data_type) { + switch (data_type) { + case miopenFloat: + return sizeof(float); + case miopenHalf: + return sizeof(Eigen::half); + default: + LOG(FATAL) << "Invalid DNN data type: " << static_cast(data_type); + } +} + +template +class MixinBase : public Base {}; +template <> +class MixinBase {}; + +dnn::DataType GetConvAccumulatorType(dnn::DataType data_type) { + switch (data_type) { + case dnn::DataType::kFloat: + case dnn::DataType::kDouble: + return data_type; + case dnn::DataType::kHalf: + // FIXME: Check if MIOpen can switch dynamically change accumulator type + return dnn::DataType::kFloat; + case dnn::DataType::kInt8: + case dnn::DataType::kInt32: + return dnn::DataType::kInt32; + default: + LOG(FATAL) << "Invalid DNN data type: " << static_cast(data_type); + } +} + +} // namespace + +#define RETURN_IF_MIOPEN_ERROR(STATUS, ...) \ + if (!SE_PREDICT_TRUE((STATUS) == miopenStatusSuccess)) { \ + string error_msg = absl::StrCat(ToString(STATUS), " ", __VA_ARGS__); \ + SetFailure(port::Status(port::error::UNKNOWN, error_msg)); \ + LOG(ERROR) << error_msg; \ + return; \ + } + +template +class MIOpenDescriptorCommon : public MixinBase { + public: + bool ok() const { return status_.ok(); } + port::Status Status() const { return status_; } + + protected: + void SetFailure(const port::Status& status) { status_.Update(status); } + port::Status status_; +}; + +class MIOpenRnnParamsDescriptor : public MIOpenDescriptorCommon { + public: + typedef dnn::RnnDescriptor::ParamsRegion ParamsRegion; + typedef dnn::RnnDescriptor::ParamsRegions ParamsRegions; + MIOpenRnnParamsDescriptor(miopenHandle_t miopen_handle, + const MIOpenRnnDescriptor& rnn_desc); + ~MIOpenRnnParamsDescriptor() { + auto status = wrap::miopenDestroyTensorDescriptor(handle_); + RETURN_IF_MIOPEN_ERROR(status, "Failed to destroy RNN tensor descriptor"); + } + miopenTensorDescriptor_t handle() const { + if (!ok()) return nullptr; + return handle_; + } + int64 params_size_in_bytes() const { return params_size_in_bytes_; } + ParamsRegions params_weights() const { + if (!ok()) return ParamsRegions(); + return weights_; + } + ParamsRegions params_biases() const { + if (!ok()) return ParamsRegions(); + return biases_; + } + + private: + int GetRegionCountPerLayer() const; + miopenTensorDescriptor_t handle_; + const MIOpenRnnDescriptor* rnn_desc_; + int64 params_size_in_bytes_; + ParamsRegions weights_; + ParamsRegions biases_; + port::Status status_; + SE_DISALLOW_COPY_AND_ASSIGN(MIOpenRnnParamsDescriptor); +}; + +class MIOpenRnnDescriptor : public MIOpenDescriptorCommon { + public: + MIOpenRnnDescriptor(miopenHandle_t miopen_handle, int num_layers, + int hidden_size, int input_size, + miopenRNNInputMode_t input_mode, + miopenRNNDirectionMode_t direction_mode, + miopenRNNMode_t rnn_mode, miopenDataType_t data_type, + float dropout, uint64 seed, + ScratchAllocator* state_allocator) + : rnn_desc_(nullptr), + num_layers_(num_layers), + hidden_size_(hidden_size), + input_size_(input_size), + input_mode_(input_mode), + direction_mode_(direction_mode), + rnn_mode_(rnn_mode), + data_type_(data_type) { + // Create the RNN handle + auto status = wrap::miopenCreateRNNDescriptor(&rnn_desc_); + RETURN_IF_MIOPEN_ERROR(status, "Unable to create RNN descriptor"); + status = wrap::miopenSetRNNDescriptor( + rnn_desc_ /*rnnDesc*/, hidden_size /*hiddenSize*/, + num_layers /*numLayers*/, input_mode /*inputMode*/, + direction_mode /*direction*/, rnn_mode /*mode*/, + miopenRNNwithBias /*biasMode*/, miopenRNNdefault /*algo*/, + data_type /*dataType*/); + RETURN_IF_MIOPEN_ERROR(status, "Unable to update RNN descriptor"); + // Create the params handle. + miopen_params_desc_.reset( + new MIOpenRnnParamsDescriptor(miopen_handle, *this)); + if (!miopen_params_desc_->ok()) { + SetFailure(miopen_params_desc_->Status()); + return; + } + } + ~MIOpenRnnDescriptor() override { + if (rnn_desc_) { + auto status = wrap::miopenDestroyRNNDescriptor(rnn_desc_); + RETURN_IF_MIOPEN_ERROR(status, "Unable to destroy RNN descriptor"); + } + } + miopenRNNDescriptor_t handle() const { + if (!ok()) return nullptr; + return rnn_desc_; + } + int num_layers() const { return num_layers_; } + int hidden_size() const { return hidden_size_; } + int input_size() const { return input_size_; } + miopenRNNInputMode_t input_mode() const { return input_mode_; } + miopenRNNDirectionMode_t direction_mode() const { return direction_mode_; } + miopenRNNMode_t rnn_mode() const { return rnn_mode_; } + miopenDataType_t data_type() const { return data_type_; } + int64 ParamsSizeInBytes() const override { + return miopen_params_desc_->params_size_in_bytes(); + } + miopenTensorDescriptor_t params_handle() const { + if (!miopen_params_desc_) return nullptr; + return miopen_params_desc_->handle(); + } + ParamsRegions ParamsWeightRegions() const override { + if (!ok()) return ParamsRegions(); + return miopen_params_desc_->params_weights(); + } + ParamsRegions ParamsBiasRegions() const override { + if (!ok()) return ParamsRegions(); + return miopen_params_desc_->params_biases(); + } + + private: + miopenRNNDescriptor_t rnn_desc_; + int num_layers_; + int hidden_size_; + int input_size_; + miopenRNNInputMode_t input_mode_; + miopenRNNDirectionMode_t direction_mode_; + miopenRNNMode_t rnn_mode_; + miopenDataType_t data_type_; + port::Status status_; + // no dropout in MIOpen. + // std::unique_ptr miopen_dropout_desc_; + std::unique_ptr miopen_params_desc_; + SE_DISALLOW_COPY_AND_ASSIGN(MIOpenRnnDescriptor); +}; + +// Get ID of the internal parameter tensor. +// +int MIOpenRnnParamsDescriptor::GetRegionCountPerLayer() const { + auto rnn_mode = rnn_desc_->rnn_mode(); + switch (rnn_mode) { + case miopenRNNRELU: + case miopenRNNTANH: + return 2; + case miopenLSTM: + return 8; + case miopenGRU: + return 6; + default: + LOG(FATAL) << "Invalid RNN Mode: " << static_cast(rnn_mode); + } +} + +class MIOpenRnnSequenceTensorDescriptor + : public MIOpenDescriptorCommon { + public: + MIOpenRnnSequenceTensorDescriptor(int seq_length, int batch_size, + int data_size, miopenDataType_t data_type) + : seq_length_(seq_length), + batch_size_(batch_size), + data_size_(data_size), + data_type_(data_type) { + miopenTensorDescriptor_t handle = nullptr; + if (seq_length <= 0) { + string error_msg = + absl::StrCat("sequence length must be positive: ", seq_length); + LOG(ERROR) << error_msg; + SetFailure(port::Status(port::error::UNKNOWN, error_msg)); + return; + } + auto status = wrap::miopenCreateTensorDescriptor(&handle); + RETURN_IF_MIOPEN_ERROR(status, "Failed to create tensor descriptor"); + std::array dims = {{batch_size, data_size}}; + status = wrap::miopenSetTensorDescriptor( + handle /*tensorDesc*/, data_type /*dataType*/, 2 /*nbDims*/, + dims.data() /*dimA*/, nullptr /*strideA*/); + RETURN_IF_MIOPEN_ERROR(status, "Failed to update tensor descriptor"); + // Replicate handle across the number of steps. + handles_.assign(seq_length, handle); + } + + ~MIOpenRnnSequenceTensorDescriptor() override { + // Only the first one needs to be destroyed. All others are the same. + auto status = wrap::miopenDestroyTensorDescriptor(handles_[0]); + RETURN_IF_MIOPEN_ERROR(status, + "Failed to destroy sequence tensor descriptor"); + } + + const miopenTensorDescriptor_t* handles() const { + if (!ok()) return nullptr; + CHECK(!handles_.empty()) << "handles cannot be empty"; + return handles_.data(); + } + + int seq_length() const { return seq_length_; } + int batch_size() const { return batch_size_; } + int data_size() const { return data_size_; } + + private: + int seq_length_; + int batch_size_; + int data_size_; + miopenDataType_t data_type_; + std::vector handles_; + port::Status status_; + SE_DISALLOW_COPY_AND_ASSIGN(MIOpenRnnSequenceTensorDescriptor); +}; + +class MIOpenRnnStateTensorDescriptor + : public MIOpenDescriptorCommon { + public: + MIOpenRnnStateTensorDescriptor(int num_layers, int batch_size, int data_size, + miopenDataType_t data_type) + : handle_(nullptr), + num_layers_(num_layers), + batch_size_(batch_size), + data_size_(data_size), + data_type_(data_type) { + auto status = wrap::miopenCreateTensorDescriptor(&handle_); + RETURN_IF_MIOPEN_ERROR(status, "Failed to create tensor descriptor"); + std::array dims = {{num_layers, batch_size, data_size}}; + status = wrap::miopenSetTensorDescriptor( + handle_ /*tensorDesc*/, data_type /*dataType*/, 3 /*nbDims*/, + dims.data() /*dimA*/, nullptr /*strideA*/); + RETURN_IF_MIOPEN_ERROR(status, "Failed to update tensor descriptor"); + } + + ~MIOpenRnnStateTensorDescriptor() override { + if (!handle_) { + auto status = wrap::miopenDestroyTensorDescriptor(handle_); + RETURN_IF_MIOPEN_ERROR(status, "Unable to destroy RNN state tensor"); + } + } + + miopenTensorDescriptor_t handle() const { + if (!ok()) return nullptr; + return handle_; + } + int num_layers() const { return num_layers_; } + int batch_size() const { return batch_size_; } + int data_size() const { return data_size_; } + + private: + miopenTensorDescriptor_t handle_; + int num_layers_; + int batch_size_; + int data_size_; + port::Status status_; + miopenDataType_t data_type_; + SE_DISALLOW_COPY_AND_ASSIGN(MIOpenRnnStateTensorDescriptor); +}; + +namespace { + +struct RnnModelDims { + int num_layers = 0; + int batch_size = 0; + int seq_length = 0; + int hidden_size = 0; + int input_size = 0; + int dir_count = 0; +}; + +template +bool ExtractAndCheckRnnForward( + const MIOpenRnnDescriptor& rnn_desc, + const MIOpenRnnSequenceTensorDescriptor& input_desc, + const DeviceMemory& input_data, + const MIOpenRnnStateTensorDescriptor& input_h_desc, + const DeviceMemory& input_h_data, + const MIOpenRnnStateTensorDescriptor& input_c_desc, + const DeviceMemory& input_c_data, const DeviceMemory& params, + const MIOpenRnnSequenceTensorDescriptor& output_desc, + const DeviceMemory& output_data, + const MIOpenRnnStateTensorDescriptor& output_h_desc, + const DeviceMemory& output_h_data, + const MIOpenRnnStateTensorDescriptor& output_c_desc, + const DeviceMemory& output_c_data, RnnModelDims* model_dims) { + // extract model parameters + model_dims->num_layers = rnn_desc.num_layers(); + model_dims->batch_size = input_desc.batch_size(); + model_dims->seq_length = input_desc.seq_length(); + model_dims->hidden_size = rnn_desc.hidden_size(); + model_dims->input_size = input_desc.data_size(); + model_dims->dir_count = + (rnn_desc.direction_mode() == miopenRNNbidirection) ? 2 : 1; + + // check parameters + if (!(input_h_desc.num_layers() == + model_dims->num_layers * model_dims->dir_count && + input_h_desc.batch_size() == model_dims->batch_size && + input_h_desc.data_size() == model_dims->hidden_size)) { + LOG(ERROR) << "Invalid input_h shape"; + return false; + } + if (!(input_h_desc.num_layers() == input_c_desc.num_layers() && + input_h_desc.batch_size() == input_c_desc.batch_size() && + input_h_desc.data_size() == input_c_desc.data_size())) { + LOG(ERROR) << "Invalid input_c shape"; + return false; + } + if (!(output_desc.seq_length() == model_dims->seq_length && + output_desc.batch_size() == model_dims->batch_size && + output_desc.data_size() == + model_dims->hidden_size * model_dims->dir_count)) { + LOG(ERROR) << "Invalid output shape"; + return false; + } + if (!(input_h_desc.num_layers() == output_h_desc.num_layers() && + input_h_desc.batch_size() == output_h_desc.batch_size() && + input_h_desc.data_size() == output_h_desc.data_size())) { + LOG(ERROR) << "Invalid output_h shape"; + return false; + } + if (!(input_h_desc.num_layers() == output_c_desc.num_layers() && + input_h_desc.batch_size() == output_c_desc.batch_size() && + input_h_desc.data_size() == output_c_desc.data_size())) { + LOG(ERROR) << "Invalid output_h shape"; + return false; + } + + return true; +} + +bool CheckRNNParameterSize( + miopenHandle_t miopen_handle, const MIOpenRnnDescriptor& rnn_desc, + const MIOpenRnnSequenceTensorDescriptor& input_desc) { + size_t params_size_in_bytes = 0; + auto status = wrap::miopenGetRNNParamsSize( + miopen_handle /*handle*/, rnn_desc.handle() /*rnnDesc*/, + input_desc.handles()[0] /*xDesc*/, ¶ms_size_in_bytes /*sizeInBytes*/, + rnn_desc.data_type() /*dataType*/); + if (status != miopenStatusSuccess) { + LOG(ERROR) << "Unable to check RNN param size: " << ToString(status); + return false; + } + return static_cast(params_size_in_bytes) == + rnn_desc.ParamsSizeInBytes(); +} + +bool CreateRnnWorkspace(Stream* stream, miopenHandle_t miopen_handle, + const MIOpenRnnDescriptor& rnn_desc, + const MIOpenRnnSequenceTensorDescriptor& input_desc, + ScratchAllocator* workspace_allocator, + DeviceMemory* workspace) { + // Query the workspace size. + size_t workspace_size_in_bytes = 0; + auto status = wrap::miopenGetRNNWorkspaceSize( + miopen_handle /*handle*/, rnn_desc.handle() /*rnnDesc*/, + input_desc.seq_length() /*seqLength*/, input_desc.handles() /*xDesc*/, + &workspace_size_in_bytes /*sizeInBytes*/); + if (status != miopenStatusSuccess) { + LOG(ERROR) << "Unable to query workspace size: " << ToString(status); + return false; + } + // Allocate the workspace. + if (workspace_size_in_bytes > 0) { + auto allocated = + workspace_allocator->AllocateBytes(stream, workspace_size_in_bytes); + if (!allocated.ok() || (*workspace = allocated.ValueOrDie()) == nullptr) { + LOG(ERROR) << "Failed to allocate RNN workspace"; + + return false; + } + stream->ThenMemZero(workspace, workspace_size_in_bytes); + } else { + *workspace = DeviceMemory(); + } + return true; +} + +} // namespace + +template +bool MIOpenSupport::DoRnnForwardImpl( + Stream* stream, const MIOpenRnnDescriptor& rnn_desc, + const MIOpenRnnSequenceTensorDescriptor& input_desc, + const DeviceMemory& input_data, + const MIOpenRnnStateTensorDescriptor& input_h_desc, + const DeviceMemory& input_h_data, + const MIOpenRnnStateTensorDescriptor& input_c_desc, + const DeviceMemory& input_c_data, const DeviceMemory& params, + const MIOpenRnnSequenceTensorDescriptor& output_desc, + DeviceMemory* output_data, + const MIOpenRnnStateTensorDescriptor& output_h_desc, + DeviceMemory* output_h_data, + const MIOpenRnnStateTensorDescriptor& output_c_desc, + DeviceMemory* output_c_data, bool is_training, + ScratchAllocator* reserve_space_allocator, + ScratchAllocator* workspace_allocator) { + // extract model parameters + RnnModelDims model_dims; + bool res = ExtractAndCheckRnnForward( + rnn_desc, input_desc, input_data, input_h_desc, input_h_data, + input_c_desc, input_c_data, params, output_desc, *output_data, + output_h_desc, *output_h_data, output_c_desc, *output_c_data, + &model_dims); + if (!res) { + LOG(ERROR) << "Invalid parameters for RNN Model"; + return false; + } + + auto miopen = miopen_->GetHandle(parent_, stream); + + // check params size + + if (!CheckRNNParameterSize(miopen.handle(), rnn_desc, input_desc)) { + LOG(ERROR) << "Invalid parameters"; + return false; + } + + // create the workspace + DeviceMemory workspace; + if (!CreateRnnWorkspace(stream, miopen.handle(), rnn_desc, input_desc, + workspace_allocator, &workspace)) { + LOG(ERROR) << "Unable to create rnn workspace"; + + return false; + } + + // query the reserve space size + // allocate the reserve space + DeviceMemory reserve_space; + if (is_training) { + size_t reserve_space_size_in_bytes = 0; + auto status = wrap::miopenGetRNNTrainingReserveSize( + miopen.handle() /*handle*/, rnn_desc.handle() /*rnnDesc*/, + model_dims.seq_length /*seqLength*/, input_desc.handles() /*xDesc*/, + &reserve_space_size_in_bytes /*sizeInBytes*/); + if (status != miopenStatusSuccess) { + LOG(ERROR) << "Unable to query reserve space size: " << ToString(status); + return false; + } + + if (reserve_space_size_in_bytes > 0) { + auto allocated = reserve_space_allocator->AllocateBytes( + stream, reserve_space_size_in_bytes); + if (!allocated.ok() || + (reserve_space = allocated.ValueOrDie()) == nullptr) { + LOG(ERROR) << "Fail to allocate RNN reserve space"; + return false; + } + stream->ThenMemZero(&reserve_space, reserve_space_size_in_bytes); + } + } + + // make the forward call + if (!is_training) { + auto status = wrap::miopenRNNForwardInference( + miopen.handle() /*handle*/, rnn_desc.handle() /*rnnDesc*/, + model_dims.seq_length /*seqLength*/, input_desc.handles() /*xDesc*/, + input_data.opaque() /*x*/, input_h_desc.handle() /*hxDesc*/, + input_h_data.opaque() /*hx*/, input_c_desc.handle() /*cxDesc*/, + input_c_data.opaque() /*cx*/, rnn_desc.params_handle() /*wDesc*/, + params.opaque() /*w*/, output_desc.handles() /*yDesc*/, + output_data->opaque() /*y*/, output_h_desc.handle() /*hyDesc*/, + output_h_data->opaque() /*hy*/, output_c_desc.handle() /*cyDesc*/, + output_c_data->opaque() /*cy*/, workspace.opaque() /*workspace*/, + workspace.size() /*workSpaceSizeInBytes*/); + + if (status != miopenStatusSuccess) { + LOG(ERROR) << "Failed to call miopenRNNForwardInference: " + << ToString(status); + return false; + } + } else { + auto status = wrap::miopenRNNForwardTraining( + miopen.handle() /*handle*/, rnn_desc.handle() /*rnnDesc*/, + model_dims.seq_length /*seqLength*/, input_desc.handles() /*xDesc*/, + input_data.opaque() /*x*/, input_h_desc.handle() /*hxDesc*/, + input_h_data.opaque() /*hx*/, input_c_desc.handle() /*cxDesc*/, + input_c_data.opaque() /*cx*/, rnn_desc.params_handle() /*wDesc*/, + params.opaque() /*w*/, output_desc.handles() /*yDesc*/, + output_data->opaque() /*y*/, output_h_desc.handle() /*hyDesc*/, + output_h_data->opaque() /*hy*/, output_c_desc.handle() /*cyDesc*/, + output_c_data->opaque() /*cy*/, workspace.opaque() /*workspace*/, + workspace.size() /*workSpaceSizeInBytes*/, + reserve_space.opaque() /*reserveSpace*/, + reserve_space.size() /*reserveSpaceSizeInBytes*/); + if (status != miopenStatusSuccess) { + LOG(ERROR) << "Failed to call miopenRNNForwardTraining" + << ToString(status); + return false; + } + } + return true; +} + +template +bool MIOpenSupport::DoRnnBackwardImpl( + Stream* stream, const MIOpenRnnDescriptor& rnn_desc, + const MIOpenRnnSequenceTensorDescriptor& input_desc, + const DeviceMemory& input_data, + const MIOpenRnnStateTensorDescriptor& input_h_desc, + const DeviceMemory& input_h_data, + const MIOpenRnnStateTensorDescriptor& input_c_desc, + const DeviceMemory& input_c_data, const DeviceMemory& params, + const MIOpenRnnSequenceTensorDescriptor& output_desc, + const DeviceMemory& output_data, + const MIOpenRnnStateTensorDescriptor& output_h_desc, + const DeviceMemory& output_h_data, + const MIOpenRnnStateTensorDescriptor& output_c_desc, + const DeviceMemory& output_c_data, + const DeviceMemory& output_backprop_data, + const DeviceMemory& output_h_backprop_data, + const DeviceMemory& output_c_backprop_data, + DeviceMemory* input_backprop_data, + DeviceMemory* input_h_backprop_data, + DeviceMemory* input_c_backprop_data, + DeviceMemory* params_backprop_data, + DeviceMemory* reserve_space_data, + ScratchAllocator* workspace_allocator) { + // extract model parameters + RnnModelDims model_dims; + bool res = ExtractAndCheckRnnForward( + rnn_desc, input_desc, input_data, input_h_desc, input_h_data, + input_c_desc, input_c_data, params, output_desc, output_data, + output_h_desc, output_h_data, output_c_desc, output_c_data, &model_dims); + if (!res) { + LOG(ERROR) << "Invalid parameters for RNN Model"; + return false; + } + + auto miopen = miopen_->GetHandle(parent_, stream); + + // check params size + + if (!CheckRNNParameterSize(miopen.handle(), rnn_desc, input_desc)) { + LOG(ERROR) << "Invalid parameters"; + return false; + } + + // create the workspace + DeviceMemory workspace; + if (!CreateRnnWorkspace(stream, miopen.handle(), rnn_desc, input_desc, + workspace_allocator, &workspace)) { + LOG(ERROR) << "Unable to create rnn workspace"; + return false; + } + + // workaround for missing initialization support in MIOpen. + // TODO: remove this when MIOpen is ready. + long size_data = input_desc.seq_length() * input_desc.batch_size() * + input_desc.data_size(); + if ((size_data > 0) && (input_backprop_data->opaque() != nullptr)) + stream->ThenMemZero(input_backprop_data, size_data * sizeof(float)); + + size_data = input_h_desc.num_layers() * input_h_desc.batch_size() * + input_h_desc.data_size(); + if ((size_data > 0) && (input_h_backprop_data->opaque() != nullptr)) + stream->ThenMemZero(input_h_backprop_data, size_data * sizeof(float)); + + size_data = input_c_desc.num_layers() * input_c_desc.batch_size() * + input_c_desc.data_size(); + if ((size_data > 0) && (input_c_backprop_data->opaque() != nullptr)) + stream->ThenMemZero(input_c_backprop_data, size_data * sizeof(float)); + + // make the backward data call + auto status = wrap::miopenRNNBackwardData( + miopen.handle() /*handle*/, rnn_desc.handle() /*rnnDesc*/, + model_dims.seq_length /*seqLength*/, output_desc.handles() /*yDesc*/, + output_data.opaque() /*y*/, output_desc.handles() /*dyDesc*/, + output_backprop_data.opaque() /*dy*/, output_h_desc.handle() /*dhyDesc*/, + output_h_backprop_data.opaque() /*dhy*/, + output_c_desc.handle() /*dcyDesc*/, + output_c_backprop_data.opaque() /*dcy*/, + rnn_desc.params_handle() /*wDesc*/, params.opaque() /*w*/, + input_h_desc.handle() /*hxDesc*/, input_h_data.opaque() /*hx*/, + input_c_desc.handle() /*cxDesc*/, input_c_data.opaque() /*cx*/, + input_desc.handles() /*dxDesc*/, input_backprop_data->opaque() /*dx*/, + input_h_desc.handle() /*dhxDesc*/, + input_h_backprop_data->opaque() /*dhx*/, + input_c_desc.handle() /*dcxDesc*/, + input_c_backprop_data->opaque() /*dcx*/, workspace.opaque() /*workspace*/, + workspace.size() /*workSpaceSizeInBytes*/, + reserve_space_data->opaque() /*reserveSpace*/, + reserve_space_data->size() /*reserveSpaceSizeInBytes*/); + if (status != miopenStatusSuccess) { + LOG(ERROR) << "Failed to call miopenRNNBackwardData: " << ToString(status); + return false; + } + + if (params_backprop_data != nullptr) { + // Clear the dw to zeros. + stream->ThenMemZero(params_backprop_data, params_backprop_data->size()); + // make the backward weight call + status = wrap::miopenRNNBackwardWeights( + miopen.handle() /*handle*/, rnn_desc.handle() /*rnnDesc*/, + model_dims.seq_length /*seqLength*/, input_desc.handles() /*xDesc*/, + input_data.opaque() /*x*/, input_h_desc.handle() /*hxDesc*/, + input_h_data.opaque() /*hx*/, output_desc.handles() /*yDesc*/, + output_data.opaque() /*y*/, rnn_desc.params_handle() /*dwDesc*/, + params_backprop_data->opaque() /*dw*/, workspace.opaque() /*workspace*/, + workspace.size() /*workSpaceSizeInBytes*/, + reserve_space_data->opaque() /*reserveSpace*/, + reserve_space_data->size() /*reserveSpaceSizeInBytes*/); + if (status != miopenStatusSuccess) { + LOG(ERROR) << "Failed to call miopenRNNBackwardWeights: " + << ToString(status); + return false; + } + } + + return true; +} + +MIOpenRnnParamsDescriptor::MIOpenRnnParamsDescriptor( + miopenHandle_t miopen_handle, const MIOpenRnnDescriptor& rnn_desc) + : handle_(nullptr), rnn_desc_(&rnn_desc), params_size_in_bytes_(0) { + miopenTensorDescriptor_t input_desc = nullptr; + { + // Query the params size. + auto status = wrap::miopenCreateTensorDescriptor(&input_desc); + RETURN_IF_MIOPEN_ERROR(status, "MIOpen fails to create tensor descriptor"); + std::array dims = {{1, rnn_desc.input_size()}}; + status = wrap::miopenSetTensorDescriptor( + input_desc /*tensorDesc*/, rnn_desc.data_type() /*dataType*/, + 2 /*nbDims*/, dims.data() /*dimA*/, nullptr /*strideA*/); + RETURN_IF_MIOPEN_ERROR(status, "MIOpen fails to set tensor descriptor"); + + size_t params_size = 0; + status = wrap::miopenGetRNNParamsSize( + miopen_handle /*handle*/, rnn_desc.handle() /*rnnDesc*/, + input_desc /*xDesc*/, ¶ms_size /*sizeInBytes*/, + rnn_desc.data_type() /*dataType*/); + RETURN_IF_MIOPEN_ERROR(status, "MIOpen fails to get RNN parameter size"); + params_size_in_bytes_ = static_cast(params_size); + } + + { + // Create the params descriptor. + auto status = wrap::miopenCreateTensorDescriptor(&handle_); + RETURN_IF_MIOPEN_ERROR(status, + "MIOpen fails to create RNN params descriptor"); + status = wrap::miopenGetRNNParamsDescriptor(miopen_handle, + rnn_desc.handle(), input_desc, + handle_, rnn_desc.data_type()); + RETURN_IF_MIOPEN_ERROR(status, + "MIOpen fails to update RNN filter descriptor"); + } + { + // Release the dummy input tensor descriptor. + auto status = wrap::miopenDestroyTensorDescriptor(input_desc); + RETURN_IF_MIOPEN_ERROR(status, "MIOpen fails to destroy tensor descriptor"); + } +} + +port::StatusOr> +MIOpenSupport::createRnnDescriptor( + int num_layers, int hidden_size, int input_size, int batch_size, + dnn::RnnInputMode input_mode, dnn::RnnDirectionMode direction_mode, + dnn::RnnMode rnn_mode, dnn::DataType data_type, + const dnn::AlgorithmConfig& algorithm_config, float dropout, uint64 seed, + ScratchAllocator* state_allocator) { + // ROCM TODO: batch_size is ignored for now + + auto miopen = miopen_->GetHandle(parent_, nullptr); + std::unique_ptr rnn_desc(new MIOpenRnnDescriptor( + miopen.handle(), num_layers, hidden_size, input_size, + ToMIOpenRnnInputMode(input_mode), + ToMIOpenRnnDirectionMode(direction_mode), ToMIOpenRnnMode(rnn_mode), + ToMIOpenDataType(data_type), dropout, seed, state_allocator)); + if (!rnn_desc->ok()) { + return rnn_desc->Status(); + } + return port::StatusOr>( + std::move(rnn_desc)); +} + +port::StatusOr> +MIOpenSupport::createRnnSequenceTensorDescriptor(int seq_length, int batch_size, + int data_size, + dnn::DataType data_type) { + std::unique_ptr seq_desc( + new MIOpenRnnSequenceTensorDescriptor(seq_length, batch_size, data_size, + ToMIOpenDataType(data_type))); + if (!seq_desc->ok()) { + return seq_desc->Status(); + } + return port::StatusOr>( + std::move(seq_desc)); +} + +port::StatusOr> +MIOpenSupport::createRnnStateTensorDescriptor(int num_layer, int batch_size, + int data_size, + dnn::DataType data_type) { + std::unique_ptr state_desc( + new MIOpenRnnStateTensorDescriptor(num_layer, batch_size, data_size, + ToMIOpenDataType(data_type))); + if (!state_desc->ok()) { + return state_desc->Status(); + } + return port::StatusOr>( + std::move(state_desc)); +} + +bool MIOpenSupport::DoRnnForward( + Stream* stream, const dnn::RnnDescriptor& rnn_desc, + const dnn::RnnSequenceTensorDescriptor& input_desc, + const DeviceMemory& input_data, + const dnn::RnnStateTensorDescriptor& input_h_desc, + const DeviceMemory& input_h_data, + const dnn::RnnStateTensorDescriptor& input_c_desc, + const DeviceMemory& input_c_data, + const DeviceMemory& params, + const dnn::RnnSequenceTensorDescriptor& output_desc, + DeviceMemory* output_data, + const dnn::RnnStateTensorDescriptor& output_h_desc, + DeviceMemory* output_h_data, + const dnn::RnnStateTensorDescriptor& output_c_desc, + DeviceMemory* output_c_data, bool is_training, + ScratchAllocator* reserve_space_allocator, + ScratchAllocator* workspace_allocator, + dnn::ProfileResult* output_profile_result) { + // ROCM TODO: output_profile_result is ignore for now + + const MIOpenRnnDescriptor& miopen_rnn_desc = + static_cast(rnn_desc); + const MIOpenRnnSequenceTensorDescriptor& miopen_input_desc = + static_cast(input_desc); + const MIOpenRnnStateTensorDescriptor& miopen_input_h_desc = + static_cast(input_h_desc); + const MIOpenRnnStateTensorDescriptor& miopen_input_c_desc = + static_cast(input_c_desc); + const MIOpenRnnSequenceTensorDescriptor& miopen_output_desc = + static_cast(output_desc); + const MIOpenRnnStateTensorDescriptor& miopen_output_h_desc = + static_cast(output_h_desc); + const MIOpenRnnStateTensorDescriptor& miopen_output_c_desc = + static_cast(output_c_desc); + + return DoRnnForwardImpl( + stream, miopen_rnn_desc, miopen_input_desc, input_data, + miopen_input_h_desc, input_h_data, miopen_input_c_desc, input_c_data, + params, miopen_output_desc, output_data, miopen_output_h_desc, + output_h_data, miopen_output_c_desc, output_c_data, is_training, + reserve_space_allocator, workspace_allocator); +} + +bool MIOpenSupport::DoRnnForward( + Stream* stream, const dnn::RnnDescriptor& rnn_desc, + const dnn::RnnSequenceTensorDescriptor& input_desc, + const DeviceMemory& input_data, + const dnn::RnnStateTensorDescriptor& input_h_desc, + const DeviceMemory& input_h_data, + const dnn::RnnStateTensorDescriptor& input_c_desc, + const DeviceMemory& input_c_data, const DeviceMemory& params, + const dnn::RnnSequenceTensorDescriptor& output_desc, + DeviceMemory* output_data, + const dnn::RnnStateTensorDescriptor& output_h_desc, + DeviceMemory* output_h_data, + const dnn::RnnStateTensorDescriptor& output_c_desc, + DeviceMemory* output_c_data, bool is_training, + ScratchAllocator* reserve_space_allocator, + ScratchAllocator* workspace_allocator, + dnn::ProfileResult* output_profile_result) { + // ROCM TODO: output_profile_result is ignore for now + + const MIOpenRnnDescriptor& miopen_rnn_desc = + static_cast(rnn_desc); + const MIOpenRnnSequenceTensorDescriptor& miopen_input_desc = + static_cast(input_desc); + const MIOpenRnnStateTensorDescriptor& miopen_input_h_desc = + static_cast(input_h_desc); + const MIOpenRnnStateTensorDescriptor& miopen_input_c_desc = + static_cast(input_c_desc); + const MIOpenRnnSequenceTensorDescriptor& miopen_output_desc = + static_cast(output_desc); + const MIOpenRnnStateTensorDescriptor& miopen_output_h_desc = + static_cast(output_h_desc); + const MIOpenRnnStateTensorDescriptor& miopen_output_c_desc = + static_cast(output_c_desc); + + return DoRnnForwardImpl( + stream, miopen_rnn_desc, miopen_input_desc, input_data, + miopen_input_h_desc, input_h_data, miopen_input_c_desc, input_c_data, + params, miopen_output_desc, output_data, miopen_output_h_desc, + output_h_data, miopen_output_c_desc, output_c_data, is_training, + reserve_space_allocator, workspace_allocator); +} + +bool MIOpenSupport::DoRnnForward( + Stream* stream, const dnn::RnnDescriptor& rnn_desc, + const dnn::RnnSequenceTensorDescriptor& input_desc, + const DeviceMemory& input_data, + const dnn::RnnStateTensorDescriptor& input_h_desc, + const DeviceMemory& input_h_data, + const dnn::RnnStateTensorDescriptor& input_c_desc, + const DeviceMemory& input_c_data, + const DeviceMemory& params, + const dnn::RnnSequenceTensorDescriptor& output_desc, + DeviceMemory* output_data, + const dnn::RnnStateTensorDescriptor& output_h_desc, + DeviceMemory* output_h_data, + const dnn::RnnStateTensorDescriptor& output_c_desc, + DeviceMemory* output_c_data, bool is_training, + ScratchAllocator* reserve_space_allocator, + ScratchAllocator* workspace_allocator, + dnn::ProfileResult* output_profile_result) { + LOG(ERROR) << "miopen does not support double type RNN fwd yet"; + return false; +} + +bool MIOpenSupport::DoRnnBackward( + Stream* stream, const dnn::RnnDescriptor& rnn_desc, + const dnn::RnnSequenceTensorDescriptor& input_desc, + const DeviceMemory& input_data, + const dnn::RnnStateTensorDescriptor& input_h_desc, + const DeviceMemory& input_h_data, + const dnn::RnnStateTensorDescriptor& input_c_desc, + const DeviceMemory& input_c_data, + const DeviceMemory& params, + const dnn::RnnSequenceTensorDescriptor& output_desc, + const DeviceMemory& output_data, + const dnn::RnnStateTensorDescriptor& output_h_desc, + const DeviceMemory& output_h_data, + const dnn::RnnStateTensorDescriptor& output_c_desc, + const DeviceMemory& output_c_data, + const DeviceMemory& output_backprop_data, + const DeviceMemory& output_h_backprop_data, + const DeviceMemory& output_c_backprop_data, + DeviceMemory* input_backprop_data, + DeviceMemory* input_h_backprop_data, + DeviceMemory* input_c_backprop_data, + DeviceMemory* params_backprop_data, + DeviceMemory* reserve_space_data, + ScratchAllocator* workspace_allocator, + dnn::ProfileResult* output_profile_result) { + // ROCM TODO: output_profile_result is ignore for now + + const MIOpenRnnDescriptor& miopen_rnn_desc = + static_cast(rnn_desc); + const MIOpenRnnSequenceTensorDescriptor& miopen_input_desc = + static_cast(input_desc); + const MIOpenRnnStateTensorDescriptor& miopen_input_h_desc = + static_cast(input_h_desc); + const MIOpenRnnStateTensorDescriptor& miopen_input_c_desc = + static_cast(input_c_desc); + const MIOpenRnnSequenceTensorDescriptor& miopen_output_desc = + static_cast(output_desc); + const MIOpenRnnStateTensorDescriptor& miopen_output_h_desc = + static_cast(output_h_desc); + const MIOpenRnnStateTensorDescriptor& miopen_output_c_desc = + static_cast(output_c_desc); + + return DoRnnBackwardImpl( + stream, miopen_rnn_desc, miopen_input_desc, input_data, + miopen_input_h_desc, input_h_data, miopen_input_c_desc, input_c_data, + params, miopen_output_desc, output_data, miopen_output_h_desc, + output_h_data, miopen_output_c_desc, output_c_data, output_backprop_data, + output_h_backprop_data, output_c_backprop_data, input_backprop_data, + input_h_backprop_data, input_c_backprop_data, params_backprop_data, + reserve_space_data, workspace_allocator); +} + +bool MIOpenSupport::DoRnnBackward( + Stream* stream, const dnn::RnnDescriptor& rnn_desc, + const dnn::RnnSequenceTensorDescriptor& input_desc, + const DeviceMemory& input_data, + const dnn::RnnStateTensorDescriptor& input_h_desc, + const DeviceMemory& input_h_data, + const dnn::RnnStateTensorDescriptor& input_c_desc, + const DeviceMemory& input_c_data, const DeviceMemory& params, + const dnn::RnnSequenceTensorDescriptor& output_desc, + const DeviceMemory& output_data, + const dnn::RnnStateTensorDescriptor& output_h_desc, + const DeviceMemory& output_h_data, + const dnn::RnnStateTensorDescriptor& output_c_desc, + const DeviceMemory& output_c_data, + const DeviceMemory& output_backprop_data, + const DeviceMemory& output_h_backprop_data, + const DeviceMemory& output_c_backprop_data, + DeviceMemory* input_backprop_data, + DeviceMemory* input_h_backprop_data, + DeviceMemory* input_c_backprop_data, + DeviceMemory* params_backprop_data, + DeviceMemory* reserve_space_data, + ScratchAllocator* workspace_allocator, + dnn::ProfileResult* output_profile_result) { + // ROCM TODO: output_profile_result is ignore for now + + const MIOpenRnnDescriptor& miopen_rnn_desc = + static_cast(rnn_desc); + const MIOpenRnnSequenceTensorDescriptor& miopen_input_desc = + static_cast(input_desc); + const MIOpenRnnStateTensorDescriptor& miopen_input_h_desc = + static_cast(input_h_desc); + const MIOpenRnnStateTensorDescriptor& miopen_input_c_desc = + static_cast(input_c_desc); + const MIOpenRnnSequenceTensorDescriptor& miopen_output_desc = + static_cast(output_desc); + const MIOpenRnnStateTensorDescriptor& miopen_output_h_desc = + static_cast(output_h_desc); + const MIOpenRnnStateTensorDescriptor& miopen_output_c_desc = + static_cast(output_c_desc); + + return DoRnnBackwardImpl( + stream, miopen_rnn_desc, miopen_input_desc, input_data, + miopen_input_h_desc, input_h_data, miopen_input_c_desc, input_c_data, + params, miopen_output_desc, output_data, miopen_output_h_desc, + output_h_data, miopen_output_c_desc, output_c_data, output_backprop_data, + output_h_backprop_data, output_c_backprop_data, input_backprop_data, + input_h_backprop_data, input_c_backprop_data, params_backprop_data, + reserve_space_data, workspace_allocator); +} + +bool MIOpenSupport::DoRnnBackward( + Stream* stream, const dnn::RnnDescriptor& rnn_desc, + const dnn::RnnSequenceTensorDescriptor& input_desc, + const DeviceMemory& input_data, + const dnn::RnnStateTensorDescriptor& input_h_desc, + const DeviceMemory& input_h_data, + const dnn::RnnStateTensorDescriptor& input_c_desc, + const DeviceMemory& input_c_data, + const DeviceMemory& params, + const dnn::RnnSequenceTensorDescriptor& output_desc, + const DeviceMemory& output_data, + const dnn::RnnStateTensorDescriptor& output_h_desc, + const DeviceMemory& output_h_data, + const dnn::RnnStateTensorDescriptor& output_c_desc, + const DeviceMemory& output_c_data, + const DeviceMemory& output_backprop_data, + const DeviceMemory& output_h_backprop_data, + const DeviceMemory& output_c_backprop_data, + DeviceMemory* input_backprop_data, + DeviceMemory* input_h_backprop_data, + DeviceMemory* input_c_backprop_data, + DeviceMemory* params_backprop_data, + DeviceMemory* reserve_space_data, + ScratchAllocator* workspace_allocator, + dnn::ProfileResult* output_profile_result) { + LOG(ERROR) << "miopen does not support half type RNN bwd yet"; + return false; +} + +// This is the context required to use the TF scratch allocator: +struct MIOpenAllocatorContext { + MIOpenAllocatorContext(ScratchAllocator* scratch_allocator, Stream* stream) + : scratch_allocator_(scratch_allocator), stream_(stream){}; + + ScratchAllocator* scratch_allocator_; + Stream* stream_; +}; + +void* MIOpenAllocatorCallback(void* ctx, size_t size_in_bytes) { + auto* mac = static_cast(ctx); + auto allocated = + mac->scratch_allocator_->AllocateBytes(mac->stream_, size_in_bytes); + + DeviceMemory scratch; + if (allocated.ok()) { + scratch = allocated.ValueOrDie(); + return scratch.opaque(); + } else { + return nullptr; + } +} + +void MIOpenDeallocatorCallback(void* ctx, void* mem) { + // Don't need dealloactor since the TensorFlow heap will automatically reclaim + // the memory +} + +port::Status MIOpenSupport::DoPrepareForConvolution( + dnn::ConvolutionKind kind, dnn::DataType element_type, Stream* stream, + const dnn::BatchDescriptor& input_descriptor, DeviceMemoryBase input_data, + const dnn::FilterDescriptor& filter_descriptor, + DeviceMemoryBase filter_data, const dnn::BatchDescriptor& output_descriptor, + DeviceMemoryBase output_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::AlgorithmConfig& algorithm_config, + ScratchAllocator* scratch_allocator, dnn::AlgorithmDesc* algorithm_desc, + DeviceMemory* scratch_memory) { + ScopedTensorDescriptor input_nd{ + input_descriptor, + ToMIOpenDataType(element_type, input_descriptor.layout())}; + ScopedFilterDescriptor filter{ + filter_descriptor, input_descriptor, + ToMIOpenDataType(element_type, filter_descriptor.layout())}; + ScopedTensorDescriptor output_nd{ + output_descriptor, + ToMIOpenDataType(element_type, output_descriptor.layout())}; + ScopedConvolutionDescriptor conv{ + convolution_descriptor, + ToMIOpenDataType(GetConvAccumulatorType(element_type))}; + + auto miopen = miopen_->GetHandle(parent_, stream); + + absl::optional algo_desc = algorithm_config.algorithm(); + size_t scratch_memory_size; + + if (!algo_desc.has_value()) { + // With the default algorithm, use MIOpen's heuristics. + assert(scratch_allocator); + + DeviceMemory scratch_memory_temp; + MIOpenAllocatorContext mac(scratch_allocator, stream); + wrap::miopenSetAllocator(miopen.handle(), MIOpenAllocatorCallback, + MIOpenDeallocatorCallback, &mac); + size_t size_in_bytes; + miopenStatus_t status = miopenStatusSuccess; + + switch (kind) { + case dnn::ConvolutionKind::FORWARD: { + status = wrap::miopenConvolutionForwardGetWorkSpaceSize( + miopen.handle(), /*filterDesc=*/filter.handle(), + /*srcDesc=*/input_nd.handle(), /*convDesc=*/conv.handle(), + /*destDesc=*/output_nd.handle(), /*sizeInBytes=*/&size_in_bytes); + break; + } + case dnn::ConvolutionKind::BACKWARD_DATA: { + status = wrap::miopenConvolutionBackwardDataGetWorkSpaceSize( + miopen.handle(), /*diffDesc=*/output_nd.handle(), + /*filterDesc=*/filter.handle(), /*convDesc=*/conv.handle(), + /*gradDesc=*/input_nd.handle(), /*sizeInBytes=*/&size_in_bytes); + break; + } + case dnn::ConvolutionKind::BACKWARD_FILTER: { + status = wrap::miopenConvolutionBackwardWeightsGetWorkSpaceSize( + miopen.handle(), /*diffDesc=*/output_nd.handle(), + /*srcDesc=*/input_nd.handle(), /*convDesc=*/conv.handle(), + /*gradDesc=*/filter.handle(), /*sizeInBytes=*/&size_in_bytes); + break; + } + default: + return port::InternalError(absl::StrCat("Unexpected convolution kind ", + static_cast(kind))); + } + + if (status == miopenStatusSuccess && size_in_bytes != 0) { + auto allocated = scratch_allocator->AllocateBytes(stream, size_in_bytes); + if (allocated.ok()) { + scratch_memory_temp = allocated.ValueOrDie(); + } + } + + miopenConvAlgoPerf_t preference; + int returnedAlgoCount; + + switch (kind) { + case dnn::ConvolutionKind::FORWARD: { + auto status = wrap::miopenFindConvolutionForwardAlgorithm( + miopen.handle(), input_nd.handle(), input_data.opaque(), + filter.handle(), filter_data.opaque(), conv.handle(), + output_nd.handle(), output_data.opaque(), + /*requestAlgoCount=*/1, &returnedAlgoCount, + /*preference=*/&preference, + /*workspace*/ scratch_memory_temp.opaque(), + /*WorkSpaceSize*/ scratch_memory_temp.size(), + /*exhaustiveSearch*/ false); + CHECK_EQ(status, miopenStatusSuccess) << "Unable to find a suitable " + "algorithm for doing forward " + "convolution"; + *algorithm_desc = dnn::AlgorithmDesc(preference.fwd_algo, false); + break; + } + case dnn::ConvolutionKind::BACKWARD_DATA: { + auto status = wrap::miopenFindConvolutionBackwardDataAlgorithm( + miopen.handle(), + /*diffDesc=*/output_nd.handle(), output_data.opaque(), + /*filterDesc=*/filter.handle(), filter_data.opaque(), + /*convDesc=*/conv.handle(), + /*gradDesc=*/input_nd.handle(), input_data.opaque(), + /*requestCount=*/1, /*returnedAlgoCount=*/&returnedAlgoCount, + /*preference=*/&preference, + /*WorkSpace=*/scratch_memory_temp.opaque(), + /*WorkSpaceSize=*/scratch_memory_temp.size(), + /*exhaustiveSearch=*/false); + CHECK_EQ(status, miopenStatusSuccess) << "Unable to find a suitable " + "algorithm for doing backward " + "data convolution"; + *algorithm_desc = dnn::AlgorithmDesc(preference.bwd_data_algo, false); + break; + } + case dnn::ConvolutionKind::BACKWARD_FILTER: { + auto status = wrap::miopenFindConvolutionBackwardWeightsAlgorithm( + miopen.handle(), + /*diffDesc=*/output_nd.handle(), output_data.opaque(), + /*srcDesc=*/input_nd.handle(), input_data.opaque(), + /*convDesc=*/conv.handle(), + /*gradDesc=*/filter.handle(), filter_data.opaque(), + /*requestAlgoCount=*/1, /*returnedAlgoCount=*/&returnedAlgoCount, + /*preference=*/&preference, + /*WorkSpace=*/scratch_memory_temp.opaque(), + /*WorkSpaceSize=*/scratch_memory_temp.size(), + /*exhaustiveSearch=*/false); + CHECK_EQ(status, miopenStatusSuccess) << "Unable to find a suitable " + "algorithm for doing backward " + "filter convolution"; + *algorithm_desc = + dnn::AlgorithmDesc(preference.bwd_weights_algo, false); + break; + } + default: + return port::InternalError(absl::StrCat("Unexpected convolution kind ", + static_cast(kind))); + } + + // Restore default allocator, note mac is stack temp + wrap::miopenSetAllocator(miopen.handle(), nullptr, nullptr, nullptr); + + scratch_memory_size = preference.memory; + } else { + // An algorithm has been specified. + *algorithm_desc = *algo_desc; + // commenting this line out for the upstream repo, since + // AlgorithmConfig::scratch_size_ has been removed in the upstream repo but + // is still used in the ROCM develop-upstream repo + // + // scratch_memory_size = *(algorithm_config.scratch_size()); + // + } + + // allocate scratch memory + if (scratch_memory_size != 0) { + if (scratch_allocator == nullptr) { + return port::InternalError( + absl::StrCat("An allocator must be specified when scratch memory is " + "needed")); + } + auto allocated = + scratch_allocator->AllocateBytes(stream, scratch_memory_size); + if (!allocated.ok()) { + return port::InternalError(absl::StrCat( + "Failed to allocate scratch memory of size: ", scratch_memory_size)); + } + if (allocated.ok()) { + *scratch_memory = allocated.ValueOrDie(); + } + } + + return port::Status::OK(); +} + +// NOTE(keveman): Temporary data layout transformation until MIOpen supports +// kBatchYXDepth for backward pass. This function allocates temporary memory, +// lays out the source data into the temporary but in the kBatchDepthXY +// layout, and returns the temporary memory. The caller is responsible for +// deallocating the temporary. Since the allocation is done using Stream's +// AllocateTemporaryMemory, a later BlockHostUntilDone could be used for +// deallocation. +// +// transform_scratch is populated with a legitimate temporary allocation iff +// the original output data needs to be transformed. +static DeviceMemoryBase MaybeTransformLayout( + Stream* stream, miopenHandle_t handle_, + int miopen_type, // Actually miopenDataType_t. + BatchDescriptor* output_descriptor, DeviceMemoryBase backward_output_data, + std::unique_ptr>* transform_scratch) { + if (output_descriptor->layout() == dnn::DataLayout::kBatchDepthYX) { + return backward_output_data; + } + CHECK(output_descriptor->layout() == dnn::DataLayout::kBatchYXDepth); + *transform_scratch = + stream->AllocateTemporaryArray(backward_output_data.size()) + .ConsumeValueOrDie(); + BatchDescriptor transformed_output_descriptor; + transformed_output_descriptor.CloneFrom(*output_descriptor); + transformed_output_descriptor.set_layout(dnn::DataLayout::kBatchDepthYX); + ScopedTensorDescriptor orig_out_back_nd{ + *output_descriptor, static_cast(miopen_type)}; + ScopedTensorDescriptor transformed_out_back_nd{ + transformed_output_descriptor, + static_cast(miopen_type)}; + + float alpha1 = 1.0f; + float alpha2 = 0.0f; + float beta = 0.0f; + auto status = wrap::miopenOpTensor( + handle_, miopenTensorOpAdd, &alpha1, orig_out_back_nd.handle(), + backward_output_data.opaque(), &alpha2, orig_out_back_nd.handle(), + backward_output_data.opaque(), &beta, transformed_out_back_nd.handle(), + (*transform_scratch)->mutable_device_memory()->opaque()); + + if (status != miopenStatusSuccess) { + LOG(FATAL) << "Failed to transform the data layout."; + } + output_descriptor->set_layout(dnn::DataLayout::kBatchDepthYX); + return (*transform_scratch)->device_memory(); +} + +port::Status MIOpenSupport::DoConvolve( + dnn::ConvolutionKind kind, dnn::DataType element_type, Stream* stream, + const dnn::BatchDescriptor& input_descriptor, DeviceMemoryBase input_data, + const dnn::FilterDescriptor& filter_descriptor, + DeviceMemoryBase filter_data, const dnn::BatchDescriptor& output_descriptor, + DeviceMemoryBase output_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + dnn::AlgorithmDesc algorithm_desc, DeviceMemory scratch_memory, + dnn::ProfileResult* output_profile_result) { + auto miopen = miopen_->GetHandle(parent_, stream); + ScopedTensorDescriptor input_nd{input_descriptor, + ToMIOpenDataType(element_type)}; + ScopedTensorDescriptor output_nd{output_descriptor, + ToMIOpenDataType(element_type)}; + ScopedFilterDescriptor filter{filter_descriptor, input_descriptor, + ToMIOpenDataType(element_type)}; + ScopedConvolutionDescriptor conv{convolution_descriptor, + ToMIOpenDataType(element_type)}; + + // Alpha is the scaling factor for input. + float alpha = 1.0; + // Beta is the scaling factor for output. + float beta = 0.0; + + const bool is_profiling = output_profile_result != nullptr; + + std::unique_ptr timer; + if (is_profiling) { + timer.reset(new GpuTimer(parent_)); + if (!timer->Init()) { + return port::Status(port::error::INTERNAL, "Failed to init timer"); + } + // The start and stop of the timer should be as close to the MIOpen call as + // possible. It is still possible for other threads to issue workload on + // to this stream. So it could take multiple profiling measurements. + if (!timer->Start(AsGpuStream(stream))) { + timer->Destroy(); + return port::Status(port::error::INTERNAL, "Failed to start timer"); + } + } + + miopenStatus_t status = miopenStatusSuccess; + switch (kind) { + case dnn::ConvolutionKind::FORWARD: { + status = wrap::miopenConvolutionForward( + miopen.handle(), + /*alpha=*/&alpha, /*srcDesc=*/input_nd.handle(), + /*srcData=*/input_data.opaque(), /*filterDesc=*/filter.handle(), + /*filterData=*/filter_data.opaque(), /*convDesc=*/conv.handle(), + /*algo=*/ + static_cast(algorithm_desc.algo_id()), + /*beta=*/&beta, /*destDesc=*/output_nd.handle(), + /*destData=*/output_data.opaque(), + /*workSpace=*/scratch_memory.opaque(), + /*workSpaceSizeInBytes=*/scratch_memory.size()); + break; + } + case dnn::ConvolutionKind::BACKWARD_DATA: { + // TBD: remove once MIOpen supports kBatchYXDepth for backward pass. + BatchDescriptor output_back_descriptor; + output_back_descriptor.CloneFrom(output_descriptor); + std::unique_ptr> transform_scratch; + output_data = MaybeTransformLayout( + stream, miopen.handle(), ToMIOpenDataType(element_type), + &output_back_descriptor, output_data, &transform_scratch); + + status = wrap::miopenConvolutionBackwardData( + miopen.handle(), + /*alpha=*/&alpha, + /*diffDesc=*/output_nd.handle(), + /*diffData=*/output_data.opaque(), + /*filterDesc=*/filter.handle(), + /*filterData=*/filter_data.opaque(), + /*convDesc=*/conv.handle(), + /*algo=*/ + static_cast(algorithm_desc.algo_id()), + /*beta=*/&beta, + /*gradDesc=*/input_nd.handle(), + /*gradData=*/input_data.opaque(), + /*workSpace=*/scratch_memory.opaque(), + /*workSpaceSizeInBytes=*/scratch_memory.size()); + break; + } + case dnn::ConvolutionKind::BACKWARD_FILTER: { + // TBD: remove once MIOpen supports kBatchYXDepth for backward pass. + BatchDescriptor output_back_descriptor; + output_back_descriptor.CloneFrom(output_descriptor); + std::unique_ptr> transform_scratch; + output_data = MaybeTransformLayout( + stream, miopen.handle(), ToMIOpenDataType(element_type), + &output_back_descriptor, output_data, &transform_scratch); + + status = wrap::miopenConvolutionBackwardWeights( + miopen.handle(), + /*alpha=*/&alpha, + /*diffDesc=*/output_nd.handle(), + /*diffData=*/output_data.opaque(), + /*srcDesc=*/input_nd.handle(), + /*srcData=*/input_data.opaque(), + /*convDesc=*/conv.handle(), + /*algo=*/ + static_cast( + algorithm_desc.algo_id()), + /*beta=*/&beta, + /*gradDesc=*/filter.handle(), + /*gradData=*/filter_data.opaque(), + /*workSpace=*/scratch_memory.opaque(), + /*workSpaceSizeInBytes=*/scratch_memory.size()); + break; + } + default: + return port::InternalError( + absl::StrCat("Unexpected convolution kind ", static_cast(kind))); + } + + if (is_profiling) { + if (!timer->Stop(AsGpuStream(stream))) { + timer->Destroy(); + return port::Status(port::error::INTERNAL, "Failed to stop timer"); + } + if (status == miopenStatusSuccess) { + dnn::AlgorithmDesc algotype(algorithm_desc.algo_id(), false); + output_profile_result->set_algorithm(algotype); + output_profile_result->set_elapsed_time_in_ms( + timer->GetElapsedMilliseconds()); + output_profile_result->set_scratch_size(scratch_memory.size()); + } + timer->Destroy(); + } + + if (status != miopenStatusSuccess) { + return port::InternalError(absl::StrCat( + "Failed to euqueue convolution on stream: ", ToString(status))); + } + + return port::Status::OK(); +} + +bool MIOpenSupport::GetConvolveAlgorithms( + // ROCM TODO: refactor cc_major / cc_minor + bool with_winograd_nonfused, int cc_major, int cc_minor, + std::vector* out_algorithms) { + out_algorithms->assign({ + // clang-format off + dnn::AlgorithmDesc(miopenConvolutionFwdAlgoGEMM, false), + dnn::AlgorithmDesc(miopenConvolutionFwdAlgoDirect, false), + dnn::AlgorithmDesc(miopenConvolutionFwdAlgoFFT, false), + dnn::AlgorithmDesc(miopenConvolutionFwdAlgoWinograd, false), + // clang-format on + }); + return true; +} + +bool MIOpenSupport::GetRnnAlgorithms( + std::vector* out_algorithms) { + // ROCM TODO: implement this with proper MIOpen API + return true; +} + +bool MIOpenSupport::GetConvolveBackwardDataAlgorithms( + // ROCM TODO: refactor cc_major / cc_minor + bool with_winograd_nonfused, int cc_major, int cc_minor, + std::vector* out_algorithms) { + out_algorithms->assign({ + // clang-format off + dnn::AlgorithmDesc(miopenConvolutionBwdDataAlgoGEMM, false), + dnn::AlgorithmDesc(miopenConvolutionBwdDataAlgoDirect, false), + dnn::AlgorithmDesc(miopenConvolutionBwdDataAlgoFFT, false), + dnn::AlgorithmDesc(miopenConvolutionBwdDataAlgoWinograd, false), + // clang-format on + }); + return true; +} + +bool MIOpenSupport::GetConvolveBackwardFilterAlgorithms( + // ROCM TODO: refactor cc_major / cc_minor + bool with_winograd_nonfused, int cc_major, int cc_minor, + std::vector* out_algorithms) { + out_algorithms->assign({ + // clang-format off + dnn::AlgorithmDesc(miopenConvolutionBwdWeightsAlgoGEMM, false), + dnn::AlgorithmDesc(miopenConvolutionBwdWeightsAlgoDirect, false), + // clang-format on + }); + return true; +} + +bool MIOpenSupport::DoBatchNormalizationForward( + Stream* stream, const DeviceMemory& x, + const DeviceMemory& scale, const DeviceMemory& offset, + const DeviceMemory& estimated_mean, + const DeviceMemory& estimated_variance, + const dnn::BatchDescriptor& x_desc, + const dnn::BatchDescriptor& scale_offset_desc, const double epsilon, + DeviceMemory* y, DeviceMemory* batch_mean, + DeviceMemory* batch_var, DeviceMemory* saved_mean, + DeviceMemory* saved_inv_var, bool is_training, + std::function&()> var_to_inv_var, + std::function inv_var_to_var) { + return DoBatchNormalizationForwardImpl( + stream, dnn::DataType::kHalf, dnn::DataType::kFloat, x, scale, offset, + estimated_mean, estimated_variance, x_desc, scale_offset_desc, epsilon, y, + batch_mean, batch_var, saved_mean, saved_inv_var, is_training, + std::move(var_to_inv_var), std::move(inv_var_to_var)); +} + +bool MIOpenSupport::DoBatchNormalizationForward( + Stream* stream, const DeviceMemory& x, + const DeviceMemory& scale, const DeviceMemory& offset, + const DeviceMemory& estimated_mean, + const DeviceMemory& estimated_variance, + const dnn::BatchDescriptor& x_desc, + const dnn::BatchDescriptor& scale_offset_desc, const double epsilon, + DeviceMemory* y, DeviceMemory* batch_mean, + DeviceMemory* batch_var, DeviceMemory* saved_mean, + DeviceMemory* saved_inv_var, bool is_training, + std::function&()> var_to_inv_var, + std::function inv_var_to_var) { + return DoBatchNormalizationForwardImpl( + stream, dnn::DataType::kFloat, dnn::DataType::kFloat, x, scale, offset, + estimated_mean, estimated_variance, x_desc, scale_offset_desc, epsilon, y, + batch_mean, batch_var, saved_mean, saved_inv_var, is_training, + std::move(var_to_inv_var), std::move(inv_var_to_var)); +} + +template +bool MIOpenSupport::DoBatchNormalizationForwardImpl( + Stream* stream, dnn::DataType input_data_type, + dnn::DataType scale_data_type, const DeviceMemory& x, + const DeviceMemory& scale, const DeviceMemory& offset, + const DeviceMemory& estimated_mean, + const DeviceMemory& estimated_variance, + const dnn::BatchDescriptor& x_desc, + const dnn::BatchDescriptor& scale_offset_desc, const double epsilon, + DeviceMemory* y, DeviceMemory* batch_mean, DeviceMemory* batch_var, + DeviceMemory* saved_mean, DeviceMemory* saved_inv_var, + bool is_training, std::function&()> var_to_inv_var, + std::function inv_var_to_var) { + auto miopen = miopen_->GetHandle(parent_, stream); + + ScopedTensorDescriptor x_descriptor{x_desc, + ToMIOpenDataType(input_data_type)}; + ScopedTensorDescriptor scale_offset_descriptor{ + scale_offset_desc, ToMIOpenDataType(scale_data_type)}; + miopenBatchNormMode_t mode = miopenBNSpatial; + float one = 1.0; + float zero = 0.0; + + auto status = miopenStatusInvalidValue; + if (is_training) { + stream->ThenMemZero(batch_mean, batch_mean->size()); + stream->ThenMemZero(batch_var, batch_var->size()); + status = wrap::miopenBatchNormalizationForwardTraining( + miopen.handle(), mode, &one, &zero, x_descriptor.handle(), x.opaque(), + x_descriptor.handle(), y->opaque(), scale_offset_descriptor.handle(), + const_cast(scale.opaque()), const_cast(offset.opaque()), + 1.0, batch_mean->opaque(), batch_var->opaque(), epsilon, + saved_mean->opaque(), saved_inv_var->opaque()); + } else { + const void* maybe_inv_var = estimated_variance.opaque(); + status = wrap::miopenBatchNormalizationForwardInference( + miopen.handle(), mode, &one, &zero, x_descriptor.handle(), x.opaque(), + x_descriptor.handle(), y->opaque(), scale_offset_descriptor.handle(), + const_cast(scale.opaque()), const_cast(offset.opaque()), + const_cast(estimated_mean.opaque()), + const_cast(maybe_inv_var), epsilon); + } + if (status != miopenStatusSuccess) { + LOG(ERROR) << "failed to enqueue forward batch normalization on stream: " + << ToString(status); + return false; + } + return true; +} + +bool MIOpenSupport::DoBatchNormalizationBackward( + Stream* stream, const DeviceMemory& y_backprop, + const DeviceMemory& x, const DeviceMemory& scale, + const DeviceMemory& mean, const DeviceMemory& inv_var, + const dnn::BatchDescriptor& x_desc, + const dnn::BatchDescriptor& scale_offset_desc, const double epsilon, + DeviceMemory* x_backprop, DeviceMemory* scale_backprop, + DeviceMemory* offset_backprop) { + return DoBatchNormalizationBackwardImpl( + stream, miopenHalf, miopenFloat, y_backprop, x, scale, mean, inv_var, + x_desc, scale_offset_desc, epsilon, x_backprop, scale_backprop, + offset_backprop); +} + +bool MIOpenSupport::DoBatchNormalizationBackward( + Stream* stream, const DeviceMemory& y_backprop, + const DeviceMemory& x, const DeviceMemory& scale, + const DeviceMemory& mean, const DeviceMemory& variance, + const dnn::BatchDescriptor& x_desc, + const dnn::BatchDescriptor& scale_offset_desc, const double epsilon, + DeviceMemory* x_backprop, DeviceMemory* scale_backprop, + DeviceMemory* offset_backprop) { + return DoBatchNormalizationBackwardImpl( + stream, miopenFloat, miopenFloat, y_backprop, x, scale, mean, variance, + x_desc, scale_offset_desc, epsilon, x_backprop, scale_backprop, + offset_backprop); +} + +template +bool MIOpenSupport::DoBatchNormalizationBackwardImpl( + Stream* stream, int miopen_input_type, int miopen_scale_type, + const DeviceMemory& y_backprop, const DeviceMemory& x, + const DeviceMemory& scale, const DeviceMemory& mean, + const DeviceMemory& variance, const dnn::BatchDescriptor& x_desc, + const dnn::BatchDescriptor& scale_offset_desc, const double epsilon, + DeviceMemory* x_backprop, DeviceMemory* scale_backprop, + DeviceMemory* offset_backprop) { + auto miopen = miopen_->GetHandle(parent_, stream); + ScopedTensorDescriptor x_descriptor{ + x_desc, static_cast(miopen_input_type)}; + ScopedTensorDescriptor scale_offset_descriptor{ + scale_offset_desc, static_cast(miopen_scale_type)}; + miopenBatchNormMode_t mode = miopenBNSpatial; + float one = 1.0; + float zero = 0.0; + + auto status = wrap::miopenBatchNormalizationBackward( + miopen.handle(), mode, &one, &zero, &one, &zero, x_descriptor.handle(), + x.opaque(), x_descriptor.handle(), y_backprop.opaque(), + x_descriptor.handle(), x_backprop->opaque(), + scale_offset_descriptor.handle(), scale.opaque(), + scale_backprop->opaque(), offset_backprop->opaque(), epsilon, + mean.opaque(), variance.opaque()); + if (status != miopenStatusSuccess) { + LOG(ERROR) << "failed to enqueue backward batch normalization on stream: " + << ToString(status); + return false; + } + return true; +} + +bool MIOpenSupport::DoFusedConvolve( + Stream* stream, const dnn::BatchDescriptor& conv_input_descriptor, + const DeviceMemory& conv_input_data, double conv_input_scale, + const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const DeviceMemory& side_input_data, double side_input_scale, + const dnn::BatchDescriptor& bias_descriptor, + const DeviceMemory& biases, dnn::ActivationMode activation_mode, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory* output_data, ScratchAllocator* scratch_allocator, + const dnn::AlgorithmConfig& algorithm_config, + dnn::ProfileResult* output_profile_result) { + LOG(ERROR) << "fused convolve not implemented yet"; + return false; +} + +bool MIOpenSupport::DoFusedConvolve( + Stream* stream, const dnn::BatchDescriptor& conv_input_descriptor, + const DeviceMemory& conv_input_data, float conv_input_scale, + const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const DeviceMemory& side_input_data, float side_input_scale, + const dnn::BatchDescriptor& bias_descriptor, + const DeviceMemory& biases, dnn::ActivationMode activation_mode, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory* output_data, ScratchAllocator* scratch_allocator, + const dnn::AlgorithmConfig& algorithm_config, + dnn::ProfileResult* output_profile_result) { + LOG(ERROR) << "fused convolve not implemented yet"; + return false; +} + +bool MIOpenSupport::DoFusedConvolve( + Stream* stream, const dnn::BatchDescriptor& conv_input_descriptor, + const DeviceMemory& conv_input_data, float conv_input_scale, + const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const DeviceMemory& side_input_data, float side_input_scale, + const dnn::BatchDescriptor& bias_descriptor, + const DeviceMemory& biases, + dnn::ActivationMode activation_mode, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory* output_data, ScratchAllocator* scratch_allocator, + const dnn::AlgorithmConfig& algorithm_config, + dnn::ProfileResult* output_profile_result) { + LOG(ERROR) << "fused convolve not implemented yet"; + return false; +} + +bool MIOpenSupport::DoFusedConvolve( + Stream* stream, const dnn::BatchDescriptor& conv_input_descriptor, + const DeviceMemory& conv_input_data, float conv_input_scale, + const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const DeviceMemory& side_input_data, float side_input_scale, + const dnn::BatchDescriptor& bias_descriptor, + const DeviceMemory& biases, dnn::ActivationMode activation_mode, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory* output_data, ScratchAllocator* scratch_allocator, + const dnn::AlgorithmConfig& algorithm_config, + dnn::ProfileResult* output_profile_result) { + LOG(ERROR) << "fused convolve not implemented yet"; + return false; +} + +bool MIOpenSupport::DoTransformTensor(Stream* stream, + const dnn::BatchDescriptor& input_desc, + dnn::DataType input_type, + const DeviceMemoryBase& input_data, + const dnn::BatchDescriptor& output_desc, + dnn::DataType output_type, float scale, + DeviceMemoryBase* output_data) { + // ROCM TODO implement this operation + LOG(ERROR) << "transform tensor not implemented yet"; + return false; +} + +template +bool MIOpenSupport::DoConvolveBackwardBiasImpl( + Stream* stream, int miopen_type, // Actually miopenDataType_t. + const dnn::BatchDescriptor& input_descriptor, + const DeviceMemory& input_data, + const dnn::BatchDescriptor& bias_descriptor, + DeviceMemory* backward_bias_data) { + auto miopen = miopen_->GetHandle(parent_, stream); + + ScopedTensorDescriptor input_nd{input_descriptor, + static_cast(miopen_type)}; + ScopedTensorDescriptor bias_nd{bias_descriptor, + static_cast(miopen_type)}; + + // Alpha is the scaling factor for input. + float alpha = 1.0; + // Beta is the scaling factor for output. + float beta = 0.0; + + auto status = wrap::miopenConvolutionBackwardBias( + miopen.handle(), &alpha, input_nd.handle(), input_data.opaque(), &beta, + bias_nd.handle(), backward_bias_data->opaque()); + if (status != miopenStatusSuccess) { + LOG(FATAL) << "failed to enqueue backward convolution on stream: " + << ToString(status); + return false; + } + return true; +} + +bool MIOpenSupport::DoConvolveBackwardBias( + Stream* stream, const BatchDescriptor& input_descriptor, + const DeviceMemory& input_data, + const BatchDescriptor& bias_descriptor, + DeviceMemory* backward_bias_data) { + LOG(ERROR) << "miopen does not support double bwd bias yet"; + return false; +} + +bool MIOpenSupport::DoConvolveBackwardBias( + Stream* stream, const BatchDescriptor& input_descriptor, + const DeviceMemory& input_data, + const BatchDescriptor& bias_descriptor, + DeviceMemory* backward_bias_data) { + return DoConvolveBackwardBiasImpl(stream, miopenFloat, input_descriptor, + input_data, bias_descriptor, + backward_bias_data); +} + +bool MIOpenSupport::DoConvolveBackwardBias( + Stream* stream, const BatchDescriptor& input_descriptor, + const DeviceMemory& input_data, + const BatchDescriptor& bias_descriptor, + DeviceMemory* backward_bias_data) { + return DoConvolveBackwardBiasImpl(stream, miopenHalf, input_descriptor, + input_data, bias_descriptor, + backward_bias_data); +} + +bool MIOpenSupport::DoMatMul(Stream* stream, + const DeviceMemory& input_data, + const DeviceMemory& weights, + const dnn::BatchDescriptor& input_dimensions, + const dnn::BatchDescriptor& output_dimensions, + DeviceMemory* output_data) { + if (input_dimensions.count() != output_dimensions.count()) { + LOG(ERROR) << "MatMul input and output dimensions are not compatible."; + return false; + } + + // We do not permute the input or output, instead we just + // reinterpret the layout. We are working with row-major matrices + // and the rows of the input and output correspond to batch, so + // batch has to be outermost in both the input and output. + // + // By adding transposes to the BLAS gemm call we could perhaps make + // the kYXDepthBatch layout work as well, but there has been no need + // for that so far. + if (input_dimensions.layout() != dnn::DataLayout::kBatchYXDepth && + input_dimensions.layout() != dnn::DataLayout::kBatchDepthYX) { + LOG(ERROR) << "Unsupported MatMul input layout."; + return false; + } + if (output_dimensions.layout() != dnn::DataLayout::kBatchYXDepth && + output_dimensions.layout() != dnn::DataLayout::kBatchDepthYX) { + LOG(ERROR) << "Unsupported MatMul output layout."; + return false; + } + + if (output_dimensions.width() == 1 && output_dimensions.height() == 1) { + // This is a fast path that also supports the kBatchYXDepth layout. + + // The matrices here are in row-major format while BLAS expects + // column-major, i.e. our matrices are transposed as far as BLAS + // is concerned. So we need to compute output^T = + // input^T*weights^T. There is no parameter for transposing the + // output in BLAS gemm, but instead we can transpose both sides of + // the equality to see that this is equivalent to + // output=weights*input. So we only need to swap the order of + // weights and input in the matrix product to correct for the + // row-major versus column-major difference. + const float alpha = 1.0f; // Take the matrix product without scaling it. + const float beta = 0.0f; // Ignore the original values in output_data. + const int64 m = output_dimensions.NodesAcrossFeatureMaps(); + const int64 n = input_dimensions.count(); + const int64 k = input_dimensions.NodesAcrossFeatureMaps(); + stream->ThenBlasGemm(blas::Transpose::kNoTranspose, + blas::Transpose::kNoTranspose, m, n, k, alpha, weights, + m, input_data, k, beta, output_data, m); + } else { + // This is a slower and more complex path that supports output + // width() * height() > 1, though it only supports the + // kBatchYXDepth layout. Does support kBatchDepthYX if output + // feature_map_count() == 1, as then there is no difference + // between the two layouts. + // + // The operation here is the same as above, except that we have to + // do the matrix multiplication for each (y,x) output coordinate + // separately. We then interpret weights as containing K = width() + // * height() different matrices, which we all multiply onto the + // matrix from input_data, yielding K matrix products. We then + // combine these together into one matrix by concatenating all the + // first rows of these matrices, then all the seconds rows and so + // on. We can do this with a batched matrix multiplication, where + // the result is written to a different submatrix of the output + // for each matrix multiplication. + // + // The reason that we only support the kBatchYXDepth output layout + // is that we have to do something in the depth for each (y,x) + // coordinate. The kBatchYXDepth layout has the depth information + // for each point (y,x) in contiguous memory while the + // kBatchDepthYX layout does not. + // + // TODO(broune): Consider a special case for when output depth == + // 1, as then possibly this could all be done as one matrix + // multiplication instead of a batched one, which should be + // faster. Another possibility would be to add a weights layout + // parameter and then support kBatchDepthYX for a different + // weights layout. + if (output_dimensions.layout() != dnn::DataLayout::kBatchYXDepth && + !(output_dimensions.layout() == dnn::DataLayout::kBatchDepthYX && + output_dimensions.feature_map_count() == 1)) { + LOG(ERROR) << "Unsupported MatMul output layout."; + return false; + } + + const float alpha = 1.0f; // Take the matrix product without scaling it. + const float beta = 0.0f; // Ignore the original values in output_data. + const uint64 m = output_dimensions.feature_map_count(); + const uint64 n = input_dimensions.count(); + const uint64 k = input_dimensions.NodesAcrossFeatureMaps(); + const int lda = m; + const int ldb = k; + const int ldc = output_dimensions.NodesAcrossFeatureMaps(); + const int batch_count = output_dimensions.NodesPerFeatureMap(); + + std::vector> a(batch_count); + std::vector> b(batch_count); + std::vector> c(batch_count); + for (int i = 0; i < batch_count; ++i) { + const int weights_offset = i * input_dimensions.NodesAcrossFeatureMaps() * + output_dimensions.feature_map_count(); + a[i] = DeviceMemory::MakeFromByteSize( + const_cast(reinterpret_cast(weights.opaque())) + + weights_offset, + weights.ElementCount() - weights_offset); + + b[i] = input_data; + + const int output_offset = i * output_dimensions.feature_map_count(); + c[i] = DeviceMemory::MakeFromByteSize( + const_cast( + reinterpret_cast(output_data->opaque())) + + output_offset, + output_data->ElementCount() - output_offset); + } + const auto toPtrs = [](std::vector>& v) { + std::vector*> ptrs; + ptrs.reserve(v.size()); + for (auto& mem : v) { + ptrs.push_back(&mem); + } + return ptrs; + }; + + stream->ThenBlasGemmBatched(blas::Transpose::kNoTranspose, + blas::Transpose::kNoTranspose, m, n, k, alpha, + toPtrs(a), lda, toPtrs(b), ldb, beta, toPtrs(c), + ldc, batch_count); + } + + return stream->ok(); +} + +bool MIOpenSupport::DoBiasAdd(Stream* stream, + const DeviceMemory& input_data, + const DeviceMemory& biases, + const dnn::BatchDescriptor& dimensions, + DeviceMemory* output_data) { + ScopedTensorDescriptor input_descriptor{dimensions, miopenFloat}; + + BatchDescriptor bias_dimensions; + bias_dimensions.set_count(1) + .set_feature_map_count(dimensions.feature_map_count()) + .set_height(1) + .set_width(1) + .set_layout(dnn::DataLayout::kBatchYXDepth); + ScopedTensorDescriptor bias_descriptor{bias_dimensions, miopenFloat}; + + if (input_data.opaque() != output_data->opaque()) { + stream->ThenMemcpy(output_data, input_data, + dimensions.ElementCount() * sizeof(float)); + if (!stream->ok()) { + LOG(ERROR) + << "stream " << stream + << " could not enqueue a tensor copy as part of bias addition."; + return false; + } + } + + auto miopen = miopen_->GetHandle(parent_, stream); + + const float alpha1 = 1.0f; + const float alpha2 = 0.0f; + const float beta = 1.0f; + + auto status = wrap::miopenOpTensor( + miopen.handle(), miopenTensorOpAdd, &alpha1, bias_descriptor.handle(), + biases.opaque(), &alpha2, bias_descriptor.handle(), biases.opaque(), + &beta, input_descriptor.handle(), output_data->opaque()); + + if (status != miopenStatusSuccess) { + LOG(ERROR) << "stream " << stream << " could not enqueue bias addition."; + return false; + } + + return true; +} + +bool MIOpenSupport::DoActivate(Stream* stream, + dnn::ActivationMode activation_mode, + const dnn::BatchDescriptor& dimensions, + const DeviceMemory& input_data, + DeviceMemory* output_data, + uint64 options) { + LOG(ERROR) << "miopen does not support activation yet"; + return false; +} + +bool MIOpenSupport::DoPoolForward( + Stream* stream, const dnn::PoolingDescriptor& pooling_dimensions, + const dnn::BatchDescriptor& input_dimensions, + const DeviceMemory& input_data, + const dnn::BatchDescriptor& output_dimensions, + DeviceMemory* output_data, ScratchAllocator* workspace_allocator) { + LOG(ERROR) << "miopen does not support pooling for dobule type yet"; + return false; +} + +bool MIOpenSupport::DoPoolForward( + Stream* stream, const dnn::PoolingDescriptor& pooling_dimensions, + const dnn::BatchDescriptor& input_dimensions, + const DeviceMemory& input_data, + const dnn::BatchDescriptor& output_dimensions, + DeviceMemory* output_data, ScratchAllocator* workspace_allocator) { + auto miopen = miopen_->GetHandle(parent_, stream); + + // Alpha is the scaling factor for input. + float alpha = 1.0; + // Beta is the scaling factor for output. + float beta = 0.0; + + ScopedTensorDescriptor src_desc{input_dimensions, miopenFloat}; + ScopedTensorDescriptor dest_desc{output_dimensions, miopenFloat}; + ScopedPoolingDescriptor pooling_desc{pooling_dimensions}; + + auto status = wrap::miopenPoolingForward( + miopen.handle(), pooling_desc.handle(), &alpha, src_desc.handle(), + input_data.opaque(), &beta, dest_desc.handle(), output_data->opaque(), + false, nullptr, 0); + if (status != miopenStatusSuccess) { + LOG(ERROR) << "failed to enqueue forward pooling on stream: " + << ToString(status); + return false; + } + return true; +} + +bool MIOpenSupport::DoPoolForward( + Stream* stream, const dnn::PoolingDescriptor& pooling_dimensions, + const dnn::BatchDescriptor& input_dimensions, + const DeviceMemory& input_data, + const dnn::BatchDescriptor& output_dimensions, + DeviceMemory* output_data, + ScratchAllocator* workspace_allocator) { + auto miopen = miopen_->GetHandle(parent_, stream); + + // Alpha is the scaling factor for input. + float alpha = 1.0; + // Beta is the scaling factor for output. + float beta = 0.0; + + ScopedTensorDescriptor src_desc{input_dimensions, miopenHalf}; + ScopedTensorDescriptor dest_desc{output_dimensions, miopenHalf}; + ScopedPoolingDescriptor pooling_desc{pooling_dimensions}; + + auto status = wrap::miopenPoolingForward( + miopen.handle(), pooling_desc.handle(), &alpha, src_desc.handle(), + input_data.opaque(), &beta, dest_desc.handle(), output_data->opaque(), + false, nullptr, 0); + if (status != miopenStatusSuccess) { + LOG(ERROR) << "failed to enqueue forward pooling on stream: " + << ToString(status); + return false; + } + return true; +} + +bool MIOpenSupport::DoPoolBackward( + Stream* stream, const dnn::PoolingDescriptor& pooling_dimensions, + const dnn::BatchDescriptor& input_dimensions, + const DeviceMemory& input_data, + const dnn::BatchDescriptor& output_dimensions, + const DeviceMemory& output_data, + const DeviceMemory& input_diff_data, + DeviceMemory* output_diff_data, + ScratchAllocator* workspace_allocator) { + LOG(ERROR) << "miopen does not support backward pooling on double type yet"; + return false; +} + +bool MIOpenSupport::DoPoolBackward( + Stream* stream, const dnn::PoolingDescriptor& pooling_dimensions, + const dnn::BatchDescriptor& input_dimensions, + const DeviceMemory& input_data, + const dnn::BatchDescriptor& output_dimensions, + const DeviceMemory& output_data, + const DeviceMemory& input_diff_data, + DeviceMemory* output_diff_data, + ScratchAllocator* workspace_allocator) { + auto miopen = miopen_->GetHandle(parent_, stream); + + // Alpha is the scaling factor for input. + float alpha = 1.0; + // Beta is the scaling factor for output. + float beta = 0.0; + + ScopedTensorDescriptor src_desc{input_dimensions, miopenFloat}; + ScopedTensorDescriptor dest_desc{output_dimensions, miopenFloat}; + ScopedPoolingDescriptor pooling_desc{pooling_dimensions}; + + DeviceMemory workspace; + size_t workspace_size_in_bytes = 0; + auto status = wrap::miopenPoolingGetWorkSpaceSize(dest_desc.handle(), + &workspace_size_in_bytes); + + if (status != miopenStatusSuccess) { + LOG(ERROR) + << "failed to obtain workspace size for backward pooling on stream: " + << ToString(status); + return false; + } + + // Allocate the workspace. + if (workspace_size_in_bytes > 0) { + assert(workspace_allocator); + auto allocated = + workspace_allocator->AllocateBytes(stream, workspace_size_in_bytes); + if (!allocated.ok() || (workspace = allocated.ValueOrDie()) == nullptr) { + LOG(ERROR) << "Failed to allocate backward pooling workspace"; + return false; + } + } + + DeviceMemory dest2; // duplicated dest from forward: + int dest2_size = 0; + + // miopen requires the strides and dims to be ordered as BDYX. + std::vector dims64 = + output_dimensions.full_dims(dnn::DataLayout::kBatchDepthYX); + + // miopen does not use strides and must have 4D tensor. + std::vector dims(4); + + std::transform(dims64.cbegin(), dims64.cend(), dims.begin(), + &CheckedNarrowing); + + dest2_size = dims[0] * dims[1] * dims[2] * dims[3] * sizeof(float); + + if (dest2_size > 0) { + assert(workspace_allocator); + auto allocated = workspace_allocator->AllocateBytes(stream, dest2_size); + if (!allocated.ok() || (dest2 = allocated.ValueOrDie()) == nullptr) { + LOG(ERROR) << "Failed to allocate backward pooling workspace"; + return false; + } + } else { + LOG(ERROR) << "Failed to calcuate tensor size to chain forward and " + "backward pooling"; + } + + status = wrap::miopenPoolingForward( + miopen.handle(), pooling_desc.handle(), &alpha, src_desc.handle(), + input_data.opaque(), &beta, dest_desc.handle(), dest2.opaque(), true, + workspace.opaque(), workspace_size_in_bytes); + + if (status != miopenStatusSuccess) { + LOG(ERROR) + << "failed to enqueue forward pooling (before backward) on stream: " + << ToString(status); + return false; + } + + status = wrap::miopenPoolingBackward( + miopen.handle(), pooling_desc.handle(), &alpha, dest_desc.handle(), + dest2.opaque(), dest_desc.handle(), input_diff_data.opaque(), + src_desc.handle(), input_data.opaque(), &beta, src_desc.handle(), + output_diff_data->opaque(), workspace.opaque()); + + if (status != miopenStatusSuccess) { + LOG(ERROR) << "failed to enqueue backward pooling on stream: " + << ToString(status); + return false; + } + return true; +} + +bool MIOpenSupport::DoPoolBackward( + Stream* stream, const dnn::PoolingDescriptor& pooling_dimensions, + const dnn::BatchDescriptor& input_dimensions, + const DeviceMemory& input_data, + const dnn::BatchDescriptor& output_dimensions, + const DeviceMemory& output_data, + const DeviceMemory& input_diff_data, + DeviceMemory* output_diff_data, + ScratchAllocator* workspace_allocator) { + auto miopen = miopen_->GetHandle(parent_, stream); + + // Alpha is the scaling factor for input. + float alpha = 1.0; + // Beta is the scaling factor for output. + float beta = 0.0; + + ScopedTensorDescriptor src_desc{input_dimensions, miopenHalf}; + ScopedTensorDescriptor dest_desc{output_dimensions, miopenHalf}; + ScopedPoolingDescriptor pooling_desc{pooling_dimensions}; + + DeviceMemory workspace; + size_t workspace_size_in_bytes = 0; + auto status = wrap::miopenPoolingGetWorkSpaceSize(dest_desc.handle(), + &workspace_size_in_bytes); + + if (status != miopenStatusSuccess) { + LOG(ERROR) + << "failed to obtain workspace size for backward pooling on stream: " + << ToString(status); + return false; + } + + // Allocate the workspace. + if (workspace_size_in_bytes > 0) { + assert(workspace_allocator); + auto allocated = + workspace_allocator->AllocateBytes(stream, workspace_size_in_bytes); + if (!allocated.ok() || (workspace = allocated.ValueOrDie()) == nullptr) { + LOG(ERROR) << "Failed to allocate backward pooling workspace"; + return false; + } + } + + DeviceMemory dest2; // duplicated dest from forward: + int dest2_size = 0; + + // miopen requires the strides and dims to be ordered as BDYX. + std::vector dims64 = + output_dimensions.full_dims(dnn::DataLayout::kBatchDepthYX); + + // miopen does not use strides and must have 4D tensor. + std::vector dims(4); + + std::transform(dims64.cbegin(), dims64.cend(), dims.begin(), + &CheckedNarrowing); + + dest2_size = dims[0] * dims[1] * dims[2] * dims[3] * sizeof(float); + + if (dest2_size > 0) { + assert(workspace_allocator); + auto allocated = workspace_allocator->AllocateBytes(stream, dest2_size); + if (!allocated.ok() || (dest2 = allocated.ValueOrDie()) == nullptr) { + LOG(ERROR) << "Failed to allocate backward pooling workspace"; + return false; + } + } else { + LOG(ERROR) << "Failed to calcuate tensor size to chain forward and " + "backward pooling"; + } + + status = wrap::miopenPoolingForward( + miopen.handle(), pooling_desc.handle(), &alpha, src_desc.handle(), + input_data.opaque(), &beta, dest_desc.handle(), dest2.opaque(), true, + workspace.opaque(), workspace_size_in_bytes); + + if (status != miopenStatusSuccess) { + LOG(ERROR) + << "failed to enqueue forward pooling (before backward) on stream: " + << ToString(status); + return false; + } + + status = wrap::miopenPoolingBackward( + miopen.handle(), pooling_desc.handle(), &alpha, dest_desc.handle(), + dest2.opaque(), dest_desc.handle(), input_diff_data.opaque(), + src_desc.handle(), input_data.opaque(), &beta, src_desc.handle(), + output_diff_data->opaque(), workspace.opaque()); + + if (status != miopenStatusSuccess) { + LOG(ERROR) << "failed to enqueue backward pooling on stream: " + << ToString(status); + return false; + } + return true; +} + +bool MIOpenSupport::DoNormalizeWithDimensions( + Stream* stream, const dnn::NormalizeDescriptor& normalize_descriptor, + const dnn::BatchDescriptor& dimensions, + const DeviceMemory& input_data, DeviceMemory* output_data) { + // Check for unsupported modes. + if (normalize_descriptor.wrap_around()) { + LOG(ERROR) << "MIOpen LRN does not support wrap-around mode"; + return false; + } + if (normalize_descriptor.segment_size()) { + LOG(ERROR) << "MIOpen LRN does not support segmentation"; + return false; + } + + auto miopen = miopen_->GetHandle(parent_, stream); + + // Launch the normalization. + ScopedTensorDescriptor dims{dimensions, miopenFloat}; + ScopedNormalizeDescriptor normalize{normalize_descriptor}; + + // Alpha is the scaling factor for input. + float alpha = 1.0f; + // Beta is the scaling factor for output. + float beta = 0.0f; + + auto status = wrap::miopenLRNForward( + miopen.handle(), normalize.handle(), &alpha, dims.handle(), + input_data.opaque(), &beta, dims.handle(), output_data->opaque(), false, + nullptr); + if (status != miopenStatusSuccess) { + LOG(ERROR) << "failed to run miopenLRNForward"; + return false; + } + return true; +} + +bool MIOpenSupport::DoNormalizeBackwardWithDimensions( + Stream* stream, const dnn::NormalizeDescriptor& normalize_descriptor, + const dnn::BatchDescriptor& dimensions, const DeviceMemory& raw_data, + const DeviceMemory& normalized_data, + const DeviceMemory& normalized_variable_gradient, + DeviceMemory* raw_variable_gradient, + ScratchAllocator* workspace_allocator) { + // Check for unsupported modes. + if (normalize_descriptor.wrap_around()) { + LOG(ERROR) << "MIOpen LRN does not support wrap-around mode"; + return false; + } + if (normalize_descriptor.segment_size()) { + LOG(ERROR) << "MIOpen LRN does not support segmentation"; + return false; + } + + auto miopen = miopen_->GetHandle(parent_, stream); + + ScopedTensorDescriptor dims{dimensions, miopenFloat}; + ScopedNormalizeDescriptor normalize{normalize_descriptor}; + + float alpha = 1.0f; + float beta = 0.0f; + + DeviceMemory workspace; + size_t workspace_size_in_bytes = 0; + auto status = + wrap::miopenLRNGetWorkSpaceSize(dims.handle(), &workspace_size_in_bytes); + + if (status != miopenStatusSuccess) { + LOG(ERROR) << "failed to obtain workspace size for miopenLRNBackward"; + return false; + } + + // Allocate the workspace. + if (workspace_size_in_bytes > 0) { + assert(workspace_allocator); + auto allocated = + workspace_allocator->AllocateBytes(stream, workspace_size_in_bytes); + if (!allocated.ok() || (workspace = allocated.ValueOrDie()) == nullptr) { + LOG(ERROR) << "Failed to allocate backward pooling workspace"; + return false; + } + } + + DeviceMemory dest2; // duplicated dest from forward: + int dest2_size = 0; + + // miopen requires the strides and dims to be ordered as BDYX. + std::vector dims64 = + dimensions.full_dims(dnn::DataLayout::kBatchDepthYX); + + // miopen does not use strides and must have 4D tensor. + std::vector dimsint(4); + + std::transform(dims64.cbegin(), dims64.cend(), dimsint.begin(), + &CheckedNarrowing); + + dest2_size = + dimsint[0] * dimsint[1] * dimsint[2] * dimsint[3] * sizeof(float); + + if (dest2_size > 0) { + assert(workspace_allocator); + auto allocated = workspace_allocator->AllocateBytes(stream, dest2_size); + if (!allocated.ok() || (dest2 = allocated.ValueOrDie()) == nullptr) { + LOG(ERROR) + << "Failed to allocate tensor to chain forward and backward LRN"; + return false; + } + } else { + LOG(ERROR) + << "Failed to calcuate tensor size to chain forward and backward LRN"; + } + + status = wrap::miopenLRNForward(miopen.handle(), normalize.handle(), &alpha, + dims.handle(), raw_data.opaque(), &beta, + dims.handle(), dest2.opaque(), true, + workspace.opaque()); + + if (status != miopenStatusSuccess) { + LOG(ERROR) << "failed to run miopenLRNForward"; + return false; + } + + status = wrap::miopenLRNBackward( + miopen.handle(), normalize.handle(), &alpha, dims.handle(), + normalized_data.opaque(), dims.handle(), + normalized_variable_gradient.opaque(), dims.handle(), raw_data.opaque(), + &beta, dims.handle(), raw_variable_gradient->opaque(), + workspace.opaque()); + + if (status != miopenStatusSuccess) { + LOG(ERROR) << "failed to run miopenLRNBackward"; + return false; + } + return true; +} + +bool MIOpenSupport::DoDepthConcatenate( + Stream* stream, port::ArraySlice input_dimensions, + port::ArraySlice*> input_data, + DeviceMemory* output_data) { + CHECK_EQ(input_dimensions.size(), input_data.size()); + + for (const auto& dimensions : input_dimensions) { + if (dimensions.layout() != dnn::DataLayout::kBatchDepthYX) { + LOG(ERROR) << "MIOpenSupport::DoDepthConcatenate currently only " + "supports the kBatchDepthYX layout."; + return false; + } + } + + if (input_dimensions.empty()) { + return true; // Nothing to do. + } + + dnn::BatchDescriptor output_dimensions = + dnn::BatchDescriptor::DepthConcatenateOutputDescriptor(input_dimensions); + + const int64 area = output_dimensions.width() * output_dimensions.height(); + const auto index = [area](int64 batch, int64 depth, int64 yx, + int64 max_depth) { + return (batch * max_depth + depth) * area + yx; + }; + + std::vector output_host(output_dimensions.ElementCount()); + std::vector tmp; + int64 depth_sum = 0; + for (size_t i = 0; i < input_data.size(); ++i) { + const auto& dimensions = input_dimensions[i]; + tmp.resize(dimensions.ElementCount()); + stream->ThenMemcpyD2H(*input_data[i], absl::MakeSpan(tmp)); + port::Status block_status = stream->BlockHostUntilDone(); + if (!block_status.ok()) { + LOG(ERROR) << "BlockHostUntilDone failed: " << block_status; + return false; + } + + for (int64 batch = 0; batch < output_dimensions.count(); ++batch) { + for (int64 yx = 0; yx < area; ++yx) { + for (int64 depth = 0; depth < dimensions.feature_map_count(); ++depth) { + LOG(INFO) << output_dimensions.ElementCount() << ' ' << batch << ' ' + << yx << ' ' << depth; + output_host[index(batch, depth + depth_sum, yx, + output_dimensions.feature_map_count())] = + tmp[index(batch, depth, yx, dimensions.feature_map_count())]; + } + } + } + depth_sum += dimensions.feature_map_count(); + } + stream->ThenMemcpyH2D(output_host, output_data); + return true; +} + +bool MIOpenSupport::DoElementwiseOperate( + Stream* stream, dnn::ElementwiseOperation operation, + port::ArraySlice input_dimensions, + port::ArraySlice*> input_data, + const dnn::BatchDescriptor& output_dimensions, + DeviceMemory* output_data) { + LOG(FATAL) << "not yet implemented"; // TODO(leary) + return false; +} + +bool MIOpenSupport::DoXYPad(Stream* stream, + const dnn::BatchDescriptor& dimensions, + const DeviceMemory& input_data, + int64 left_pad, int64 right_pad, int64 top_pad, + int64 bottom_pad, + DeviceMemory* output_data) { + LOG(FATAL) << "not yet implemented"; // TODO(leary) + return false; +} + +bool MIOpenSupport::DoXYSlice(Stream* stream, + const dnn::BatchDescriptor& dimensions, + const DeviceMemory& input_data, + int64 left_trim, int64 right_trim, int64 top_trim, + int64 bottom_trim, + DeviceMemory* output_data) { + LOG(FATAL) << "not yet implemented"; // TODO(leary) + return false; +} + +bool MIOpenSupport::DoMemcpyD2HQuantized( + Stream* stream, const DeviceMemory& gpu_unquantized_src, + dnn::QuantizedActivationMode mode, void* host_dst, int64 size) { + LOG(ERROR) << "quantized memcpy not supported by MIOpen"; + return false; +} + +bool MIOpenSupport::DoMemcpyH2DQuantized( + Stream* stream, const void* host_src, int64 size, + dnn::QuantizedActivationMode mode, + DeviceMemory* gpu_unquantized_dst) { + LOG(ERROR) << "quantized memcpy not supported by MIOpen"; + return false; +} + +bool MIOpenSupport::DeriveOutputBatchDescriptor( + const BatchDescriptor& batch_descriptor, + const FilterDescriptor& filter_descriptor, + const dnn::ConvolutionDescriptor& convolution_descriptor, + dnn::BatchDescriptor* output_batch_descriptor) { + ScopedTensorDescriptor input_nd{batch_descriptor, miopenFloat}; + ScopedFilterDescriptor filter{filter_descriptor, batch_descriptor, + miopenFloat}; + ScopedConvolutionDescriptor conv{convolution_descriptor, miopenFloat}; + + int dn = batch_descriptor.ndims() + 2; + std::vector dims(dn); // in BDYX + auto status = wrap::miopenGetConvolutionForwardOutputDim( + conv.handle(), input_nd.handle(), filter.handle(), &dims[0], &dims[1], + &dims[2], &dims[3]); + if (status != miopenStatusSuccess) { + LOG(ERROR) << "could not get output tensor for convolution: " + << ToString(status); + return false; + } + + output_batch_descriptor->set_count(dims[0]) + .set_feature_map_count(dims[1]) + .set_layout(batch_descriptor.layout()); + + for (int i = 0; i < batch_descriptor.ndims(); i++) { + output_batch_descriptor->set_spatial_dim(static_cast(i), + dims.rbegin()[i]); + } + + return true; +} + +template +bool MIOpenSupport::DoFusedConvolutionBiasActivationImpl( + Stream* stream, + int miopen_type, // Actually miopenDataType_t. + const dnn::BatchDescriptor& conv_input_descriptor, + const DeviceMemory& conv_input_data, + const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::BatchDescriptor& bias_descriptor, + const DeviceMemory& bias_data, dnn::ActivationMode activation_mode, + const dnn::BatchDescriptor& output_descriptor, DeviceMemory* output_data, + dnn::ProfileResult* output_profile_result) { + auto miopen = miopen_->GetHandle(parent_, stream); + + ScopedTensorDescriptor conv_input_nd{ + conv_input_descriptor, static_cast(miopen_type)}; + + ScopedTensorDescriptor bias_nd{bias_descriptor, + static_cast(miopen_type)}; + + ScopedTensorDescriptor output_nd{output_descriptor, + static_cast(miopen_type)}; + + ScopedConvolutionDescriptor conv{convolution_descriptor, + static_cast(miopen_type)}; + + ScopedFilterDescriptor filter{filter_descriptor, conv_input_descriptor, + static_cast(miopen_type)}; + + ScopedActivationDescriptor activation_desc{activation_mode}; + + ScopedFusionPlanConvolutionBiasActivation fusion_plan{ + miopen.handle(), conv_input_nd.handle(), filter.handle(), + conv.handle(), bias_nd.handle(), activation_desc}; + + bool retval = false; + + if (fusion_plan.CompilationSucceeded()) { + const bool is_profiling = output_profile_result != nullptr; + + std::unique_ptr timer; + if (is_profiling) { + timer.reset(new GpuTimer(parent_)); + timer->Init(); + timer->Start(AsGpuStream(stream)); + } + + miopenStatus_t status = miopenStatusSuccess; + + if (status == miopenStatusSuccess) { + fusion_plan.SetConvolutionArgs(filter_data.opaque()); + } + + if (status == miopenStatusSuccess) { + status = fusion_plan.SetBiasArgs(bias_data.opaque()); + } + + if (status == miopenStatusSuccess) { + status = fusion_plan.SetActivationForwardArgs(activation_desc); + } + + if (status == miopenStatusSuccess) { + status = + fusion_plan.Execute(conv_input_nd.handle(), conv_input_data.opaque(), + output_nd.handle(), output_data->opaque()); + } + + if (is_profiling) { + timer->Stop(AsGpuStream(stream)); + if (status == miopenStatusSuccess) { + output_profile_result->set_elapsed_time_in_ms( + timer->GetElapsedMilliseconds()); + } + timer->Destroy(); + } + + if (status != miopenStatusSuccess) { + // Silently return when we are profiling. + if (!is_profiling) { + LOG(FATAL) << "failed to enqueue fused-convolution on stream: " + << ToString(status); + } + } + + retval = true; + } + + return retval; +} + +bool MIOpenSupport::DoFusedConvolutionBiasActivation( + Stream* stream, const dnn::BatchDescriptor& conv_input_descriptor, + const DeviceMemory& conv_input_data, + const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::BatchDescriptor& bias_descriptor, + const DeviceMemory& bias_data, dnn::ActivationMode activation_mode, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory* output_data, + dnn::ProfileResult* output_profile_result) { + return DoFusedConvolutionBiasActivationImpl( + stream, miopenFloat, conv_input_descriptor, conv_input_data, + filter_descriptor, filter_data, convolution_descriptor, bias_descriptor, + bias_data, activation_mode, output_descriptor, output_data, + output_profile_result); +} + +template +bool MIOpenSupport::DoFusedBatchNormActivationInferenceImpl( + Stream* stream, + int miopen_type, // Actually miopenDataType_t. + const dnn::BatchDescriptor& x_descriptor, const DeviceMemory& x_data, + const dnn::BatchDescriptor& scale_offset_mean_variance_descriptor, + const DeviceMemory& scale_data, const DeviceMemory& offset_data, + const DeviceMemory& mean_data, const DeviceMemory& variance_data, + double epsilon, dnn::ActivationMode activation_mode, + DeviceMemory* y_data, dnn::ProfileResult* output_profile_result) { + auto miopen = miopen_->GetHandle(parent_, stream); + + ScopedTensorDescriptor x_nd{x_descriptor, + static_cast(miopen_type)}; + + ScopedTensorDescriptor scale_offset_mean_variance_nd{ + scale_offset_mean_variance_descriptor, + static_cast(miopen_type)}; + + ScopedActivationDescriptor activation_desc{activation_mode}; + + ScopedFusionPlanBatchNormActivationInference fusion_plan{ + miopen.handle(), x_nd.handle(), scale_offset_mean_variance_nd.handle(), + activation_desc}; + + bool retval = false; + + if (fusion_plan.CompilationSucceeded()) { + const bool is_profiling = output_profile_result != nullptr; + + std::unique_ptr timer; + if (is_profiling) { + timer.reset(new GpuTimer(parent_)); + timer->Init(); + timer->Start(AsGpuStream(stream)); + } + + miopenStatus_t status = miopenStatusSuccess; + + if (status == miopenStatusSuccess) { + fusion_plan.SetBatchNormInferenceArgs( + scale_data.opaque(), offset_data.opaque(), mean_data.opaque(), + variance_data.opaque(), epsilon); + } + + if (status == miopenStatusSuccess) { + status = fusion_plan.SetActivationForwardArgs(activation_desc); + } + + if (status == miopenStatusSuccess) { + status = fusion_plan.Execute(x_nd.handle(), x_data.opaque(), + x_nd.handle(), y_data->opaque()); + } + + if (is_profiling) { + timer->Stop(AsGpuStream(stream)); + if (status == miopenStatusSuccess) { + output_profile_result->set_elapsed_time_in_ms( + timer->GetElapsedMilliseconds()); + } + timer->Destroy(); + } + + if (status != miopenStatusSuccess) { + // Silently return when we are profiling. + if (!is_profiling) { + LOG(FATAL) << "failed to enqueue fused-convolution on stream: " + << ToString(status); + } + } + + retval = true; + } + + return retval; +} + +bool MIOpenSupport::DoFusedBatchNormActivationInference( + Stream* stream, const dnn::BatchDescriptor& x_descriptor, + const DeviceMemory& x_data, + const dnn::BatchDescriptor& scale_offset_mean_variance_descriptor, + const DeviceMemory& scale_data, + const DeviceMemory& offset_data, + const DeviceMemory& mean_data, + const DeviceMemory& variance_data, double epsilon, + dnn::ActivationMode activation_mode, DeviceMemory* y_data, + dnn::ProfileResult* output_profile_result) { + return DoFusedBatchNormActivationInferenceImpl( + stream, miopenFloat, x_descriptor, x_data, + scale_offset_mean_variance_descriptor, scale_data, offset_data, mean_data, + variance_data, epsilon, activation_mode, y_data, output_profile_result); +} + +bool MIOpenSupport::DoFusedBatchNormActivationInference( + Stream* stream, const dnn::BatchDescriptor& x_descriptor, + const DeviceMemory& x_data, + const dnn::BatchDescriptor& scale_offset_mean_variance_descriptor, + const DeviceMemory& scale_data, + const DeviceMemory& offset_data, + const DeviceMemory& mean_data, + const DeviceMemory& variance_data, double epsilon, + dnn::ActivationMode activation_mode, DeviceMemory* y_data, + dnn::ProfileResult* output_profile_result) { + return DoFusedBatchNormActivationInferenceImpl( + stream, miopenHalf, x_descriptor, x_data, + scale_offset_mean_variance_descriptor, scale_data, offset_data, mean_data, + variance_data, epsilon, activation_mode, y_data, output_profile_result); +} + +template +bool MIOpenSupport::DoFusedBatchNormActivationForwardImpl( + Stream* stream, + int miopen_type, // Actually miopenDataType_t. + const dnn::BatchDescriptor& x_descriptor, const DeviceMemory& x_data, + const dnn::BatchDescriptor& scale_offset_mean_variance_descriptor, + const DeviceMemory& scale_data, const DeviceMemory& offset_data, + double epsilon, dnn::ActivationMode activation_mode, + DeviceMemory* y_data, DeviceMemory* batch_mean_data, + DeviceMemory* batch_var_data, DeviceMemory* saved_mean_data, + DeviceMemory* saved_var_data, + dnn::ProfileResult* output_profile_result) { + auto miopen = miopen_->GetHandle(parent_, stream); + + ScopedTensorDescriptor x_nd{x_descriptor, + static_cast(miopen_type)}; + + ScopedTensorDescriptor scale_offset_mean_variance_nd{ + scale_offset_mean_variance_descriptor, + static_cast(miopen_type)}; + + ScopedActivationDescriptor activation_desc{activation_mode}; + + ScopedFusionPlanBatchNormActivationForward fusion_plan{ + miopen.handle(), x_nd.handle(), scale_offset_mean_variance_nd.handle(), + activation_desc}; + + bool retval = false; + + if (fusion_plan.CompilationSucceeded()) { + const bool is_profiling = output_profile_result != nullptr; + + std::unique_ptr timer; + if (is_profiling) { + timer.reset(new GpuTimer(parent_)); + timer->Init(); + timer->Start(AsGpuStream(stream)); + } + + miopenStatus_t status = miopenStatusSuccess; + + if (status == miopenStatusSuccess) { + fusion_plan.SetBatchNormForwardArgs( + scale_data.opaque(), offset_data.opaque(), batch_mean_data->opaque(), + batch_var_data->opaque(), saved_mean_data->opaque(), + saved_var_data->opaque(), epsilon); + } + + if (status == miopenStatusSuccess) { + status = fusion_plan.SetActivationForwardArgs(activation_desc); + } + + if (status == miopenStatusSuccess) { + status = fusion_plan.Execute(x_nd.handle(), x_data.opaque(), + x_nd.handle(), y_data->opaque()); + } + + if (is_profiling) { + timer->Stop(AsGpuStream(stream)); + if (status == miopenStatusSuccess) { + output_profile_result->set_elapsed_time_in_ms( + timer->GetElapsedMilliseconds()); + } + timer->Destroy(); + } + + if (status != miopenStatusSuccess) { + // Silently return when we are profiling. + if (!is_profiling) { + LOG(FATAL) << "failed to enqueue fused-convolution on stream: " + << ToString(status); + } + } + + retval = true; + } + + return retval; +} + +bool MIOpenSupport::DoFusedBatchNormActivationForward( + Stream* stream, const dnn::BatchDescriptor& x_descriptor, + const DeviceMemory& x_data, + const dnn::BatchDescriptor& scale_offset_mean_variance_descriptor, + const DeviceMemory& scale_data, + const DeviceMemory& offset_data, double epsilon, + dnn::ActivationMode activation_mode, DeviceMemory* y_data, + DeviceMemory* batch_mean_data, DeviceMemory* batch_var_data, + DeviceMemory* saved_mean_data, DeviceMemory* saved_var_data, + dnn::ProfileResult* output_profile_result) { + return DoFusedBatchNormActivationForwardImpl( + stream, miopenFloat, x_descriptor, x_data, + scale_offset_mean_variance_descriptor, scale_data, offset_data, epsilon, + activation_mode, y_data, batch_mean_data, batch_var_data, saved_mean_data, + saved_var_data, output_profile_result); +} + +bool MIOpenSupport::DoFusedBatchNormActivationForward( + Stream* stream, const dnn::BatchDescriptor& x_descriptor, + const DeviceMemory& x_data, + const dnn::BatchDescriptor& scale_offset_mean_variance_descriptor, + const DeviceMemory& scale_data, + const DeviceMemory& offset_data, double epsilon, + dnn::ActivationMode activation_mode, DeviceMemory* y_data, + DeviceMemory* batch_mean_data, DeviceMemory* batch_var_data, + DeviceMemory* saved_mean_data, DeviceMemory* saved_var_data, + dnn::ProfileResult* output_profile_result) { + return DoFusedBatchNormActivationForwardImpl( + stream, miopenHalf, x_descriptor, x_data, + scale_offset_mean_variance_descriptor, scale_data, offset_data, epsilon, + activation_mode, y_data, batch_mean_data, batch_var_data, saved_mean_data, + saved_var_data, output_profile_result); +} + +template +bool MIOpenSupport::DoFusedBatchNormActivationBackwardImpl( + Stream* stream, + int miopen_type, // Actually miopenDataType_t. + const dnn::BatchDescriptor& y_act_backprop_descriptor, + const DeviceMemory& y_act_backprop_data, + const DeviceMemory& y_act_data, dnn::ActivationMode activation_mode, + const DeviceMemory& x_bn_data, + const dnn::BatchDescriptor& scale_offset_mean_variance_descriptor, + const DeviceMemory& scale_data, const DeviceMemory& offset_data, + const DeviceMemory& saved_mean_data, + const DeviceMemory& saved_var_data, DeviceMemory* x_bn_backprop_data, + DeviceMemory* scale_backprop_data, DeviceMemory* offset_backprop_data, + dnn::ProfileResult* output_profile_result) { + auto miopen = miopen_->GetHandle(parent_, stream); + + ScopedTensorDescriptor y_act_backprop_nd{ + y_act_backprop_descriptor, static_cast(miopen_type)}; + + ScopedTensorDescriptor scale_offset_mean_variance_nd{ + scale_offset_mean_variance_descriptor, + static_cast(miopen_type)}; + + ScopedActivationDescriptor activation_desc{activation_mode}; + + ScopedFusionPlanBatchNormActivationBackward fusion_plan{ + miopen.handle(), y_act_backprop_nd.handle(), + scale_offset_mean_variance_nd.handle(), activation_desc}; + + bool retval = false; + + if (fusion_plan.CompilationSucceeded()) { + const bool is_profiling = output_profile_result != nullptr; + + std::unique_ptr timer; + if (is_profiling) { + timer.reset(new GpuTimer(parent_)); + timer->Init(); + timer->Start(AsGpuStream(stream)); + } + + miopenStatus_t status = miopenStatusSuccess; + + if (status == miopenStatusSuccess) { + fusion_plan.SetBatchNormBackwardArgs( + x_bn_data.opaque(), scale_data.opaque(), offset_data.opaque(), + saved_mean_data.opaque(), saved_var_data.opaque(), + scale_backprop_data->opaque(), offset_backprop_data->opaque()); + } + + if (status == miopenStatusSuccess) { + status = fusion_plan.SetActivationBackwardArgs(activation_desc, + y_act_data.opaque()); + } + + if (status == miopenStatusSuccess) { + status = fusion_plan.Execute( + y_act_backprop_nd.handle(), y_act_backprop_data.opaque(), + y_act_backprop_nd.handle(), x_bn_backprop_data->opaque()); + } + + if (is_profiling) { + timer->Stop(AsGpuStream(stream)); + if (status == miopenStatusSuccess) { + output_profile_result->set_elapsed_time_in_ms( + timer->GetElapsedMilliseconds()); + } + timer->Destroy(); + } + + if (status != miopenStatusSuccess) { + // Silently return when we are profiling. + if (!is_profiling) { + LOG(FATAL) << "failed to enqueue fused-convolution on stream: " + << ToString(status); + } + } + + retval = true; + } + + return retval; +} + +bool MIOpenSupport::DoFusedBatchNormActivationBackward( + Stream* stream, const dnn::BatchDescriptor& y_act_backprop_descriptor, + const DeviceMemory& y_act_backprop_data, + const DeviceMemory& y_act_data, dnn::ActivationMode activation_mode, + const DeviceMemory& x_bn_data, + const dnn::BatchDescriptor& scale_offset_mean_variance_descriptor, + const DeviceMemory& scale_data, + const DeviceMemory& offset_data, + const DeviceMemory& saved_mean_data, + const DeviceMemory& saved_var_data, + DeviceMemory* x_bn_backprop_data, + DeviceMemory* scale_backprop_data, + DeviceMemory* offset_backprop_data, + dnn::ProfileResult* output_profile_result) { + return DoFusedBatchNormActivationBackwardImpl( + stream, miopenFloat, y_act_backprop_descriptor, y_act_backprop_data, + y_act_data, activation_mode, x_bn_data, + scale_offset_mean_variance_descriptor, scale_data, offset_data, + saved_mean_data, saved_var_data, x_bn_backprop_data, scale_backprop_data, + offset_backprop_data, output_profile_result); +} + +bool MIOpenSupport::DoFusedBatchNormActivationBackward( + Stream* stream, const dnn::BatchDescriptor& y_act_backprop_descriptor, + const DeviceMemory& y_act_backprop_data, + const DeviceMemory& y_act_data, + dnn::ActivationMode activation_mode, + const DeviceMemory& x_bn_data, + const dnn::BatchDescriptor& scale_offset_mean_variance_descriptor, + const DeviceMemory& scale_data, + const DeviceMemory& offset_data, + const DeviceMemory& saved_mean_data, + const DeviceMemory& saved_var_data, + DeviceMemory* x_bn_backprop_data, + DeviceMemory* scale_backprop_data, + DeviceMemory* offset_backprop_data, + dnn::ProfileResult* output_profile_result) { + return DoFusedBatchNormActivationBackwardImpl( + stream, miopenHalf, y_act_backprop_descriptor, y_act_backprop_data, + y_act_data, activation_mode, x_bn_data, + scale_offset_mean_variance_descriptor, scale_data, offset_data, + saved_mean_data, saved_var_data, x_bn_backprop_data, scale_backprop_data, + offset_backprop_data, output_profile_result); +} + +} // namespace gpu + +void initialize_miopen() { + auto miopenAlreadyRegistered = PluginRegistry::Instance()->HasFactory( + rocm::kROCmPlatformId, PluginKind::kDnn, gpu::kMIOpenPlugin); + + if (!miopenAlreadyRegistered) { + port::Status status = + PluginRegistry::Instance()->RegisterFactory( + rocm::kROCmPlatformId, gpu::kMIOpenPlugin, "MIOpen", + [](internal::StreamExecutorInterface* parent) -> dnn::DnnSupport* { + gpu::GpuExecutor* rocm_executor = + dynamic_cast(parent); + if (rocm_executor == nullptr) { + LOG(ERROR) + << "Attempting to initialize an instance of the MIOpen " + << "support library with a non-ROCM StreamExecutor"; + return nullptr; + } + + gpu::MIOpenSupport* dnn = new gpu::MIOpenSupport(rocm_executor); + if (!dnn->Init().ok()) { + // Note: Init() will log a more specific error. + delete dnn; + return nullptr; + } + return dnn; + }); + + if (!status.ok()) { + LOG(ERROR) << "Unable to register MIOpen factory: " + << status.error_message(); + } + + PluginRegistry::Instance()->SetDefaultFactory( + rocm::kROCmPlatformId, PluginKind::kDnn, gpu::kMIOpenPlugin); + } +} + +} // namespace stream_executor + +REGISTER_MODULE_INITIALIZER(register_miopen, + { stream_executor::initialize_miopen(); }); diff --git a/tensorflow/stream_executor/rocm/rocm_dnn.h b/tensorflow/stream_executor/rocm/rocm_dnn.h new file mode 100644 index 0000000000..643f9b3a73 --- /dev/null +++ b/tensorflow/stream_executor/rocm/rocm_dnn.h @@ -0,0 +1,776 @@ +/* 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. +==============================================================================*/ + +// The ROCM-specific DNN library support, implementing the general DnnSupport +// interface. + +#ifndef TENSORFLOW_STREAM_EXECUTOR_ROCM_ROCM_DNN_H_ +#define TENSORFLOW_STREAM_EXECUTOR_ROCM_ROCM_DNN_H_ + +#include "tensorflow/stream_executor/dnn.h" +#include "tensorflow/stream_executor/lib/status.h" +#include "tensorflow/stream_executor/platform/mutex.h" +#include "tensorflow/stream_executor/platform/thread_annotations.h" +#include "tensorflow/stream_executor/plugin_registry.h" +#include "tensorflow/stream_executor/temporary_device_memory.h" + +namespace stream_executor { +namespace gpu { + +class GpuExecutor; +class MIOpenRnnDescriptor; +class MIOpenRnnSequenceTensorDescriptor; +class MIOpenRnnStateTensorDescriptor; +// Opaque and unique identifier for the MIOpen plugin. +extern const PluginId kMIOpenPlugin; + +// miopen-library based DNN support. For details on overridden interface +// functions, see dnn.h. +class MIOpenSupport : public dnn::DnnSupport { + public: + explicit MIOpenSupport(GpuExecutor* parent); + + port::Status Init() override; + port::StatusOr GetVersion() override; + + port::StatusOr> createRnnDescriptor( + int num_layers, int hidden_size, int input_size, int batch_size, + dnn::RnnInputMode input_mode, dnn::RnnDirectionMode direction_mode, + dnn::RnnMode rnn_mode, dnn::DataType data_type, + const dnn::AlgorithmConfig& algorithm_config, float dropout, uint64 seed, + ScratchAllocator* state_allocator) override; + + port::StatusOr> + createRnnSequenceTensorDescriptor(int seq_length, int batch_size, + int data_size, + dnn::DataType data_type) override; + + port::StatusOr> + createRnnStateTensorDescriptor(int num_layer, int batch_size, int data_size, + dnn::DataType data_type) override; + + bool DoRnnForward(Stream* stream, const dnn::RnnDescriptor& rnn_desc, + const dnn::RnnSequenceTensorDescriptor& input_desc, + const DeviceMemory& input_data, + const dnn::RnnStateTensorDescriptor& input_h_desc, + const DeviceMemory& input_h_data, + const dnn::RnnStateTensorDescriptor& input_c_desc, + const DeviceMemory& input_c_data, + const DeviceMemory& params, + const dnn::RnnSequenceTensorDescriptor& output_desc, + DeviceMemory* output_data, + const dnn::RnnStateTensorDescriptor& output_h_desc, + DeviceMemory* output_h_data, + const dnn::RnnStateTensorDescriptor& output_c_desc, + DeviceMemory* output_c_data, bool is_training, + ScratchAllocator* reserve_space_allocator, + ScratchAllocator* workspace_allocator, + dnn::ProfileResult* output_profile_result) override; + + bool DoRnnForward(Stream* stream, const dnn::RnnDescriptor& rnn_desc, + const dnn::RnnSequenceTensorDescriptor& input_desc, + const DeviceMemory& input_data, + const dnn::RnnStateTensorDescriptor& input_h_desc, + const DeviceMemory& input_h_data, + const dnn::RnnStateTensorDescriptor& input_c_desc, + const DeviceMemory& input_c_data, + const DeviceMemory& params, + const dnn::RnnSequenceTensorDescriptor& output_desc, + DeviceMemory* output_data, + const dnn::RnnStateTensorDescriptor& output_h_desc, + DeviceMemory* output_h_data, + const dnn::RnnStateTensorDescriptor& output_c_desc, + DeviceMemory* output_c_data, bool is_training, + ScratchAllocator* reserve_space_allocator, + ScratchAllocator* workspace_allocator, + dnn::ProfileResult* output_profile_result) override; + + bool DoRnnForward(Stream* stream, const dnn::RnnDescriptor& rnn_desc, + const dnn::RnnSequenceTensorDescriptor& input_desc, + const DeviceMemory& input_data, + const dnn::RnnStateTensorDescriptor& input_h_desc, + const DeviceMemory& input_h_data, + const dnn::RnnStateTensorDescriptor& input_c_desc, + const DeviceMemory& input_c_data, + const DeviceMemory& params, + const dnn::RnnSequenceTensorDescriptor& output_desc, + DeviceMemory* output_data, + const dnn::RnnStateTensorDescriptor& output_h_desc, + DeviceMemory* output_h_data, + const dnn::RnnStateTensorDescriptor& output_c_desc, + DeviceMemory* output_c_data, bool is_training, + ScratchAllocator* reserve_space_allocator, + ScratchAllocator* workspace_allocator, + dnn::ProfileResult* output_profile_result) override; + + bool DoRnnBackward(Stream* stream, const dnn::RnnDescriptor& rnn_desc, + const dnn::RnnSequenceTensorDescriptor& input_desc, + const DeviceMemory& input_data, + const dnn::RnnStateTensorDescriptor& input_h_desc, + const DeviceMemory& input_h_data, + const dnn::RnnStateTensorDescriptor& input_c_desc, + const DeviceMemory& input_c_data, + const DeviceMemory& params, + const dnn::RnnSequenceTensorDescriptor& output_desc, + const DeviceMemory& output_data, + const dnn::RnnStateTensorDescriptor& output_h_desc, + const DeviceMemory& output_h_data, + const dnn::RnnStateTensorDescriptor& output_c_desc, + const DeviceMemory& output_c_data, + const DeviceMemory& output_backprop_data, + const DeviceMemory& output_h_backprop_data, + const DeviceMemory& output_c_backprop_data, + DeviceMemory* input_backprop_data, + DeviceMemory* input_h_backprop_data, + DeviceMemory* input_c_backprop_data, + DeviceMemory* params_backprop_data, + DeviceMemory* reserve_space_data, + ScratchAllocator* workspace_allocator, + dnn::ProfileResult* output_profile_result) override; + + bool DoRnnBackward(Stream* stream, const dnn::RnnDescriptor& rnn_desc, + const dnn::RnnSequenceTensorDescriptor& input_desc, + const DeviceMemory& input_data, + const dnn::RnnStateTensorDescriptor& input_h_desc, + const DeviceMemory& input_h_data, + const dnn::RnnStateTensorDescriptor& input_c_desc, + const DeviceMemory& input_c_data, + const DeviceMemory& params, + const dnn::RnnSequenceTensorDescriptor& output_desc, + const DeviceMemory& output_data, + const dnn::RnnStateTensorDescriptor& output_h_desc, + const DeviceMemory& output_h_data, + const dnn::RnnStateTensorDescriptor& output_c_desc, + const DeviceMemory& output_c_data, + const DeviceMemory& output_backprop_data, + const DeviceMemory& output_h_backprop_data, + const DeviceMemory& output_c_backprop_data, + DeviceMemory* input_backprop_data, + DeviceMemory* input_h_backprop_data, + DeviceMemory* input_c_backprop_data, + DeviceMemory* params_backprop_data, + DeviceMemory* reserve_space_data, + ScratchAllocator* workspace_allocator, + dnn::ProfileResult* output_profile_result) override; + + bool DoRnnBackward(Stream* stream, const dnn::RnnDescriptor& rnn_desc, + const dnn::RnnSequenceTensorDescriptor& input_desc, + const DeviceMemory& input_data, + const dnn::RnnStateTensorDescriptor& input_h_desc, + const DeviceMemory& input_h_data, + const dnn::RnnStateTensorDescriptor& input_c_desc, + const DeviceMemory& input_c_data, + const DeviceMemory& params, + const dnn::RnnSequenceTensorDescriptor& output_desc, + const DeviceMemory& output_data, + const dnn::RnnStateTensorDescriptor& output_h_desc, + const DeviceMemory& output_h_data, + const dnn::RnnStateTensorDescriptor& output_c_desc, + const DeviceMemory& output_c_data, + const DeviceMemory& output_backprop_data, + const DeviceMemory& output_h_backprop_data, + const DeviceMemory& output_c_backprop_data, + DeviceMemory* input_backprop_data, + DeviceMemory* input_h_backprop_data, + DeviceMemory* input_c_backprop_data, + DeviceMemory* params_backprop_data, + DeviceMemory* reserve_space_data, + ScratchAllocator* workspace_allocator, + dnn::ProfileResult* output_profile_result) override; + + bool GetConvolveAlgorithms( + bool with_winograd_nonfused, int cc_major, int cc_minor, + std::vector* out_algorithms) override; + + bool GetRnnAlgorithms( + std::vector* out_algorithms) override; + + bool GetConvolveBackwardDataAlgorithms( + bool with_winograd_nonfused, int cc_major, int cc_minor, + std::vector* out_algorithms) override; + + bool GetConvolveBackwardFilterAlgorithms( + bool with_winograd_nonfused, int cc_major, int cc_minor, + std::vector* out_algorithms) override; + + bool DoBatchNormalizationForward( + Stream* stream, const DeviceMemory& x, + const DeviceMemory& scale, const DeviceMemory& offset, + const DeviceMemory& estimated_mean, + const DeviceMemory& estimated_variance, + const dnn::BatchDescriptor& x_desc, + const dnn::BatchDescriptor& scale_offset_desc, const double epsilon, + DeviceMemory* y, DeviceMemory* batch_mean, + DeviceMemory* batch_var, DeviceMemory* saved_mean, + DeviceMemory* saved_inv_var, bool is_training, + std::function&()> var_to_inv_var, + std::function inv_var_to_var) override; + + bool DoBatchNormalizationForward( + Stream* stream, const DeviceMemory& x, + const DeviceMemory& scale, const DeviceMemory& offset, + const DeviceMemory& estimated_mean, + const DeviceMemory& estimated_variance, + const dnn::BatchDescriptor& x_desc, + const dnn::BatchDescriptor& scale_offset_desc, const double epsilon, + DeviceMemory* y, DeviceMemory* batch_mean, + DeviceMemory* batch_var, DeviceMemory* saved_mean, + DeviceMemory* saved_inv_var, bool is_training, + std::function&()> var_to_inv_var, + std::function inv_var_to_var) override; + + bool DoBatchNormalizationBackward( + Stream* stream, const DeviceMemory& y_backprop, + const DeviceMemory& x, const DeviceMemory& scale, + const DeviceMemory& mean, const DeviceMemory& variance, + const dnn::BatchDescriptor& x_desc, + const dnn::BatchDescriptor& scale_offset_desc, const double epsilon, + DeviceMemory* x_backprop, DeviceMemory* scale_backprop, + DeviceMemory* offset_backprop) override; + + bool DoBatchNormalizationBackward( + Stream* stream, const DeviceMemory& y_backprop, + const DeviceMemory& x, const DeviceMemory& scale, + const DeviceMemory& mean, const DeviceMemory& inv_var, + const dnn::BatchDescriptor& x_desc, + const dnn::BatchDescriptor& scale_offset_desc, const double epsilon, + DeviceMemory* x_backprop, + DeviceMemory* scale_backprop, + DeviceMemory* offset_backprop) override; + + port::Status DoConvolve( + dnn::ConvolutionKind kind, dnn::DataType element_type, Stream* stream, + const dnn::BatchDescriptor& input_descriptor, DeviceMemoryBase input_data, + const dnn::FilterDescriptor& filter_descriptor, + DeviceMemoryBase filter_data, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemoryBase output_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + dnn::AlgorithmDesc algorithm_desc, DeviceMemory scratch_memory, + dnn::ProfileResult* output_profile_result) override; + + bool DoFusedConvolve( + Stream* stream, const dnn::BatchDescriptor& conv_input_descriptor, + const DeviceMemory& conv_input_data, double conv_input_scale, + const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const DeviceMemory& side_input_data, double side_input_scale, + const dnn::BatchDescriptor& bias_descriptor, + const DeviceMemory& biases, dnn::ActivationMode activation_mode, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory* output_data, ScratchAllocator* scratch_allocator, + const dnn::AlgorithmConfig& algorithm_config, + dnn::ProfileResult* output_profile_result) override; + + bool DoFusedConvolve( + Stream* stream, const dnn::BatchDescriptor& conv_input_descriptor, + const DeviceMemory& conv_input_data, float conv_input_scale, + const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const DeviceMemory& side_input_data, float side_input_scale, + const dnn::BatchDescriptor& bias_descriptor, + const DeviceMemory& biases, dnn::ActivationMode activation_mode, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory* output_data, ScratchAllocator* scratch_allocator, + const dnn::AlgorithmConfig& algorithm_config, + dnn::ProfileResult* output_profile_result) override; + + bool DoFusedConvolve(Stream* stream, + const dnn::BatchDescriptor& conv_input_descriptor, + const DeviceMemory& conv_input_data, + float conv_input_scale, + const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const DeviceMemory& side_input_data, + float side_input_scale, + const dnn::BatchDescriptor& bias_descriptor, + const DeviceMemory& biases, + dnn::ActivationMode activation_mode, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory* output_data, + ScratchAllocator* scratch_allocator, + const dnn::AlgorithmConfig& algorithm_config, + dnn::ProfileResult* output_profile_result) override; + + bool DoFusedConvolve( + Stream* stream, const dnn::BatchDescriptor& conv_input_descriptor, + const DeviceMemory& conv_input_data, float conv_input_scale, + const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const DeviceMemory& side_input_data, float side_input_scale, + const dnn::BatchDescriptor& bias_descriptor, + const DeviceMemory& biases, dnn::ActivationMode activation_mode, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory* output_data, ScratchAllocator* scratch_allocator, + const dnn::AlgorithmConfig& algorithm_config, + dnn::ProfileResult* output_profile_result) override; + + bool DoConvolveQuantized( + Stream* stream, const dnn::BatchDescriptor& input_descriptor, + const DeviceMemory& input_data, + const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_coefficients, + const DeviceMemory& coefficient_scales, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory* output_data) override { + LOG(ERROR) << "DoConvolveQuantized not supported by MIOpen"; + return false; + } + + bool DoConvolveQuantized( + Stream* stream, const dnn::BatchDescriptor& input_descriptor, + const DeviceMemory& input_data, + const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_coefficients, + const DeviceMemory& coefficient_scales, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory* output_data) override { + LOG(ERROR) << "DoConvolveQuantized not supported by MIOpen"; + return false; + } + + bool DoSeparableConvolve( + Stream* stream, const dnn::BatchDescriptor& batch_descriptor, + const DeviceMemory& input_data, + const dnn::FilterDescriptor& filter_descriptor, int depth_multiplier, + const DeviceMemory& first_weights, + const DeviceMemory& second_weights, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory* output_data) override { + LOG(ERROR) << "separable convolution not supported by MIOpen"; + return false; + } + + bool DoConvolveBackwardBias( + Stream* stream, const dnn::BatchDescriptor& input_descriptor, + const DeviceMemory& input_data, + const dnn::BatchDescriptor& bias_descriptor, + DeviceMemory* backward_bias_data) override; + + bool DoConvolveBackwardBias(Stream* stream, + const dnn::BatchDescriptor& input_descriptor, + const DeviceMemory& input_data, + const dnn::BatchDescriptor& bias_descriptor, + DeviceMemory* backward_bias_data) override; + + bool DoConvolveBackwardBias( + Stream* stream, const dnn::BatchDescriptor& input_descriptor, + const DeviceMemory& input_data, + const dnn::BatchDescriptor& bias_descriptor, + DeviceMemory* backward_bias_data) override; + + bool DoMatMul(Stream* stream, const DeviceMemory& input_data, + const DeviceMemory& weights, + const dnn::BatchDescriptor& input_dimensions, + const dnn::BatchDescriptor& output_dimensions, + DeviceMemory* output_data) override; + + bool DoMatMulQuantized(Stream* stream, const DeviceMemory& input_data, + const DeviceMemory& quantized_weights, + const DeviceMemory& weight_scales, + const dnn::BatchDescriptor& input_dimensions, + const dnn::BatchDescriptor& output_dimensions, + DeviceMemory* output_data) override { + LOG(ERROR) << "DNN MatMulQuantized not supported by MIOpen"; + return false; + } + + bool DoMatMulQuantized(Stream* stream, const DeviceMemory& input_data, + const DeviceMemory& quantized_weights, + const DeviceMemory& weight_scales, + const dnn::BatchDescriptor& input_dimensions, + const dnn::BatchDescriptor& output_dimensions, + DeviceMemory* output_data) override { + LOG(ERROR) << "DNN MatMulQuantized not supported by MIOpen"; + return false; + } + + bool DoBiasAdd(Stream* stream, const DeviceMemory& input_data, + const DeviceMemory& biases, + const dnn::BatchDescriptor& dimensions, + DeviceMemory* output_data) override; + + bool DoActivate(Stream* stream, dnn::ActivationMode activation_mode, + const dnn::BatchDescriptor& dimensions, + const DeviceMemory& input_data, + DeviceMemory* output_data, uint64 options) override; + + bool DoPoolForward(Stream* stream, + const dnn::PoolingDescriptor& pooling_dimensions, + const dnn::BatchDescriptor& input_dimensions, + const DeviceMemory& input_data, + const dnn::BatchDescriptor& output_dimensions, + DeviceMemory* output_data, + ScratchAllocator* workspace_allocator = nullptr) override; + + bool DoPoolForward(Stream* stream, + const dnn::PoolingDescriptor& pooling_dimensions, + const dnn::BatchDescriptor& input_dimensions, + const DeviceMemory& input_data, + const dnn::BatchDescriptor& output_dimensions, + DeviceMemory* output_data, + ScratchAllocator* workspace_allocator = nullptr) override; + + bool DoPoolForward(Stream* stream, + const dnn::PoolingDescriptor& pooling_dimensions, + const dnn::BatchDescriptor& input_dimensions, + const DeviceMemory& input_data, + const dnn::BatchDescriptor& output_dimensions, + DeviceMemory* output_data, + ScratchAllocator* workspace_allocator = nullptr) override; + + bool DoPoolBackward(Stream* stream, + const dnn::PoolingDescriptor& pooling_dimensions, + const dnn::BatchDescriptor& input_dimensions, + const DeviceMemory& input_data, + const dnn::BatchDescriptor& output_dimensions, + const DeviceMemory& output_data, + const DeviceMemory& input_diff_data, + DeviceMemory* output_diff_data, + ScratchAllocator* workspace_allocator = nullptr) override; + + bool DoPoolBackward(Stream* stream, + const dnn::PoolingDescriptor& pooling_dimensions, + const dnn::BatchDescriptor& input_dimensions, + const DeviceMemory& input_data, + const dnn::BatchDescriptor& output_dimensions, + const DeviceMemory& output_data, + const DeviceMemory& input_diff_data, + DeviceMemory* output_diff_data, + ScratchAllocator* workspace_allocator = nullptr) override; + + bool DoPoolBackward(Stream* stream, + const dnn::PoolingDescriptor& pooling_dimensions, + const dnn::BatchDescriptor& input_dimensions, + const DeviceMemory& input_data, + const dnn::BatchDescriptor& output_dimensions, + const DeviceMemory& output_data, + const DeviceMemory& input_diff_data, + DeviceMemory* output_diff_data, + ScratchAllocator* workspace_allocator = nullptr) override; + + bool DoNormalizeWithDimensions( + Stream* stream, const dnn::NormalizeDescriptor& normalize_descriptor, + const dnn::BatchDescriptor& dimensions, + const DeviceMemory& input_data, + DeviceMemory* output_data) override; + + bool DoNormalizeBackwardWithDimensions( + Stream* stream, const dnn::NormalizeDescriptor& normalize_descriptor, + const dnn::BatchDescriptor& dimensions, + const DeviceMemory& raw_data, + const DeviceMemory& normalized_data, + const DeviceMemory& normalized_variable_gradient, + DeviceMemory* raw_variable_gradient, + ScratchAllocator* workspace_allocator = nullptr) override; + + bool DoDepthConcatenate( + Stream* stream, port::ArraySlice input_dimensions, + port::ArraySlice*> input_data, + DeviceMemory* output_data) override; + + bool DoElementwiseOperate( + Stream* stream, dnn::ElementwiseOperation operation, + port::ArraySlice input_dimensions, + port::ArraySlice*> input_data, + const dnn::BatchDescriptor& output_dimensions, + DeviceMemory* output_data) override; + + bool DoXYPad(Stream* stream, const dnn::BatchDescriptor& dimensions, + const DeviceMemory& input_data, int64 left_pad, + int64 right_pad, int64 top_pad, int64 bottom_pad, + DeviceMemory* output_data) override; + + bool DoXYSlice(Stream* stream, const dnn::BatchDescriptor& dimensions, + const DeviceMemory& input_data, int64 left_trim, + int64 right_trim, int64 top_trim, int64 bottom_trim, + DeviceMemory* output_data) override; + + bool DoMemcpyD2HQuantized(Stream* stream, + const DeviceMemory& device_unquantized_src, + dnn::QuantizedActivationMode mode, void* host_dst, + int64 size) override; + + bool DoMemcpyH2DQuantized( + Stream* stream, const void* host_src, int64 size, + dnn::QuantizedActivationMode mode, + DeviceMemory* device_unquantized_dst) override; + + // Derives an output batch descriptor from an input batch and convolution + // descriptors. + bool DeriveOutputBatchDescriptor( + const dnn::BatchDescriptor& batch_descriptor, + const dnn::FilterDescriptor& filter_descriptor, + const dnn::ConvolutionDescriptor& convolution_descriptor, + dnn::BatchDescriptor* output_batch_descriptor); + + bool DoTransformTensor(Stream* stream, const dnn::BatchDescriptor& input_desc, + dnn::DataType input_type, + const DeviceMemoryBase& input_data, + const dnn::BatchDescriptor& output_desc, + dnn::DataType output_type, float scale, + DeviceMemoryBase* output_data) override; + + bool DoFusedConvolutionBiasActivation( + Stream* stream, const dnn::BatchDescriptor& conv_input_descriptor, + const DeviceMemory& conv_input_data, + const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::BatchDescriptor& bias_descriptor, + const DeviceMemory& bias_data, dnn::ActivationMode activation_mode, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory* output_data, + dnn::ProfileResult* output_profile_result) override; + + bool DoFusedBatchNormActivationInference( + Stream* stream, const dnn::BatchDescriptor& x_descriptor, + const DeviceMemory& x_data, + const dnn::BatchDescriptor& scale_mean_variance_descriptor, + const DeviceMemory& scale_data, + const DeviceMemory& offset_data, + const DeviceMemory& mean_data, + const DeviceMemory& variance_data, double epsilon, + dnn::ActivationMode activation_mode, DeviceMemory* y_data, + dnn::ProfileResult* output_profile_result) override; + + bool DoFusedBatchNormActivationInference( + Stream* stream, const dnn::BatchDescriptor& x_descriptor, + const DeviceMemory& x_data, + const dnn::BatchDescriptor& scale_mean_variance_descriptor, + const DeviceMemory& scale_data, + const DeviceMemory& offset_data, + const DeviceMemory& mean_data, + const DeviceMemory& variance_data, double epsilon, + dnn::ActivationMode activation_mode, DeviceMemory* y_data, + dnn::ProfileResult* output_profile_result) override; + + bool DoFusedBatchNormActivationForward( + Stream* stream, const dnn::BatchDescriptor& x_descriptor, + const DeviceMemory& x_data, + const dnn::BatchDescriptor& scale_offset_mean_variance_descriptor, + const DeviceMemory& scale_data, + const DeviceMemory& offset_data, double epsilon, + dnn::ActivationMode activation_mode, DeviceMemory* y_data, + DeviceMemory* batch_mean_data, DeviceMemory* batch_var_data, + DeviceMemory* saved_mean_data, DeviceMemory* saved_var_data, + dnn::ProfileResult* output_profile_result) override; + + bool DoFusedBatchNormActivationForward( + Stream* stream, const dnn::BatchDescriptor& x_descriptor, + const DeviceMemory& x_data, + const dnn::BatchDescriptor& scale_offset_mean_variance_descriptor, + const DeviceMemory& scale_data, + const DeviceMemory& offset_data, double epsilon, + dnn::ActivationMode activation_mode, DeviceMemory* y_data, + DeviceMemory* batch_mean_data, DeviceMemory* batch_var_data, + DeviceMemory* saved_mean_data, DeviceMemory* saved_var_data, + dnn::ProfileResult* output_profile_result) override; + + bool DoFusedBatchNormActivationBackward( + Stream* stream, const dnn::BatchDescriptor& y_act_backprop_descriptor, + const DeviceMemory& y_act_backprop_data, + const DeviceMemory& y_act_data, + dnn::ActivationMode activation_mode, const DeviceMemory& x_bn_data, + const dnn::BatchDescriptor& scale_offset_mean_variance_descriptor, + const DeviceMemory& scale_data, + const DeviceMemory& offset_data, + const DeviceMemory& saved_mean_data, + const DeviceMemory& saved_var_data, + DeviceMemory* x_bn_backprop_data, + DeviceMemory* scale_backprop_data, + DeviceMemory* offset_backprop_data, + dnn::ProfileResult* output_profile_result) override; + + bool DoFusedBatchNormActivationBackward( + Stream* stream, const dnn::BatchDescriptor& y_act_backprop_descriptor, + const DeviceMemory& y_act_backprop_data, + const DeviceMemory& y_act_data, + dnn::ActivationMode activation_mode, + const DeviceMemory& x_bn_data, + const dnn::BatchDescriptor& scale_offset_mean_variance_descriptor, + const DeviceMemory& scale_data, + const DeviceMemory& offset_data, + const DeviceMemory& saved_mean_data, + const DeviceMemory& saved_var_data, + DeviceMemory* x_bn_backprop_data, + DeviceMemory* scale_backprop_data, + DeviceMemory* offset_backprop_data, + dnn::ProfileResult* output_profile_result) override; + + GpuExecutor* GetParentExecutor() { return parent_; } + + private: + GpuExecutor* parent_; // Parent executor object. Not owned. + + // Provide access to the MIOpen handle. + std::unique_ptr miopen_; + + template + bool DoBatchNormalizationForwardImpl( + Stream* stream, dnn::DataType input_data_type, + dnn::DataType scale_data_type, const DeviceMemory& x, + const DeviceMemory& scale, const DeviceMemory& offset, + const DeviceMemory& estimated_mean, + const DeviceMemory& estimated_variance, + const dnn::BatchDescriptor& x_desc, + const dnn::BatchDescriptor& scale_offset_desc, const double epsilon, + DeviceMemory* y, DeviceMemory* batch_mean, + DeviceMemory* batch_var, DeviceMemory* saved_mean, + DeviceMemory* saved_inv_var, bool is_training, + std::function&()> var_to_inv_var, + std::function inv_var_to_var); + + template + bool DoBatchNormalizationBackwardImpl( + Stream* stream, int miopen_input_type, int miopen_scale_type, + const DeviceMemory& y_backprop, const DeviceMemory& x, + const DeviceMemory& scale, const DeviceMemory& mean, + const DeviceMemory& variance, const dnn::BatchDescriptor& x_desc, + const dnn::BatchDescriptor& scale_offset_desc, const double epsilon, + DeviceMemory* x_backprop, DeviceMemory* scale_backprop, + DeviceMemory* offset_backprop); + + template + bool DoConvolveBackwardBiasImpl( + Stream* stream, + int miopen_type, // Actually miopenDataType_t. + const dnn::BatchDescriptor& input_descriptor, + const DeviceMemory& input_data, + const dnn::BatchDescriptor& bias_descriptor, + DeviceMemory* backward_bias_data); + + template + bool DoRnnForwardImpl(Stream* stream, const MIOpenRnnDescriptor& rnn_desc, + const MIOpenRnnSequenceTensorDescriptor& input_desc, + const DeviceMemory& input_data, + const MIOpenRnnStateTensorDescriptor& input_h_desc, + const DeviceMemory& input_h_data, + const MIOpenRnnStateTensorDescriptor& input_c_desc, + const DeviceMemory& input_c_data, + const DeviceMemory& params, + const MIOpenRnnSequenceTensorDescriptor& output_desc, + DeviceMemory* output_data, + const MIOpenRnnStateTensorDescriptor& output_h_desc, + DeviceMemory* output_h_data, + const MIOpenRnnStateTensorDescriptor& output_c_desc, + DeviceMemory* output_c_data, bool is_training, + ScratchAllocator* reserve_space_allocator, + ScratchAllocator* workspace_allocator); + template + bool DoRnnBackwardImpl(Stream* stream, const MIOpenRnnDescriptor& rnn_desc, + const MIOpenRnnSequenceTensorDescriptor& input_desc, + const DeviceMemory& input_data, + const MIOpenRnnStateTensorDescriptor& input_h_desc, + const DeviceMemory& input_h_data, + const MIOpenRnnStateTensorDescriptor& input_c_desc, + const DeviceMemory& input_c_data, + const DeviceMemory& params, + const MIOpenRnnSequenceTensorDescriptor& output_desc, + const DeviceMemory& output_data, + const MIOpenRnnStateTensorDescriptor& output_h_desc, + const DeviceMemory& output_h_data, + const MIOpenRnnStateTensorDescriptor& output_c_desc, + const DeviceMemory& output_c_data, + const DeviceMemory& output_backprop_data, + const DeviceMemory& output_h_backprop_data, + const DeviceMemory& output_c_backprop_data, + DeviceMemory* input_backprop_data, + DeviceMemory* input_h_backprop_data, + DeviceMemory* input_c_backprop_data, + DeviceMemory* params_backprop_data, + DeviceMemory* reserve_space_data, + ScratchAllocator* workspace_allocator); + + template + bool DoFusedConvolutionBiasActivationImpl( + Stream* stream, + int miopen_type, // Actually miopenDataType_t. + const dnn::BatchDescriptor& conv_input_descriptor, + const DeviceMemory& conv_input_data, + const dnn::FilterDescriptor& filter_descriptor, + const DeviceMemory& filter_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::BatchDescriptor& bias_descriptor, + const DeviceMemory& bias_data, dnn::ActivationMode activation_mode, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemory* output_data, dnn::ProfileResult* output_profile_result); + + template + bool DoFusedBatchNormActivationInferenceImpl( + Stream* stream, + int miopen_type, // Actually miopenDataType_t. + const dnn::BatchDescriptor& x_descriptor, const DeviceMemory& x_data, + const dnn::BatchDescriptor& scale_offset_mean_variance_descriptor, + const DeviceMemory& scale_data, const DeviceMemory& offset_data, + const DeviceMemory& mean_data, const DeviceMemory& variance_data, + double epsilon, dnn::ActivationMode activation_mode, + DeviceMemory* y_data, dnn::ProfileResult* output_profile_result); + + template + bool DoFusedBatchNormActivationForwardImpl( + Stream* stream, + int miopen_type, // Actually miopenDataType_t. + const dnn::BatchDescriptor& x_descriptor, const DeviceMemory& x_data, + const dnn::BatchDescriptor& scale_offset_mean_variance_descriptor, + const DeviceMemory& scale_data, const DeviceMemory& offset_data, + double epsilon, dnn::ActivationMode activation_mode, + DeviceMemory* y_data, DeviceMemory* batch_mean_data, + DeviceMemory* batch_var_data, DeviceMemory* saved_mean_data, + DeviceMemory* saved_var_data, + dnn::ProfileResult* output_profile_result); + + template + bool DoFusedBatchNormActivationBackwardImpl( + Stream* stream, + int miopen_type, // Actually miopenDataType_t. + const dnn::BatchDescriptor& y_act_backprop_descriptor, + const DeviceMemory& y_act_backprop_data, + const DeviceMemory& y_act_data, dnn::ActivationMode activation_mode, + const DeviceMemory& x_bn_data, + const dnn::BatchDescriptor& scale_offset_mean_variance_descriptor, + const DeviceMemory& scale_data, const DeviceMemory& offset_data, + const DeviceMemory& saved_mean_data, + const DeviceMemory& saved_var_data, + DeviceMemory* x_bn_backprop_data, DeviceMemory* scale_backprop_data, + DeviceMemory* offset_backprop_data, + dnn::ProfileResult* output_profile_result); + + port::Status DoPrepareForConvolution( + dnn::ConvolutionKind kind, dnn::DataType element_type, Stream* stream, + const dnn::BatchDescriptor& input_descriptor, DeviceMemoryBase input_data, + const dnn::FilterDescriptor& filter_descriptor, + DeviceMemoryBase filter_data, + const dnn::BatchDescriptor& output_descriptor, + DeviceMemoryBase output_data, + const dnn::ConvolutionDescriptor& convolution_descriptor, + const dnn::AlgorithmConfig& algorithm_config, + ScratchAllocator* scratch_allocator, dnn::AlgorithmDesc* algorithm_desc, + DeviceMemory* scratch_memory) override; + + SE_DISALLOW_COPY_AND_ASSIGN(MIOpenSupport); +}; + +} // namespace gpu +} // namespace stream_executor + +#endif // TENSORFLOW_STREAM_EXECUTOR_ROCM_ROCM_DNN_H_ -- GitLab From 9ca8321d68b74ce4f30756366919c80282ca2b68 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Sun, 24 Feb 2019 14:09:20 -0800 Subject: [PATCH 427/766] Unbreak Windows GPU Build PiperOrigin-RevId: 235435925 --- tensorflow/compiler/jit/xla_device_context.cc | 17 ++++++-------- tensorflow/compiler/jit/xla_device_context.h | 2 +- .../memory_stats/kernels/memory_stats_ops.cc | 9 +++----- .../core/common_runtime/bfc_allocator.cc | 2 +- .../core/common_runtime/bfc_allocator.h | 2 +- .../common_runtime/eager/kernel_and_device.cc | 7 ++---- .../gpu/gpu_bfc_allocator_test.cc | 22 +++++++------------ .../common_runtime/gpu/gpu_debug_allocator.cc | 4 ++-- .../common_runtime/gpu/gpu_debug_allocator.h | 4 ++-- .../core/common_runtime/gpu/gpu_device.cc | 7 ++---- .../core/common_runtime/process_state.h | 2 +- .../common_runtime/step_stats_collector.cc | 6 ++--- tensorflow/core/framework/allocator.cc | 6 ++--- tensorflow/core/framework/allocator.h | 11 +++++----- tensorflow/core/framework/allocator_test.cc | 20 +++++++---------- .../core/framework/tracking_allocator.cc | 4 +--- .../core/framework/tracking_allocator.h | 2 +- .../core/framework/tracking_allocator_test.cc | 2 -- .../core/grappler/clusters/single_machine.cc | 5 ++--- tensorflow/core/kernels/stack.cc | 6 ++--- tensorflow/stream_executor/BUILD | 3 --- tensorflow/stream_executor/allocator_stats.cc | 4 ++-- tensorflow/stream_executor/allocator_stats.h | 8 +++---- .../stream_executor_internal.h | 4 +--- .../stream_executor/stream_executor_pimpl.cc | 2 +- .../stream_executor/stream_executor_pimpl.h | 3 +-- 26 files changed, 64 insertions(+), 100 deletions(-) diff --git a/tensorflow/compiler/jit/xla_device_context.cc b/tensorflow/compiler/jit/xla_device_context.cc index 05b9c51186..b5830edb87 100644 --- a/tensorflow/compiler/jit/xla_device_context.cc +++ b/tensorflow/compiler/jit/xla_device_context.cc @@ -51,19 +51,16 @@ void XlaDeviceAllocator::DeallocateRaw(void* ptr) { delete XlaTensor::FromOpaquePointer(ptr); } -absl::optional XlaDeviceAllocator::GetStats() { - absl::optional se_stats = +AllocatorStats XlaDeviceAllocator::GetStats() { + stream_executor::AllocatorStats se_stats = stream_executor_->GetAllocatorStats(); - if (!se_stats) { - return absl::nullopt; - } tensorflow::AllocatorStats tf_stats; - tf_stats.num_allocs = se_stats->num_allocs; - tf_stats.bytes_in_use = se_stats->bytes_in_use; - tf_stats.peak_bytes_in_use = se_stats->peak_bytes_in_use; - tf_stats.largest_alloc_size = se_stats->largest_alloc_size; - tf_stats.bytes_limit = se_stats->bytes_limit; + tf_stats.num_allocs = se_stats.num_allocs; + tf_stats.bytes_in_use = se_stats.bytes_in_use; + tf_stats.peak_bytes_in_use = se_stats.peak_bytes_in_use; + tf_stats.largest_alloc_size = se_stats.largest_alloc_size; + tf_stats.bytes_limit = se_stats.bytes_limit; return tf_stats; } diff --git a/tensorflow/compiler/jit/xla_device_context.h b/tensorflow/compiler/jit/xla_device_context.h index 1ce64ad323..709a196ddc 100644 --- a/tensorflow/compiler/jit/xla_device_context.h +++ b/tensorflow/compiler/jit/xla_device_context.h @@ -41,7 +41,7 @@ class XlaDeviceAllocator : public Allocator { void* AllocateRaw(size_t alignment, size_t num_bytes) override; void DeallocateRaw(void* ptr) override; - absl::optional GetStats() override; + AllocatorStats GetStats() override; private: // The stream executor of the device. diff --git a/tensorflow/contrib/memory_stats/kernels/memory_stats_ops.cc b/tensorflow/contrib/memory_stats/kernels/memory_stats_ops.cc index 7ae1dbeaa2..796d139555 100644 --- a/tensorflow/contrib/memory_stats/kernels/memory_stats_ops.cc +++ b/tensorflow/contrib/memory_stats/kernels/memory_stats_ops.cc @@ -24,15 +24,12 @@ class MemoryStatsOp : public OpKernel { void Compute(OpKernelContext* context) override { Allocator* allocator = context->device()->GetAllocator(AllocatorAttributes()); - absl::optional allocator_stats = allocator->GetStats(); - if (!allocator_stats) { - *allocator_stats = AllocatorStats(); - } + AllocatorStats allocator_stats = allocator->GetStats(); Tensor* output_tensor = nullptr; OP_REQUIRES_OK( context, context->allocate_output(0, TensorShape({}), &output_tensor)); - output_tensor->scalar()() = ExtractAllocatorStats(*allocator_stats); + output_tensor->scalar()() = ExtractAllocatorStats(allocator_stats); } protected: @@ -73,7 +70,7 @@ class BytesLimitOp : public MemoryStatsOp { private: int64 ExtractAllocatorStats( const AllocatorStats& allocator_stats) const override { - return allocator_stats.bytes_limit ? *allocator_stats.bytes_limit : -1; + return allocator_stats.bytes_limit; } }; diff --git a/tensorflow/core/common_runtime/bfc_allocator.cc b/tensorflow/core/common_runtime/bfc_allocator.cc index 0e4ddb1020..37cc6f9432 100644 --- a/tensorflow/core/common_runtime/bfc_allocator.cc +++ b/tensorflow/core/common_runtime/bfc_allocator.cc @@ -686,7 +686,7 @@ void BFCAllocator::DumpMemoryLog(size_t num_bytes) { LOG(INFO) << "Stats: \n" << stats_.DebugString(); } -absl::optional BFCAllocator::GetStats() { +AllocatorStats BFCAllocator::GetStats() { mutex_lock l(lock_); return stats_; } diff --git a/tensorflow/core/common_runtime/bfc_allocator.h b/tensorflow/core/common_runtime/bfc_allocator.h index b0fd0d8667..5fcfb57f97 100644 --- a/tensorflow/core/common_runtime/bfc_allocator.h +++ b/tensorflow/core/common_runtime/bfc_allocator.h @@ -69,7 +69,7 @@ class BFCAllocator : public Allocator { int64 AllocationId(const void* ptr) override; - absl::optional GetStats() override; + AllocatorStats GetStats() override; void ClearStats() override; diff --git a/tensorflow/core/common_runtime/eager/kernel_and_device.cc b/tensorflow/core/common_runtime/eager/kernel_and_device.cc index 8a06aa87e8..59c8b7e7d1 100644 --- a/tensorflow/core/common_runtime/eager/kernel_and_device.cc +++ b/tensorflow/core/common_runtime/eager/kernel_and_device.cc @@ -156,11 +156,8 @@ void UpdateStats(OpKernelContext* context, memory->set_peak_bytes(std::get<1>(sizes)); memory->set_live_bytes(std::get<2>(sizes)); - absl::optional allocator_stats = - allocator_pair.first->GetStats(); - if (stats) { - memory->set_allocator_bytes_in_use(allocator_stats->bytes_in_use); - } + AllocatorStats allocator_stats = allocator_pair.first->GetStats(); + memory->set_allocator_bytes_in_use(allocator_stats.bytes_in_use); allocator_pair.second->GetRecordsAndUnRef(); } auto* ms = stats->mutable_memory_stats(); diff --git a/tensorflow/core/common_runtime/gpu/gpu_bfc_allocator_test.cc b/tensorflow/core/common_runtime/gpu/gpu_bfc_allocator_test.cc index 9c0abd97a1..8c2cac30b9 100644 --- a/tensorflow/core/common_runtime/gpu/gpu_bfc_allocator_test.cc +++ b/tensorflow/core/common_runtime/gpu/gpu_bfc_allocator_test.cc @@ -37,16 +37,12 @@ namespace { static void CheckStats(Allocator* a, int64 num_allocs, int64 bytes_in_use, int64 peak_bytes_in_use, int64 largest_alloc_size) { - absl::optional stats = a->GetStats(); - EXPECT_TRUE(stats); - if (!stats) { - return; - } - LOG(INFO) << "Alloc stats: " << std::endl << stats->DebugString(); - EXPECT_EQ(stats->bytes_in_use, bytes_in_use); - EXPECT_EQ(stats->peak_bytes_in_use, peak_bytes_in_use); - EXPECT_EQ(stats->num_allocs, num_allocs); - EXPECT_EQ(stats->largest_alloc_size, largest_alloc_size); + AllocatorStats stats = a->GetStats(); + LOG(INFO) << "Alloc stats: " << std::endl << stats.DebugString(); + EXPECT_EQ(stats.bytes_in_use, bytes_in_use); + EXPECT_EQ(stats.peak_bytes_in_use, peak_bytes_in_use); + EXPECT_EQ(stats.num_allocs, num_allocs); + EXPECT_EQ(stats.largest_alloc_size, largest_alloc_size); } TEST(GPUBFCAllocatorTest, NoDups) { @@ -294,10 +290,8 @@ TEST(GPUBFCAllocatorTest, AllocationsAndDeallocationsWithGrowth) { a.DeallocateRaw(existing_ptrs[i]); } - absl::optional stats = a.GetStats(); - if (stats) { - LOG(INFO) << "Alloc stats: \n" << stats->DebugString(); - } + AllocatorStats stats = a.GetStats(); + LOG(INFO) << "Alloc stats: \n" << stats.DebugString(); } TEST(GPUBFCAllocatorTest, DISABLED_AllocatorReceivesZeroMemory) { diff --git a/tensorflow/core/common_runtime/gpu/gpu_debug_allocator.cc b/tensorflow/core/common_runtime/gpu/gpu_debug_allocator.cc index 0727196e1c..0805f274af 100644 --- a/tensorflow/core/common_runtime/gpu/gpu_debug_allocator.cc +++ b/tensorflow/core/common_runtime/gpu/gpu_debug_allocator.cc @@ -132,7 +132,7 @@ int64 GPUDebugAllocator::AllocationId(const void* ptr) { MASK_BYTES); } -absl::optional GPUDebugAllocator::GetStats() { +AllocatorStats GPUDebugAllocator::GetStats() { return base_allocator_->GetStats(); } @@ -208,7 +208,7 @@ size_t GPUNanResetAllocator::AllocatedSize(const void* ptr) { return base_allocator_->AllocatedSize(ptr); } -absl::optional GPUNanResetAllocator::GetStats() { +AllocatorStats GPUNanResetAllocator::GetStats() { return base_allocator_->GetStats(); } diff --git a/tensorflow/core/common_runtime/gpu/gpu_debug_allocator.h b/tensorflow/core/common_runtime/gpu/gpu_debug_allocator.h index fa0394c19d..cc6d347f83 100644 --- a/tensorflow/core/common_runtime/gpu/gpu_debug_allocator.h +++ b/tensorflow/core/common_runtime/gpu/gpu_debug_allocator.h @@ -43,7 +43,7 @@ class GPUDebugAllocator : public Allocator { size_t RequestedSize(const void* ptr) override; size_t AllocatedSize(const void* ptr) override; int64 AllocationId(const void* ptr) override; - absl::optional GetStats() override; + AllocatorStats GetStats() override; void ClearStats() override; // For testing. @@ -71,7 +71,7 @@ class GPUNanResetAllocator : public Allocator { void DeallocateRaw(void* ptr) override; size_t RequestedSize(const void* ptr) override; size_t AllocatedSize(const void* ptr) override; - absl::optional GetStats() override; + AllocatorStats GetStats() override; void ClearStats() override; private: diff --git a/tensorflow/core/common_runtime/gpu/gpu_device.cc b/tensorflow/core/common_runtime/gpu/gpu_device.cc index 607193a001..28dbb777e1 100644 --- a/tensorflow/core/common_runtime/gpu/gpu_device.cc +++ b/tensorflow/core/common_runtime/gpu/gpu_device.cc @@ -1130,10 +1130,7 @@ Status BaseGPUDeviceFactory::CreateGPUDevice( tf_gpu_id.value(), " with ", memory_limit, " bytes of memory."); } - absl::optional stats = gpu_allocator->GetStats(); - if (!stats) { - return errors::Internal("No allocator statistics"); - } + AllocatorStats stats = gpu_allocator->GetStats(); // 'memory_limit' is the required memory size, but if the allocator with given // tf_gpu_id was created before, we'll use it instead of creating a new one // (as TF gpu device is a shared resource), in which case the actual memory @@ -1141,7 +1138,7 @@ Status BaseGPUDeviceFactory::CreateGPUDevice( // different (which should be an error). // // TODO(laigd): report error if memory_limit doesn't match stats->bytes_limit. - int64 bytes_limit = stats->bytes_limit ? *stats->bytes_limit : 0; + int64 bytes_limit = stats.bytes_limit; std::unique_ptr gpu_device = CreateGPUDevice( options, device_name, static_cast(bytes_limit), dev_locality, tf_gpu_id, GetShortDeviceDescription(platform_gpu_id, desc), diff --git a/tensorflow/core/common_runtime/process_state.h b/tensorflow/core/common_runtime/process_state.h index bc877db99e..a093bd528d 100644 --- a/tensorflow/core/common_runtime/process_state.h +++ b/tensorflow/core/common_runtime/process_state.h @@ -130,7 +130,7 @@ class RecordingAllocator : public Allocator { bool TracksAllocationSizes() override { return a_->TracksAllocationSizes(); } size_t RequestedSize(const void* p) override { return a_->RequestedSize(p); } size_t AllocatedSize(const void* p) override { return a_->AllocatedSize(p); } - absl::optional GetStats() override { return a_->GetStats(); } + AllocatorStats GetStats() override { return a_->GetStats(); } void ClearStats() override { a_->ClearStats(); } ProcessState::MDMap* mm_; // not owned Allocator* a_; // not owned diff --git a/tensorflow/core/common_runtime/step_stats_collector.cc b/tensorflow/core/common_runtime/step_stats_collector.cc index 318cfec21a..ba3c38a755 100644 --- a/tensorflow/core/common_runtime/step_stats_collector.cc +++ b/tensorflow/core/common_runtime/step_stats_collector.cc @@ -176,10 +176,8 @@ void NodeExecStatsWrapper::AddAllocation( memory->set_peak_bytes(std::get<1>(sizes)); memory->set_live_bytes(std::get<2>(sizes)); - absl::optional stats = allocator->GetStats(); - if (stats) { - memory->set_allocator_bytes_in_use(stats->bytes_in_use); - } + AllocatorStats stats = allocator->GetStats(); + memory->set_allocator_bytes_in_use(stats.bytes_in_use); allocations_.push_back(std::make_pair(memory, tracking_allocator)); } diff --git a/tensorflow/core/framework/allocator.cc b/tensorflow/core/framework/allocator.cc index f21f76fec5..4bd6febf2a 100644 --- a/tensorflow/core/framework/allocator.cc +++ b/tensorflow/core/framework/allocator.cc @@ -33,8 +33,8 @@ string AllocatorStats::DebugString() const { "MaxInUse: %20lld\n" "NumAllocs: %20lld\n" "MaxAllocSize: %20lld\n", - this->bytes_limit ? *this->bytes_limit : 0, this->bytes_in_use, - this->peak_bytes_in_use, this->num_allocs, this->largest_alloc_size); + this->bytes_limit, this->bytes_in_use, this->peak_bytes_in_use, + this->num_allocs, this->largest_alloc_size); } constexpr size_t Allocator::kAllocatorAlignment; @@ -150,7 +150,7 @@ class CPUAllocator : public Allocator { port::AlignedFree(ptr); } - absl::optional GetStats() override { + AllocatorStats GetStats() override { mutex_lock l(mu_); return stats_; } diff --git a/tensorflow/core/framework/allocator.h b/tensorflow/core/framework/allocator.h index 4dc5eaf16d..d232fde5f8 100644 --- a/tensorflow/core/framework/allocator.h +++ b/tensorflow/core/framework/allocator.h @@ -20,8 +20,6 @@ limitations under the License. #include -#include "absl/strings/string_view.h" -#include "absl/types/optional.h" #include "tensorflow/core/framework/numeric_types.h" #include "tensorflow/core/framework/resource_handle.h" #include "tensorflow/core/framework/type_traits.h" @@ -64,14 +62,15 @@ struct AllocatorStats { int64 largest_alloc_size; // The largest single allocation seen. // The upper limit of bytes of user allocatable device memory, if such a limit - // is known. - absl::optional bytes_limit; + // is known. Certain allocators may return 0 to indicate the limit is unknown. + int64 bytes_limit; AllocatorStats() : num_allocs(0), bytes_in_use(0), peak_bytes_in_use(0), - largest_alloc_size(0) {} + largest_alloc_size(0), + bytes_limit(0) {} string DebugString() const; }; @@ -204,7 +203,7 @@ class Allocator { } // Fills in 'stats' with statistics collected by this allocator. - virtual absl::optional GetStats() { return absl::nullopt; } + virtual AllocatorStats GetStats() { return AllocatorStats(); } // Clears the internal stats except for the `in_use` field. virtual void ClearStats() {} diff --git a/tensorflow/core/framework/allocator_test.cc b/tensorflow/core/framework/allocator_test.cc index 85e8ba6a71..6b02a6210e 100644 --- a/tensorflow/core/framework/allocator_test.cc +++ b/tensorflow/core/framework/allocator_test.cc @@ -26,22 +26,18 @@ namespace tensorflow { static void CheckStats(Allocator* a, int64 num_allocs, int64 bytes_in_use, int64 peak_bytes_in_use, int64 largest_alloc_size) { - absl::optional stats = a->GetStats(); - EXPECT_TRUE(stats); - if (!stats) { - return; - } - LOG(INFO) << "Alloc stats: \n" << stats->DebugString(); + AllocatorStats stats = a->GetStats(); + LOG(INFO) << "Alloc stats: \n" << stats.DebugString(); #if defined(PLATFORM_GOOGLE) && defined(NDEBUG) // NOTE: allocator stats expectation depends on the system malloc, // and can vary as that changes. static const int64 kSlop = 5 * 1024; - EXPECT_GT(stats->bytes_in_use, bytes_in_use - kSlop); - EXPECT_LT(stats->bytes_in_use, bytes_in_use + kSlop); - EXPECT_GT(stats->peak_bytes_in_use, peak_bytes_in_use - kSlop); - EXPECT_LT(stats->peak_bytes_in_use, peak_bytes_in_use + kSlop); - EXPECT_EQ(stats->num_allocs, num_allocs); - EXPECT_EQ(stats->largest_alloc_size, largest_alloc_size); + EXPECT_GT(stats.bytes_in_use, bytes_in_use - kSlop); + EXPECT_LT(stats.bytes_in_use, bytes_in_use + kSlop); + EXPECT_GT(stats.peak_bytes_in_use, peak_bytes_in_use - kSlop); + EXPECT_LT(stats.peak_bytes_in_use, peak_bytes_in_use + kSlop); + EXPECT_EQ(stats.num_allocs, num_allocs); + EXPECT_EQ(stats.largest_alloc_size, largest_alloc_size); #endif } diff --git a/tensorflow/core/framework/tracking_allocator.cc b/tensorflow/core/framework/tracking_allocator.cc index ff454f5847..63397dec72 100644 --- a/tensorflow/core/framework/tracking_allocator.cc +++ b/tensorflow/core/framework/tracking_allocator.cc @@ -152,9 +152,7 @@ int64 TrackingAllocator::AllocationId(const void* ptr) { } } -absl::optional TrackingAllocator::GetStats() { - return allocator_->GetStats(); -} +AllocatorStats TrackingAllocator::GetStats() { return allocator_->GetStats(); } void TrackingAllocator::ClearStats() { allocator_->ClearStats(); } diff --git a/tensorflow/core/framework/tracking_allocator.h b/tensorflow/core/framework/tracking_allocator.h index 3b45d1cab8..7a739cf721 100644 --- a/tensorflow/core/framework/tracking_allocator.h +++ b/tensorflow/core/framework/tracking_allocator.h @@ -66,7 +66,7 @@ class TrackingAllocator : public Allocator { size_t RequestedSize(const void* ptr) override; size_t AllocatedSize(const void* ptr) override; int64 AllocationId(const void* ptr) override; - absl::optional GetStats() override; + AllocatorStats GetStats() override; void ClearStats() override; // If the underlying allocator tracks allocation sizes, this returns diff --git a/tensorflow/core/framework/tracking_allocator_test.cc b/tensorflow/core/framework/tracking_allocator_test.cc index 554af60986..7aaf5d3478 100644 --- a/tensorflow/core/framework/tracking_allocator_test.cc +++ b/tensorflow/core/framework/tracking_allocator_test.cc @@ -44,7 +44,6 @@ class TestableSizeTrackingAllocator : public Allocator { EXPECT_NE(size_map_.end(), iter); return iter->second; } - absl::optional GetStats() override { return absl::nullopt; } private: std::unordered_map size_map_; @@ -58,7 +57,6 @@ class NoMemoryAllocator : public Allocator { } void DeallocateRaw(void* ptr) override {} bool TracksAllocationSizes() override { return true; } - absl::optional GetStats() override { return absl::nullopt; } }; TEST(TrackingAllocatorTest, SimpleNoTracking) { diff --git a/tensorflow/core/grappler/clusters/single_machine.cc b/tensorflow/core/grappler/clusters/single_machine.cc index e7c72b8088..8b4c1b7592 100644 --- a/tensorflow/core/grappler/clusters/single_machine.cc +++ b/tensorflow/core/grappler/clusters/single_machine.cc @@ -232,9 +232,8 @@ Status SingleMachine::GetPeakMemoryUsage( return Status(error::INVALID_ARGUMENT, "Tracking allocation is not enabled."); } - absl::optional stats = allocator->GetStats(); - (*device_peak_memory)[device->name()] = - (stats ? stats->peak_bytes_in_use : 0); + AllocatorStats stats = allocator->GetStats(); + (*device_peak_memory)[device->name()] = stats.peak_bytes_in_use; } return Status::OK(); diff --git a/tensorflow/core/kernels/stack.cc b/tensorflow/core/kernels/stack.cc index 033b9f3478..3d560e39ac 100644 --- a/tensorflow/core/kernels/stack.cc +++ b/tensorflow/core/kernels/stack.cc @@ -244,9 +244,9 @@ void StackPushOp::ComputeAsync(OpKernelContext* ctx, DoneCallback done) { DeviceContext* device_ctxt = ctx->op_device_context(); auto device = static_cast(ctx->device()); Allocator* allocator = device->GetAllocator(alloc_attrs); - absl::optional stats = allocator->GetStats(); - if (stats && *stats->bytes_limit && - stats->bytes_in_use > (*stats->bytes_limit * kOccupancy)) { + AllocatorStats stats = allocator->GetStats(); + if (stats.bytes_limit && + stats.bytes_in_use > (stats.bytes_limit * kOccupancy)) { // Asynchronously copy the tensor from GPU to CPU memory. // TODO(yuanbyu): Swap the oldest tensor first. AllocatorAttributes host_alloc_attrs; diff --git a/tensorflow/stream_executor/BUILD b/tensorflow/stream_executor/BUILD index 03c8d6f535..1eac7e81dc 100644 --- a/tensorflow/stream_executor/BUILD +++ b/tensorflow/stream_executor/BUILD @@ -515,7 +515,6 @@ cc_library( "//tensorflow/core:lib", "//tensorflow/stream_executor/lib", "//tensorflow/stream_executor/platform", - "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/types:optional", "@com_google_absl//absl/types:span", ], @@ -568,7 +567,6 @@ cc_library( "//tensorflow/stream_executor/platform", "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/strings", - "@com_google_absl//absl/types:optional", ], ) @@ -656,7 +654,6 @@ cc_library( deps = [ "//tensorflow/stream_executor/platform", "@com_google_absl//absl/strings:str_format", - "@com_google_absl//absl/types:optional", ], ) diff --git a/tensorflow/stream_executor/allocator_stats.cc b/tensorflow/stream_executor/allocator_stats.cc index 440d6f46a3..4ee2c7a847 100644 --- a/tensorflow/stream_executor/allocator_stats.cc +++ b/tensorflow/stream_executor/allocator_stats.cc @@ -25,8 +25,8 @@ string AllocatorStats::DebugString() const { "MaxInUse: %20lld\n" "NumAllocs: %20lld\n" "MaxAllocSize: %20lld\n", - this->bytes_limit ? *this->bytes_limit : 0, this->bytes_in_use, - this->peak_bytes_in_use, this->num_allocs, this->largest_alloc_size); + this->bytes_limit, this->bytes_in_use, this->peak_bytes_in_use, + this->num_allocs, this->largest_alloc_size); } } // namespace stream_executor diff --git a/tensorflow/stream_executor/allocator_stats.h b/tensorflow/stream_executor/allocator_stats.h index 786ceb0fdd..d523491d9a 100644 --- a/tensorflow/stream_executor/allocator_stats.h +++ b/tensorflow/stream_executor/allocator_stats.h @@ -18,7 +18,6 @@ limitations under the License. #include -#include "absl/types/optional.h" #include "tensorflow/stream_executor/platform/port.h" namespace stream_executor { @@ -33,14 +32,15 @@ struct AllocatorStats { int64 largest_alloc_size; // The largest single allocation seen. // The upper limit of bytes of user allocatable device memory, if such a limit - // is known. - absl::optional bytes_limit; + // is known. Certain allocators may return 0 to indicate the limit is unknown. + int64 bytes_limit; AllocatorStats() : num_allocs(0), bytes_in_use(0), peak_bytes_in_use(0), - largest_alloc_size(0) {} + largest_alloc_size(0), + bytes_limit(0) {} string DebugString() const; }; diff --git a/tensorflow/stream_executor/stream_executor_internal.h b/tensorflow/stream_executor/stream_executor_internal.h index 36eabda459..4f55daf7a9 100644 --- a/tensorflow/stream_executor/stream_executor_internal.h +++ b/tensorflow/stream_executor/stream_executor_internal.h @@ -370,9 +370,7 @@ class StreamExecutorInterface { virtual void *GpuContextHack() { return nullptr; } // Return allocator statistics. - virtual absl::optional GetAllocatorStats() { - return absl::nullopt; - } + virtual AllocatorStats GetAllocatorStats() { return AllocatorStats(); } private: SE_DISALLOW_COPY_AND_ASSIGN(StreamExecutorInterface); diff --git a/tensorflow/stream_executor/stream_executor_pimpl.cc b/tensorflow/stream_executor/stream_executor_pimpl.cc index aae1efc7c4..dc37181665 100644 --- a/tensorflow/stream_executor/stream_executor_pimpl.cc +++ b/tensorflow/stream_executor/stream_executor_pimpl.cc @@ -871,7 +871,7 @@ bool StreamExecutor::UnregisterTraceListener(TraceListener *listener) { return true; } -absl::optional StreamExecutor::GetAllocatorStats() { +AllocatorStats StreamExecutor::GetAllocatorStats() { return implementation_->GetAllocatorStats(); } diff --git a/tensorflow/stream_executor/stream_executor_pimpl.h b/tensorflow/stream_executor/stream_executor_pimpl.h index 09fe0a5c5d..41f154005f 100644 --- a/tensorflow/stream_executor/stream_executor_pimpl.h +++ b/tensorflow/stream_executor/stream_executor_pimpl.h @@ -23,7 +23,6 @@ limitations under the License. #include #include "absl/base/macros.h" -#include "absl/types/optional.h" #include "tensorflow/stream_executor/lib/status.h" #include "tensorflow/stream_executor/lib/statusor.h" #include "tensorflow/stream_executor/lib/threadpool.h" @@ -487,7 +486,7 @@ class StreamExecutor { bool UnregisterTraceListener(TraceListener* listener); // Return allocator statistics. - absl::optional GetAllocatorStats(); + AllocatorStats GetAllocatorStats(); private: template Date: Sun, 24 Feb 2019 14:52:31 -0800 Subject: [PATCH 428/766] Automated rollback of commit 9ca8321d68b74ce4f30756366919c80282ca2b68 PiperOrigin-RevId: 235438039 --- tensorflow/compiler/jit/xla_device_context.cc | 17 ++++++++------ tensorflow/compiler/jit/xla_device_context.h | 2 +- .../memory_stats/kernels/memory_stats_ops.cc | 9 +++++--- .../core/common_runtime/bfc_allocator.cc | 2 +- .../core/common_runtime/bfc_allocator.h | 2 +- .../common_runtime/eager/kernel_and_device.cc | 7 ++++-- .../gpu/gpu_bfc_allocator_test.cc | 22 ++++++++++++------- .../common_runtime/gpu/gpu_debug_allocator.cc | 4 ++-- .../common_runtime/gpu/gpu_debug_allocator.h | 4 ++-- .../core/common_runtime/gpu/gpu_device.cc | 7 ++++-- .../core/common_runtime/process_state.h | 2 +- .../common_runtime/step_stats_collector.cc | 6 +++-- tensorflow/core/framework/allocator.cc | 6 ++--- tensorflow/core/framework/allocator.h | 11 +++++----- tensorflow/core/framework/allocator_test.cc | 20 ++++++++++------- .../core/framework/tracking_allocator.cc | 4 +++- .../core/framework/tracking_allocator.h | 2 +- .../core/framework/tracking_allocator_test.cc | 2 ++ .../core/grappler/clusters/single_machine.cc | 5 +++-- tensorflow/core/kernels/stack.cc | 6 ++--- tensorflow/stream_executor/BUILD | 3 +++ tensorflow/stream_executor/allocator_stats.cc | 4 ++-- tensorflow/stream_executor/allocator_stats.h | 8 +++---- .../stream_executor_internal.h | 4 +++- .../stream_executor/stream_executor_pimpl.cc | 2 +- .../stream_executor/stream_executor_pimpl.h | 3 ++- 26 files changed, 100 insertions(+), 64 deletions(-) diff --git a/tensorflow/compiler/jit/xla_device_context.cc b/tensorflow/compiler/jit/xla_device_context.cc index b5830edb87..05b9c51186 100644 --- a/tensorflow/compiler/jit/xla_device_context.cc +++ b/tensorflow/compiler/jit/xla_device_context.cc @@ -51,16 +51,19 @@ void XlaDeviceAllocator::DeallocateRaw(void* ptr) { delete XlaTensor::FromOpaquePointer(ptr); } -AllocatorStats XlaDeviceAllocator::GetStats() { - stream_executor::AllocatorStats se_stats = +absl::optional XlaDeviceAllocator::GetStats() { + absl::optional se_stats = stream_executor_->GetAllocatorStats(); + if (!se_stats) { + return absl::nullopt; + } tensorflow::AllocatorStats tf_stats; - tf_stats.num_allocs = se_stats.num_allocs; - tf_stats.bytes_in_use = se_stats.bytes_in_use; - tf_stats.peak_bytes_in_use = se_stats.peak_bytes_in_use; - tf_stats.largest_alloc_size = se_stats.largest_alloc_size; - tf_stats.bytes_limit = se_stats.bytes_limit; + tf_stats.num_allocs = se_stats->num_allocs; + tf_stats.bytes_in_use = se_stats->bytes_in_use; + tf_stats.peak_bytes_in_use = se_stats->peak_bytes_in_use; + tf_stats.largest_alloc_size = se_stats->largest_alloc_size; + tf_stats.bytes_limit = se_stats->bytes_limit; return tf_stats; } diff --git a/tensorflow/compiler/jit/xla_device_context.h b/tensorflow/compiler/jit/xla_device_context.h index 709a196ddc..1ce64ad323 100644 --- a/tensorflow/compiler/jit/xla_device_context.h +++ b/tensorflow/compiler/jit/xla_device_context.h @@ -41,7 +41,7 @@ class XlaDeviceAllocator : public Allocator { void* AllocateRaw(size_t alignment, size_t num_bytes) override; void DeallocateRaw(void* ptr) override; - AllocatorStats GetStats() override; + absl::optional GetStats() override; private: // The stream executor of the device. diff --git a/tensorflow/contrib/memory_stats/kernels/memory_stats_ops.cc b/tensorflow/contrib/memory_stats/kernels/memory_stats_ops.cc index 796d139555..7ae1dbeaa2 100644 --- a/tensorflow/contrib/memory_stats/kernels/memory_stats_ops.cc +++ b/tensorflow/contrib/memory_stats/kernels/memory_stats_ops.cc @@ -24,12 +24,15 @@ class MemoryStatsOp : public OpKernel { void Compute(OpKernelContext* context) override { Allocator* allocator = context->device()->GetAllocator(AllocatorAttributes()); - AllocatorStats allocator_stats = allocator->GetStats(); + absl::optional allocator_stats = allocator->GetStats(); + if (!allocator_stats) { + *allocator_stats = AllocatorStats(); + } Tensor* output_tensor = nullptr; OP_REQUIRES_OK( context, context->allocate_output(0, TensorShape({}), &output_tensor)); - output_tensor->scalar()() = ExtractAllocatorStats(allocator_stats); + output_tensor->scalar()() = ExtractAllocatorStats(*allocator_stats); } protected: @@ -70,7 +73,7 @@ class BytesLimitOp : public MemoryStatsOp { private: int64 ExtractAllocatorStats( const AllocatorStats& allocator_stats) const override { - return allocator_stats.bytes_limit; + return allocator_stats.bytes_limit ? *allocator_stats.bytes_limit : -1; } }; diff --git a/tensorflow/core/common_runtime/bfc_allocator.cc b/tensorflow/core/common_runtime/bfc_allocator.cc index 37cc6f9432..0e4ddb1020 100644 --- a/tensorflow/core/common_runtime/bfc_allocator.cc +++ b/tensorflow/core/common_runtime/bfc_allocator.cc @@ -686,7 +686,7 @@ void BFCAllocator::DumpMemoryLog(size_t num_bytes) { LOG(INFO) << "Stats: \n" << stats_.DebugString(); } -AllocatorStats BFCAllocator::GetStats() { +absl::optional BFCAllocator::GetStats() { mutex_lock l(lock_); return stats_; } diff --git a/tensorflow/core/common_runtime/bfc_allocator.h b/tensorflow/core/common_runtime/bfc_allocator.h index 5fcfb57f97..b0fd0d8667 100644 --- a/tensorflow/core/common_runtime/bfc_allocator.h +++ b/tensorflow/core/common_runtime/bfc_allocator.h @@ -69,7 +69,7 @@ class BFCAllocator : public Allocator { int64 AllocationId(const void* ptr) override; - AllocatorStats GetStats() override; + absl::optional GetStats() override; void ClearStats() override; diff --git a/tensorflow/core/common_runtime/eager/kernel_and_device.cc b/tensorflow/core/common_runtime/eager/kernel_and_device.cc index 59c8b7e7d1..8a06aa87e8 100644 --- a/tensorflow/core/common_runtime/eager/kernel_and_device.cc +++ b/tensorflow/core/common_runtime/eager/kernel_and_device.cc @@ -156,8 +156,11 @@ void UpdateStats(OpKernelContext* context, memory->set_peak_bytes(std::get<1>(sizes)); memory->set_live_bytes(std::get<2>(sizes)); - AllocatorStats allocator_stats = allocator_pair.first->GetStats(); - memory->set_allocator_bytes_in_use(allocator_stats.bytes_in_use); + absl::optional allocator_stats = + allocator_pair.first->GetStats(); + if (stats) { + memory->set_allocator_bytes_in_use(allocator_stats->bytes_in_use); + } allocator_pair.second->GetRecordsAndUnRef(); } auto* ms = stats->mutable_memory_stats(); diff --git a/tensorflow/core/common_runtime/gpu/gpu_bfc_allocator_test.cc b/tensorflow/core/common_runtime/gpu/gpu_bfc_allocator_test.cc index 8c2cac30b9..9c0abd97a1 100644 --- a/tensorflow/core/common_runtime/gpu/gpu_bfc_allocator_test.cc +++ b/tensorflow/core/common_runtime/gpu/gpu_bfc_allocator_test.cc @@ -37,12 +37,16 @@ namespace { static void CheckStats(Allocator* a, int64 num_allocs, int64 bytes_in_use, int64 peak_bytes_in_use, int64 largest_alloc_size) { - AllocatorStats stats = a->GetStats(); - LOG(INFO) << "Alloc stats: " << std::endl << stats.DebugString(); - EXPECT_EQ(stats.bytes_in_use, bytes_in_use); - EXPECT_EQ(stats.peak_bytes_in_use, peak_bytes_in_use); - EXPECT_EQ(stats.num_allocs, num_allocs); - EXPECT_EQ(stats.largest_alloc_size, largest_alloc_size); + absl::optional stats = a->GetStats(); + EXPECT_TRUE(stats); + if (!stats) { + return; + } + LOG(INFO) << "Alloc stats: " << std::endl << stats->DebugString(); + EXPECT_EQ(stats->bytes_in_use, bytes_in_use); + EXPECT_EQ(stats->peak_bytes_in_use, peak_bytes_in_use); + EXPECT_EQ(stats->num_allocs, num_allocs); + EXPECT_EQ(stats->largest_alloc_size, largest_alloc_size); } TEST(GPUBFCAllocatorTest, NoDups) { @@ -290,8 +294,10 @@ TEST(GPUBFCAllocatorTest, AllocationsAndDeallocationsWithGrowth) { a.DeallocateRaw(existing_ptrs[i]); } - AllocatorStats stats = a.GetStats(); - LOG(INFO) << "Alloc stats: \n" << stats.DebugString(); + absl::optional stats = a.GetStats(); + if (stats) { + LOG(INFO) << "Alloc stats: \n" << stats->DebugString(); + } } TEST(GPUBFCAllocatorTest, DISABLED_AllocatorReceivesZeroMemory) { diff --git a/tensorflow/core/common_runtime/gpu/gpu_debug_allocator.cc b/tensorflow/core/common_runtime/gpu/gpu_debug_allocator.cc index 0805f274af..0727196e1c 100644 --- a/tensorflow/core/common_runtime/gpu/gpu_debug_allocator.cc +++ b/tensorflow/core/common_runtime/gpu/gpu_debug_allocator.cc @@ -132,7 +132,7 @@ int64 GPUDebugAllocator::AllocationId(const void* ptr) { MASK_BYTES); } -AllocatorStats GPUDebugAllocator::GetStats() { +absl::optional GPUDebugAllocator::GetStats() { return base_allocator_->GetStats(); } @@ -208,7 +208,7 @@ size_t GPUNanResetAllocator::AllocatedSize(const void* ptr) { return base_allocator_->AllocatedSize(ptr); } -AllocatorStats GPUNanResetAllocator::GetStats() { +absl::optional GPUNanResetAllocator::GetStats() { return base_allocator_->GetStats(); } diff --git a/tensorflow/core/common_runtime/gpu/gpu_debug_allocator.h b/tensorflow/core/common_runtime/gpu/gpu_debug_allocator.h index cc6d347f83..fa0394c19d 100644 --- a/tensorflow/core/common_runtime/gpu/gpu_debug_allocator.h +++ b/tensorflow/core/common_runtime/gpu/gpu_debug_allocator.h @@ -43,7 +43,7 @@ class GPUDebugAllocator : public Allocator { size_t RequestedSize(const void* ptr) override; size_t AllocatedSize(const void* ptr) override; int64 AllocationId(const void* ptr) override; - AllocatorStats GetStats() override; + absl::optional GetStats() override; void ClearStats() override; // For testing. @@ -71,7 +71,7 @@ class GPUNanResetAllocator : public Allocator { void DeallocateRaw(void* ptr) override; size_t RequestedSize(const void* ptr) override; size_t AllocatedSize(const void* ptr) override; - AllocatorStats GetStats() override; + absl::optional GetStats() override; void ClearStats() override; private: diff --git a/tensorflow/core/common_runtime/gpu/gpu_device.cc b/tensorflow/core/common_runtime/gpu/gpu_device.cc index 28dbb777e1..607193a001 100644 --- a/tensorflow/core/common_runtime/gpu/gpu_device.cc +++ b/tensorflow/core/common_runtime/gpu/gpu_device.cc @@ -1130,7 +1130,10 @@ Status BaseGPUDeviceFactory::CreateGPUDevice( tf_gpu_id.value(), " with ", memory_limit, " bytes of memory."); } - AllocatorStats stats = gpu_allocator->GetStats(); + absl::optional stats = gpu_allocator->GetStats(); + if (!stats) { + return errors::Internal("No allocator statistics"); + } // 'memory_limit' is the required memory size, but if the allocator with given // tf_gpu_id was created before, we'll use it instead of creating a new one // (as TF gpu device is a shared resource), in which case the actual memory @@ -1138,7 +1141,7 @@ Status BaseGPUDeviceFactory::CreateGPUDevice( // different (which should be an error). // // TODO(laigd): report error if memory_limit doesn't match stats->bytes_limit. - int64 bytes_limit = stats.bytes_limit; + int64 bytes_limit = stats->bytes_limit ? *stats->bytes_limit : 0; std::unique_ptr gpu_device = CreateGPUDevice( options, device_name, static_cast(bytes_limit), dev_locality, tf_gpu_id, GetShortDeviceDescription(platform_gpu_id, desc), diff --git a/tensorflow/core/common_runtime/process_state.h b/tensorflow/core/common_runtime/process_state.h index a093bd528d..bc877db99e 100644 --- a/tensorflow/core/common_runtime/process_state.h +++ b/tensorflow/core/common_runtime/process_state.h @@ -130,7 +130,7 @@ class RecordingAllocator : public Allocator { bool TracksAllocationSizes() override { return a_->TracksAllocationSizes(); } size_t RequestedSize(const void* p) override { return a_->RequestedSize(p); } size_t AllocatedSize(const void* p) override { return a_->AllocatedSize(p); } - AllocatorStats GetStats() override { return a_->GetStats(); } + absl::optional GetStats() override { return a_->GetStats(); } void ClearStats() override { a_->ClearStats(); } ProcessState::MDMap* mm_; // not owned Allocator* a_; // not owned diff --git a/tensorflow/core/common_runtime/step_stats_collector.cc b/tensorflow/core/common_runtime/step_stats_collector.cc index ba3c38a755..318cfec21a 100644 --- a/tensorflow/core/common_runtime/step_stats_collector.cc +++ b/tensorflow/core/common_runtime/step_stats_collector.cc @@ -176,8 +176,10 @@ void NodeExecStatsWrapper::AddAllocation( memory->set_peak_bytes(std::get<1>(sizes)); memory->set_live_bytes(std::get<2>(sizes)); - AllocatorStats stats = allocator->GetStats(); - memory->set_allocator_bytes_in_use(stats.bytes_in_use); + absl::optional stats = allocator->GetStats(); + if (stats) { + memory->set_allocator_bytes_in_use(stats->bytes_in_use); + } allocations_.push_back(std::make_pair(memory, tracking_allocator)); } diff --git a/tensorflow/core/framework/allocator.cc b/tensorflow/core/framework/allocator.cc index 4bd6febf2a..f21f76fec5 100644 --- a/tensorflow/core/framework/allocator.cc +++ b/tensorflow/core/framework/allocator.cc @@ -33,8 +33,8 @@ string AllocatorStats::DebugString() const { "MaxInUse: %20lld\n" "NumAllocs: %20lld\n" "MaxAllocSize: %20lld\n", - this->bytes_limit, this->bytes_in_use, this->peak_bytes_in_use, - this->num_allocs, this->largest_alloc_size); + this->bytes_limit ? *this->bytes_limit : 0, this->bytes_in_use, + this->peak_bytes_in_use, this->num_allocs, this->largest_alloc_size); } constexpr size_t Allocator::kAllocatorAlignment; @@ -150,7 +150,7 @@ class CPUAllocator : public Allocator { port::AlignedFree(ptr); } - AllocatorStats GetStats() override { + absl::optional GetStats() override { mutex_lock l(mu_); return stats_; } diff --git a/tensorflow/core/framework/allocator.h b/tensorflow/core/framework/allocator.h index d232fde5f8..4dc5eaf16d 100644 --- a/tensorflow/core/framework/allocator.h +++ b/tensorflow/core/framework/allocator.h @@ -20,6 +20,8 @@ limitations under the License. #include +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" #include "tensorflow/core/framework/numeric_types.h" #include "tensorflow/core/framework/resource_handle.h" #include "tensorflow/core/framework/type_traits.h" @@ -62,15 +64,14 @@ struct AllocatorStats { int64 largest_alloc_size; // The largest single allocation seen. // The upper limit of bytes of user allocatable device memory, if such a limit - // is known. Certain allocators may return 0 to indicate the limit is unknown. - int64 bytes_limit; + // is known. + absl::optional bytes_limit; AllocatorStats() : num_allocs(0), bytes_in_use(0), peak_bytes_in_use(0), - largest_alloc_size(0), - bytes_limit(0) {} + largest_alloc_size(0) {} string DebugString() const; }; @@ -203,7 +204,7 @@ class Allocator { } // Fills in 'stats' with statistics collected by this allocator. - virtual AllocatorStats GetStats() { return AllocatorStats(); } + virtual absl::optional GetStats() { return absl::nullopt; } // Clears the internal stats except for the `in_use` field. virtual void ClearStats() {} diff --git a/tensorflow/core/framework/allocator_test.cc b/tensorflow/core/framework/allocator_test.cc index 6b02a6210e..85e8ba6a71 100644 --- a/tensorflow/core/framework/allocator_test.cc +++ b/tensorflow/core/framework/allocator_test.cc @@ -26,18 +26,22 @@ namespace tensorflow { static void CheckStats(Allocator* a, int64 num_allocs, int64 bytes_in_use, int64 peak_bytes_in_use, int64 largest_alloc_size) { - AllocatorStats stats = a->GetStats(); - LOG(INFO) << "Alloc stats: \n" << stats.DebugString(); + absl::optional stats = a->GetStats(); + EXPECT_TRUE(stats); + if (!stats) { + return; + } + LOG(INFO) << "Alloc stats: \n" << stats->DebugString(); #if defined(PLATFORM_GOOGLE) && defined(NDEBUG) // NOTE: allocator stats expectation depends on the system malloc, // and can vary as that changes. static const int64 kSlop = 5 * 1024; - EXPECT_GT(stats.bytes_in_use, bytes_in_use - kSlop); - EXPECT_LT(stats.bytes_in_use, bytes_in_use + kSlop); - EXPECT_GT(stats.peak_bytes_in_use, peak_bytes_in_use - kSlop); - EXPECT_LT(stats.peak_bytes_in_use, peak_bytes_in_use + kSlop); - EXPECT_EQ(stats.num_allocs, num_allocs); - EXPECT_EQ(stats.largest_alloc_size, largest_alloc_size); + EXPECT_GT(stats->bytes_in_use, bytes_in_use - kSlop); + EXPECT_LT(stats->bytes_in_use, bytes_in_use + kSlop); + EXPECT_GT(stats->peak_bytes_in_use, peak_bytes_in_use - kSlop); + EXPECT_LT(stats->peak_bytes_in_use, peak_bytes_in_use + kSlop); + EXPECT_EQ(stats->num_allocs, num_allocs); + EXPECT_EQ(stats->largest_alloc_size, largest_alloc_size); #endif } diff --git a/tensorflow/core/framework/tracking_allocator.cc b/tensorflow/core/framework/tracking_allocator.cc index 63397dec72..ff454f5847 100644 --- a/tensorflow/core/framework/tracking_allocator.cc +++ b/tensorflow/core/framework/tracking_allocator.cc @@ -152,7 +152,9 @@ int64 TrackingAllocator::AllocationId(const void* ptr) { } } -AllocatorStats TrackingAllocator::GetStats() { return allocator_->GetStats(); } +absl::optional TrackingAllocator::GetStats() { + return allocator_->GetStats(); +} void TrackingAllocator::ClearStats() { allocator_->ClearStats(); } diff --git a/tensorflow/core/framework/tracking_allocator.h b/tensorflow/core/framework/tracking_allocator.h index 7a739cf721..3b45d1cab8 100644 --- a/tensorflow/core/framework/tracking_allocator.h +++ b/tensorflow/core/framework/tracking_allocator.h @@ -66,7 +66,7 @@ class TrackingAllocator : public Allocator { size_t RequestedSize(const void* ptr) override; size_t AllocatedSize(const void* ptr) override; int64 AllocationId(const void* ptr) override; - AllocatorStats GetStats() override; + absl::optional GetStats() override; void ClearStats() override; // If the underlying allocator tracks allocation sizes, this returns diff --git a/tensorflow/core/framework/tracking_allocator_test.cc b/tensorflow/core/framework/tracking_allocator_test.cc index 7aaf5d3478..554af60986 100644 --- a/tensorflow/core/framework/tracking_allocator_test.cc +++ b/tensorflow/core/framework/tracking_allocator_test.cc @@ -44,6 +44,7 @@ class TestableSizeTrackingAllocator : public Allocator { EXPECT_NE(size_map_.end(), iter); return iter->second; } + absl::optional GetStats() override { return absl::nullopt; } private: std::unordered_map size_map_; @@ -57,6 +58,7 @@ class NoMemoryAllocator : public Allocator { } void DeallocateRaw(void* ptr) override {} bool TracksAllocationSizes() override { return true; } + absl::optional GetStats() override { return absl::nullopt; } }; TEST(TrackingAllocatorTest, SimpleNoTracking) { diff --git a/tensorflow/core/grappler/clusters/single_machine.cc b/tensorflow/core/grappler/clusters/single_machine.cc index 8b4c1b7592..e7c72b8088 100644 --- a/tensorflow/core/grappler/clusters/single_machine.cc +++ b/tensorflow/core/grappler/clusters/single_machine.cc @@ -232,8 +232,9 @@ Status SingleMachine::GetPeakMemoryUsage( return Status(error::INVALID_ARGUMENT, "Tracking allocation is not enabled."); } - AllocatorStats stats = allocator->GetStats(); - (*device_peak_memory)[device->name()] = stats.peak_bytes_in_use; + absl::optional stats = allocator->GetStats(); + (*device_peak_memory)[device->name()] = + (stats ? stats->peak_bytes_in_use : 0); } return Status::OK(); diff --git a/tensorflow/core/kernels/stack.cc b/tensorflow/core/kernels/stack.cc index 3d560e39ac..033b9f3478 100644 --- a/tensorflow/core/kernels/stack.cc +++ b/tensorflow/core/kernels/stack.cc @@ -244,9 +244,9 @@ void StackPushOp::ComputeAsync(OpKernelContext* ctx, DoneCallback done) { DeviceContext* device_ctxt = ctx->op_device_context(); auto device = static_cast(ctx->device()); Allocator* allocator = device->GetAllocator(alloc_attrs); - AllocatorStats stats = allocator->GetStats(); - if (stats.bytes_limit && - stats.bytes_in_use > (stats.bytes_limit * kOccupancy)) { + absl::optional stats = allocator->GetStats(); + if (stats && *stats->bytes_limit && + stats->bytes_in_use > (*stats->bytes_limit * kOccupancy)) { // Asynchronously copy the tensor from GPU to CPU memory. // TODO(yuanbyu): Swap the oldest tensor first. AllocatorAttributes host_alloc_attrs; diff --git a/tensorflow/stream_executor/BUILD b/tensorflow/stream_executor/BUILD index 1eac7e81dc..03c8d6f535 100644 --- a/tensorflow/stream_executor/BUILD +++ b/tensorflow/stream_executor/BUILD @@ -515,6 +515,7 @@ cc_library( "//tensorflow/core:lib", "//tensorflow/stream_executor/lib", "//tensorflow/stream_executor/platform", + "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/types:optional", "@com_google_absl//absl/types:span", ], @@ -567,6 +568,7 @@ cc_library( "//tensorflow/stream_executor/platform", "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:optional", ], ) @@ -654,6 +656,7 @@ cc_library( deps = [ "//tensorflow/stream_executor/platform", "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/types:optional", ], ) diff --git a/tensorflow/stream_executor/allocator_stats.cc b/tensorflow/stream_executor/allocator_stats.cc index 4ee2c7a847..440d6f46a3 100644 --- a/tensorflow/stream_executor/allocator_stats.cc +++ b/tensorflow/stream_executor/allocator_stats.cc @@ -25,8 +25,8 @@ string AllocatorStats::DebugString() const { "MaxInUse: %20lld\n" "NumAllocs: %20lld\n" "MaxAllocSize: %20lld\n", - this->bytes_limit, this->bytes_in_use, this->peak_bytes_in_use, - this->num_allocs, this->largest_alloc_size); + this->bytes_limit ? *this->bytes_limit : 0, this->bytes_in_use, + this->peak_bytes_in_use, this->num_allocs, this->largest_alloc_size); } } // namespace stream_executor diff --git a/tensorflow/stream_executor/allocator_stats.h b/tensorflow/stream_executor/allocator_stats.h index d523491d9a..786ceb0fdd 100644 --- a/tensorflow/stream_executor/allocator_stats.h +++ b/tensorflow/stream_executor/allocator_stats.h @@ -18,6 +18,7 @@ limitations under the License. #include +#include "absl/types/optional.h" #include "tensorflow/stream_executor/platform/port.h" namespace stream_executor { @@ -32,15 +33,14 @@ struct AllocatorStats { int64 largest_alloc_size; // The largest single allocation seen. // The upper limit of bytes of user allocatable device memory, if such a limit - // is known. Certain allocators may return 0 to indicate the limit is unknown. - int64 bytes_limit; + // is known. + absl::optional bytes_limit; AllocatorStats() : num_allocs(0), bytes_in_use(0), peak_bytes_in_use(0), - largest_alloc_size(0), - bytes_limit(0) {} + largest_alloc_size(0) {} string DebugString() const; }; diff --git a/tensorflow/stream_executor/stream_executor_internal.h b/tensorflow/stream_executor/stream_executor_internal.h index 4f55daf7a9..36eabda459 100644 --- a/tensorflow/stream_executor/stream_executor_internal.h +++ b/tensorflow/stream_executor/stream_executor_internal.h @@ -370,7 +370,9 @@ class StreamExecutorInterface { virtual void *GpuContextHack() { return nullptr; } // Return allocator statistics. - virtual AllocatorStats GetAllocatorStats() { return AllocatorStats(); } + virtual absl::optional GetAllocatorStats() { + return absl::nullopt; + } private: SE_DISALLOW_COPY_AND_ASSIGN(StreamExecutorInterface); diff --git a/tensorflow/stream_executor/stream_executor_pimpl.cc b/tensorflow/stream_executor/stream_executor_pimpl.cc index dc37181665..aae1efc7c4 100644 --- a/tensorflow/stream_executor/stream_executor_pimpl.cc +++ b/tensorflow/stream_executor/stream_executor_pimpl.cc @@ -871,7 +871,7 @@ bool StreamExecutor::UnregisterTraceListener(TraceListener *listener) { return true; } -AllocatorStats StreamExecutor::GetAllocatorStats() { +absl::optional StreamExecutor::GetAllocatorStats() { return implementation_->GetAllocatorStats(); } diff --git a/tensorflow/stream_executor/stream_executor_pimpl.h b/tensorflow/stream_executor/stream_executor_pimpl.h index 41f154005f..09fe0a5c5d 100644 --- a/tensorflow/stream_executor/stream_executor_pimpl.h +++ b/tensorflow/stream_executor/stream_executor_pimpl.h @@ -23,6 +23,7 @@ limitations under the License. #include #include "absl/base/macros.h" +#include "absl/types/optional.h" #include "tensorflow/stream_executor/lib/status.h" #include "tensorflow/stream_executor/lib/statusor.h" #include "tensorflow/stream_executor/lib/threadpool.h" @@ -486,7 +487,7 @@ class StreamExecutor { bool UnregisterTraceListener(TraceListener* listener); // Return allocator statistics. - AllocatorStats GetAllocatorStats(); + absl::optional GetAllocatorStats(); private: template Date: Sun, 24 Feb 2019 15:36:27 -0800 Subject: [PATCH 429/766] [XLA:CPU] Do not output fuse bias-add to complex GEMV PiperOrigin-RevId: 235440605 --- .../xla/service/cpu/cpu_instruction_fusion.cc | 8 +++-- .../compiler/xla/tests/dot_operation_test.cc | 17 ++++++++++ tensorflow/compiler/xla/tests/test_utils.cc | 31 +++++++++++++++++++ 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/tensorflow/compiler/xla/service/cpu/cpu_instruction_fusion.cc b/tensorflow/compiler/xla/service/cpu/cpu_instruction_fusion.cc index 6f79ad7c14..2fbd60eb72 100644 --- a/tensorflow/compiler/xla/service/cpu/cpu_instruction_fusion.cc +++ b/tensorflow/compiler/xla/service/cpu/cpu_instruction_fusion.cc @@ -42,9 +42,10 @@ bool CanBeLoopFused(const HloInstruction& hlo) { hlo.opcode() == HloOpcode::kTranspose; } -bool IsMatrixVectorDot(const HloInstruction* hlo) { +bool IsNonComplexMatrixVectorDot(const HloInstruction* hlo) { const Shape& hlo_shape = hlo->shape(); - return hlo->opcode() == HloOpcode::kDot && hlo_shape.dimensions_size() == 2 && + return !ShapeUtil::ElementIsComplex(hlo_shape) && + hlo->opcode() == HloOpcode::kDot && hlo_shape.dimensions_size() == 2 && (hlo_shape.dimensions(0) == 1 || hlo_shape.dimensions(1) == 1); } @@ -55,7 +56,8 @@ bool HasExactlyOneUse(const HloInstruction& hlo_instr) { bool CanBeOutputFused(const HloInstruction* producer, const HloInstruction* consumer) { - return consumer->opcode() == HloOpcode::kAdd && IsMatrixVectorDot(producer) && + return consumer->opcode() == HloOpcode::kAdd && + IsNonComplexMatrixVectorDot(producer) && HasExactlyOneUse(*producer) == 1; } diff --git a/tensorflow/compiler/xla/tests/dot_operation_test.cc b/tensorflow/compiler/xla/tests/dot_operation_test.cc index 6ee2178a22..414d0b14a6 100644 --- a/tensorflow/compiler/xla/tests/dot_operation_test.cc +++ b/tensorflow/compiler/xla/tests/dot_operation_test.cc @@ -1344,5 +1344,22 @@ ENTRY TransposeOutput { EXPECT_TRUE(RunAndCompare(hlo_string, ErrorSpec{4e-3, 4e-3})); } +XLA_TEST_F(DotOperationTextTest, MatrixVectorComplex) { + absl::string_view hlo_string = + R"( +HloModule MatrixVectorComplex + +ENTRY MatrixVectorComplex { + p0 = c64[5,5] parameter(0) + p1 = c64[5,1] parameter(1) + p2 = c64[5,1] parameter(2) + dot = c64[5,1] dot(p0, p1), lhs_contracting_dims={1}, rhs_contracting_dims={0} + ROOT add = c64[5,1] add(dot, p2) +} +)"; + + EXPECT_TRUE(RunAndCompare(hlo_string, ErrorSpec{4e-3, 4e-3})); +} + } // namespace } // namespace xla diff --git a/tensorflow/compiler/xla/tests/test_utils.cc b/tensorflow/compiler/xla/tests/test_utils.cc index 67d2258928..4ac3dbd80c 100644 --- a/tensorflow/compiler/xla/tests/test_utils.cc +++ b/tensorflow/compiler/xla/tests/test_utils.cc @@ -112,6 +112,31 @@ void PopulateWithFloatingPointData(Literal* literal, std::minstd_rand0* engine, } } +template +void PopulateWithComplexData(Literal* result, std::minstd_rand0* engine, + bool no_duplicates) { + using InnerFloatT = typename ComplexT::value_type; + CHECK(engine != nullptr); + CHECK_EQ(result->shape().element_type(), + primitive_util::NativeToPrimitiveType()); + Shape floating_point_shape = ShapeUtil::ChangeElementType( + result->shape(), primitive_util::NativeToPrimitiveType()); + Literal real_lit(floating_point_shape); + Literal imaginary_lit(floating_point_shape); + + PopulateWithFloatingPointData(&real_lit, engine, no_duplicates); + PopulateWithFloatingPointData(&imaginary_lit, engine, + no_duplicates); + + absl::Span real_data = real_lit.data(); + absl::Span imaginary_data = + imaginary_lit.data(); + absl::Span result_data = result->data(); + for (int i = 0; i < real_lit.data().size(); i++) { + result_data[i] = ComplexT(real_data[i], imaginary_data[i]); + } +} + template <> void PopulateWithFloatingPointData(Literal* literal, std::minstd_rand0* engine, @@ -220,6 +245,12 @@ StatusOr MakeFakeLiteralInternal(const Shape& shape, case U64: PopulateWithRandomIntegralData(&literal, engine, no_duplicates); break; + case C64: + PopulateWithComplexData(&literal, engine, no_duplicates); + break; + case C128: + PopulateWithComplexData(&literal, engine, no_duplicates); + break; case PRED: { std::uniform_int_distribution generator(0, 1); TF_CHECK_OK( -- GitLab From 9a85b487faf705db54073245adae7ce486e43e36 Mon Sep 17 00:00:00 2001 From: "karl@kubx.ca" Date: Thu, 25 Oct 2018 22:24:51 -0400 Subject: [PATCH 430/766] Add option for inferring op attributes from inputs --- tensorflow/c/eager/c_api.cc | 107 ++++++++++++++++++++-- tensorflow/c/eager/c_api.h | 22 ++++- tensorflow/c/eager/c_api_debug_test.cc | 2 +- tensorflow/c/eager/c_api_internal.h | 24 ++++- tensorflow/c/eager/c_api_test.cc | 117 +++++++++++++++++++++++++ tensorflow/c/eager/c_api_test_util.cc | 28 +++++- tensorflow/c/eager/c_api_test_util.h | 8 +- 7 files changed, 293 insertions(+), 15 deletions(-) diff --git a/tensorflow/c/eager/c_api.cc b/tensorflow/c/eager/c_api.cc index 45701c7fcf..94181f3e0e 100755 --- a/tensorflow/c/eager/c_api.cc +++ b/tensorflow/c/eager/c_api.cc @@ -20,6 +20,8 @@ limitations under the License. #include #include #include +#include +#include #include "absl/memory/memory.h" #include "tensorflow/c/c_api.h" @@ -234,6 +236,67 @@ tensorflow::Status UpdateTFE_ContextWithServerDef( return tensorflow::Status::OK(); #undef LOG_AND_RETURN_IF_ERROR } + +tensorflow::Status OpInferSingleInputAttrs(TFE_Op* op, + TFE_TensorHandle* input) { + TFE_OpInferenceContext* ictx = op->inference_ctx; + const std::string& type_attr = + ictx->op_def->input_arg(ictx->input_idx++).type_attr(); + if (!type_attr.empty() && ictx->attrs.find(type_attr) == ictx->attrs.cend()) { + op->operation.MutableAttrs()->Set(type_attr, input->handle->dtype); + ictx->attrs.insert(type_attr); + } + return tensorflow::Status::OK(); +} + +void OpInferSingleTypeInputListAttrs(TFE_Op* op, + const tensorflow::OpDef_ArgDef& input_def, + TFE_TensorHandle** inputs, + int num_inputs) { + TFE_OpInferenceContext* ictx = op->inference_ctx; + if (ictx->attrs.find(input_def.number_attr()) == ictx->attrs.cend()) { + op->operation.MutableAttrs()->Set(input_def.number_attr(), num_inputs); + ictx->attrs.insert(input_def.number_attr()); + } + if (ictx->attrs.find(input_def.type_attr()) == ictx->attrs.cend()) { + op->operation.MutableAttrs()->Set(input_def.type_attr(), + inputs[0]->handle->dtype); + ictx->attrs.insert(input_def.type_attr()); + } +} + +void OpInferMixedTypeInputListAttrs(TFE_Op* op, + const tensorflow::OpDef_ArgDef& input_def, + TFE_TensorHandle** inputs, int num_inputs) { + TFE_OpInferenceContext* ictx = op->inference_ctx; + if (ictx->attrs.find(input_def.type_list_attr()) == ictx->attrs.cend()) { + std::unique_ptr dtypes( + new tensorflow::DataType[num_inputs]); + for (int i = 0; i < num_inputs; ++i) { + dtypes[i] = inputs[i]->handle->dtype; + } + op->operation.MutableAttrs()->Set(input_def.type_list_attr(), + tensorflow::gtl::ArraySlice( + dtypes.get(), num_inputs)); + ictx->attrs.insert(input_def.type_list_attr()); + } +} + +tensorflow::Status OpInferInputListAttrs(TFE_Op* op, TFE_TensorHandle** inputs, + int num_inputs) { + TFE_OpInferenceContext* ictx = op->inference_ctx; + const auto& input_def = ictx->op_def->input_arg(ictx->input_idx++); + if (!input_def.type_list_attr().empty()) { + OpInferMixedTypeInputListAttrs(op, input_def, inputs, num_inputs); + } else if (!input_def.type_attr().empty() + && !input_def.number_attr().empty()) { + OpInferSingleTypeInputListAttrs(op, input_def, inputs, num_inputs); + } else { + return tensorflow::errors::InvalidArgument("Invalid input list definition"); + } + return tensorflow::Status::OK(); +} + } // namespace extern "C" { @@ -249,11 +312,17 @@ void TFE_ContextOptionsSetAsync(TFE_ContextOptions* options, unsigned char enable) { options->async = enable; } + void TFE_ContextOptionsSetDevicePlacementPolicy( TFE_ContextOptions* options, TFE_ContextDevicePlacementPolicy policy) { options->policy = policy; } +void TFE_ContextOptionsSetInferInputAttrs(TFE_ContextOptions* options, + unsigned char enable) { + options->input_attrs_inference = enable; +} + TF_CAPI_EXPORT extern void TFE_ContextSetAsyncForThread(TFE_Context* ctx, unsigned char enable, TF_Status* status) { @@ -276,7 +345,8 @@ TFE_Context* TFE_NewContext(const TFE_ContextOptions* opts, TF_Status* status) { return new TFE_Context(opts->session_options.options, opts->policy, opts->async, device_mgr.release(), - /*device_mgr_owned*/ true, r); + /*device_mgr_owned*/ true, r, + opts->input_attrs_inference); } TFE_Context* TFE_NewContextFromSession(const TFE_ContextOptions* opts, @@ -288,7 +358,7 @@ TFE_Context* TFE_NewContextFromSession(const TFE_ContextOptions* opts, new tensorflow::IntraProcessRendezvous(device_mgr); return new TFE_Context(opts->session_options.options, opts->policy, opts->async, device_mgr, /*device_mgr_owned*/ false, - r); + r, opts->input_attrs_inference); } void TFE_DeleteContext(TFE_Context* ctx) { delete ctx; } @@ -503,12 +573,24 @@ TFE_Op* TFE_NewOp(TFE_Context* ctx, const char* op_or_function_name, "registered in the binary running in this process."); return nullptr; } - return new TFE_Op(ctx, name, is_function, types); + if (!is_function && ctx->input_attrs_inference) { + const tensorflow::OpDef* op_def; + status->status = tensorflow::OpDefForOp(op_or_function_name, &op_def); + TF_RETURN_IF_ERROR(status->status); + return new TFE_Op(ctx, name, is_function, types, + new TFE_OpInferenceContext(op_def)); + } + return new TFE_Op(ctx, name, is_function, types, nullptr); } return nullptr; } -void TFE_DeleteOp(TFE_Op* op) { delete op; } +void TFE_DeleteOp(TFE_Op* op) { + if (op->inference_ctx != nullptr) { + delete op->inference_ctx; + } + delete op; +} void TFE_OpSetDevice(TFE_Op* op, const char* device_name, TF_Status* status) { status->status = op->operation.SetDevice(device_name); @@ -529,8 +611,21 @@ void TFE_OpSetXLACompilation(TFE_Op* op, unsigned char enable) { #endif // TENSORFLOW_EAGER_USE_XLA } -void TFE_OpAddInput(TFE_Op* op, TFE_TensorHandle* h, TF_Status* status) { - op->operation.AddInput(h->handle); +void TFE_OpAddInput(TFE_Op* op, TFE_TensorHandle* input, TF_Status* status) { + op->operation.AddInput(input->handle); + if (op->inference_ctx != nullptr) { + status->status = OpInferSingleInputAttrs(op, input); + } +} + +void TFE_OpAddInputList(TFE_Op* op, TFE_TensorHandle** inputs, int num_inputs, + TF_Status* status) { + for (int i = 0; i < num_inputs; ++i) { + op->operation.AddInput(inputs[i]->handle); + } + if (op->inference_ctx != nullptr && num_inputs > 0) { + status->status = OpInferInputListAttrs(op, inputs, num_inputs); + } } TF_AttrType TFE_OpGetAttrType(TFE_Op* op, const char* attr_name, diff --git a/tensorflow/c/eager/c_api.h b/tensorflow/c/eager/c_api.h index 044dfb7415..b8adaee9df 100755 --- a/tensorflow/c/eager/c_api.h +++ b/tensorflow/c/eager/c_api.h @@ -81,6 +81,18 @@ TF_CAPI_EXPORT extern void TFE_ContextOptionsSetAsync(TFE_ContextOptions*, TF_CAPI_EXPORT extern void TFE_ContextOptionsSetDevicePlacementPolicy( TFE_ContextOptions*, TFE_ContextDevicePlacementPolicy); +// Infer type and number attributes from the operation inputs, disabled by +// default. +// +// Enabling it replicates the inference behaviour present in graph construction. +// It is assumed that inputs are added to the operaton in the same order as they +// are declared in its definition. +// +// It is also assumed that inferable attributes are not set explicitly when +// this option is enabled. +TF_CAPI_EXPORT extern void TFE_ContextOptionsSetInferInputAttributes( + TFE_ContextOptions*, unsigned char enable); + // Destroy an options object. TF_CAPI_EXPORT extern void TFE_DeleteContextOptions(TFE_ContextOptions*); @@ -282,9 +294,15 @@ TF_CAPI_EXPORT extern const char* TFE_OpGetDevice(TFE_Op* op, TF_CAPI_EXPORT extern void TFE_OpSetXLACompilation(TFE_Op* op, unsigned char enable); -TF_CAPI_EXPORT extern void TFE_OpAddInput(TFE_Op* op, TFE_TensorHandle* h, +TF_CAPI_EXPORT extern void TFE_OpAddInput(TFE_Op* op, + TFE_TensorHandle* input, TF_Status* status); +TF_CAPI_EXPORT extern void TFE_OpAddInputList(TFE_Op* op, + TFE_TensorHandle** inputs, + int num_inputs, + TF_Status* status); + TF_CAPI_EXPORT extern TF_AttrType TFE_OpGetAttrType(TFE_Op* op, const char* attr_name, unsigned char* is_list, @@ -361,6 +379,8 @@ TF_CAPI_EXPORT extern void TFE_OpSetAttrFunctionList(TFE_Op* op, const TFE_Op** value, int num_values); +TF_CAPI_EXPORT extern void TFE_OpInferSingleInputTypeAttrs(TFE_Op* op); + // Execute the operation defined by 'op' and return handles to computed // tensors in `retvals`. // diff --git a/tensorflow/c/eager/c_api_debug_test.cc b/tensorflow/c/eager/c_api_debug_test.cc index cddb9f6e00..4e987c745e 100644 --- a/tensorflow/c/eager/c_api_debug_test.cc +++ b/tensorflow/c/eager/c_api_debug_test.cc @@ -21,7 +21,7 @@ limitations under the License. #include "tensorflow/core/platform/test.h" TEST(CApiDebug, ScalarCPU) { - TFE_TensorHandle* h = TestScalarTensorHandle(); + TFE_TensorHandle* h = TestScalarTensorHandle(1.0f); TF_Status* status = TF_NewStatus(); TFE_TensorDebugInfo* debug_info = TFE_TensorHandleTensorDebugInfo(h, status); CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); diff --git a/tensorflow/c/eager/c_api_internal.h b/tensorflow/c/eager/c_api_internal.h index a563e4b8f5..c6773dd334 100644 --- a/tensorflow/c/eager/c_api_internal.h +++ b/tensorflow/c/eager/c_api_internal.h @@ -25,6 +25,7 @@ limitations under the License. #include #include #include +#include #include "tensorflow/c/c_api.h" #include "tensorflow/c/c_api_internal.h" @@ -60,19 +61,22 @@ struct TFE_ContextOptions { // true if async execution is enabled. bool async = false; TFE_ContextDevicePlacementPolicy policy{TFE_DEVICE_PLACEMENT_SILENT}; + bool input_attrs_inference = false; }; struct TFE_Context { TFE_Context(const tensorflow::SessionOptions& opts, TFE_ContextDevicePlacementPolicy default_policy, bool async, const tensorflow::DeviceMgr* device_mgr, bool device_mgr_owned, - tensorflow::Rendezvous* rendezvous) + tensorflow::Rendezvous* rendezvous, bool input_attrs_inference) : context(opts, static_cast( default_policy), - async, device_mgr, device_mgr_owned, rendezvous) {} + async, device_mgr, device_mgr_owned, rendezvous), + input_attrs_inference(input_attrs_inference) {} tensorflow::EagerContext context; + bool input_attrs_inference; }; struct TFE_TensorHandle { @@ -99,12 +103,24 @@ struct TFE_TensorDebugInfo { std::vector dev_dims; }; +struct TFE_OpInferenceContext { + explicit TFE_OpInferenceContext(const tensorflow::OpDef* op_def) + : op_def(op_def) {} + + const tensorflow::OpDef* op_def; // op definition from protobuf + int input_idx = 0; // index of the next input to be added + std::set attrs; // attributes inferred so far +}; + struct TFE_Op { TFE_Op(TFE_Context* ctx, const char* op, bool is_function, - const tensorflow::AttrTypeMap* t) - : operation(&ctx->context, op, is_function, t) {} + const tensorflow::AttrTypeMap* t, + TFE_OpInferenceContext* inference_ctx) + : operation(&ctx->context, op, is_function, t), + inference_ctx(inference_ctx) {} tensorflow::EagerOperation operation; + TFE_OpInferenceContext* inference_ctx = nullptr; }; struct TFE_ProfilerContext { diff --git a/tensorflow/c/eager/c_api_test.cc b/tensorflow/c/eager/c_api_test.cc index 3d1ca4fb4b..393453fcb5 100644 --- a/tensorflow/c/eager/c_api_test.cc +++ b/tensorflow/c/eager/c_api_test.cc @@ -14,6 +14,7 @@ limitations under the License. ==============================================================================*/ #include "tensorflow/c/eager/c_api.h" +#include "tensorflow/c/eager/c_api_internal.h" #include #include "absl/strings/match.h" @@ -1626,4 +1627,120 @@ TEST(CAPI, TestTFE_TensorHandleCopySharingUnderlyingTensorHandle) { TFE_DeleteTensorHandle(h); TFE_DeleteTensorHandle(h_shares_tensor); } + +TEST(CAPI, TestTFE_OpInferSingleInputAttrs) { + TF_Status* status = TF_NewStatus(); + TFE_ContextOptions* opts = TFE_NewContextOptions(); + opts->input_attrs_inference = true; + TFE_Context* ctx = TFE_NewContext(opts, status); + CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + TFE_DeleteContextOptions(opts); + + TFE_TensorHandle* input = TestMatrixTensorHandle(); + TFE_TensorHandle* axis = TestAxisTensorHandle(); + TFE_Op* minOp = TFE_NewOp(ctx, "Min", status); + CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + TFE_OpAddInput(minOp, input, status); + CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + TFE_OpAddInput(minOp, axis, status); + CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + + tensorflow::AttrValueMap attr_values; + minOp->operation.Attrs().FillAttrValueMap(&attr_values); + tensorflow::AttrValueMap::iterator attr_found = attr_values.find("T"); + EXPECT_NE(attr_found, attr_values.cend()); + EXPECT_EQ(attr_found->second.type(), tensorflow::DataType::DT_FLOAT); + attr_found = attr_values.find("Tidx"); + EXPECT_NE(attr_found, attr_values.cend()); + EXPECT_EQ(attr_found->second.type(), tensorflow::DataType::DT_INT32); + + TFE_TensorHandle* retvals[1] = {nullptr}; + int num_retvals = 1; + TFE_Execute(minOp, &retvals[0], &num_retvals, status); + EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + + TF_DeleteStatus(status); + TFE_DeleteOp(minOp); + TFE_DeleteTensorHandle(input); + TFE_DeleteTensorHandle(axis); +} + +TEST(CAPI, TestTFE_OpInferSingleTypeInputListAttrs) { + TF_Status* status = TF_NewStatus(); + TFE_ContextOptions* opts = TFE_NewContextOptions(); + opts->input_attrs_inference = true; + TFE_Context* ctx = TFE_NewContext(opts, status); + CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + TFE_DeleteContextOptions(opts); + + TFE_TensorHandle* input1 = TestMatrixTensorHandle(); + TFE_TensorHandle* input2 = TestMatrixTensorHandle(); + TFE_TensorHandle* dim = TestScalarTensorHandle(0); + TFE_Op* concatOp = TFE_NewOp(ctx, "Concat", status); + CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + TFE_TensorHandle* inputs[] = { input1, input2 }; + TFE_OpAddInput(concatOp, dim, status); + CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + TFE_OpAddInputList(concatOp, inputs, 2, status); + CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + + tensorflow::AttrValueMap attr_values; + concatOp->operation.Attrs().FillAttrValueMap(&attr_values); + tensorflow::AttrValueMap::iterator attr_found = attr_values.find("T"); + EXPECT_NE(attr_found, attr_values.cend()); + EXPECT_EQ(attr_found->second.type(), tensorflow::DataType::DT_FLOAT); + attr_found = attr_values.find("N"); + EXPECT_NE(attr_found, attr_values.cend()); + EXPECT_EQ(attr_found->second.i(), 2); + + TFE_TensorHandle* retvals[1] = {nullptr}; + int num_retvals = 1; + TFE_Execute(concatOp, &retvals[0], &num_retvals, status); + EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + + TF_DeleteStatus(status); + TFE_DeleteOp(concatOp); + TFE_DeleteTensorHandle(input1); + TFE_DeleteTensorHandle(input2); + TFE_DeleteTensorHandle(dim); +} + +TEST(CAPI, TestTFE_OpInferMixedTypeInputListAttrs) { + TF_Status* status = TF_NewStatus(); + TFE_ContextOptions* opts = TFE_NewContextOptions(); + opts->input_attrs_inference = true; + TFE_Context* ctx = TFE_NewContext(opts, status); + CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + TFE_DeleteContextOptions(opts); + + TFE_TensorHandle* condition = TestScalarTensorHandle(true); + TFE_TensorHandle* t1 = TestMatrixTensorHandle(); + TFE_TensorHandle* t2 = TestAxisTensorHandle(); + TFE_Op* assertOp = TFE_NewOp(ctx, "Assert", status); + CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + TFE_OpAddInput(assertOp, condition, status); + CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + TFE_TensorHandle* data[] = { condition, t1, t2 }; + TFE_OpAddInputList(assertOp, data, 3, status); + CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + + tensorflow::AttrValueMap attr_values; + assertOp->operation.Attrs().FillAttrValueMap(&attr_values); + tensorflow::AttrValueMap::iterator attr_found = attr_values.find("T"); + EXPECT_NE(attr_found, attr_values.cend()); + EXPECT_EQ(attr_found->second.list().type(0), tensorflow::DataType::DT_BOOL); + EXPECT_EQ(attr_found->second.list().type(1), tensorflow::DataType::DT_FLOAT); + EXPECT_EQ(attr_found->second.list().type(2), tensorflow::DataType::DT_INT32); + + TFE_TensorHandle* retvals[1] = {nullptr}; + int num_retvals = 1; + TFE_Execute(assertOp, &retvals[0], &num_retvals, status); + EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + + TF_DeleteStatus(status); + TFE_DeleteOp(assertOp); + TFE_DeleteTensorHandle(condition); + TFE_DeleteTensorHandle(t1); + TFE_DeleteTensorHandle(t2); +} } // namespace diff --git a/tensorflow/c/eager/c_api_test_util.cc b/tensorflow/c/eager/c_api_test_util.cc index bd38127d50..17d17c0b7f 100644 --- a/tensorflow/c/eager/c_api_test_util.cc +++ b/tensorflow/c/eager/c_api_test_util.cc @@ -21,8 +21,8 @@ limitations under the License. using tensorflow::string; -TFE_TensorHandle* TestScalarTensorHandle() { - float data[] = {1.0f}; +TFE_TensorHandle* TestScalarTensorHandle(float value) { + float data[] = {value}; TF_Tensor* t = TF_AllocateTensor(TF_FLOAT, nullptr, 0, sizeof(float)); memcpy(TF_TensorData(t), &data[0], TF_TensorByteSize(t)); TF_Status* status = TF_NewStatus(); @@ -33,6 +33,30 @@ TFE_TensorHandle* TestScalarTensorHandle() { return th; } +TFE_TensorHandle* TestScalarTensorHandle(int value) { + int data[] = {value}; + TF_Tensor* t = TF_AllocateTensor(TF_INT32, nullptr, 0, sizeof(int)); + memcpy(TF_TensorData(t), &data[0], TF_TensorByteSize(t)); + TF_Status* status = TF_NewStatus(); + TFE_TensorHandle* th = TFE_NewTensorHandle(t, status); + CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + TF_DeleteTensor(t); + TF_DeleteStatus(status); + return th; +} + +TFE_TensorHandle* TestScalarTensorHandle(bool value) { + bool data[] = {value}; + TF_Tensor* t = TF_AllocateTensor(TF_BOOL, nullptr, 0, sizeof(bool)); + memcpy(TF_TensorData(t), &data[0], TF_TensorByteSize(t)); + TF_Status* status = TF_NewStatus(); + TFE_TensorHandle* th = TFE_NewTensorHandle(t, status); + CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + TF_DeleteTensor(t); + TF_DeleteStatus(status); + return th; +} + TFE_TensorHandle* DoubleTestMatrixTensorHandle() { int64_t dims[] = {2, 2}; double data[] = {1.0, 2.0, 3.0, 4.0}; diff --git a/tensorflow/c/eager/c_api_test_util.h b/tensorflow/c/eager/c_api_test_util.h index 75ef9459e9..4ff3ff4301 100644 --- a/tensorflow/c/eager/c_api_test_util.h +++ b/tensorflow/c/eager/c_api_test_util.h @@ -20,7 +20,13 @@ limitations under the License. #include "tensorflow/core/platform/types.h" // Return a tensor handle containing a float scalar -TFE_TensorHandle* TestScalarTensorHandle(); +TFE_TensorHandle* TestScalarTensorHandle(float value); + +// Return a tensor handle containing a int scalar +TFE_TensorHandle* TestScalarTensorHandle(int value); + +// Return a tensor handle containing a bool scalar +TFE_TensorHandle* TestScalarTensorHandle(bool value); // Return a tensor handle containing a 2x2 matrix of doubles TFE_TensorHandle* DoubleTestMatrixTensorHandle(); -- GitLab From 33de8ecaa4fbaa80864f665520c014754fe5ee44 Mon Sep 17 00:00:00 2001 From: Karl Lessard Date: Thu, 8 Nov 2018 00:09:02 -0500 Subject: [PATCH 431/766] Introduce review changes in the C API --- tensorflow/c/eager/c_api.cc | 74 +++++++++++++---------------- tensorflow/c/eager/c_api.h | 14 ------ tensorflow/c/eager/c_api_internal.h | 15 +++--- tensorflow/c/eager/c_api_test.cc | 9 ++-- 4 files changed, 41 insertions(+), 71 deletions(-) diff --git a/tensorflow/c/eager/c_api.cc b/tensorflow/c/eager/c_api.cc index 94181f3e0e..ff7a9a2038 100755 --- a/tensorflow/c/eager/c_api.cc +++ b/tensorflow/c/eager/c_api.cc @@ -21,7 +21,6 @@ limitations under the License. #include #include #include -#include #include "absl/memory/memory.h" #include "tensorflow/c/c_api.h" @@ -239,9 +238,9 @@ tensorflow::Status UpdateTFE_ContextWithServerDef( tensorflow::Status OpInferSingleInputAttrs(TFE_Op* op, TFE_TensorHandle* input) { - TFE_OpInferenceContext* ictx = op->inference_ctx; + TFE_OpInferenceContext* ictx = op->inference_ctx.get(); const std::string& type_attr = - ictx->op_def->input_arg(ictx->input_idx++).type_attr(); + ictx->op_def->input_arg(ictx->input_arg_idx++).type_attr(); if (!type_attr.empty() && ictx->attrs.find(type_attr) == ictx->attrs.cend()) { op->operation.MutableAttrs()->Set(type_attr, input->handle->dtype); ictx->attrs.insert(type_attr); @@ -250,10 +249,10 @@ tensorflow::Status OpInferSingleInputAttrs(TFE_Op* op, } void OpInferSingleTypeInputListAttrs(TFE_Op* op, - const tensorflow::OpDef_ArgDef& input_def, + const tensorflow::OpDef::ArgDef& input_def, TFE_TensorHandle** inputs, int num_inputs) { - TFE_OpInferenceContext* ictx = op->inference_ctx; + TFE_OpInferenceContext* ictx = op->inference_ctx.get(); if (ictx->attrs.find(input_def.number_attr()) == ictx->attrs.cend()) { op->operation.MutableAttrs()->Set(input_def.number_attr(), num_inputs); ictx->attrs.insert(input_def.number_attr()); @@ -266,9 +265,9 @@ void OpInferSingleTypeInputListAttrs(TFE_Op* op, } void OpInferMixedTypeInputListAttrs(TFE_Op* op, - const tensorflow::OpDef_ArgDef& input_def, + const tensorflow::OpDef::ArgDef& input_def, TFE_TensorHandle** inputs, int num_inputs) { - TFE_OpInferenceContext* ictx = op->inference_ctx; + TFE_OpInferenceContext* ictx = op->inference_ctx.get(); if (ictx->attrs.find(input_def.type_list_attr()) == ictx->attrs.cend()) { std::unique_ptr dtypes( new tensorflow::DataType[num_inputs]); @@ -284,8 +283,8 @@ void OpInferMixedTypeInputListAttrs(TFE_Op* op, tensorflow::Status OpInferInputListAttrs(TFE_Op* op, TFE_TensorHandle** inputs, int num_inputs) { - TFE_OpInferenceContext* ictx = op->inference_ctx; - const auto& input_def = ictx->op_def->input_arg(ictx->input_idx++); + TFE_OpInferenceContext* ictx = op->inference_ctx.get(); + const auto& input_def = ictx->op_def->input_arg(ictx->input_arg_idx++); if (!input_def.type_list_attr().empty()) { OpInferMixedTypeInputListAttrs(op, input_def, inputs, num_inputs); } else if (!input_def.type_attr().empty() @@ -318,11 +317,6 @@ void TFE_ContextOptionsSetDevicePlacementPolicy( options->policy = policy; } -void TFE_ContextOptionsSetInferInputAttrs(TFE_ContextOptions* options, - unsigned char enable) { - options->input_attrs_inference = enable; -} - TF_CAPI_EXPORT extern void TFE_ContextSetAsyncForThread(TFE_Context* ctx, unsigned char enable, TF_Status* status) { @@ -345,8 +339,7 @@ TFE_Context* TFE_NewContext(const TFE_ContextOptions* opts, TF_Status* status) { return new TFE_Context(opts->session_options.options, opts->policy, opts->async, device_mgr.release(), - /*device_mgr_owned*/ true, r, - opts->input_attrs_inference); + /*device_mgr_owned*/ true, r); } TFE_Context* TFE_NewContextFromSession(const TFE_ContextOptions* opts, @@ -358,7 +351,7 @@ TFE_Context* TFE_NewContextFromSession(const TFE_ContextOptions* opts, new tensorflow::IntraProcessRendezvous(device_mgr); return new TFE_Context(opts->session_options.options, opts->policy, opts->async, device_mgr, /*device_mgr_owned*/ false, - r, opts->input_attrs_inference); + r); } void TFE_DeleteContext(TFE_Context* ctx) { delete ctx; } @@ -562,33 +555,30 @@ TFE_Op* TFE_NewOp(TFE_Context* ctx, const char* op_or_function_name, const tensorflow::AttrTypeMap* types; bool is_function = false; status->status = tensorflow::AttrTypeMapForOp(name, &types, &is_function); - if (status->status.ok()) { - if (is_function && !ctx->context.FindFunctionByName(name)) { - status->status = tensorflow::errors::NotFound( - "'", name, - "' is neither a type of a primitive operation nor a name " - "of a function registered in binary running on ", - tensorflow::port::Hostname(), - ". Make sure the operation or function is " - "registered in the binary running in this process."); - return nullptr; - } - if (!is_function && ctx->input_attrs_inference) { - const tensorflow::OpDef* op_def; - status->status = tensorflow::OpDefForOp(op_or_function_name, &op_def); - TF_RETURN_IF_ERROR(status->status); - return new TFE_Op(ctx, name, is_function, types, - new TFE_OpInferenceContext(op_def)); - } - return new TFE_Op(ctx, name, is_function, types, nullptr); + if (!status->status.ok()) { + return nullptr; } - return nullptr; + if (!is_function) { + const tensorflow::OpDef* op_def; + status->status = tensorflow::OpDefForOp(op_or_function_name, &op_def); + TF_RETURN_IF_ERROR(status->status); + return new TFE_Op(ctx, name, is_function, types, + new TFE_OpInferenceContext(op_def)); + } + if (!ctx->context.FindFunctionByName(name)) { + status->status = tensorflow::errors::NotFound( + "'", name, + "' is neither a type of a primitive operation nor a name " + "of a function registered in binary running on ", + tensorflow::port::Hostname(), + ". Make sure the operation or function is " + "registered in the binary running in this process."); + return nullptr; + } + return new TFE_Op(ctx, name, is_function, types, nullptr); } void TFE_DeleteOp(TFE_Op* op) { - if (op->inference_ctx != nullptr) { - delete op->inference_ctx; - } delete op; } @@ -613,7 +603,7 @@ void TFE_OpSetXLACompilation(TFE_Op* op, unsigned char enable) { void TFE_OpAddInput(TFE_Op* op, TFE_TensorHandle* input, TF_Status* status) { op->operation.AddInput(input->handle); - if (op->inference_ctx != nullptr) { + if (op->inference_ctx) { status->status = OpInferSingleInputAttrs(op, input); } } @@ -623,7 +613,7 @@ void TFE_OpAddInputList(TFE_Op* op, TFE_TensorHandle** inputs, int num_inputs, for (int i = 0; i < num_inputs; ++i) { op->operation.AddInput(inputs[i]->handle); } - if (op->inference_ctx != nullptr && num_inputs > 0) { + if (op->inference_ctx) { status->status = OpInferInputListAttrs(op, inputs, num_inputs); } } diff --git a/tensorflow/c/eager/c_api.h b/tensorflow/c/eager/c_api.h index b8adaee9df..d90615263f 100755 --- a/tensorflow/c/eager/c_api.h +++ b/tensorflow/c/eager/c_api.h @@ -81,18 +81,6 @@ TF_CAPI_EXPORT extern void TFE_ContextOptionsSetAsync(TFE_ContextOptions*, TF_CAPI_EXPORT extern void TFE_ContextOptionsSetDevicePlacementPolicy( TFE_ContextOptions*, TFE_ContextDevicePlacementPolicy); -// Infer type and number attributes from the operation inputs, disabled by -// default. -// -// Enabling it replicates the inference behaviour present in graph construction. -// It is assumed that inputs are added to the operaton in the same order as they -// are declared in its definition. -// -// It is also assumed that inferable attributes are not set explicitly when -// this option is enabled. -TF_CAPI_EXPORT extern void TFE_ContextOptionsSetInferInputAttributes( - TFE_ContextOptions*, unsigned char enable); - // Destroy an options object. TF_CAPI_EXPORT extern void TFE_DeleteContextOptions(TFE_ContextOptions*); @@ -379,8 +367,6 @@ TF_CAPI_EXPORT extern void TFE_OpSetAttrFunctionList(TFE_Op* op, const TFE_Op** value, int num_values); -TF_CAPI_EXPORT extern void TFE_OpInferSingleInputTypeAttrs(TFE_Op* op); - // Execute the operation defined by 'op' and return handles to computed // tensors in `retvals`. // diff --git a/tensorflow/c/eager/c_api_internal.h b/tensorflow/c/eager/c_api_internal.h index c6773dd334..cf65518551 100644 --- a/tensorflow/c/eager/c_api_internal.h +++ b/tensorflow/c/eager/c_api_internal.h @@ -25,8 +25,8 @@ limitations under the License. #include #include #include -#include +#include "absl/container/flat_hash_set.h" #include "tensorflow/c/c_api.h" #include "tensorflow/c/c_api_internal.h" #include "tensorflow/core/common_runtime/device_factory.h" @@ -61,22 +61,19 @@ struct TFE_ContextOptions { // true if async execution is enabled. bool async = false; TFE_ContextDevicePlacementPolicy policy{TFE_DEVICE_PLACEMENT_SILENT}; - bool input_attrs_inference = false; }; struct TFE_Context { TFE_Context(const tensorflow::SessionOptions& opts, TFE_ContextDevicePlacementPolicy default_policy, bool async, const tensorflow::DeviceMgr* device_mgr, bool device_mgr_owned, - tensorflow::Rendezvous* rendezvous, bool input_attrs_inference) + tensorflow::Rendezvous* rendezvous) : context(opts, static_cast( default_policy), - async, device_mgr, device_mgr_owned, rendezvous), - input_attrs_inference(input_attrs_inference) {} + async, device_mgr, device_mgr_owned, rendezvous) {} tensorflow::EagerContext context; - bool input_attrs_inference; }; struct TFE_TensorHandle { @@ -108,8 +105,8 @@ struct TFE_OpInferenceContext { : op_def(op_def) {} const tensorflow::OpDef* op_def; // op definition from protobuf - int input_idx = 0; // index of the next input to be added - std::set attrs; // attributes inferred so far + int input_arg_idx = 0; // arg definition index for the next input to be added + absl::flat_hash_set attrs; // attributes inferred so far }; struct TFE_Op { @@ -120,7 +117,7 @@ struct TFE_Op { inference_ctx(inference_ctx) {} tensorflow::EagerOperation operation; - TFE_OpInferenceContext* inference_ctx = nullptr; + std::unique_ptr inference_ctx; }; struct TFE_ProfilerContext { diff --git a/tensorflow/c/eager/c_api_test.cc b/tensorflow/c/eager/c_api_test.cc index 393453fcb5..94dbadf566 100644 --- a/tensorflow/c/eager/c_api_test.cc +++ b/tensorflow/c/eager/c_api_test.cc @@ -1631,7 +1631,6 @@ TEST(CAPI, TestTFE_TensorHandleCopySharingUnderlyingTensorHandle) { TEST(CAPI, TestTFE_OpInferSingleInputAttrs) { TF_Status* status = TF_NewStatus(); TFE_ContextOptions* opts = TFE_NewContextOptions(); - opts->input_attrs_inference = true; TFE_Context* ctx = TFE_NewContext(opts, status); CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); TFE_DeleteContextOptions(opts); @@ -1647,7 +1646,7 @@ TEST(CAPI, TestTFE_OpInferSingleInputAttrs) { tensorflow::AttrValueMap attr_values; minOp->operation.Attrs().FillAttrValueMap(&attr_values); - tensorflow::AttrValueMap::iterator attr_found = attr_values.find("T"); + tensorflow::AttrValueMap::const_iterator attr_found = attr_values.find("T"); EXPECT_NE(attr_found, attr_values.cend()); EXPECT_EQ(attr_found->second.type(), tensorflow::DataType::DT_FLOAT); attr_found = attr_values.find("Tidx"); @@ -1668,7 +1667,6 @@ TEST(CAPI, TestTFE_OpInferSingleInputAttrs) { TEST(CAPI, TestTFE_OpInferSingleTypeInputListAttrs) { TF_Status* status = TF_NewStatus(); TFE_ContextOptions* opts = TFE_NewContextOptions(); - opts->input_attrs_inference = true; TFE_Context* ctx = TFE_NewContext(opts, status); CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); TFE_DeleteContextOptions(opts); @@ -1686,7 +1684,7 @@ TEST(CAPI, TestTFE_OpInferSingleTypeInputListAttrs) { tensorflow::AttrValueMap attr_values; concatOp->operation.Attrs().FillAttrValueMap(&attr_values); - tensorflow::AttrValueMap::iterator attr_found = attr_values.find("T"); + tensorflow::AttrValueMap::const_iterator attr_found = attr_values.find("T"); EXPECT_NE(attr_found, attr_values.cend()); EXPECT_EQ(attr_found->second.type(), tensorflow::DataType::DT_FLOAT); attr_found = attr_values.find("N"); @@ -1708,7 +1706,6 @@ TEST(CAPI, TestTFE_OpInferSingleTypeInputListAttrs) { TEST(CAPI, TestTFE_OpInferMixedTypeInputListAttrs) { TF_Status* status = TF_NewStatus(); TFE_ContextOptions* opts = TFE_NewContextOptions(); - opts->input_attrs_inference = true; TFE_Context* ctx = TFE_NewContext(opts, status); CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); TFE_DeleteContextOptions(opts); @@ -1726,7 +1723,7 @@ TEST(CAPI, TestTFE_OpInferMixedTypeInputListAttrs) { tensorflow::AttrValueMap attr_values; assertOp->operation.Attrs().FillAttrValueMap(&attr_values); - tensorflow::AttrValueMap::iterator attr_found = attr_values.find("T"); + tensorflow::AttrValueMap::const_iterator attr_found = attr_values.find("T"); EXPECT_NE(attr_found, attr_values.cend()); EXPECT_EQ(attr_found->second.list().type(0), tensorflow::DataType::DT_BOOL); EXPECT_EQ(attr_found->second.list().type(1), tensorflow::DataType::DT_FLOAT); -- GitLab From e677ed25f71f83b241bb11c5e37d29838cf25c4e Mon Sep 17 00:00:00 2001 From: Karl Lessard Date: Wed, 14 Nov 2018 12:58:17 -0500 Subject: [PATCH 432/766] Validate inferred attrs vs manual + handle non-migrated clients properly --- tensorflow/c/eager/c_api.cc | 23 +++++++++---- tensorflow/c/eager/c_api_test.cc | 34 +++++++++++++++++++ .../core/common_runtime/eager/attr_builder.h | 15 +++++--- 3 files changed, 61 insertions(+), 11 deletions(-) diff --git a/tensorflow/c/eager/c_api.cc b/tensorflow/c/eager/c_api.cc index ff7a9a2038..982cff8513 100755 --- a/tensorflow/c/eager/c_api.cc +++ b/tensorflow/c/eager/c_api.cc @@ -20,7 +20,6 @@ limitations under the License. #include #include #include -#include #include "absl/memory/memory.h" #include "tensorflow/c/c_api.h" @@ -239,8 +238,18 @@ tensorflow::Status UpdateTFE_ContextWithServerDef( tensorflow::Status OpInferSingleInputAttrs(TFE_Op* op, TFE_TensorHandle* input) { TFE_OpInferenceContext* ictx = op->inference_ctx.get(); - const std::string& type_attr = - ictx->op_def->input_arg(ictx->input_arg_idx++).type_attr(); + const auto& input_def = ictx->op_def->input_arg(ictx->input_arg_idx++); + if (!input_def.number_attr().empty() || !input_def.type_list_attr().empty()) { + // Some clients that are still setting their input attributes manually are + // adding input list to their op by calling `TFE_OpAddInput` for each of + // its elements instead of calling `TFE_OpAddInputList`. When this happens, + // we cannot detect the end of such list, thus lose track of the input + // arguments in the op definition. To guarantee backward compatibility with + // those clients, disable automatic inference in this case. + op->inference_ctx.release(); + return tensorflow::Status::OK(); + } + const std::string& type_attr = input_def.type_attr(); if (!type_attr.empty() && ictx->attrs.find(type_attr) == ictx->attrs.cend()) { op->operation.MutableAttrs()->Set(type_attr, input->handle->dtype); ictx->attrs.insert(type_attr); @@ -561,8 +570,10 @@ TFE_Op* TFE_NewOp(TFE_Context* ctx, const char* op_or_function_name, if (!is_function) { const tensorflow::OpDef* op_def; status->status = tensorflow::OpDefForOp(op_or_function_name, &op_def); - TF_RETURN_IF_ERROR(status->status); - return new TFE_Op(ctx, name, is_function, types, + if (!status->status.ok()) { + return nullptr; + } + return new TFE_Op(ctx, name, false, types, new TFE_OpInferenceContext(op_def)); } if (!ctx->context.FindFunctionByName(name)) { @@ -575,7 +586,7 @@ TFE_Op* TFE_NewOp(TFE_Context* ctx, const char* op_or_function_name, "registered in the binary running in this process."); return nullptr; } - return new TFE_Op(ctx, name, is_function, types, nullptr); + return new TFE_Op(ctx, name, true, types, nullptr); } void TFE_DeleteOp(TFE_Op* op) { diff --git a/tensorflow/c/eager/c_api_test.cc b/tensorflow/c/eager/c_api_test.cc index 94dbadf566..54c920e0a2 100644 --- a/tensorflow/c/eager/c_api_test.cc +++ b/tensorflow/c/eager/c_api_test.cc @@ -1740,4 +1740,38 @@ TEST(CAPI, TestTFE_OpInferMixedTypeInputListAttrs) { TFE_DeleteTensorHandle(t1); TFE_DeleteTensorHandle(t2); } + +TEST(CAPI, TestTFE_OpAttrsInferenceDisabledWhenNotCallingOpAddInputList) { + TF_Status* status = TF_NewStatus(); + TFE_ContextOptions* opts = TFE_NewContextOptions(); + TFE_Context* ctx = TFE_NewContext(opts, status); + CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + TFE_DeleteContextOptions(opts); + + TFE_TensorHandle* input1 = TestMatrixTensorHandle(); + TFE_TensorHandle* input2 = TestMatrixTensorHandle(); + TFE_TensorHandle* dim = TestScalarTensorHandle(0); + TFE_Op* concatOp = TFE_NewOp(ctx, "Concat", status); + CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + TFE_TensorHandle* inputs[] = { input1, input2 }; + TFE_OpAddInput(concatOp, dim, status); + CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + CHECK(concatOp->inference_ctx); + TFE_OpAddInput(concatOp, inputs[0], status); + CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + EXPECT_FALSE(concatOp->inference_ctx) << "Inference context is still present"; + TFE_OpAddInput(concatOp, inputs[1], status); + CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + + tensorflow::AttrValueMap attr_values; + concatOp->operation.Attrs().FillAttrValueMap(&attr_values); + EXPECT_EQ(attr_values.find("T"), attr_values.end()); + EXPECT_EQ(attr_values.find("N"), attr_values.end()); + + TF_DeleteStatus(status); + TFE_DeleteOp(concatOp); + TFE_DeleteTensorHandle(input1); + TFE_DeleteTensorHandle(input2); + TFE_DeleteTensorHandle(dim); +} } // namespace diff --git a/tensorflow/core/common_runtime/eager/attr_builder.h b/tensorflow/core/common_runtime/eager/attr_builder.h index 1b3fbcbd4a..578e866a65 100644 --- a/tensorflow/core/common_runtime/eager/attr_builder.h +++ b/tensorflow/core/common_runtime/eager/attr_builder.h @@ -129,14 +129,19 @@ class AttrBuilder { // Copied from NodeDefBuilder::Attr const AttrValue* found = AttrSlice(m).Find(attr_name); AttrValue attr_value; + SetAttrValue(value, &attr_value); if (found == nullptr) { - SetAttrValue(value, &attr_value); m->insert(AttrValueMap::value_type(attr_name, attr_value)); } else { - // TODO(ashankar): Do what is done in - // NodeDefBuilder::CheckInconsistency(attr_name, *found, attr_value); - SetAttrValue(std::forward(value), &attr_value); - (*m)[attr_name] = attr_value; + // TODO(klessard): remove this validation once automatic input attributes + // inference of the C API is used by all clients, or at least Python. + // We use it only to verify that attributes inferred have the same values + // as those currently added manually (see + // github.com/tensorflow/tensorflow/pull/23468#issuecomment-438484058). + CHECK_EQ(found->i(), attr_value.i()) << " Number attribute \"" + << attr_name << "\" was set multiple times with different values"; + CHECK_EQ(found->type(), attr_value.type()) << " Type attribute \"" + << attr_name << "\" was set multiple times with different values"; } } -- GitLab From f064df8da3748d9eb76245fc8a13c2db853da234 Mon Sep 17 00:00:00 2001 From: Karl Lessard Date: Mon, 25 Feb 2019 00:34:12 -0500 Subject: [PATCH 433/766] Remove CHECKs that prevent code to be merged --- .../core/common_runtime/eager/attr_builder.h | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/tensorflow/core/common_runtime/eager/attr_builder.h b/tensorflow/core/common_runtime/eager/attr_builder.h index 578e866a65..d9f110e639 100644 --- a/tensorflow/core/common_runtime/eager/attr_builder.h +++ b/tensorflow/core/common_runtime/eager/attr_builder.h @@ -126,22 +126,11 @@ class AttrBuilder { T&& value) const { DCHECK(!node_def_finalized_) << "Calling SetInAttrValueMap after BuildNodeDef."; - // Copied from NodeDefBuilder::Attr - const AttrValue* found = AttrSlice(m).Find(attr_name); - AttrValue attr_value; - SetAttrValue(value, &attr_value); - if (found == nullptr) { + // If attribute is set more than once, its first value prevails + if (AttrSlice(m).Find(attr_name) == nullptr) { + AttrValue attr_value; + SetAttrValue(value, &attr_value); m->insert(AttrValueMap::value_type(attr_name, attr_value)); - } else { - // TODO(klessard): remove this validation once automatic input attributes - // inference of the C API is used by all clients, or at least Python. - // We use it only to verify that attributes inferred have the same values - // as those currently added manually (see - // github.com/tensorflow/tensorflow/pull/23468#issuecomment-438484058). - CHECK_EQ(found->i(), attr_value.i()) << " Number attribute \"" - << attr_name << "\" was set multiple times with different values"; - CHECK_EQ(found->type(), attr_value.type()) << " Type attribute \"" - << attr_name << "\" was set multiple times with different values"; } } -- GitLab From fb7f8cc24b49d4b2de0f7612b133efe98fd0280d Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Sun, 24 Feb 2019 23:55:18 -0800 Subject: [PATCH 434/766] Automated rollback of commit ced4557e76a896523f5df2ae628110ee84813b3f PiperOrigin-RevId: 235476472 --- tensorflow/contrib/cmake/external/grpc.cmake | 2 +- tensorflow/workspace.bzl | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tensorflow/contrib/cmake/external/grpc.cmake b/tensorflow/contrib/cmake/external/grpc.cmake index 594f3ca8e5..30b4e2dbde 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 5f00cfd3bd667852c23a9e2d52b1e8aed93f0d9e) +set(GRPC_TAG 62688b6a05cc85b47fb77dd408611734253e47e2) if(WIN32) # We use unsecure gRPC because boringssl does not build on windows diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl index 8a79467941..091f1371cf 100755 --- a/tensorflow/workspace.bzl +++ b/tensorflow/workspace.bzl @@ -477,12 +477,12 @@ def tf_workspace(path_prefix = "", tf_repo_name = ""): # WARNING: make sure ncteisen@ and vpai@ are cc-ed on any CL to change the below rule tf_http_archive( name = "grpc", - sha256 = "f4b8476bb3a531ee47810e4bc39cdc5d5ec1005424a32cbf02c9da7751e261b7", - strip_prefix = "grpc-5f00cfd3bd667852c23a9e2d52b1e8aed93f0d9e", + sha256 = "e1e3a9edbfbe4230bee174d4aa45a15c1ec2b203cedb02d20df3e6345d8fa63e", + strip_prefix = "grpc-62688b6a05cc85b47fb77dd408611734253e47e2", system_build_file = clean_dep("//third_party/systemlibs:grpc.BUILD"), urls = [ - "https://mirror.bazel.build/github.com/grpc/grpc/archive/5f00cfd3bd667852c23a9e2d52b1e8aed93f0d9e.tar.gz", - "https://github.com/grpc/grpc/archive/5f00cfd3bd667852c23a9e2d52b1e8aed93f0d9e.tar.gz", + "https://mirror.bazel.build/github.com/grpc/grpc/archive/62688b6a05cc85b47fb77dd408611734253e47e2.tar.gz", + "https://github.com/grpc/grpc/archive/62688b6a05cc85b47fb77dd408611734253e47e2.tar.gz", ], ) -- GitLab From af82e5f08705612386c19a1c700fb379c976f477 Mon Sep 17 00:00:00 2001 From: Adrian Kuegel Date: Mon, 25 Feb 2019 00:38:09 -0800 Subject: [PATCH 435/766] Convert called bfloat16 computations in HandleMultipleOutputs(). This probably didn't matter before, but now Sort has a comparison computation. Also add an optimization to not use a convert to BF16 if the user is a convert to F32. PiperOrigin-RevId: 235481597 --- .../xla/service/bfloat16_normalization.cc | 31 +++++++++++++++++-- .../service/bfloat16_normalization_test.cc | 8 +++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/tensorflow/compiler/xla/service/bfloat16_normalization.cc b/tensorflow/compiler/xla/service/bfloat16_normalization.cc index d1b14d604f..7245996148 100644 --- a/tensorflow/compiler/xla/service/bfloat16_normalization.cc +++ b/tensorflow/compiler/xla/service/bfloat16_normalization.cc @@ -84,7 +84,12 @@ Status BFloat16NormalizationVisitor::InsertConvertAfterOutput( auto convert = computation->AddInstruction( HloInstruction::CreateConvert(hlo->shape(), hlo)); for (auto* user : materialized_users) { - TF_RETURN_IF_ERROR(hlo->ReplaceUseWith(user, convert)); + if (user->opcode() == HloOpcode::kConvert && + user->shape().element_type() == F32) { + TF_RETURN_IF_ERROR(user->ReplaceAllUsesWith(hlo)); + } else { + TF_RETURN_IF_ERROR(hlo->ReplaceUseWith(user, convert)); + } } if (is_root) { computation->set_root_instruction(convert); @@ -205,6 +210,28 @@ Status BFloat16NormalizationVisitor::HandleMultipleOutputs( return Status::OK(); } + std::vector bf16_called_comps; + for (auto* comp : hlo->called_computations()) { + bool comp_has_bf16 = false; + if (comp->root_instruction()->shape().element_type() == F32) { + f32_count += 1; + } else if (comp->root_instruction()->shape().element_type() == BF16) { + bf16_count += 1; + comp_has_bf16 = true; + } + for (auto* param : comp->parameter_instructions()) { + if (param->shape().element_type() == F32) { + f32_count += 1; + } else if (param->shape().element_type() == BF16) { + bf16_count += 1; + comp_has_bf16 = true; + } + } + if (comp_has_bf16) { + bf16_called_comps.push_back(comp); + } + } + std::vector materialized_users = hlo->users(); std::vector output_elements(hlo->operand_count()); auto original_shape = hlo->shape(); @@ -236,7 +263,7 @@ Status BFloat16NormalizationVisitor::HandleMultipleOutputs( computation_->set_root_instruction(tuple); } *tuple->mutable_shape() = original_shape; - return Status::OK(); + return ConvertCalledComputations(hlo, bf16_called_comps); } Status BFloat16NormalizationVisitor::HandleInstruction(HloInstruction* hlo) { diff --git a/tensorflow/compiler/xla/service/bfloat16_normalization_test.cc b/tensorflow/compiler/xla/service/bfloat16_normalization_test.cc index 2caa979745..7dd46ca4e0 100644 --- a/tensorflow/compiler/xla/service/bfloat16_normalization_test.cc +++ b/tensorflow/compiler/xla/service/bfloat16_normalization_test.cc @@ -326,6 +326,14 @@ TEST_F(BFloat16NormalizationTest, ResolveMixedPrecisionTupleSortRoot) { EXPECT_EQ(ShapeUtil::GetSubshape(sort->shape(), {0}).element_type(), F32); EXPECT_NE(computation->root_instruction(), sort); EXPECT_EQ(computation->root_instruction()->opcode(), HloOpcode::kTuple); + EXPECT_EQ(sort->to_apply()->parameter_instruction(1)->shape().element_type(), + F32); + // Make sure that no convert to BF16 was added to the 'to_apply' comparison + // computation. + auto users = sort->to_apply()->parameter_instruction(1)->users(); + for (auto user : users) { + EXPECT_NE(user->opcode(), HloOpcode::kConvert); + } } // Tests that the normalization should not cause unsupported mixed precision due -- GitLab From e9a09aafb6c9098fa514eb48a0ab41a729c5a733 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 25 Feb 2019 01:03:10 -0800 Subject: [PATCH 436/766] compat: Update forward compatibility horizon to 2019-02-25 PiperOrigin-RevId: 235484830 --- tensorflow/python/compat/compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/compat/compat.py b/tensorflow/python/compat/compat.py index 5e886dc3e8..d949d20368 100644 --- a/tensorflow/python/compat/compat.py +++ b/tensorflow/python/compat/compat.py @@ -27,7 +27,7 @@ import datetime from tensorflow.python.util import tf_contextlib from tensorflow.python.util.tf_export import tf_export -_FORWARD_COMPATIBILITY_HORIZON = datetime.date(2019, 2, 24) +_FORWARD_COMPATIBILITY_HORIZON = datetime.date(2019, 2, 25) @tf_export("compat.forward_compatible") -- GitLab From 9d8bc6b2cecc094408e9ece13312ee1b059ddb78 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 25 Feb 2019 01:06:43 -0800 Subject: [PATCH 437/766] Multi-threading reduce_mean quantized using gemmlowp::tasks & optimized using neon. PiperOrigin-RevId: 235485786 --- .../internal/optimized/optimized_ops.h | 217 +++++++++++++++++- tensorflow/lite/kernels/reduce.cc | 11 +- tensorflow/lite/kernels/reduce_test.cc | 23 +- 3 files changed, 245 insertions(+), 6 deletions(-) diff --git a/tensorflow/lite/kernels/internal/optimized/optimized_ops.h b/tensorflow/lite/kernels/internal/optimized/optimized_ops.h index 6e8390ce55..723aa7abd2 100644 --- a/tensorflow/lite/kernels/internal/optimized/optimized_ops.h +++ b/tensorflow/lite/kernels/internal/optimized/optimized_ops.h @@ -20,6 +20,7 @@ limitations under the License. #include #include #include +#include #include #include #include @@ -76,7 +77,6 @@ using reference_ops::Less; using reference_ops::LessEqual; using reference_ops::LessEqualWithScaling; using reference_ops::LessWithScaling; -using reference_ops::Mean; using reference_ops::ProcessBroadcastShapes; using reference_ops::RankOneSelect; using reference_ops::Relu1; @@ -1743,6 +1743,221 @@ inline void ShuffledFullyConnected( gemm_context->workers_pool()->Execute(tasks); } +inline void MeanImpl(const tflite::MeanParams& op_params, + const RuntimeShape& input_shape, const uint8_t* input_data, + int32 input_zero_point, float input_scale, + const RuntimeShape& output_shape, uint8_t* output_data, + int32 output_zero_point, float output_scale, + int start_depth, int end_depth) { + gemmlowp::ScopedProfilingLabel label("Mean4D/Uint8/MeanImpl"); + + // Current implementation only supports dimension equals 4 and simultaneous + // reduction over width and height. + const int output_batch = output_shape.Dims(0); + const int output_height = output_shape.Dims(2); + const int output_width = output_shape.Dims(2); + const int input_height = input_shape.Dims(1); + const int input_width = input_shape.Dims(2); + const float num_elements_in_axis = input_width * input_height; + + TFLITE_DCHECK_EQ(op_params.axis_count, 2); + TFLITE_DCHECK((op_params.axis[0] == 1 && op_params.axis[1] == 2) || + (op_params.axis[0] == 2 && op_params.axis[1] == 1)); + TFLITE_DCHECK_EQ(output_height, 1); + TFLITE_DCHECK_EQ(output_width, 1); + + const bool ordinary_mean = + (input_zero_point == output_zero_point && input_scale == output_scale); + float scale, bias; + if (!ordinary_mean) { + scale = input_scale / output_scale; + bias = -input_zero_point * scale + 0.5; + } + +#ifdef USE_NEON + const float32x4_t num_elements_dup = vdupq_n_f32(num_elements_in_axis); + // This is only an approximation as NEON does not offer division instruction. + const float32x4_t num_elements_reverse = vrecpeq_f32(num_elements_dup); + const float32x4_t kRounding = vdupq_n_f32(0.5); + float32x4_t bias_dup; + float32x4_t output_zero_point_dup; + if (!ordinary_mean) { + bias_dup = vdupq_n_f32(bias); + output_zero_point_dup = vdupq_n_f32(output_zero_point); + } +#endif + + for (int out_b = 0; out_b < output_batch; ++out_b) { + int out_d = start_depth; +#ifdef USE_NEON + + for (; out_d < end_depth - 8; out_d += 8) { + float32x4_t temp_sum_1 = vdupq_n_f32(0); + float32x4_t temp_sum_2 = vdupq_n_f32(0); + for (int in_h = 0; in_h < input_height; ++in_h) { + for (int in_w = 0; in_w < input_width; ++in_w) { + const uint8_t* input_data_ptr = + input_data + Offset(input_shape, out_b, in_h, in_w, out_d); + uint8x8_t input_data_val = vld1_u8(input_data_ptr); + int16x8_t input_data_val_shift = + vreinterpretq_s16_u16(vmovl_u8(input_data_val)); + float32x4_t input_float_1 = + vcvtq_f32_s32(vmovl_s16(vget_high_s16(input_data_val_shift))); + float32x4_t input_float_2 = + vcvtq_f32_s32(vmovl_s16(vget_low_s16(input_data_val_shift))); + temp_sum_1 = vaddq_f32(temp_sum_1, input_float_1); + temp_sum_2 = vaddq_f32(temp_sum_2, input_float_2); + } + } + + float32x4_t mean_1 = vmulq_f32(temp_sum_1, num_elements_reverse); + float32x4_t mean_2 = vmulq_f32(temp_sum_2, num_elements_reverse); + + if (!ordinary_mean) { + // maq is not supported, break down into two ops. + mean_1 = vmulq_n_f32(mean_1, scale); + mean_1 = vaddq_f32(mean_1, bias_dup); + mean_2 = vmulq_n_f32(mean_2, scale); + mean_2 = vaddq_f32(mean_2, bias_dup); + } + + if (!ordinary_mean) { + mean_1 = vaddq_f32(mean_1, output_zero_point_dup); + mean_2 = vaddq_f32(mean_2, output_zero_point_dup); + } + + // Rounding. + mean_1 = vaddq_f32(mean_1, kRounding); + mean_2 = vaddq_f32(mean_2, kRounding); + uint32x4_t casted_mean_1 = vcvtq_u32_f32(mean_1); + uint16x4_t narrow_range_mean_1 = vmovn_u32(casted_mean_1); + uint32x4_t casted_mean_2 = vcvtq_u32_f32(mean_2); + uint16x4_t narrow_range_mean_2 = vmovn_u32(casted_mean_2); + uint16x8_t combined_mean = + vcombine_u16(narrow_range_mean_2, narrow_range_mean_1); + uint8x8_t narrowed_combined_mean = vmovn_u16(combined_mean); + uint8_t* output_data_ptr = + output_data + Offset(output_shape, out_b, 0, 0, out_d); + vst1_u8(output_data_ptr, narrowed_combined_mean); + } +#endif + + for (; out_d < end_depth; ++out_d) { + float temp_value = 0; + for (int in_h = 0; in_h < input_height; ++in_h) { + for (int in_w = 0; in_w < input_width; ++in_w) { + temp_value += + input_data[Offset(input_shape, out_b, in_h, in_w, out_d)]; + } + } + + temp_value = temp_value / num_elements_in_axis; + if (ordinary_mean) { + output_data[Offset(output_shape, out_b, 0, 0, out_d)] = + static_cast(round(temp_value)); + } else { + output_data[Offset(output_shape, out_b, 0, 0, out_d)] = + static_cast(round(temp_value * scale + bias)) + + output_zero_point; + } + } + } +} + +struct MeanWorkerTask : public gemmlowp::Task { + MeanWorkerTask(const tflite::MeanParams& op_params, + const RuntimeShape& input_shape, const uint8_t* input_data, + int32 input_zero_point, float input_scale, + const RuntimeShape& output_shape, uint8_t* output_data, + int32 output_zero_point, float output_scale, int start_height, + int end_height) + : op_params_(op_params), + input_shape_(input_shape), + input_data_(input_data), + input_zero_point_(input_zero_point), + input_scale_(input_scale), + output_shape_(output_shape), + output_data_(output_data), + output_zero_point_(output_zero_point), + output_scale_(output_scale), + start_height_(start_height), + end_height_(end_height) {} + + void Run() override { + MeanImpl(op_params_, input_shape_, input_data_, input_zero_point_, + input_scale_, output_shape_, output_data_, output_zero_point_, + output_scale_, start_height_, end_height_); + } + + private: + const tflite::MeanParams& op_params_; + const RuntimeShape& input_shape_; + const uint8_t* input_data_; + int32 input_zero_point_; + float input_scale_; + const RuntimeShape& output_shape_; + uint8_t* output_data_; + int32 output_zero_point_; + float output_scale_; + int start_height_; + int end_height_; + gemmlowp::GemmContext* gemm_context_; +}; + +inline void Mean(const tflite::MeanParams& op_params, + const RuntimeShape& unextended_input_shape, + const uint8_t* input_data, int32 input_zero_point, + float input_scale, const RuntimeShape& unextended_output_shape, + uint8_t* output_data, int32 output_zero_point, + float output_scale, gemmlowp::GemmContext* gemm_context) { + gemmlowp::ScopedProfilingLabel label("Mean4D/Uint8"); + + // Current implementation only supports dimension equals 4 and simultaneous + // reduction over width and height. + TFLITE_CHECK_EQ(unextended_input_shape.DimensionsCount(), 4); + TFLITE_CHECK_LE(unextended_output_shape.DimensionsCount(), 4); + const RuntimeShape input_shape = + RuntimeShape::ExtendedShape(4, unextended_input_shape); + const RuntimeShape output_shape = + RuntimeShape::ExtendedShape(4, unextended_output_shape); + const int output_height = output_shape.Dims(1); + const int output_width = output_shape.Dims(2); + const int output_depth = output_shape.Dims(3); + + TFLITE_DCHECK_EQ(op_params.axis_count, 2); + TFLITE_DCHECK((op_params.axis[0] == 1 && op_params.axis[1] == 2) || + (op_params.axis[0] == 2 && op_params.axis[1] == 1)); + TFLITE_DCHECK_EQ(output_height, 1); + TFLITE_DCHECK_EQ(output_width, 1); + + constexpr int kMinDepthPerThread = 8; + int thread_count = output_depth / kMinDepthPerThread; + thread_count = thread_count > 0 ? thread_count : 1; + const int capped_thread_count = + std::min(thread_count, gemm_context->max_num_threads()); + + if (thread_count == 1) { + MeanImpl(op_params, input_shape, input_data, input_zero_point, input_scale, + output_shape, output_data, output_zero_point, output_scale, 0, + output_depth); + } else { + // Instead parrallel for batch, we loop for the output_depth since batch + // is typical 1. + std::vector tasks(capped_thread_count); + int depth_start = 0; + for (int i = 0; i < capped_thread_count; ++i) { + // Try to distribute the tasks as even as possible. + int depth_end = (output_depth - depth_start) / (capped_thread_count - i); + tasks[i] = new MeanWorkerTask(op_params, input_shape, input_data, + input_zero_point, input_scale, output_shape, + output_data, output_zero_point, + output_scale, depth_start, depth_end); + depth_start = depth_end; + } + gemm_context->workers_pool()->Execute(tasks); + } +} + template inline void ExtractPatchIntoBufferColumn(const RuntimeShape& input_shape, int w, int h, int b, int kheight, int kwidth, diff --git a/tensorflow/lite/kernels/reduce.cc b/tensorflow/lite/kernels/reduce.cc index 3fb2715e48..95d2370aaa 100644 --- a/tensorflow/lite/kernels/reduce.cc +++ b/tensorflow/lite/kernels/reduce.cc @@ -17,6 +17,8 @@ limitations under the License. #include #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/c_api_internal.h" +#include "tensorflow/lite/kernels/gemm_support.h" +#include "tensorflow/lite/kernels/internal/optimized/optimized_ops.h" #include "tensorflow/lite/kernels/internal/quantization_util.h" #include "tensorflow/lite/kernels/internal/reference/reference_ops.h" #include "tensorflow/lite/kernels/internal/tensor.h" @@ -49,6 +51,7 @@ struct OpContext { }; void* Init(TfLiteContext* context, const char* buffer, size_t length) { + gemm_support::IncrementUsageCounter(context); // Creates two temp tensors to store index and axis for internal // implementation only. auto* scratch_tensor_index = new int; @@ -57,6 +60,7 @@ void* Init(TfLiteContext* context, const char* buffer, size_t length) { } void Free(TfLiteContext* context, void* buffer) { + gemm_support::DecrementUsageCounter(context); delete reinterpret_cast(buffer); } @@ -248,6 +252,7 @@ void ResolveAxis(const int* axis_data, int axis_count, template TfLiteStatus EvalMean(TfLiteContext* context, TfLiteNode* node) { OpContext op_context(context, node); + int num_axis = static_cast(NumElements(op_context.axis)); TfLiteTensor* temp_index = GetTemporary(context, node, /*index=*/0); TfLiteTensor* resolved_axis = GetTemporary(context, node, /*index=*/1); @@ -272,13 +277,15 @@ TfLiteStatus EvalMean(TfLiteContext* context, TfLiteNode* node) { ((op_params.axis[0] == 1 && op_params.axis[1] == 2) || (op_params.axis[0] == 2 && op_params.axis[1] == 1))) { if (op_context.input->type == kTfLiteUInt8) { - reference_ops::Mean( + gemmlowp::GemmContext* gemm_context = + gemm_support::GetFromContext(context); + optimized_ops::Mean( op_params, GetTensorShape(input), GetTensorData(input), op_context.input->params.zero_point, op_context.input->params.scale, GetTensorShape(op_context.output), GetTensorData(op_context.output), op_context.output->params.zero_point, - op_context.output->params.scale); + op_context.output->params.scale, gemm_context); } else { reference_ops::Mean(op_params, GetTensorShape(input), GetTensorData(input), diff --git a/tensorflow/lite/kernels/reduce_test.cc b/tensorflow/lite/kernels/reduce_test.cc index 373fa56fc1..f9f3cdecbd 100644 --- a/tensorflow/lite/kernels/reduce_test.cc +++ b/tensorflow/lite/kernels/reduce_test.cc @@ -259,7 +259,7 @@ TEST(ConstFloatMeanOpTest, KeepDims) { // Uses a set of reduction conditions that trigger the specialized 4D version // of Mean. -TEST(ConstFloatMeanOpTest, KeepDims_4DMean) { +TEST(ConstFloatMeanOpTest, KeepDims4DMean) { std::vector data = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0}; @@ -272,7 +272,7 @@ TEST(ConstFloatMeanOpTest, KeepDims_4DMean) { ElementsAreArray(ArrayFloatNear({6, 7, 18, 19}))); } -TEST(ConstFloatMeanOpTest, KeepDims_4DMean_UInt8) { +TEST(ConstFloatMeanOpTest, KeepDims4DMeanUInt8) { float kQuantizedTolerance = GetTolerance(-1.0, 1.0); std::vector data = {0.1, 0.2, 0.3, 0.4, 0.1, 0.2, 0.3, 0.4, 0.1, 0.2, 0.3, 0.4}; @@ -286,7 +286,24 @@ TEST(ConstFloatMeanOpTest, KeepDims_4DMean_UInt8) { kQuantizedTolerance))); } -TEST(ConstFloatMeanOpTest, KeepDims_4DMean_Quantized) { +TEST(ConstFloatMeanOpTest, KeepDims4DMeanLargeDepthUInt8) { + float kQuantizedTolerance = GetTolerance(-5.0, 5.0); + std::vector data = {0.1, 0.2, 0.3, 0.4, 0.2, 0.3, 0.4, 0.5, 0.1, + 0.1, 0.1, 0.1, 0.4, 0.2, 0.2, 0.2, 0.9, 0.9, + 0.9, 0.9, 0.2, 0.3, 0.7, 0.7, 0.1, 0.1, 0.3, + 0.3, 0.1, 0.2, 0.3, 0.4, 0.1, 0.2, 0.3, 0.4}; + MeanOpConstModel m({TensorType_UINT8, {1, 2, 2, 9}, -1.0, 1.0}, + {TensorType_UINT8, {2}, -1.0, 1.0}, {2}, {1, 2}, true); + m.QuantizeAndPopulate(m.Input(), data); + m.Invoke(); + EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 1, 1, 9})); + EXPECT_THAT(m.GetDequantizedOutput(), + ElementsAreArray(ArrayFloatNear( + {0.35, 0.325, 0.2, 0.35, 0.375, 0.325, 0.225, 0.45, 0.425}, + kQuantizedTolerance))); +} + +TEST(ConstFloatMeanOpTest, KeepDims4DMeanQuantized) { float kQuantizedTolerance = GetTolerance(-5.0, 5.0); std::vector data = {0.1, 0.2, 0.3, 0.4, 0.1, 0.2, 0.3, 0.4, 0.1, 0.2, 0.3, 0.4}; -- GitLab From 59f19d31b2bdb7e79373efe1772a27c56ac61393 Mon Sep 17 00:00:00 2001 From: Albin Joy Date: Mon, 25 Feb 2019 14:45:24 +0530 Subject: [PATCH 438/766] Updated lite_test.py --- tensorflow/lite/python/lite_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/python/lite_test.py b/tensorflow/lite/python/lite_test.py index d41b7a75fd..8761ceffe9 100644 --- a/tensorflow/lite/python/lite_test.py +++ b/tensorflow/lite/python/lite_test.py @@ -741,7 +741,7 @@ class FromFrozenGraphFile(test_util.TensorFlowTestCase): # TODO(nupurgarg): Test model loading in open source. def _initObjectDetectionArgs(self): # Initializes the arguments required for the object detection model. - # Looks for the model file which is saved in a different location interally + # Looks for the model file which is saved in a different location internally # and externally. filename = resource_loader.get_path_to_datafile('testdata/tflite_graph.pb') if not os.path.exists(filename): -- GitLab From ad675e014848c2262c1532d9cb7000a216911a45 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 25 Feb 2019 01:29:18 -0800 Subject: [PATCH 439/766] Automated rollback of commit 9d8bc6b2cecc094408e9ece13312ee1b059ddb78 PiperOrigin-RevId: 235488332 --- .../internal/optimized/optimized_ops.h | 217 +----------------- tensorflow/lite/kernels/reduce.cc | 11 +- tensorflow/lite/kernels/reduce_test.cc | 23 +- 3 files changed, 6 insertions(+), 245 deletions(-) diff --git a/tensorflow/lite/kernels/internal/optimized/optimized_ops.h b/tensorflow/lite/kernels/internal/optimized/optimized_ops.h index 723aa7abd2..6e8390ce55 100644 --- a/tensorflow/lite/kernels/internal/optimized/optimized_ops.h +++ b/tensorflow/lite/kernels/internal/optimized/optimized_ops.h @@ -20,7 +20,6 @@ limitations under the License. #include #include #include -#include #include #include #include @@ -77,6 +76,7 @@ using reference_ops::Less; using reference_ops::LessEqual; using reference_ops::LessEqualWithScaling; using reference_ops::LessWithScaling; +using reference_ops::Mean; using reference_ops::ProcessBroadcastShapes; using reference_ops::RankOneSelect; using reference_ops::Relu1; @@ -1743,221 +1743,6 @@ inline void ShuffledFullyConnected( gemm_context->workers_pool()->Execute(tasks); } -inline void MeanImpl(const tflite::MeanParams& op_params, - const RuntimeShape& input_shape, const uint8_t* input_data, - int32 input_zero_point, float input_scale, - const RuntimeShape& output_shape, uint8_t* output_data, - int32 output_zero_point, float output_scale, - int start_depth, int end_depth) { - gemmlowp::ScopedProfilingLabel label("Mean4D/Uint8/MeanImpl"); - - // Current implementation only supports dimension equals 4 and simultaneous - // reduction over width and height. - const int output_batch = output_shape.Dims(0); - const int output_height = output_shape.Dims(2); - const int output_width = output_shape.Dims(2); - const int input_height = input_shape.Dims(1); - const int input_width = input_shape.Dims(2); - const float num_elements_in_axis = input_width * input_height; - - TFLITE_DCHECK_EQ(op_params.axis_count, 2); - TFLITE_DCHECK((op_params.axis[0] == 1 && op_params.axis[1] == 2) || - (op_params.axis[0] == 2 && op_params.axis[1] == 1)); - TFLITE_DCHECK_EQ(output_height, 1); - TFLITE_DCHECK_EQ(output_width, 1); - - const bool ordinary_mean = - (input_zero_point == output_zero_point && input_scale == output_scale); - float scale, bias; - if (!ordinary_mean) { - scale = input_scale / output_scale; - bias = -input_zero_point * scale + 0.5; - } - -#ifdef USE_NEON - const float32x4_t num_elements_dup = vdupq_n_f32(num_elements_in_axis); - // This is only an approximation as NEON does not offer division instruction. - const float32x4_t num_elements_reverse = vrecpeq_f32(num_elements_dup); - const float32x4_t kRounding = vdupq_n_f32(0.5); - float32x4_t bias_dup; - float32x4_t output_zero_point_dup; - if (!ordinary_mean) { - bias_dup = vdupq_n_f32(bias); - output_zero_point_dup = vdupq_n_f32(output_zero_point); - } -#endif - - for (int out_b = 0; out_b < output_batch; ++out_b) { - int out_d = start_depth; -#ifdef USE_NEON - - for (; out_d < end_depth - 8; out_d += 8) { - float32x4_t temp_sum_1 = vdupq_n_f32(0); - float32x4_t temp_sum_2 = vdupq_n_f32(0); - for (int in_h = 0; in_h < input_height; ++in_h) { - for (int in_w = 0; in_w < input_width; ++in_w) { - const uint8_t* input_data_ptr = - input_data + Offset(input_shape, out_b, in_h, in_w, out_d); - uint8x8_t input_data_val = vld1_u8(input_data_ptr); - int16x8_t input_data_val_shift = - vreinterpretq_s16_u16(vmovl_u8(input_data_val)); - float32x4_t input_float_1 = - vcvtq_f32_s32(vmovl_s16(vget_high_s16(input_data_val_shift))); - float32x4_t input_float_2 = - vcvtq_f32_s32(vmovl_s16(vget_low_s16(input_data_val_shift))); - temp_sum_1 = vaddq_f32(temp_sum_1, input_float_1); - temp_sum_2 = vaddq_f32(temp_sum_2, input_float_2); - } - } - - float32x4_t mean_1 = vmulq_f32(temp_sum_1, num_elements_reverse); - float32x4_t mean_2 = vmulq_f32(temp_sum_2, num_elements_reverse); - - if (!ordinary_mean) { - // maq is not supported, break down into two ops. - mean_1 = vmulq_n_f32(mean_1, scale); - mean_1 = vaddq_f32(mean_1, bias_dup); - mean_2 = vmulq_n_f32(mean_2, scale); - mean_2 = vaddq_f32(mean_2, bias_dup); - } - - if (!ordinary_mean) { - mean_1 = vaddq_f32(mean_1, output_zero_point_dup); - mean_2 = vaddq_f32(mean_2, output_zero_point_dup); - } - - // Rounding. - mean_1 = vaddq_f32(mean_1, kRounding); - mean_2 = vaddq_f32(mean_2, kRounding); - uint32x4_t casted_mean_1 = vcvtq_u32_f32(mean_1); - uint16x4_t narrow_range_mean_1 = vmovn_u32(casted_mean_1); - uint32x4_t casted_mean_2 = vcvtq_u32_f32(mean_2); - uint16x4_t narrow_range_mean_2 = vmovn_u32(casted_mean_2); - uint16x8_t combined_mean = - vcombine_u16(narrow_range_mean_2, narrow_range_mean_1); - uint8x8_t narrowed_combined_mean = vmovn_u16(combined_mean); - uint8_t* output_data_ptr = - output_data + Offset(output_shape, out_b, 0, 0, out_d); - vst1_u8(output_data_ptr, narrowed_combined_mean); - } -#endif - - for (; out_d < end_depth; ++out_d) { - float temp_value = 0; - for (int in_h = 0; in_h < input_height; ++in_h) { - for (int in_w = 0; in_w < input_width; ++in_w) { - temp_value += - input_data[Offset(input_shape, out_b, in_h, in_w, out_d)]; - } - } - - temp_value = temp_value / num_elements_in_axis; - if (ordinary_mean) { - output_data[Offset(output_shape, out_b, 0, 0, out_d)] = - static_cast(round(temp_value)); - } else { - output_data[Offset(output_shape, out_b, 0, 0, out_d)] = - static_cast(round(temp_value * scale + bias)) + - output_zero_point; - } - } - } -} - -struct MeanWorkerTask : public gemmlowp::Task { - MeanWorkerTask(const tflite::MeanParams& op_params, - const RuntimeShape& input_shape, const uint8_t* input_data, - int32 input_zero_point, float input_scale, - const RuntimeShape& output_shape, uint8_t* output_data, - int32 output_zero_point, float output_scale, int start_height, - int end_height) - : op_params_(op_params), - input_shape_(input_shape), - input_data_(input_data), - input_zero_point_(input_zero_point), - input_scale_(input_scale), - output_shape_(output_shape), - output_data_(output_data), - output_zero_point_(output_zero_point), - output_scale_(output_scale), - start_height_(start_height), - end_height_(end_height) {} - - void Run() override { - MeanImpl(op_params_, input_shape_, input_data_, input_zero_point_, - input_scale_, output_shape_, output_data_, output_zero_point_, - output_scale_, start_height_, end_height_); - } - - private: - const tflite::MeanParams& op_params_; - const RuntimeShape& input_shape_; - const uint8_t* input_data_; - int32 input_zero_point_; - float input_scale_; - const RuntimeShape& output_shape_; - uint8_t* output_data_; - int32 output_zero_point_; - float output_scale_; - int start_height_; - int end_height_; - gemmlowp::GemmContext* gemm_context_; -}; - -inline void Mean(const tflite::MeanParams& op_params, - const RuntimeShape& unextended_input_shape, - const uint8_t* input_data, int32 input_zero_point, - float input_scale, const RuntimeShape& unextended_output_shape, - uint8_t* output_data, int32 output_zero_point, - float output_scale, gemmlowp::GemmContext* gemm_context) { - gemmlowp::ScopedProfilingLabel label("Mean4D/Uint8"); - - // Current implementation only supports dimension equals 4 and simultaneous - // reduction over width and height. - TFLITE_CHECK_EQ(unextended_input_shape.DimensionsCount(), 4); - TFLITE_CHECK_LE(unextended_output_shape.DimensionsCount(), 4); - const RuntimeShape input_shape = - RuntimeShape::ExtendedShape(4, unextended_input_shape); - const RuntimeShape output_shape = - RuntimeShape::ExtendedShape(4, unextended_output_shape); - const int output_height = output_shape.Dims(1); - const int output_width = output_shape.Dims(2); - const int output_depth = output_shape.Dims(3); - - TFLITE_DCHECK_EQ(op_params.axis_count, 2); - TFLITE_DCHECK((op_params.axis[0] == 1 && op_params.axis[1] == 2) || - (op_params.axis[0] == 2 && op_params.axis[1] == 1)); - TFLITE_DCHECK_EQ(output_height, 1); - TFLITE_DCHECK_EQ(output_width, 1); - - constexpr int kMinDepthPerThread = 8; - int thread_count = output_depth / kMinDepthPerThread; - thread_count = thread_count > 0 ? thread_count : 1; - const int capped_thread_count = - std::min(thread_count, gemm_context->max_num_threads()); - - if (thread_count == 1) { - MeanImpl(op_params, input_shape, input_data, input_zero_point, input_scale, - output_shape, output_data, output_zero_point, output_scale, 0, - output_depth); - } else { - // Instead parrallel for batch, we loop for the output_depth since batch - // is typical 1. - std::vector tasks(capped_thread_count); - int depth_start = 0; - for (int i = 0; i < capped_thread_count; ++i) { - // Try to distribute the tasks as even as possible. - int depth_end = (output_depth - depth_start) / (capped_thread_count - i); - tasks[i] = new MeanWorkerTask(op_params, input_shape, input_data, - input_zero_point, input_scale, output_shape, - output_data, output_zero_point, - output_scale, depth_start, depth_end); - depth_start = depth_end; - } - gemm_context->workers_pool()->Execute(tasks); - } -} - template inline void ExtractPatchIntoBufferColumn(const RuntimeShape& input_shape, int w, int h, int b, int kheight, int kwidth, diff --git a/tensorflow/lite/kernels/reduce.cc b/tensorflow/lite/kernels/reduce.cc index 95d2370aaa..3fb2715e48 100644 --- a/tensorflow/lite/kernels/reduce.cc +++ b/tensorflow/lite/kernels/reduce.cc @@ -17,8 +17,6 @@ limitations under the License. #include #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/c_api_internal.h" -#include "tensorflow/lite/kernels/gemm_support.h" -#include "tensorflow/lite/kernels/internal/optimized/optimized_ops.h" #include "tensorflow/lite/kernels/internal/quantization_util.h" #include "tensorflow/lite/kernels/internal/reference/reference_ops.h" #include "tensorflow/lite/kernels/internal/tensor.h" @@ -51,7 +49,6 @@ struct OpContext { }; void* Init(TfLiteContext* context, const char* buffer, size_t length) { - gemm_support::IncrementUsageCounter(context); // Creates two temp tensors to store index and axis for internal // implementation only. auto* scratch_tensor_index = new int; @@ -60,7 +57,6 @@ void* Init(TfLiteContext* context, const char* buffer, size_t length) { } void Free(TfLiteContext* context, void* buffer) { - gemm_support::DecrementUsageCounter(context); delete reinterpret_cast(buffer); } @@ -252,7 +248,6 @@ void ResolveAxis(const int* axis_data, int axis_count, template TfLiteStatus EvalMean(TfLiteContext* context, TfLiteNode* node) { OpContext op_context(context, node); - int num_axis = static_cast(NumElements(op_context.axis)); TfLiteTensor* temp_index = GetTemporary(context, node, /*index=*/0); TfLiteTensor* resolved_axis = GetTemporary(context, node, /*index=*/1); @@ -277,15 +272,13 @@ TfLiteStatus EvalMean(TfLiteContext* context, TfLiteNode* node) { ((op_params.axis[0] == 1 && op_params.axis[1] == 2) || (op_params.axis[0] == 2 && op_params.axis[1] == 1))) { if (op_context.input->type == kTfLiteUInt8) { - gemmlowp::GemmContext* gemm_context = - gemm_support::GetFromContext(context); - optimized_ops::Mean( + reference_ops::Mean( op_params, GetTensorShape(input), GetTensorData(input), op_context.input->params.zero_point, op_context.input->params.scale, GetTensorShape(op_context.output), GetTensorData(op_context.output), op_context.output->params.zero_point, - op_context.output->params.scale, gemm_context); + op_context.output->params.scale); } else { reference_ops::Mean(op_params, GetTensorShape(input), GetTensorData(input), diff --git a/tensorflow/lite/kernels/reduce_test.cc b/tensorflow/lite/kernels/reduce_test.cc index f9f3cdecbd..373fa56fc1 100644 --- a/tensorflow/lite/kernels/reduce_test.cc +++ b/tensorflow/lite/kernels/reduce_test.cc @@ -259,7 +259,7 @@ TEST(ConstFloatMeanOpTest, KeepDims) { // Uses a set of reduction conditions that trigger the specialized 4D version // of Mean. -TEST(ConstFloatMeanOpTest, KeepDims4DMean) { +TEST(ConstFloatMeanOpTest, KeepDims_4DMean) { std::vector data = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0}; @@ -272,7 +272,7 @@ TEST(ConstFloatMeanOpTest, KeepDims4DMean) { ElementsAreArray(ArrayFloatNear({6, 7, 18, 19}))); } -TEST(ConstFloatMeanOpTest, KeepDims4DMeanUInt8) { +TEST(ConstFloatMeanOpTest, KeepDims_4DMean_UInt8) { float kQuantizedTolerance = GetTolerance(-1.0, 1.0); std::vector data = {0.1, 0.2, 0.3, 0.4, 0.1, 0.2, 0.3, 0.4, 0.1, 0.2, 0.3, 0.4}; @@ -286,24 +286,7 @@ TEST(ConstFloatMeanOpTest, KeepDims4DMeanUInt8) { kQuantizedTolerance))); } -TEST(ConstFloatMeanOpTest, KeepDims4DMeanLargeDepthUInt8) { - float kQuantizedTolerance = GetTolerance(-5.0, 5.0); - std::vector data = {0.1, 0.2, 0.3, 0.4, 0.2, 0.3, 0.4, 0.5, 0.1, - 0.1, 0.1, 0.1, 0.4, 0.2, 0.2, 0.2, 0.9, 0.9, - 0.9, 0.9, 0.2, 0.3, 0.7, 0.7, 0.1, 0.1, 0.3, - 0.3, 0.1, 0.2, 0.3, 0.4, 0.1, 0.2, 0.3, 0.4}; - MeanOpConstModel m({TensorType_UINT8, {1, 2, 2, 9}, -1.0, 1.0}, - {TensorType_UINT8, {2}, -1.0, 1.0}, {2}, {1, 2}, true); - m.QuantizeAndPopulate(m.Input(), data); - m.Invoke(); - EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 1, 1, 9})); - EXPECT_THAT(m.GetDequantizedOutput(), - ElementsAreArray(ArrayFloatNear( - {0.35, 0.325, 0.2, 0.35, 0.375, 0.325, 0.225, 0.45, 0.425}, - kQuantizedTolerance))); -} - -TEST(ConstFloatMeanOpTest, KeepDims4DMeanQuantized) { +TEST(ConstFloatMeanOpTest, KeepDims_4DMean_Quantized) { float kQuantizedTolerance = GetTolerance(-5.0, 5.0); std::vector data = {0.1, 0.2, 0.3, 0.4, 0.1, 0.2, 0.3, 0.4, 0.1, 0.2, 0.3, 0.4}; -- GitLab From 64e7e114ef1757fad52eaeedb3f22216165587ab Mon Sep 17 00:00:00 2001 From: Benjamin Kramer Date: Mon, 25 Feb 2019 02:54:45 -0800 Subject: [PATCH 440/766] [XLA] Implement elemental LLVM codegen for replica-id This is a stub implementation that always returns zero as replication is not supported on CPU or GPU currently. PiperOrigin-RevId: 235497567 --- tensorflow/compiler/xla/python/xla_client_test.py | 3 +-- tensorflow/compiler/xla/service/elemental_ir_emitter.cc | 9 +++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/tensorflow/compiler/xla/python/xla_client_test.py b/tensorflow/compiler/xla/python/xla_client_test.py index 745f7b2093..4e9e959fe6 100644 --- a/tensorflow/compiler/xla/python/xla_client_test.py +++ b/tensorflow/compiler/xla/python/xla_client_test.py @@ -603,8 +603,7 @@ class SingleOpTest(ComputationTest): c.CrossReplicaSum(c.Constant(lhs)) self._ExecuteAndCompareExact(c, expected=lhs) - def DISABLED_testReplicaId(self): - # TODO(b/125740859): enable this test + def testReplicaId(self): c = self._NewComputation() _ = c.ReplicaId() self._ExecuteAndCompareExact(c, expected=0) diff --git a/tensorflow/compiler/xla/service/elemental_ir_emitter.cc b/tensorflow/compiler/xla/service/elemental_ir_emitter.cc index a62a743802..926e673d12 100644 --- a/tensorflow/compiler/xla/service/elemental_ir_emitter.cc +++ b/tensorflow/compiler/xla/service/elemental_ir_emitter.cc @@ -2436,6 +2436,15 @@ llvm_ir::ElementGenerator ElementalIrEmitter::MakeElementGenerator( -> StatusOr { return EmitElementalDot(hlo, operand_to_generator, dot_result_index); }; + case HloOpcode::kReplicaId: + return [this, hlo](const IrArray::Index&) -> StatusOr { + if (hlo_module_config_.replica_count() != 1) { + return Unimplemented("Replication is not implemented on CPU/GPU."); + } + llvm::Type* type = llvm_ir::PrimitiveTypeToIrType( + hlo->shape().element_type(), module_); + return llvm::ConstantInt::getNullValue(type); + }; default: return [hlo](const IrArray::Index& index) { return Unimplemented("Unhandled opcode for elemental IR emission: %s", -- GitLab From 0da7f0fd4a063cec61323f0974056c67be5f4bcb Mon Sep 17 00:00:00 2001 From: Benjamin Kramer Date: Mon, 25 Feb 2019 03:28:49 -0800 Subject: [PATCH 441/766] [XLA] Fix and re-enable clamp test. This was fixed in 0bd78003c36dd194083ec22501c2b0b6db208f4c PiperOrigin-RevId: 235501348 --- tensorflow/compiler/xla/python/xla_client_test.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tensorflow/compiler/xla/python/xla_client_test.py b/tensorflow/compiler/xla/python/xla_client_test.py index 4e9e959fe6..3094d48fb0 100644 --- a/tensorflow/compiler/xla/python/xla_client_test.py +++ b/tensorflow/compiler/xla/python/xla_client_test.py @@ -1023,14 +1023,13 @@ class SingleOpTest(ComputationTest): c.Constant(NumpyArrayF32(2))) self._ExecuteAndCompareExact(c, expected=[-1, -1, 0, 1, 2, 2]) - # TODO(b/72689392): re-enable when bug S32 resolved - def DISABLED_testClampS32(self): + def testClampS32(self): c = self._NewComputation() c.Clamp( c.Constant(NumpyArrayS32(-1)), c.Constant(NumpyArrayS32([-2, -1, 0, 1, 2, 3])), c.Constant(NumpyArrayS32(2))) - self._ExecuteAndCompareExact(c, expected=[-1, 0, 1, 2, 2]) + self._ExecuteAndCompareExact(c, expected=[-1, -1, 0, 1, 2, 2]) def testSelect(self): c = self._NewComputation() -- GitLab From 3dada4c8e471e19f155f28eede3b6961bdae83c4 Mon Sep 17 00:00:00 2001 From: Albin Joy Date: Mon, 25 Feb 2019 17:55:40 +0530 Subject: [PATCH 442/766] Updated lstm_eval.cc --- tensorflow/lite/kernels/lstm_eval.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tensorflow/lite/kernels/lstm_eval.cc b/tensorflow/lite/kernels/lstm_eval.cc index 244cfae4a2..045f41bd91 100644 --- a/tensorflow/lite/kernels/lstm_eval.cc +++ b/tensorflow/lite/kernels/lstm_eval.cc @@ -119,7 +119,7 @@ inline void LstmStepWithAuxInput( float* forget_gate_scratch, float* cell_scratch, float* output_gate_scratch, float* output_ptr_batch) { // Since we have already checked that weights are all there or none, we can - // check the existense of only one to the get the condition. + // check the existence of only one to the get the condition. const bool use_cifg = (input_to_input_weights_ptr == nullptr); const bool use_peephole = (cell_to_output_weights_ptr != nullptr); const bool is_layer_norm_lstm = @@ -473,7 +473,7 @@ inline void LstmStepWithAuxInput( int8_t* quantized_cell_state_ptr, float* output_state_ptr, float* cell_state_ptr, float* output_ptr_batch) { // Since we have already checked that weights are all there or none, we - // can check the existense of only one to the get the condition. + // can check the existence of only one to the get the condition. const bool use_cifg = (input_to_input_weights_ptr == nullptr); const bool use_peephole = (cell_to_output_weights_ptr != nullptr); const bool is_layer_norm_lstm = @@ -913,7 +913,7 @@ TfLiteStatus EvalFloat( const int n_output = recurrent_to_output_weights->dims->data[1]; // Since we have already checked that weights are all there or none, we can - // check the existense of only one to the get the condition. + // check the existence of only one to the get the condition. const bool use_cifg = (input_to_input_weights == nullptr); const bool use_peephole = (cell_to_output_weights != nullptr); const bool is_layer_norm_lstm = (forget_layer_norm_coefficients != nullptr); -- GitLab From 8a653bd99c69602675fc9926f201985e65c980be Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 25 Feb 2019 04:29:53 -0800 Subject: [PATCH 443/766] Return Status from CudaLaunchKernel. PiperOrigin-RevId: 235507181 --- .../kernels/adjust_hsv_in_yiq_op_gpu.cu.cc | 8 +- .../mpi_collectives/kernels/ring.cu.cc | 16 ++-- tensorflow/contrib/mpi_collectives/ring.cu.cc | 16 ++-- .../kernels/reduce_slice_ops_gpu.cu.cc | 4 +- .../resampler/kernels/resampler_ops_gpu.cu.cc | 29 +++---- .../contrib/rnn/kernels/lstm_ops_gpu.cu.cc | 39 ++++----- .../seq2seq/kernels/beam_search_ops_gpu.cu.cc | 9 +- .../inc_op_kernel.cu.cc | 4 +- .../core/kernels/adjust_hue_op_gpu.cu.cc | 7 +- .../kernels/adjust_saturation_op_gpu.cu.cc | 7 +- .../core/kernels/avgpooling_op_gpu.cu.cc | 10 +-- tensorflow/core/kernels/bias_op_gpu.cu.cc | 36 ++++---- .../core/kernels/bucketize_op_gpu.cu.cc | 8 +- .../core/kernels/check_numerics_op_gpu.cu.cc | 4 +- .../kernels/compare_and_bitpack_op_gpu.cu.cc | 27 +++--- tensorflow/core/kernels/conv_2d_gpu.h | 58 ++++++------- .../core/kernels/crop_and_resize_op_gpu.cu.cc | 46 +++++----- .../core/kernels/cwise_op_clip_gpu.cu.cc | 24 +++--- .../core/kernels/depthtospace_op_gpu.cu.cc | 51 +++++------ .../core/kernels/depthwise_conv_op_gpu.h | 12 +-- .../core/kernels/determinant_op_gpu.cu.cc | 8 +- .../core/kernels/dilation_ops_gpu.cu.cc | 23 ++--- .../kernels/dynamic_partition_op_gpu.cu.cc | 18 ++-- .../core/kernels/dynamic_stitch_op_gpu.cu.cc | 7 +- .../core/kernels/gather_functor_gpu.cu.h | 16 ++-- .../core/kernels/gather_nd_op_gpu.cu.cc | 9 +- .../kernels/inplace_ops_functor_gpu.cu.cc | 38 ++++----- .../core/kernels/matrix_set_diag_op_gpu.cu.cc | 18 ++-- ...arameterized_truncated_normal_op_gpu.cu.cc | 4 +- .../kernels/population_count_op_gpu.cu.cc | 22 ++--- tensorflow/core/kernels/random_op_gpu.cu.cc | 5 +- .../core/kernels/reduction_gpu_kernels.cu.h | 77 +++++++++-------- .../core/kernels/resize_bilinear_op_gpu.cu.cc | 28 +++---- .../resize_nearest_neighbor_op_gpu.cu.cc | 10 +-- tensorflow/core/kernels/scan_ops_gpu.h | 42 ++++++---- .../core/kernels/scatter_functor_gpu.cu.h | 17 ++-- .../core/kernels/scatter_nd_op_gpu.cu.cc | 9 +- .../core/kernels/searchsorted_op_gpu.cu.cc | 16 ++-- .../kernels/segment_reduction_ops_gpu.cu.cc | 29 ++++--- tensorflow/core/kernels/softmax_op_gpu.cu.cc | 4 +- .../kernels/spacetobatch_functor_gpu.cu.cc | 11 ++- .../core/kernels/spacetodepth_op_gpu.cu.cc | 51 +++++------ .../sparse_tensor_dense_matmul_op_gpu.cu.cc | 9 +- tensorflow/core/kernels/split_lib_gpu.cu.cc | 8 +- .../kernels/stateful_random_ops_gpu.cu.cc | 7 +- tensorflow/core/kernels/tile_functor_gpu.h | 7 +- tensorflow/core/kernels/topk_op_gpu.h | 5 +- .../core/kernels/transpose_functor_gpu.cu.cc | 8 +- tensorflow/core/kernels/where_op_gpu.cu.h | 7 +- .../core/util/cuda_kernel_helper_test.cu.cc | 84 +++++++++++-------- tensorflow/core/util/cuda_launch_config.h | 11 ++- .../adding_an_op/cuda_op_kernel.cu.cc | 3 +- .../builds/user_ops/cuda_op_kernel.cu.cc | 3 +- 53 files changed, 546 insertions(+), 483 deletions(-) diff --git a/tensorflow/contrib/image/kernels/adjust_hsv_in_yiq_op_gpu.cu.cc b/tensorflow/contrib/image/kernels/adjust_hsv_in_yiq_op_gpu.cu.cc index 41cac40906..f97e790b56 100644 --- a/tensorflow/contrib/image/kernels/adjust_hsv_in_yiq_op_gpu.cu.cc +++ b/tensorflow/contrib/image/kernels/adjust_hsv_in_yiq_op_gpu.cu.cc @@ -55,10 +55,10 @@ void AdjustHsvInYiqGPU::operator()(OpKernelContext* ctx, int channel_count, &tranformation_matrix)); // TODO(huangyp): It takes about 3.5 us to compute tranformation_matrix // with one thread. Improve its performance if necessary. - CudaLaunchKernel(internal::compute_tranformation_matrix_cuda, 1, 1, 0, - cu_stream, delta_h, scale_s, scale_v, - tranformation_matrix.flat().data(), - tranformation_matrix.flat().size()); + TF_CHECK_OK(CudaLaunchKernel(internal::compute_tranformation_matrix_cuda, 1, + 1, 0, cu_stream, delta_h, scale_s, scale_v, + tranformation_matrix.flat().data(), + tranformation_matrix.flat().size())); // Call cuBlas C = A * B directly. auto no_transpose = se::blas::Transpose::kNoTranspose; auto a_ptr = diff --git a/tensorflow/contrib/mpi_collectives/kernels/ring.cu.cc b/tensorflow/contrib/mpi_collectives/kernels/ring.cu.cc index 0f709a57a8..ca3ddfa721 100644 --- a/tensorflow/contrib/mpi_collectives/kernels/ring.cu.cc +++ b/tensorflow/contrib/mpi_collectives/kernels/ring.cu.cc @@ -96,14 +96,14 @@ __global__ void elemwise_accum(T* out, const T* in, const size_t N) { // Synchronously accumulate tensors on the GPU, using a different stream than // the default and than TensorFlow to avoid synchronizing on operations // unrelated to the allreduce. -#define GENERATE_ACCUMULATE(type) \ - template <> \ - void AccumulateTensorData(type * dst, type * src, \ - size_t size) { \ - auto stream = CudaStreamForMPI(); \ - CudaLaunchKernel(elemwise_accum, 32, 256, 0, stream, dst, src, \ - size); \ - cudaStreamSynchronize(stream); \ +#define GENERATE_ACCUMULATE(type) \ + template <> \ + void AccumulateTensorData(type * dst, type * src, \ + size_t size) { \ + auto stream = CudaStreamForMPI(); \ + TF_CHECK_OK(CudaLaunchKernel(elemwise_accum, 32, 256, 0, stream, \ + dst, src, size)); \ + cudaStreamSynchronize(stream); \ }; GENERATE_ACCUMULATE(int); GENERATE_ACCUMULATE(long long); diff --git a/tensorflow/contrib/mpi_collectives/ring.cu.cc b/tensorflow/contrib/mpi_collectives/ring.cu.cc index f8c7adf0a1..c73156d230 100644 --- a/tensorflow/contrib/mpi_collectives/ring.cu.cc +++ b/tensorflow/contrib/mpi_collectives/ring.cu.cc @@ -96,14 +96,14 @@ __global__ void elemwise_accum(T* out, const T* in, const size_t N) { // Synchronously accumulate tensors on the GPU, using a different stream than // the default and than TensorFlow to avoid synchronizing on operations // unrelated to the allreduce. -#define GENERATE_ACCUMULATE(type) \ - template <> \ - void AccumulateTensorData(type * dst, type * src, \ - size_t size) { \ - auto stream = CudaStreamForMPI(); \ - CudaLaunchKernel(elemwise_accum, 32, 256, 0, stream, dst, src, \ - size); \ - cudaStreamSynchronize(stream); \ +#define GENERATE_ACCUMULATE(type) \ + template <> \ + void AccumulateTensorData(type * dst, type * src, \ + size_t size) { \ + auto stream = CudaStreamForMPI(); \ + TF_CHECK_OK(CudaLaunchKernel(elemwise_accum, 32, 256, 0, stream, \ + dst, src, size)); \ + cudaStreamSynchronize(stream); \ }; GENERATE_ACCUMULATE(int); GENERATE_ACCUMULATE(long long); diff --git a/tensorflow/contrib/reduce_slice_ops/kernels/reduce_slice_ops_gpu.cu.cc b/tensorflow/contrib/reduce_slice_ops/kernels/reduce_slice_ops_gpu.cu.cc index cdabcfe059..13fbd974e9 100644 --- a/tensorflow/contrib/reduce_slice_ops/kernels/reduce_slice_ops_gpu.cu.cc +++ b/tensorflow/contrib/reduce_slice_ops/kernels/reduce_slice_ops_gpu.cu.cc @@ -77,10 +77,10 @@ namespace functor { sizex, sizey, sizez, d, ReduceSliceDeviceKernel##reduceop, \ 0, 0); \ \ - CudaLaunchKernel( \ + TF_CHECK_OK(CudaLaunchKernel( \ ReduceSliceDeviceKernel##reduceop, config.block_count, \ config.thread_per_block, 0, d.stream(), config, indices_width, \ - bound, beginning(), indices.data(), data.data(), output.data()); \ + bound, beginning(), indices.data(), data.data(), output.data())); \ } \ }; diff --git a/tensorflow/contrib/resampler/kernels/resampler_ops_gpu.cu.cc b/tensorflow/contrib/resampler/kernels/resampler_ops_gpu.cu.cc index ad0bec9572..3b2ee098b3 100644 --- a/tensorflow/contrib/resampler/kernels/resampler_ops_gpu.cu.cc +++ b/tensorflow/contrib/resampler/kernels/resampler_ops_gpu.cu.cc @@ -119,10 +119,10 @@ struct Resampler2DFunctor { batch_size * num_sampling_points * data_channels; ::tensorflow::CudaLaunchConfig config = ::tensorflow::GetCudaLaunchConfig(output_data_size, d); - CudaLaunchKernel(Resampler2DKernel, config.block_count, - config.thread_per_block, 0, d.stream(), data, warp, output, - batch_size, data_height, data_width, data_channels, - num_sampling_points); + TF_CHECK_OK(CudaLaunchKernel( + Resampler2DKernel, config.block_count, config.thread_per_block, 0, + d.stream(), data, warp, output, batch_size, data_height, data_width, + data_channels, num_sampling_points)); } }; @@ -254,22 +254,23 @@ struct ResamplerGrad2DFunctor { ::tensorflow::CudaLaunchConfig config = ::tensorflow::GetCudaLaunchConfig(grad_warp_size, d); - ::tensorflow::CudaLaunchKernel(SetZero, config.block_count, - config.thread_per_block, 0, d.stream(), - grad_warp_size, grad_warp); + TF_CHECK_OK(::tensorflow::CudaLaunchKernel( + SetZero, config.block_count, config.thread_per_block, 0, d.stream(), + grad_warp_size, grad_warp)); config = ::tensorflow::GetCudaLaunchConfig(grad_data_size, d); - ::tensorflow::CudaLaunchKernel(SetZero, config.block_count, - config.thread_per_block, 0, d.stream(), - grad_data_size, grad_data); + TF_CHECK_OK(::tensorflow::CudaLaunchKernel( + SetZero, config.block_count, config.thread_per_block, 0, d.stream(), + grad_data_size, grad_data)); const int resampler_output_size = batch_size * num_sampling_points * data_channels; config = ::tensorflow::GetCudaLaunchConfig(resampler_output_size, d); - CudaLaunchKernel(ResamplerGrad2DKernel, config.block_count, - config.thread_per_block, 0, d.stream(), data, warp, - grad_output, grad_data, grad_warp, batch_size, data_height, - data_width, data_channels, num_sampling_points); + TF_CHECK_OK(CudaLaunchKernel(ResamplerGrad2DKernel, config.block_count, + config.thread_per_block, 0, d.stream(), data, + warp, grad_output, grad_data, grad_warp, + batch_size, data_height, data_width, + data_channels, num_sampling_points)); } }; diff --git a/tensorflow/contrib/rnn/kernels/lstm_ops_gpu.cu.cc b/tensorflow/contrib/rnn/kernels/lstm_ops_gpu.cu.cc index 648d210ee1..81beb2942c 100644 --- a/tensorflow/contrib/rnn/kernels/lstm_ops_gpu.cu.cc +++ b/tensorflow/contrib/rnn/kernels/lstm_ops_gpu.cu.cc @@ -242,8 +242,9 @@ void LSTMBlockCellFpropWithCUDA( const int block_dim = 128; const int grid_dim = Eigen::divup(batch_size * (cell_size + input_size), block_dim); - CudaLaunchKernel(concat_xh, grid_dim, block_dim, 0, cu_stream, xh.data(), - x.data(), h_prev.data(), batch_size, cell_size, input_size); + TF_CHECK_OK(CudaLaunchKernel(concat_xh, grid_dim, block_dim, 0, cu_stream, + xh.data(), x.data(), h_prev.data(), batch_size, + cell_size, input_size)); // states1 = xh * w typename TTypes::ConstMatrix const_xh(xh.data(), xh.dimensions()); @@ -261,17 +262,17 @@ void LSTMBlockCellFpropWithCUDA( Eigen::divup(cell_size, static_cast(block_dim_2d.y))); if (use_peephole) { - CudaLaunchKernel(lstm_gates, grid_dim_2d, block_dim_2d, 0, - cu_stream, icfo.data(), b.data(), cs_prev.data(), - wci.data(), wcf.data(), wco.data(), o.data(), h.data(), - ci.data(), cs.data(), co.data(), i.data(), f.data(), - forget_bias, cell_clip, batch_size, cell_size); + TF_CHECK_OK(CudaLaunchKernel( + lstm_gates, grid_dim_2d, block_dim_2d, 0, cu_stream, + icfo.data(), b.data(), cs_prev.data(), wci.data(), wcf.data(), + wco.data(), o.data(), h.data(), ci.data(), cs.data(), co.data(), + i.data(), f.data(), forget_bias, cell_clip, batch_size, cell_size)); } else { - CudaLaunchKernel(lstm_gates, grid_dim_2d, block_dim_2d, 0, - cu_stream, icfo.data(), b.data(), cs_prev.data(), - wci.data(), wcf.data(), wco.data(), o.data(), h.data(), - ci.data(), cs.data(), co.data(), i.data(), f.data(), - forget_bias, cell_clip, batch_size, cell_size); + TF_CHECK_OK(CudaLaunchKernel( + lstm_gates, grid_dim_2d, block_dim_2d, 0, cu_stream, + icfo.data(), b.data(), cs_prev.data(), wci.data(), wcf.data(), + wco.data(), o.data(), h.data(), ci.data(), cs.data(), co.data(), + i.data(), f.data(), forget_bias, cell_clip, batch_size, cell_size)); } } @@ -376,13 +377,13 @@ void LSTMBlockCellBpropWithCUDA( dim3 grid_dim_2d(Eigen::divup(batch_size, static_cast(block_dim_2d.x)), Eigen::divup(cell_size, static_cast(block_dim_2d.y))); - CudaLaunchKernel(lstm_gates_bprop, grid_dim_2d, block_dim_2d, 0, cu_stream, - cs_prev.data(), h_prev.data(), w.data(), wci.data(), - wcf.data(), wco.data(), b.data(), i.data(), cs.data(), - f.data(), o.data(), ci.data(), co.data(), cs_grad.data(), - h_grad.data(), do_.data(), dcs.data(), dci.data(), df.data(), - di.data(), dicfo.data(), cs_prev_grad.data(), batch_size, - cell_size, use_peephole); + TF_CHECK_OK(CudaLaunchKernel( + lstm_gates_bprop, grid_dim_2d, block_dim_2d, 0, cu_stream, + cs_prev.data(), h_prev.data(), w.data(), wci.data(), wcf.data(), + wco.data(), b.data(), i.data(), cs.data(), f.data(), o.data(), ci.data(), + co.data(), cs_grad.data(), h_grad.data(), do_.data(), dcs.data(), + dci.data(), df.data(), di.data(), dicfo.data(), cs_prev_grad.data(), + batch_size, cell_size, use_peephole)); if (use_peephole) { Eigen::array p_shape({1, cell_size}); diff --git a/tensorflow/contrib/seq2seq/kernels/beam_search_ops_gpu.cu.cc b/tensorflow/contrib/seq2seq/kernels/beam_search_ops_gpu.cu.cc index 4836f80c9f..be2aa4782c 100644 --- a/tensorflow/contrib/seq2seq/kernels/beam_search_ops_gpu.cu.cc +++ b/tensorflow/contrib/seq2seq/kernels/beam_search_ops_gpu.cu.cc @@ -91,10 +91,11 @@ struct GatherTree { beams.device(d) = beams.constant(end_token); CudaLaunchConfig config = GetCudaLaunchConfig(batch_size * beam_width, d); - CudaLaunchKernel(GatherTreeOpKernel, config.block_count, - config.thread_per_block, 0, d.stream(), batch_size, - max_time, beam_width, step_ids.data(), parent_ids.data(), - max_sequence_length.data(), end_token, beams.data()); + TF_CHECK_OK(CudaLaunchKernel( + GatherTreeOpKernel, config.block_count, config.thread_per_block, 0, + d.stream(), batch_size, max_time, beam_width, step_ids.data(), + parent_ids.data(), max_sequence_length.data(), end_token, + beams.data())); } }; diff --git a/tensorflow/contrib/tensorrt/custom_plugin_examples/inc_op_kernel.cu.cc b/tensorflow/contrib/tensorrt/custom_plugin_examples/inc_op_kernel.cu.cc index 858380bb91..b683c14c0d 100644 --- a/tensorflow/contrib/tensorrt/custom_plugin_examples/inc_op_kernel.cu.cc +++ b/tensorflow/contrib/tensorrt/custom_plugin_examples/inc_op_kernel.cu.cc @@ -39,8 +39,8 @@ void IncrementKernel(const float* d_input, float inc, float* d_output, int threads_per_block = 256; int blocks_per_grid = (count + threads_per_block - 1) / threads_per_block; - CudaLaunchKernel(VecInc, threads_per_block, blocks_per_grid, 0, stream, - d_input, inc, d_output, count); + TF_CHECK_OK(CudaLaunchKernel(VecInc, threads_per_block, blocks_per_grid, 0, + stream, d_input, inc, d_output, count)); } // Note: this kernel definition is not needed in the plugin_test rule, but it is diff --git a/tensorflow/core/kernels/adjust_hue_op_gpu.cu.cc b/tensorflow/core/kernels/adjust_hue_op_gpu.cu.cc index ea22911617..985858ad9a 100644 --- a/tensorflow/core/kernels/adjust_hue_op_gpu.cu.cc +++ b/tensorflow/core/kernels/adjust_hue_op_gpu.cu.cc @@ -35,9 +35,10 @@ void AdjustHueGPU::operator()(GPUDevice* device, const int threads_per_block = config.thread_per_block; const int block_count = (number_of_elements + threads_per_block - 1) / threads_per_block; - CudaLaunchKernel(internal::adjust_hsv_nhwc, - block_count, threads_per_block, 0, stream, - number_of_elements, input, output, delta, nullptr, nullptr); + TF_CHECK_OK(CudaLaunchKernel(internal::adjust_hsv_nhwc, + block_count, threads_per_block, 0, stream, + number_of_elements, input, output, delta, + nullptr, nullptr)); } template struct AdjustHueGPU; diff --git a/tensorflow/core/kernels/adjust_saturation_op_gpu.cu.cc b/tensorflow/core/kernels/adjust_saturation_op_gpu.cu.cc index 637120d9af..ea43fc33bb 100644 --- a/tensorflow/core/kernels/adjust_saturation_op_gpu.cu.cc +++ b/tensorflow/core/kernels/adjust_saturation_op_gpu.cu.cc @@ -36,9 +36,10 @@ void AdjustSaturationGPU::operator()(GPUDevice* device, const int threads_per_block = config.thread_per_block; const int block_count = (number_of_elements + threads_per_block - 1) / threads_per_block; - CudaLaunchKernel(internal::adjust_hsv_nhwc, - block_count, threads_per_block, 0, stream, - number_of_elements, input, output, nullptr, scale, nullptr); + TF_CHECK_OK(CudaLaunchKernel(internal::adjust_hsv_nhwc, + block_count, threads_per_block, 0, stream, + number_of_elements, input, output, nullptr, + scale, nullptr)); } template struct AdjustSaturationGPU; diff --git a/tensorflow/core/kernels/avgpooling_op_gpu.cu.cc b/tensorflow/core/kernels/avgpooling_op_gpu.cu.cc index 8bf47b7ad9..0cf2e4d4cb 100644 --- a/tensorflow/core/kernels/avgpooling_op_gpu.cu.cc +++ b/tensorflow/core/kernels/avgpooling_op_gpu.cu.cc @@ -91,11 +91,11 @@ bool RunAvePoolBackwardNHWC(const T* const top_diff, const int num, const GPUDevice& d) { int x_size = num * height * width * channels; CudaLaunchConfig config = GetCudaLaunchConfig(x_size, d); - CudaLaunchKernel(AvePoolBackwardNHWC, config.block_count, - config.thread_per_block, 0, d.stream(), - config.virtual_thread_count, top_diff, num, height, width, - channels, pooled_height, pooled_width, kernel_h, kernel_w, - stride_h, stride_w, pad_t, pad_t, bottom_diff); + TF_CHECK_OK(CudaLaunchKernel( + AvePoolBackwardNHWC, config.block_count, config.thread_per_block, 0, + d.stream(), config.virtual_thread_count, top_diff, num, height, width, + channels, pooled_height, pooled_width, kernel_h, kernel_w, stride_h, + stride_w, pad_t, pad_t, bottom_diff)); return d.ok(); } diff --git a/tensorflow/core/kernels/bias_op_gpu.cu.cc b/tensorflow/core/kernels/bias_op_gpu.cu.cc index 21fefa9bb5..84c889d3e8 100644 --- a/tensorflow/core/kernels/bias_op_gpu.cu.cc +++ b/tensorflow/core/kernels/bias_op_gpu.cu.cc @@ -85,15 +85,15 @@ void BiasGPU::compute(const GPUDevice& d, const T* input, const T* bias, } CudaLaunchConfig config = GetCudaLaunchConfig(total_count, d); if (data_format == FORMAT_NHWC) { - CudaLaunchKernel(BiasNHWCKernel, config.block_count, - config.thread_per_block, 0, d.stream(), - config.virtual_thread_count, input, bias, output, - bias_size); + TF_CHECK_OK(CudaLaunchKernel(BiasNHWCKernel, config.block_count, + config.thread_per_block, 0, d.stream(), + config.virtual_thread_count, input, bias, + output, bias_size)); } else { - CudaLaunchKernel(BiasNCHWKernel, config.block_count, - config.thread_per_block, 0, d.stream(), - config.virtual_thread_count, input, bias, output, - bias_size, image_size); + TF_CHECK_OK(CudaLaunchKernel(BiasNCHWKernel, config.block_count, + config.thread_per_block, 0, d.stream(), + config.virtual_thread_count, input, bias, + output, bias_size, image_size)); } } @@ -226,22 +226,24 @@ void BiasGradGPU::compute(const GPUDevice& d, const T* output_backprop, if (config.thread_per_block < kWarpSize) { config.thread_per_block = kWarpSize; } - CudaLaunchKernel(BiasGradNCHW_SharedAtomics, config.block_count, - config.thread_per_block, 0, d.stream(), output_backprop, - bias_backprop, batch, bias_size, image_size, group_size); + TF_CHECK_OK(CudaLaunchKernel( + BiasGradNCHW_SharedAtomics, config.block_count, + config.thread_per_block, 0, d.stream(), output_backprop, + bias_backprop, batch, bias_size, image_size, group_size)); } } else { // Note that even if we don't have enough shared memory to fit the entire // output block, it is possible to process one group of elements at a time. // But for now, we simply fall back to the naive implementation. if (data_format == FORMAT_NHWC) { - CudaLaunchKernel(BiasGradNHWC_Naive, config.block_count, - config.thread_per_block, 0, d.stream(), total_count, - output_backprop, bias_backprop, bias_size); + TF_CHECK_OK(CudaLaunchKernel( + BiasGradNHWC_Naive, config.block_count, config.thread_per_block, 0, + d.stream(), total_count, output_backprop, bias_backprop, bias_size)); } else { - CudaLaunchKernel(BiasGradNCHW_Naive, config.block_count, - config.thread_per_block, 0, d.stream(), total_count, - output_backprop, bias_backprop, bias_size, image_size); + TF_CHECK_OK(CudaLaunchKernel(BiasGradNCHW_Naive, config.block_count, + config.thread_per_block, 0, d.stream(), + total_count, output_backprop, bias_backprop, + bias_size, image_size)); } } } diff --git a/tensorflow/core/kernels/bucketize_op_gpu.cu.cc b/tensorflow/core/kernels/bucketize_op_gpu.cu.cc index 4533d28c93..a0f6b755a8 100644 --- a/tensorflow/core/kernels/bucketize_op_gpu.cu.cc +++ b/tensorflow/core/kernels/bucketize_op_gpu.cu.cc @@ -103,10 +103,10 @@ struct BucketizeFunctor { d.stream()>>>(input.size(), input.data(), boundaries_vector.size(), boundaries_array.data(), output.data()); } else { - CudaLaunchKernel(BucketizeCustomKernel, config.block_count, - config.thread_per_block, 0, d.stream(), input.size(), - input.data(), boundaries_vector.size(), - boundaries_array.data(), output.data()); + TF_CHECK_OK(CudaLaunchKernel( + BucketizeCustomKernel, config.block_count, + config.thread_per_block, 0, d.stream(), input.size(), input.data(), + boundaries_vector.size(), boundaries_array.data(), output.data())); } return Status::OK(); } diff --git a/tensorflow/core/kernels/check_numerics_op_gpu.cu.cc b/tensorflow/core/kernels/check_numerics_op_gpu.cu.cc index c63c29431c..f0db314193 100644 --- a/tensorflow/core/kernels/check_numerics_op_gpu.cu.cc +++ b/tensorflow/core/kernels/check_numerics_op_gpu.cu.cc @@ -66,8 +66,8 @@ struct CheckNumericsLaunch { (d.getNumGpuMultiProcessors() * d.maxGpuThreadsPerMultiProcessor()) / block_size; - CudaLaunchKernel(CheckNumericsKernel, num_blocks, block_size, 0, - d.stream(), data, size, abnormal_detected); + TF_CHECK_OK(CudaLaunchKernel(CheckNumericsKernel, num_blocks, block_size, + 0, d.stream(), data, size, abnormal_detected)); } }; diff --git a/tensorflow/core/kernels/compare_and_bitpack_op_gpu.cu.cc b/tensorflow/core/kernels/compare_and_bitpack_op_gpu.cu.cc index e15eea1c9a..1baa27f014 100644 --- a/tensorflow/core/kernels/compare_and_bitpack_op_gpu.cu.cc +++ b/tensorflow/core/kernels/compare_and_bitpack_op_gpu.cu.cc @@ -114,19 +114,20 @@ __global__ void CompareAndBitpackKernel(const int size, } } -#define DEFINE_GPU_SPECS(T) \ - template <> \ - void CompareAndBitpack::operator()( \ - OpKernelContext* c, typename TTypes::ConstMatrix input, \ - typename TTypes::ConstScalar threshold, \ - TTypes::Matrix output) { \ - const GPUDevice& d = c->eigen_device(); \ - int64 total_count = output.size(); \ - CudaLaunchConfig config = GetCudaLaunchConfig(total_count, d); \ - \ - CudaLaunchKernel(CompareAndBitpackKernel, config.block_count, \ - config.thread_per_block, 0, d.stream(), total_count, \ - threshold.data(), input.data(), output.data()); \ +#define DEFINE_GPU_SPECS(T) \ + template <> \ + void CompareAndBitpack::operator()( \ + OpKernelContext* c, typename TTypes::ConstMatrix input, \ + typename TTypes::ConstScalar threshold, \ + TTypes::Matrix output) { \ + const GPUDevice& d = c->eigen_device(); \ + int64 total_count = output.size(); \ + CudaLaunchConfig config = GetCudaLaunchConfig(total_count, d); \ + \ + TF_CHECK_OK(CudaLaunchKernel(CompareAndBitpackKernel, \ + config.block_count, config.thread_per_block, \ + 0, d.stream(), total_count, threshold.data(), \ + input.data(), output.data())); \ } TF_CALL_GPU_NUMBER_TYPES(DEFINE_GPU_SPECS) diff --git a/tensorflow/core/kernels/conv_2d_gpu.h b/tensorflow/core/kernels/conv_2d_gpu.h index 3e35acc0e5..1f0a63044b 100644 --- a/tensorflow/core/kernels/conv_2d_gpu.h +++ b/tensorflow/core/kernels/conv_2d_gpu.h @@ -437,10 +437,10 @@ struct TransformFilter { CHECK(dst_filter_format == FORMAT_OIHW) << "Unsupported output layout: " << ToString(dst_filter_format); - CudaLaunchKernel(ShuffleInTensor3Simple, config.block_count, - config.thread_per_block, 0, d.stream(), - config.virtual_thread_count, in.data(), combined_dims, - out.data()); + TF_CHECK_OK(CudaLaunchKernel(ShuffleInTensor3Simple, + config.block_count, config.thread_per_block, 0, + d.stream(), config.virtual_thread_count, + in.data(), combined_dims, out.data())); } }; @@ -459,10 +459,10 @@ struct ReverseTransformFilter { combined_dims[2] *= in.dimension(i); } CudaLaunchConfig config = GetCudaLaunchConfig(out.size(), d); - CudaLaunchKernel(ShuffleInTensor3Simple, config.block_count, - config.thread_per_block, 0, d.stream(), - config.virtual_thread_count, in.data(), combined_dims, - out.data()); + TF_CHECK_OK(CudaLaunchKernel(ShuffleInTensor3Simple, + config.block_count, config.thread_per_block, 0, + d.stream(), config.virtual_thread_count, + in.data(), combined_dims, out.data())); } }; @@ -490,15 +490,15 @@ struct PadInput { const Dimension padding_left_dim(padding_left); if (format == FORMAT_NHWC) { - CudaLaunchKernel(PadInputCustomKernelNHWC, config.block_count, - config.thread_per_block, 0, d.stream(), - config.virtual_thread_count, in.data(), input_dims, - out.data(), output_dims, padding_left_dim); + TF_CHECK_OK(CudaLaunchKernel( + PadInputCustomKernelNHWC, config.block_count, + config.thread_per_block, 0, d.stream(), config.virtual_thread_count, + in.data(), input_dims, out.data(), output_dims, padding_left_dim)); } else if (format == FORMAT_NCHW) { - CudaLaunchKernel(PadInputCustomKernelNCHW, config.block_count, - config.thread_per_block, 0, d.stream(), - config.virtual_thread_count, in.data(), input_dims, - out.data(), output_dims, padding_left_dim); + TF_CHECK_OK(CudaLaunchKernel( + PadInputCustomKernelNCHW, config.block_count, + config.thread_per_block, 0, d.stream(), config.virtual_thread_count, + in.data(), input_dims, out.data(), output_dims, padding_left_dim)); } else { LOG(FATAL) << "Invalid data format: " << format; } @@ -607,17 +607,17 @@ void LaunchBatchNarrowMatrixTransposeKernel( const T* input, const Dimension<3>& input_dims, T* output) { constexpr int NumThreads = TileLongSide; if (tile_size_i <= TileLongSide && tile_size_j <= TileShortSide) { - CudaLaunchKernel( + TF_CHECK_OK(CudaLaunchKernel( SwapDimension1And2InTensor3UsingTiles, total_tiles_count, NumThreads, 0, d.stream(), input, input_dims, - output); + output)); } else { - CudaLaunchKernel( + TF_CHECK_OK(CudaLaunchKernel( SwapDimension1And2InTensor3UsingTiles, total_tiles_count, NumThreads, 0, d.stream(), input, input_dims, - output); + output)); } } @@ -919,11 +919,11 @@ void RunSwapDimension1And2InTensor3(const GPUDevice& d, const T* input, int total_tiles_count = input_dims_in_tiles[0] * input_dims_in_tiles[1] * input_dims_in_tiles[2]; - CudaLaunchKernel( + TF_CHECK_OK(CudaLaunchKernel( SwapDimension1And2InTensor3UsingTiles, total_tiles_count, kNumThreads, 0, d.stream(), input, input_dims, - output); + output)); } else if (narrow_matrix) { SwapDimension1And2InTensor3WithNarrowMatrices( @@ -931,9 +931,10 @@ void RunSwapDimension1And2InTensor3(const GPUDevice& d, const T* input, } else { int total_element_count = input_dims[0] * input_dims[1] * input_dims[2]; CudaLaunchConfig config = GetCudaLaunchConfig(total_element_count, d); - CudaLaunchKernel(ShuffleInTensor3Simple, - config.block_count, config.thread_per_block, 0, d.stream(), - config.virtual_thread_count, input, input_dims, output); + TF_CHECK_OK(CudaLaunchKernel(ShuffleInTensor3Simple, + config.block_count, config.thread_per_block, 0, + d.stream(), config.virtual_thread_count, input, + input_dims, output)); } } @@ -963,9 +964,10 @@ struct SwapDimension0And2InTensor3 { static_cast(combined_dims[2])}; size_t total_size = combined_dims[0] * combined_dims[1] * combined_dims[2]; CudaLaunchConfig config = GetCudaLaunchConfig(total_size, d); - CudaLaunchKernel(ShuffleInTensor3Simple, - config.block_count, config.thread_per_block, 0, d.stream(), - config.virtual_thread_count, in, input_dims, out); + TF_CHECK_OK(CudaLaunchKernel(ShuffleInTensor3Simple, + config.block_count, config.thread_per_block, 0, + d.stream(), config.virtual_thread_count, in, + input_dims, out)); } }; diff --git a/tensorflow/core/kernels/crop_and_resize_op_gpu.cu.cc b/tensorflow/core/kernels/crop_and_resize_op_gpu.cu.cc index 7bedf5c3ea..a9065acd6f 100644 --- a/tensorflow/core/kernels/crop_and_resize_op_gpu.cu.cc +++ b/tensorflow/core/kernels/crop_and_resize_op_gpu.cu.cc @@ -371,12 +371,12 @@ struct CropAndResize { if (total_count > 0) { CudaLaunchConfig config = GetCudaLaunchConfig(total_count, d); - CropAndResizeKernel<<>>( - config.virtual_thread_count, image.data(), boxes.data(), - box_ind.data(), num_boxes, batch, image_height, image_width, - crop_height, crop_width, depth, method, extrapolation_value, - crops.data()); + TF_CHECK_OK(CudaLaunchKernel( + CropAndResizeKernel, config.block_count, config.thread_per_block, + 0, d.stream(), config.virtual_thread_count, image.data(), + boxes.data(), box_ind.data(), num_boxes, batch, image_height, + image_width, crop_height, crop_width, depth, method, + extrapolation_value, crops.data())); } return d.ok(); } @@ -406,9 +406,9 @@ struct CropAndResizeBackpropImage { total_count = batch * image_height * image_width * depth; if (total_count > 0) { config = GetCudaLaunchConfig(total_count, d); - CudaLaunchKernel(SetZero, config.block_count, config.thread_per_block, - 0, d.stream(), config.virtual_thread_count, - grads_image.data()); + TF_CHECK_OK(CudaLaunchKernel( + SetZero, config.block_count, config.thread_per_block, 0, + d.stream(), config.virtual_thread_count, grads_image.data())); } // Configurate interpolation method. @@ -421,11 +421,12 @@ struct CropAndResizeBackpropImage { total_count = num_boxes * crop_height * crop_width * depth; if (total_count > 0) { config = GetCudaLaunchConfig(total_count, d); - CropAndResizeBackpropImageKernel<<< - config.block_count, config.thread_per_block, 0, d.stream()>>>( - config.virtual_thread_count, grads.data(), boxes.data(), - box_ind.data(), num_boxes, batch, image_height, image_width, - crop_height, crop_width, depth, grads_image.data(), method); + TF_CHECK_OK(CudaLaunchKernel( + CropAndResizeBackpropImageKernel, config.block_count, + config.thread_per_block, 0, d.stream(), config.virtual_thread_count, + grads.data(), boxes.data(), box_ind.data(), num_boxes, batch, + image_height, image_width, crop_height, crop_width, depth, + grads_image.data(), method)); } return d.ok(); } @@ -455,20 +456,21 @@ struct CropAndResizeBackpropBoxes { total_count = num_boxes * 4; if (total_count > 0) { config = GetCudaLaunchConfig(total_count, d); - CudaLaunchKernel(SetZero, config.block_count, - config.thread_per_block, 0, d.stream(), - config.virtual_thread_count, grads_boxes.data()); + TF_CHECK_OK(CudaLaunchKernel( + SetZero, config.block_count, config.thread_per_block, 0, + d.stream(), config.virtual_thread_count, grads_boxes.data())); } // Accumulate. total_count = num_boxes * crop_height * crop_width * depth; if (total_count > 0) { config = GetCudaLaunchConfig(total_count, d); - CropAndResizeBackpropBoxesKernel<<< - config.block_count, config.thread_per_block, 0, d.stream()>>>( - config.virtual_thread_count, grads.data(), image.data(), boxes.data(), - box_ind.data(), num_boxes, batch, image_height, image_width, - crop_height, crop_width, depth, grads_boxes.data()); + TF_CHECK_OK(CudaLaunchKernel( + CropAndResizeBackpropBoxesKernel, config.block_count, + config.thread_per_block, 0, d.stream(), config.virtual_thread_count, + grads.data(), image.data(), boxes.data(), box_ind.data(), num_boxes, + batch, image_height, image_width, crop_height, crop_width, depth, + grads_boxes.data())); } return d.ok(); } diff --git a/tensorflow/core/kernels/cwise_op_clip_gpu.cu.cc b/tensorflow/core/kernels/cwise_op_clip_gpu.cu.cc index 5069ed51bc..0122ffc6fa 100644 --- a/tensorflow/core/kernels/cwise_op_clip_gpu.cu.cc +++ b/tensorflow/core/kernels/cwise_op_clip_gpu.cu.cc @@ -62,10 +62,10 @@ struct UnaryClipOp { typename TTypes::Flat &out_flat) const { CudaLaunchConfig config = GetCudaLaunchConfig(in0_flat.size(), d); - CudaLaunchKernel(UnaryClipCustomKernel, config.block_count, - config.thread_per_block, 0, d.stream(), in0_flat.size(), - in0_flat.data(), in1_flat.data(), in2_flat.data(), - out_flat.data()); + TF_CHECK_OK(CudaLaunchKernel( + UnaryClipCustomKernel, config.block_count, config.thread_per_block, + 0, d.stream(), in0_flat.size(), in0_flat.data(), in1_flat.data(), + in2_flat.data(), out_flat.data())); } }; @@ -78,10 +78,10 @@ struct BinaryRightClipOp { typename TTypes::Flat &out_flat) const { CudaLaunchConfig config = GetCudaLaunchConfig(in0_flat.size(), d); - CudaLaunchKernel(BinaryRightClipCustomKernel, config.block_count, - config.thread_per_block, 0, d.stream(), in0_flat.size(), - in0_flat.data(), in1_flat.data(), in2_flat.data(), - out_flat.data()); + TF_CHECK_OK(CudaLaunchKernel( + BinaryRightClipCustomKernel, config.block_count, + config.thread_per_block, 0, d.stream(), in0_flat.size(), + in0_flat.data(), in1_flat.data(), in2_flat.data(), out_flat.data())); } }; @@ -94,10 +94,10 @@ struct BinaryLeftClipOp { typename TTypes::Flat &out_flat) const { CudaLaunchConfig config = GetCudaLaunchConfig(in0_flat.size(), d); - CudaLaunchKernel(BinaryLeftClipCustomKernel, config.block_count, - config.thread_per_block, 0, d.stream(), in0_flat.size(), - in0_flat.data(), in1_flat.data(), in2_flat.data(), - out_flat.data()); + TF_CHECK_OK(CudaLaunchKernel( + BinaryLeftClipCustomKernel, config.block_count, + config.thread_per_block, 0, d.stream(), in0_flat.size(), + in0_flat.data(), in1_flat.data(), in2_flat.data(), out_flat.data())); } }; diff --git a/tensorflow/core/kernels/depthtospace_op_gpu.cu.cc b/tensorflow/core/kernels/depthtospace_op_gpu.cu.cc index 22ef57b159..768dd38a60 100644 --- a/tensorflow/core/kernels/depthtospace_op_gpu.cu.cc +++ b/tensorflow/core/kernels/depthtospace_op_gpu.cu.cc @@ -162,11 +162,11 @@ struct DepthToSpaceOpFunctor { return; } CudaLaunchConfig config = GetCudaLaunchConfig(total_count, d); - CudaLaunchKernel(D2S_NHWC, config.block_count, config.thread_per_block, - 0, d.stream(), config.virtual_thread_count, input.data(), - block_size, batch_size, input_height, input_width, - input_depth, output_height, output_width, output_depth, - output.data()); + TF_CHECK_OK(CudaLaunchKernel( + D2S_NHWC, config.block_count, config.thread_per_block, 0, d.stream(), + config.virtual_thread_count, input.data(), block_size, batch_size, + input_height, input_width, input_depth, output_height, output_width, + output_depth, output.data())); } void operator()(const GPUDevice& d, typename TTypes::ConstTensor input, int block_size, typename TTypes::Tensor output) { @@ -198,23 +198,26 @@ struct DepthToSpaceOpFunctor { CudaLaunchConfig config = GetCudaLaunchConfig(total_count, d); switch (block_size) { case 2: - return CudaLaunchKernel(D2S_NCHW_LOOP, config.block_count, - config.thread_per_block, 0, d.stream(), - total_count, input.data(), input_width, - output_width, output_depth_by_input_area, - input_depth_by_input_area, output.data()); + TF_CHECK_OK(CudaLaunchKernel( + D2S_NCHW_LOOP, config.block_count, config.thread_per_block, + 0, d.stream(), total_count, input.data(), input_width, + output_width, output_depth_by_input_area, + input_depth_by_input_area, output.data())); + return; case 3: - return CudaLaunchKernel(D2S_NCHW_LOOP, config.block_count, - config.thread_per_block, 0, d.stream(), - total_count, input.data(), input_width, - output_width, output_depth_by_input_area, - input_depth_by_input_area, output.data()); + TF_CHECK_OK(CudaLaunchKernel( + D2S_NCHW_LOOP, config.block_count, config.thread_per_block, + 0, d.stream(), total_count, input.data(), input_width, + output_width, output_depth_by_input_area, + input_depth_by_input_area, output.data())); + return; case 4: - return CudaLaunchKernel(D2S_NCHW_LOOP, config.block_count, - config.thread_per_block, 0, d.stream(), - total_count, input.data(), input_width, - output_width, output_depth_by_input_area, - input_depth_by_input_area, output.data()); + TF_CHECK_OK(CudaLaunchKernel( + D2S_NCHW_LOOP, config.block_count, config.thread_per_block, + 0, d.stream(), total_count, input.data(), input_width, + output_width, output_depth_by_input_area, + input_depth_by_input_area, output.data())); + return; } } @@ -224,10 +227,10 @@ struct DepthToSpaceOpFunctor { return; } auto config = GetCudaLaunchConfig(total_count, d); - CudaLaunchKernel(D2S_NCHW, config.block_count, config.thread_per_block, - 0, d.stream(), config.virtual_thread_count, input.data(), - block_size, input_width, output_depth * input_height, - output.data()); + TF_CHECK_OK(CudaLaunchKernel( + D2S_NCHW, config.block_count, config.thread_per_block, 0, d.stream(), + config.virtual_thread_count, input.data(), block_size, input_width, + output_depth * input_height, output.data())); } void operator()(const GPUDevice& d, typename TTypes::ConstTensor input, int block_size, typename TTypes::Tensor output) { diff --git a/tensorflow/core/kernels/depthwise_conv_op_gpu.h b/tensorflow/core/kernels/depthwise_conv_op_gpu.h index 4776d5c26e..fcbd8ffd86 100644 --- a/tensorflow/core/kernels/depthwise_conv_op_gpu.h +++ b/tensorflow/core/kernels/depthwise_conv_op_gpu.h @@ -983,9 +983,9 @@ Status LaunchDepthwiseConv2dBackpropInputGPU(OpKernelContext* ctx, auto device = ctx->eigen_gpu_device(); CudaLaunchConfig config = GetCudaLaunchConfig(num_in_backprop, device, kernel, 0, 0); - CudaLaunchKernel(kernel, config.block_count, config.thread_per_block, 0, - device.stream(), args, out_backprop, filter, in_backprop, - num_in_backprop); + TF_CHECK_OK(CudaLaunchKernel( + kernel, config.block_count, config.thread_per_block, 0, device.stream(), + args, out_backprop, filter, in_backprop, num_in_backprop)); return Status::OK(); } @@ -1747,9 +1747,9 @@ Status LaunchDepthwiseConv2dBackpropFilterGPU( auto device = ctx->eigen_gpu_device(); CudaLaunchConfig config = GetCudaLaunchConfig(num_out_backprop, device, kernel, 0, 0); - CudaLaunchKernel(kernel, config.block_count, config.thread_per_block, 0, - device.stream(), args, out_backprop, input, filter_backprop, - num_out_backprop); + TF_CHECK_OK(CudaLaunchKernel( + kernel, config.block_count, config.thread_per_block, 0, device.stream(), + args, out_backprop, input, filter_backprop, num_out_backprop)); return Status::OK(); } diff --git a/tensorflow/core/kernels/determinant_op_gpu.cu.cc b/tensorflow/core/kernels/determinant_op_gpu.cu.cc index fae5850dc4..681567ef2d 100644 --- a/tensorflow/core/kernels/determinant_op_gpu.cu.cc +++ b/tensorflow/core/kernels/determinant_op_gpu.cu.cc @@ -130,11 +130,11 @@ struct DeterminantFromPivotedLUFunctor { const int64 n = lu_factor.dimension(2); CudaLaunchConfig config = GetCudaLaunchConfig(num_matrices, device); - CudaLaunchKernel( + TF_CHECK_OK(CudaLaunchKernel( DeterminantFromPivotedLUKernel, config.block_count, config.thread_per_block, 0, device.stream(), config.virtual_thread_count, n, lu_factor.data(), pivots, nullptr, - output.data()); + output.data())); } }; @@ -152,11 +152,11 @@ struct LogDeterminantFromPivotedLUFunctor { const int64 num_matrices = sign.size(); const int64 n = lu_factor.dimension(2); CudaLaunchConfig config = GetCudaLaunchConfig(num_matrices, device); - CudaLaunchKernel( + TF_CHECK_OK(CudaLaunchKernel( DeterminantFromPivotedLUKernel, config.block_count, config.thread_per_block, 0, device.stream(), config.virtual_thread_count, n, lu_factor.data(), pivots, sign.data(), - log_abs_det.data()); + log_abs_det.data())); } }; diff --git a/tensorflow/core/kernels/dilation_ops_gpu.cu.cc b/tensorflow/core/kernels/dilation_ops_gpu.cu.cc index 80d77d9b74..12408f2c41 100644 --- a/tensorflow/core/kernels/dilation_ops_gpu.cu.cc +++ b/tensorflow/core/kernels/dilation_ops_gpu.cu.cc @@ -230,18 +230,20 @@ struct DilationBackpropInput { // Initialize in_backprop with all zeros. total_count = batch * input_rows * input_cols * depth; config = GetCudaLaunchConfig(total_count, d); - CudaLaunchKernel(SetZero, config.block_count, config.thread_per_block, 0, - d.stream(), total_count, in_backprop.data()); + TF_CHECK_OK(CudaLaunchKernel(SetZero, config.block_count, + config.thread_per_block, 0, d.stream(), + total_count, in_backprop.data())); // Accumulate. total_count = batch * output_rows * output_cols * depth; config = GetCudaLaunchConfig(total_count, d); - DilationBackpropInputKernel<<>>( - config.virtual_thread_count, input.data(), filter.data(), - out_backprop.data(), batch, input_rows, input_cols, depth, filter_rows, - filter_cols, output_rows, output_cols, stride_rows, stride_cols, - rate_rows, rate_cols, pad_top, pad_left, in_backprop.data()); + TF_CHECK_OK(CudaLaunchKernel( + DilationBackpropInputKernel, config.block_count, + config.thread_per_block, 0, d.stream(), config.virtual_thread_count, + input.data(), filter.data(), out_backprop.data(), batch, input_rows, + input_cols, depth, filter_rows, filter_cols, output_rows, output_cols, + stride_rows, stride_cols, rate_rows, rate_cols, pad_top, pad_left, + in_backprop.data())); } }; @@ -270,8 +272,9 @@ struct DilationBackpropFilter { // Initialize filter_backprop with all zeros. total_count = filter_rows * filter_cols * depth; config = GetCudaLaunchConfig(total_count, d); - CudaLaunchKernel(SetZero, config.block_count, config.thread_per_block, 0, - d.stream(), total_count, filter_backprop.data()); + TF_CHECK_OK(CudaLaunchKernel(SetZero, config.block_count, + config.thread_per_block, 0, d.stream(), + total_count, filter_backprop.data())); // Accumulate. total_count = batch * output_rows * output_cols * depth; diff --git a/tensorflow/core/kernels/dynamic_partition_op_gpu.cu.cc b/tensorflow/core/kernels/dynamic_partition_op_gpu.cu.cc index 346823acec..f00baa932f 100644 --- a/tensorflow/core/kernels/dynamic_partition_op_gpu.cu.cc +++ b/tensorflow/core/kernels/dynamic_partition_op_gpu.cu.cc @@ -79,9 +79,9 @@ template void RangeInit(const GPUDevice& d, const T start, const T delta, const int32 size, typename TTypes::Flat out) { CudaLaunchConfig config = GetCudaLaunchConfig(size, d); - CudaLaunchKernel(RangeInitKernel, config.block_count, - config.thread_per_block, 0, d.stream(), start, delta, size, - out.data()); + TF_CHECK_OK(CudaLaunchKernel(RangeInitKernel, config.block_count, + config.thread_per_block, 0, d.stream(), start, + delta, size, out.data())); } // Given *num_runs pairs (key, value), this function moves the value @@ -94,8 +94,9 @@ void MoveValues(const GPUDevice& d, int32* keys, int32* values, int32* num_runs, // For wrong inputs, we may have out_size < *num_runs. In this case we will // only handle the first out_size values. CudaLaunchConfig config = GetCudaLaunchConfig(out_size, d); - MoveValuesKernel<<>>(keys, values, num_runs, out_size, out); + TF_CHECK_OK(CudaLaunchKernel(MoveValuesKernel, config.block_count, + config.thread_per_block, 0, d.stream(), keys, + values, num_runs, out_size, out)); } template @@ -103,9 +104,10 @@ void CallGatherKernel(const GPUDevice& d, const T* params, const int32* indices, T* out, int64 gather_dim_size, int64 indices_size, int64 slice_size, int64 out_size) { CudaLaunchConfig config = GetCudaLaunchConfig(out_size, d); - CudaLaunchKernel(GatherOpKernel, config.block_count, - config.thread_per_block, 0, d.stream(), params, indices, out, - gather_dim_size, indices_size, slice_size, out_size); + TF_CHECK_OK(CudaLaunchKernel( + GatherOpKernel, config.block_count, + config.thread_per_block, 0, d.stream(), params, indices, out, + gather_dim_size, indices_size, slice_size, out_size)); } struct IdentityOp { diff --git a/tensorflow/core/kernels/dynamic_stitch_op_gpu.cu.cc b/tensorflow/core/kernels/dynamic_stitch_op_gpu.cu.cc index 618038360d..9ed2c54009 100644 --- a/tensorflow/core/kernels/dynamic_stitch_op_gpu.cu.cc +++ b/tensorflow/core/kernels/dynamic_stitch_op_gpu.cu.cc @@ -57,9 +57,10 @@ void DynamicStitchGPUImpl(const Eigen::GpuDevice& gpu_device, const int32 output_size = first_dim_size * slice_size; auto config = GetCudaLaunchConfig(output_size, gpu_device); - CudaLaunchKernel(DynamicStitchKernel, config.block_count, - config.thread_per_block, 0, gpu_device.stream(), slice_size, - output_size, input_indices, input_ptrs, output); + TF_CHECK_OK(CudaLaunchKernel(DynamicStitchKernel, config.block_count, + config.thread_per_block, 0, gpu_device.stream(), + slice_size, output_size, input_indices, + input_ptrs, output)); } #define REGISTER_GPU(T) \ diff --git a/tensorflow/core/kernels/gather_functor_gpu.cu.h b/tensorflow/core/kernels/gather_functor_gpu.cu.h index 01a400aa47..fe7850f925 100644 --- a/tensorflow/core/kernels/gather_functor_gpu.cu.h +++ b/tensorflow/core/kernels/gather_functor_gpu.cu.h @@ -92,15 +92,15 @@ struct GatherFunctor { CudaLaunchConfig config = GetCudaLaunchConfig(out_size, d); if (is_axis_zero) { - CudaLaunchKernel(GatherOpKernel, config.block_count, - config.thread_per_block, 0, d.stream(), params.data(), - indices.data(), out.data(), gather_dim_size, - indices_size, slice_size, out_size); + TF_CHECK_OK(CudaLaunchKernel( + GatherOpKernel, config.block_count, + config.thread_per_block, 0, d.stream(), params.data(), indices.data(), + out.data(), gather_dim_size, indices_size, slice_size, out_size)); } else { - CudaLaunchKernel(GatherOpKernel, config.block_count, - config.thread_per_block, 0, d.stream(), params.data(), - indices.data(), out.data(), gather_dim_size, - indices_size, slice_size, out_size); + TF_CHECK_OK(CudaLaunchKernel( + GatherOpKernel, config.block_count, + config.thread_per_block, 0, d.stream(), params.data(), indices.data(), + out.data(), gather_dim_size, indices_size, slice_size, out_size)); } // TODO(fpmc): enable indices validation on GPU. // Right now checking for indicies out of bound in the kernel would diff --git a/tensorflow/core/kernels/gather_nd_op_gpu.cu.cc b/tensorflow/core/kernels/gather_nd_op_gpu.cu.cc index e6ff5e5a29..22fb667441 100644 --- a/tensorflow/core/kernels/gather_nd_op_gpu.cu.cc +++ b/tensorflow/core/kernels/gather_nd_op_gpu.cu.cc @@ -86,10 +86,11 @@ struct GatherNdSlice { } CudaLaunchConfig config = GetCudaLaunchConfig(out_size, d); - CudaLaunchKernel(GatherSliceOpKernel, config.block_count, - config.thread_per_block, 0, d.stream(), Tparams.data(), - Tindices.data(), Tout.data(), batch_strides, batch_indices, - indices_size, s_size, out_size); + TF_CHECK_OK(CudaLaunchKernel(GatherSliceOpKernel, + config.block_count, config.thread_per_block, 0, + d.stream(), Tparams.data(), Tindices.data(), + Tout.data(), batch_strides, batch_indices, + indices_size, s_size, out_size)); // TODO(ebrevdo): enable indices validation on GPU. // Right now checking for indices out of bound in the kernel would diff --git a/tensorflow/core/kernels/inplace_ops_functor_gpu.cu.cc b/tensorflow/core/kernels/inplace_ops_functor_gpu.cu.cc index 2eda23cfa7..35cfe03e8e 100644 --- a/tensorflow/core/kernels/inplace_ops_functor_gpu.cu.cc +++ b/tensorflow/core/kernels/inplace_ops_functor_gpu.cu.cc @@ -49,9 +49,9 @@ Status DoParallelConcatUpdate(const Device& d, const Tensor& value, int32 loc, const int64 ncols = Toutput.dimension(1); const T* src = value.flat().data(); T* dst = output->flat().data(); - CudaLaunchKernel(DoParallelConcatOpKernel, cfg.block_count, - cfg.thread_per_block, 0, d.stream(), - cfg.virtual_thread_count, nrows, ncols, loc, src, dst); + TF_CHECK_OK(CudaLaunchKernel( + DoParallelConcatOpKernel, cfg.block_count, cfg.thread_per_block, 0, + d.stream(), cfg.virtual_thread_count, nrows, ncols, loc, src, dst)); return Status::OK(); } @@ -117,22 +117,22 @@ void DoInplaceOp(const Device& d, InplaceOpType op, const Tensor& i, T* dst = y->flat().data(); switch (op) { case I_UPDATE: - CudaLaunchKernel(DoInplaceOpKernel, cfg.block_count, - cfg.thread_per_block, 0, d.stream(), - cfg.virtual_thread_count, nrows, ncols, n, src, rowids, - dst); + TF_CHECK_OK(CudaLaunchKernel(DoInplaceOpKernel, + cfg.block_count, cfg.thread_per_block, 0, + d.stream(), cfg.virtual_thread_count, nrows, + ncols, n, src, rowids, dst)); break; case I_ADD: - CudaLaunchKernel(DoInplaceOpKernel, cfg.block_count, - cfg.thread_per_block, 0, d.stream(), - cfg.virtual_thread_count, nrows, ncols, n, src, rowids, - dst); + TF_CHECK_OK(CudaLaunchKernel(DoInplaceOpKernel, cfg.block_count, + cfg.thread_per_block, 0, d.stream(), + cfg.virtual_thread_count, nrows, ncols, n, + src, rowids, dst)); break; case I_SUB: - CudaLaunchKernel(DoInplaceOpKernel, cfg.block_count, - cfg.thread_per_block, 0, d.stream(), - cfg.virtual_thread_count, nrows, ncols, n, src, rowids, - dst); + TF_CHECK_OK(CudaLaunchKernel(DoInplaceOpKernel, cfg.block_count, + cfg.thread_per_block, 0, d.stream(), + cfg.virtual_thread_count, nrows, ncols, n, + src, rowids, dst)); break; } } @@ -151,10 +151,10 @@ void DoInplaceOp(const Device& d, InplaceOpType op, const Tensor& i, const int32* rowids = i.flat().data(); bool* dst = y->flat().data(); if (op == I_UPDATE) { - CudaLaunchKernel(DoInplaceOpKernel, cfg.block_count, - cfg.thread_per_block, 0, d.stream(), - cfg.virtual_thread_count, nrows, ncols, n, src, rowids, - dst); + TF_CHECK_OK(CudaLaunchKernel(DoInplaceOpKernel, + cfg.block_count, cfg.thread_per_block, 0, + d.stream(), cfg.virtual_thread_count, nrows, + ncols, n, src, rowids, dst)); } } diff --git a/tensorflow/core/kernels/matrix_set_diag_op_gpu.cu.cc b/tensorflow/core/kernels/matrix_set_diag_op_gpu.cu.cc index 47a3905743..4abf666fad 100644 --- a/tensorflow/core/kernels/matrix_set_diag_op_gpu.cu.cc +++ b/tensorflow/core/kernels/matrix_set_diag_op_gpu.cu.cc @@ -73,16 +73,18 @@ struct MatrixSetDiag { if (input.data() == output.data()) { CudaLaunchConfig config = GetCudaLaunchConfig(batch_size * minsize, device); - CudaLaunchKernel(MatrixSetDiagKernel, config.block_count, - config.thread_per_block, 0, device.stream(), - config.virtual_thread_count, m, n, minsize, diag.data(), - output.data()); + TF_CHECK_OK(CudaLaunchKernel(MatrixSetDiagKernel, + config.block_count, config.thread_per_block, + 0, device.stream(), + config.virtual_thread_count, m, n, minsize, + diag.data(), output.data())); } else { CudaLaunchConfig config = GetCudaLaunchConfig(batch_size * m * n, device); - CudaLaunchKernel(MatrixCopyInputAndSetDiagKernel, - config.block_count, config.thread_per_block, 0, - device.stream(), config.virtual_thread_count, m, n, - minsize, input.data(), diag.data(), output.data()); + TF_CHECK_OK(CudaLaunchKernel(MatrixCopyInputAndSetDiagKernel, + config.block_count, config.thread_per_block, + 0, device.stream(), + config.virtual_thread_count, m, n, minsize, + input.data(), diag.data(), output.data())); } } }; diff --git a/tensorflow/core/kernels/parameterized_truncated_normal_op_gpu.cu.cc b/tensorflow/core/kernels/parameterized_truncated_normal_op_gpu.cu.cc index d8f5c5222e..4abbe2fe3b 100644 --- a/tensorflow/core/kernels/parameterized_truncated_normal_op_gpu.cu.cc +++ b/tensorflow/core/kernels/parameterized_truncated_normal_op_gpu.cu.cc @@ -242,12 +242,12 @@ struct TruncatedNormalFunctor { typename TTypes::Flat output) { const auto config = GetCudaLaunchConfig(num_elements, d); - CudaLaunchKernel( + TF_CHECK_OK(CudaLaunchKernel( TruncatedNormalKernel, config.block_count, config.thread_per_block, 0, d.stream(), gen, output.data(), num_batches, samples_per_batch, num_elements, means.data(), means.dimension(0) == 1, stddevs.data(), stddevs.dimension(0) == 1, minvals.data(), minvals.dimension(0) == 1, - maxvals.data(), maxvals.dimension(0) == 1, kMaxIterations); + maxvals.data(), maxvals.dimension(0) == 1, kMaxIterations)); } }; diff --git a/tensorflow/core/kernels/population_count_op_gpu.cu.cc b/tensorflow/core/kernels/population_count_op_gpu.cu.cc index dca8c26b79..b9a7da5687 100644 --- a/tensorflow/core/kernels/population_count_op_gpu.cu.cc +++ b/tensorflow/core/kernels/population_count_op_gpu.cu.cc @@ -63,17 +63,17 @@ __global__ void PopulationCountKernel(const int size, const int64* input, CUDA_1D_KERNEL_LOOP(i, size) { output[i] = __popcll(ldg(input + i)); } } -#define DEFINE_GPU_SPECS(T) \ - template <> \ - void PopulationCount::operator()( \ - OpKernelContext* c, typename TTypes::ConstFlat input, \ - TTypes::Flat output) { \ - const GPUDevice& d = c->eigen_device(); \ - int64 total_count = input.size(); \ - CudaLaunchConfig config = GetCudaLaunchConfig(total_count, d); \ - CudaLaunchKernel(PopulationCountKernel, config.block_count, \ - config.thread_per_block, 0, d.stream(), total_count, \ - input.data(), output.data()); \ +#define DEFINE_GPU_SPECS(T) \ + template <> \ + void PopulationCount::operator()( \ + OpKernelContext* c, typename TTypes::ConstFlat input, \ + TTypes::Flat output) { \ + const GPUDevice& d = c->eigen_device(); \ + int64 total_count = input.size(); \ + CudaLaunchConfig config = GetCudaLaunchConfig(total_count, d); \ + TF_CHECK_OK(CudaLaunchKernel(PopulationCountKernel, config.block_count, \ + config.thread_per_block, 0, d.stream(), \ + total_count, input.data(), output.data())); \ } TF_CALL_uint8(DEFINE_GPU_SPECS); diff --git a/tensorflow/core/kernels/random_op_gpu.cu.cc b/tensorflow/core/kernels/random_op_gpu.cu.cc index 24ecb3779b..55278d0480 100644 --- a/tensorflow/core/kernels/random_op_gpu.cu.cc +++ b/tensorflow/core/kernels/random_op_gpu.cu.cc @@ -59,8 +59,9 @@ void FillPhiloxRandom::operator()( (d.getNumGpuMultiProcessors() * d.maxGpuThreadsPerMultiProcessor()) / block_size; - CudaLaunchKernel(FillPhiloxRandomKernelLaunch, num_blocks, - block_size, 0, d.stream(), gen, data, size, dist); + TF_CHECK_OK(CudaLaunchKernel(FillPhiloxRandomKernelLaunch, + num_blocks, block_size, 0, d.stream(), gen, data, + size, dist)); } // Explicit instantiation of the GPU distributions functors diff --git a/tensorflow/core/kernels/reduction_gpu_kernels.cu.h b/tensorflow/core/kernels/reduction_gpu_kernels.cu.h index d6e6e45418..0f08588eba 100644 --- a/tensorflow/core/kernels/reduction_gpu_kernels.cu.h +++ b/tensorflow/core/kernels/reduction_gpu_kernels.cu.h @@ -511,9 +511,9 @@ void LaunchScalarReduction(OpKernelContext* ctx, OUT_T out, IN_T in, if (in_size <= 4096) { const int num_blocks = 1; const int num_threads = 256; - CudaLaunchKernel(BlockReduceKernel, - num_blocks, num_threads, 0, cu_stream, in, out, in_size, - op, init); + TF_CHECK_OK(CudaLaunchKernel( + BlockReduceKernel, num_blocks, + num_threads, 0, cu_stream, in, out, in_size, op, init)); return; } else if (in_size <= 1 << 18) { const int num_threads = 256; @@ -532,17 +532,19 @@ void LaunchScalarReduction(OpKernelContext* ctx, OUT_T out, IN_T in, DT_INT8, TensorShape({static_cast(num_blocks * sizeof(T))}), &temp_storage)); - CudaLaunchKernel(BlockReduceKernel, num_blocks, - num_threads, 0, cu_stream, in, - (T*)temp_storage.flat().data(), in_size, op, init); + TF_CHECK_OK(CudaLaunchKernel(BlockReduceKernel, + num_blocks, num_threads, 0, cu_stream, in, + (T*)temp_storage.flat().data(), + in_size, op, init)); // take care that we only reduce blocks that had some valid elements in them // TODO(eriche): CUB currently has a bug in HeadSegmentedReduce that // requires it to be used with a full warp. Can reduce 32 -> num_blocks // when this is fixed. - CudaLaunchKernel(CleanupSegments, 1, 32, 0, cu_stream, - (T*)temp_storage.flat().data(), out, 1, 1, - num_blocks, op, init); + TF_CHECK_OK(CudaLaunchKernel(CleanupSegments, 1, 32, 0, + cu_stream, + (T*)temp_storage.flat().data(), out, 1, + 1, num_blocks, op, init)); return; } @@ -577,9 +579,9 @@ void LaunchRowReduction(OpKernelContext* ctx, OUT_T out, IN_T in, int num_rows, const int warps_per_block = threads_per_block / 32; int num_blocks = (num_rows + warps_per_block - 1) / warps_per_block; - CudaLaunchKernel(RowReduceKernel, num_blocks, - threads_per_block, 0, cu_stream, in, out, num_rows, - num_cols, op, init); + TF_CHECK_OK(CudaLaunchKernel(RowReduceKernel, num_blocks, + threads_per_block, 0, cu_stream, in, out, + num_rows, num_cols, op, init)); return; } @@ -630,9 +632,9 @@ void LaunchColumnReduction_LTE16Cols(OpKernelContext* ctx, OUT_T out, IN_T in, } if (grid_dim.y == 1) { - CudaLaunchKernel(ColumnReduceMax16ColumnsKernel, grid_dim, - block_dim, 0, cu_stream, in, out, extent_x, extent_y, op, - init); + TF_CHECK_OK(CudaLaunchKernel( + ColumnReduceMax16ColumnsKernel, grid_dim, block_dim, 0, + cu_stream, in, out, extent_x, extent_y, op, init)); } else { Tensor temp_storage; OP_REQUIRES_OK(ctx, @@ -640,16 +642,17 @@ void LaunchColumnReduction_LTE16Cols(OpKernelContext* ctx, OUT_T out, IN_T in, TensorShape({static_cast( sizeof(T) * extent_y * grid_dim.y)}), &temp_storage)); - CudaLaunchKernel(ColumnReduceMax16ColumnsKernel, grid_dim, - block_dim, 0, cu_stream, in, - (T*)temp_storage.flat().data(), extent_x, extent_y, - op, init); + TF_CHECK_OK(CudaLaunchKernel(ColumnReduceMax16ColumnsKernel, + grid_dim, block_dim, 0, cu_stream, in, + (T*)temp_storage.flat().data(), + extent_x, extent_y, op, init)); dim3 new_grid_dim((grid_dim.y * extent_y + 31) / 32, 1, 1); dim3 num_threads(128, 1, 1); - CudaLaunchKernel(CleanupSegments, new_grid_dim, num_threads, - 0, cu_stream, (T*)temp_storage.flat().data(), out, - extent_x, extent_y, grid_dim.y, op, init); + TF_CHECK_OK(CudaLaunchKernel(CleanupSegments, new_grid_dim, + num_threads, 0, cu_stream, + (T*)temp_storage.flat().data(), out, + extent_x, extent_y, grid_dim.y, op, init)); } } @@ -668,8 +671,9 @@ void LaunchColumnReduction_LTE4096Cols(OpKernelContext* ctx, OUT_T out, IN_T in, } if (grid_dim.y == 1) { - CudaLaunchKernel(ColumnReduceKernel, grid_dim, block_dim, - 0, cu_stream, in, out, extent_x, extent_y, op, init); + TF_CHECK_OK(CudaLaunchKernel(ColumnReduceKernel, grid_dim, + block_dim, 0, cu_stream, in, out, extent_x, + extent_y, op, init)); } else { Tensor temp_storage; OP_REQUIRES_OK(ctx, @@ -678,15 +682,16 @@ void LaunchColumnReduction_LTE4096Cols(OpKernelContext* ctx, OUT_T out, IN_T in, sizeof(T) * extent_y * grid_dim.y)}), &temp_storage)); - CudaLaunchKernel(ColumnReduceKernel, grid_dim, block_dim, 0, - cu_stream, in, (T*)temp_storage.flat().data(), - extent_x, extent_y, op, init); + TF_CHECK_OK(CudaLaunchKernel( + ColumnReduceKernel, grid_dim, block_dim, 0, cu_stream, in, + (T*)temp_storage.flat().data(), extent_x, extent_y, op, init)); dim3 new_grid_dim((grid_dim.y * extent_y + 31) / 32, 1, 1); dim3 num_threads(128, 1, 1); - CudaLaunchKernel(CleanupSegments, new_grid_dim, block_dim, 0, - cu_stream, (T*)temp_storage.flat().data(), out, - extent_x, extent_y, grid_dim.y, op, init); + TF_CHECK_OK(CudaLaunchKernel(CleanupSegments, new_grid_dim, + block_dim, 0, cu_stream, + (T*)temp_storage.flat().data(), out, + extent_x, extent_y, grid_dim.y, op, init)); } } @@ -704,9 +709,9 @@ void LaunchColumnReduction(OpKernelContext* ctx, OUT_T out, IN_T in, int threads_per_block = 128; int num_blocks = Eigen::divup(extent_y, threads_per_block); - CudaLaunchKernel(ColumnReduceSimpleKernel, num_blocks, - threads_per_block, 0, cu_stream, in, out, 1, extent_x, - extent_y, op); + TF_CHECK_OK(CudaLaunchKernel(ColumnReduceSimpleKernel, + num_blocks, threads_per_block, 0, cu_stream, + in, out, 1, extent_x, extent_y, op)); } } @@ -720,9 +725,9 @@ void Launch3DYReduction(OpKernelContext* ctx, OUT_T out, IN_T in, int extent_x, // TODO(eriche): this won't be very good in the case of small x // small z and large y. - CudaLaunchKernel(ColumnReduceSimpleKernel, num_blocks, - threads_per_block, 0, cu_stream, in, out, extent_x, extent_y, - extent_z, op); + TF_CHECK_OK(CudaLaunchKernel(ColumnReduceSimpleKernel, + num_blocks, threads_per_block, 0, cu_stream, in, + out, extent_x, extent_y, extent_z, op)); } template diff --git a/tensorflow/core/kernels/resize_bilinear_op_gpu.cu.cc b/tensorflow/core/kernels/resize_bilinear_op_gpu.cu.cc index d918d9193c..935f88e540 100644 --- a/tensorflow/core/kernels/resize_bilinear_op_gpu.cu.cc +++ b/tensorflow/core/kernels/resize_bilinear_op_gpu.cu.cc @@ -164,11 +164,11 @@ struct ResizeBilinear { if (total_count == 0) return; CudaLaunchConfig config = GetCudaLaunchConfig(total_count, d); - CudaLaunchKernel(ResizeBilinearKernel, config.block_count, - config.thread_per_block, 0, d.stream(), - config.virtual_thread_count, images.data(), height_scale, - width_scale, batch, in_height, in_width, channels, - out_height, out_width, output.data()); + TF_CHECK_OK(CudaLaunchKernel( + ResizeBilinearKernel, config.block_count, config.thread_per_block, 0, + d.stream(), config.virtual_thread_count, images.data(), height_scale, + width_scale, batch, in_height, in_width, channels, out_height, + out_width, output.data())); } }; @@ -194,19 +194,19 @@ struct ResizeBilinearGrad { total_count = batch * original_height * original_width * channels; if (total_count == 0) return; config = GetCudaLaunchConfig(total_count, d); - CudaLaunchKernel(SetZero, config.block_count, config.thread_per_block, 0, - d.stream(), config.virtual_thread_count, - output_grad.data()); + TF_CHECK_OK(CudaLaunchKernel( + SetZero, config.block_count, config.thread_per_block, 0, d.stream(), + config.virtual_thread_count, output_grad.data())); // Accumulate. total_count = batch * resized_height * resized_width * channels; config = GetCudaLaunchConfig(total_count, d); - CudaLaunchKernel(ResizeBilinearGradKernel, config.block_count, - config.thread_per_block, 0, d.stream(), - config.virtual_thread_count, input_grad.data(), - height_scale, width_scale, batch, original_height, - original_width, channels, resized_height, resized_width, - output_grad.data()); + TF_CHECK_OK(CudaLaunchKernel( + ResizeBilinearGradKernel, config.block_count, + config.thread_per_block, 0, d.stream(), config.virtual_thread_count, + input_grad.data(), height_scale, width_scale, batch, original_height, + original_width, channels, resized_height, resized_width, + output_grad.data())); } }; diff --git a/tensorflow/core/kernels/resize_nearest_neighbor_op_gpu.cu.cc b/tensorflow/core/kernels/resize_nearest_neighbor_op_gpu.cu.cc index e93948f29b..b32634eef8 100644 --- a/tensorflow/core/kernels/resize_nearest_neighbor_op_gpu.cu.cc +++ b/tensorflow/core/kernels/resize_nearest_neighbor_op_gpu.cu.cc @@ -112,11 +112,11 @@ struct ResizeNearestNeighbor { if (output_size == 0) return true; CudaLaunchConfig config = GetCudaLaunchConfig(output_size, d); - CudaLaunchKernel(ResizeNearestNeighborNHWC, - config.block_count, config.thread_per_block, 0, d.stream(), - output_size, input.data(), in_height, in_width, channels, - out_height, out_width, height_scale, width_scale, - output.data()); + TF_CHECK_OK(CudaLaunchKernel( + ResizeNearestNeighborNHWC, config.block_count, + config.thread_per_block, 0, d.stream(), output_size, input.data(), + in_height, in_width, channels, out_height, out_width, height_scale, + width_scale, output.data())); return d.ok(); } }; diff --git a/tensorflow/core/kernels/scan_ops_gpu.h b/tensorflow/core/kernels/scan_ops_gpu.h index 21fc77ffd1..557b72000a 100644 --- a/tensorflow/core/kernels/scan_ops_gpu.h +++ b/tensorflow/core/kernels/scan_ops_gpu.h @@ -242,34 +242,40 @@ void LaunchScan(const GPUDevice& d, typename TTypes::ConstTensor in, // Launch on the smallest power of 2 block size that we can. if (ideal_block_size >= 1024 && std::is_same::value) { const int block_size = 1024; - CudaLaunchKernel(scan_kernel, - num_blocks, block_size, 0, d.stream(), in.data(), - out.data(), dimx, dimy, dimz, exclusive, reverse, op); + TF_CHECK_OK( + CudaLaunchKernel(scan_kernel, + num_blocks, block_size, 0, d.stream(), in.data(), + out.data(), dimx, dimy, dimz, exclusive, reverse, op)); } else if (ideal_block_size >= 512) { const int block_size = 512; - CudaLaunchKernel(scan_kernel, - num_blocks, block_size, 0, d.stream(), in.data(), - out.data(), dimx, dimy, dimz, exclusive, reverse, op); + TF_CHECK_OK( + CudaLaunchKernel(scan_kernel, + num_blocks, block_size, 0, d.stream(), in.data(), + out.data(), dimx, dimy, dimz, exclusive, reverse, op)); } else if (ideal_block_size >= 256) { const int block_size = 256; - CudaLaunchKernel(scan_kernel, - num_blocks, block_size, 0, d.stream(), in.data(), - out.data(), dimx, dimy, dimz, exclusive, reverse, op); + TF_CHECK_OK( + CudaLaunchKernel(scan_kernel, + num_blocks, block_size, 0, d.stream(), in.data(), + out.data(), dimx, dimy, dimz, exclusive, reverse, op)); } else if (ideal_block_size >= 128) { const int block_size = 128; - CudaLaunchKernel(scan_kernel, - num_blocks, block_size, 0, d.stream(), in.data(), - out.data(), dimx, dimy, dimz, exclusive, reverse, op); + TF_CHECK_OK( + CudaLaunchKernel(scan_kernel, + num_blocks, block_size, 0, d.stream(), in.data(), + out.data(), dimx, dimy, dimz, exclusive, reverse, op)); } else if (ideal_block_size >= 64) { const int block_size = 64; - CudaLaunchKernel(scan_kernel, - num_blocks, block_size, 0, d.stream(), in.data(), - out.data(), dimx, dimy, dimz, exclusive, reverse, op); + TF_CHECK_OK( + CudaLaunchKernel(scan_kernel, + num_blocks, block_size, 0, d.stream(), in.data(), + out.data(), dimx, dimy, dimz, exclusive, reverse, op)); } else { const int block_size = 32; - CudaLaunchKernel(scan_kernel, - num_blocks, block_size, 0, d.stream(), in.data(), - out.data(), dimx, dimy, dimz, exclusive, reverse, op); + TF_CHECK_OK( + CudaLaunchKernel(scan_kernel, + num_blocks, block_size, 0, d.stream(), in.data(), + out.data(), dimx, dimy, dimz, exclusive, reverse, op)); } } diff --git a/tensorflow/core/kernels/scatter_functor_gpu.cu.h b/tensorflow/core/kernels/scatter_functor_gpu.cu.h index 59d704de9f..57344c1dd2 100644 --- a/tensorflow/core/kernels/scatter_functor_gpu.cu.h +++ b/tensorflow/core/kernels/scatter_functor_gpu.cu.h @@ -127,10 +127,10 @@ struct ScatterFunctor { const Index indices_size = indices.size(); const Index updates_size = updates.size(); CudaLaunchConfig config = GetCudaLaunchConfig(updates_size, d); - CudaLaunchKernel(scatter_op_gpu::ScatterOpCustomKernel, - config.block_count, config.thread_per_block, 0, d.stream(), - params.data(), updates.data(), indices.data(), - first_dim_size, updates_size, indices_size); + TF_CHECK_OK(CudaLaunchKernel( + scatter_op_gpu::ScatterOpCustomKernel, config.block_count, + config.thread_per_block, 0, d.stream(), params.data(), updates.data(), + indices.data(), first_dim_size, updates_size, indices_size)); return -1; } }; @@ -148,10 +148,11 @@ struct ScatterScalarFunctor { const Index indices_size = indices.size(); const Index synthesized_updates_size = indices_size * params.dimension(1); CudaLaunchConfig config = GetCudaLaunchConfig(synthesized_updates_size, d); - CudaLaunchKernel(scatter_op_gpu::ScatterScalarOpCustomKernel, - config.block_count, config.thread_per_block, 0, d.stream(), - params.data(), update.data(), indices.data(), - first_dim_size, indices_size, synthesized_updates_size); + TF_CHECK_OK(CudaLaunchKernel( + scatter_op_gpu::ScatterScalarOpCustomKernel, + config.block_count, config.thread_per_block, 0, d.stream(), + params.data(), update.data(), indices.data(), first_dim_size, + indices_size, synthesized_updates_size)); return -1; } }; diff --git a/tensorflow/core/kernels/scatter_nd_op_gpu.cu.cc b/tensorflow/core/kernels/scatter_nd_op_gpu.cu.cc index 62954d178e..9936b3f9b7 100644 --- a/tensorflow/core/kernels/scatter_nd_op_gpu.cu.cc +++ b/tensorflow/core/kernels/scatter_nd_op_gpu.cu.cc @@ -137,10 +137,11 @@ struct ScatterNdFunctor { CudaLaunchConfig config = GetCudaLaunchConfig(Toutput.size(), d); - CudaLaunchKernel(ScatterNdOpKernel, config.block_count, - config.thread_per_block, 0, d.stream(), Tindices.data(), - Tupdates.data(), Toutput.data(), output_shape_prefix, - batch_strides, batch_size, slice_size); + TF_CHECK_OK(CudaLaunchKernel(ScatterNdOpKernel, + config.block_count, config.thread_per_block, 0, + d.stream(), Tindices.data(), Tupdates.data(), + Toutput.data(), output_shape_prefix, + batch_strides, batch_size, slice_size)); return -1; } diff --git a/tensorflow/core/kernels/searchsorted_op_gpu.cu.cc b/tensorflow/core/kernels/searchsorted_op_gpu.cu.cc index 22b7179fec..71580ff9a8 100644 --- a/tensorflow/core/kernels/searchsorted_op_gpu.cu.cc +++ b/tensorflow/core/kernels/searchsorted_op_gpu.cu.cc @@ -68,10 +68,10 @@ struct UpperBoundFunctor { CudaLaunchConfig config = GetCudaLaunchConfig(values.size(), context->eigen_gpu_device()); - CudaLaunchKernel(UpperBoundKernel, config.block_count, - config.thread_per_block, 0, stream, sorted_inputs.data(), - batch_size, num_inputs, num_values, values.data(), - output->data()); + TF_CHECK_OK(CudaLaunchKernel( + UpperBoundKernel, config.block_count, + config.thread_per_block, 0, stream, sorted_inputs.data(), batch_size, + num_inputs, num_values, values.data(), output->data())); return Status::OK(); } @@ -88,10 +88,10 @@ struct LowerBoundFunctor { CudaLaunchConfig config = GetCudaLaunchConfig(values.size(), context->eigen_gpu_device()); - CudaLaunchKernel(LowerBoundKernel, config.block_count, - config.thread_per_block, 0, stream, sorted_inputs.data(), - batch_size, num_inputs, num_values, values.data(), - output->data()); + TF_CHECK_OK(CudaLaunchKernel( + LowerBoundKernel, config.block_count, + config.thread_per_block, 0, stream, sorted_inputs.data(), batch_size, + num_inputs, num_values, values.data(), output->data())); return Status::OK(); } diff --git a/tensorflow/core/kernels/segment_reduction_ops_gpu.cu.cc b/tensorflow/core/kernels/segment_reduction_ops_gpu.cu.cc index 7af1f6ce2b..39406dd9a2 100644 --- a/tensorflow/core/kernels/segment_reduction_ops_gpu.cu.cc +++ b/tensorflow/core/kernels/segment_reduction_ops_gpu.cu.cc @@ -138,8 +138,9 @@ void SegmentSumFunctor::operator()( } // Set 'output' to zeros. CudaLaunchConfig config = GetCudaLaunchConfig(output.size(), d); - CudaLaunchKernel(SetZero, config.block_count, config.thread_per_block, 0, - d.stream(), output.size(), output.data()); + TF_CHECK_OK(CudaLaunchKernel(SetZero, config.block_count, + config.thread_per_block, 0, d.stream(), + output.size(), output.data())); if (data_size == 0 || segment_ids_shape.num_elements() == 0) { return; } @@ -162,10 +163,11 @@ void SegmentSumFunctor::operator()( input_inner_dim_size * input_outer_dim_num_stripe; config = GetCudaLaunchConfig(total_stripe_count, d); - CudaLaunchKernel(SortedSegmentSumCustomKernel, - config.block_count, config.thread_per_block, 0, d.stream(), - input_outer_dim_size, input_inner_dim_size, output_rows, - segment_ids.data(), data, output.data(), total_stripe_count); + TF_CHECK_OK(CudaLaunchKernel( + SortedSegmentSumCustomKernel, + config.block_count, config.thread_per_block, 0, d.stream(), + input_outer_dim_size, input_inner_dim_size, output_rows, + segment_ids.data(), data, output.data(), total_stripe_count)); } template { // Set 'output' to initial value. GPUDevice d = ctx->template eigen_device(); CudaLaunchConfig config = GetCudaLaunchConfig(output.size(), d); - CudaLaunchKernel(SetToValue, config.block_count, config.thread_per_block, - 0, d.stream(), output.size(), output.data(), - InitialValueF()()); + TF_CHECK_OK(CudaLaunchKernel( + SetToValue, config.block_count, config.thread_per_block, 0, + d.stream(), output.size(), output.data(), InitialValueF()())); if (data_size == 0 || segment_ids_shape.num_elements() == 0) { return; } @@ -197,10 +199,11 @@ struct UnsortedSegmentFunctor { const Index input_inner_dim_size = data_size / input_outer_dim_size; config = GetCudaLaunchConfig(data_size, d); - CudaLaunchKernel(UnsortedSegmentCustomKernel, - config.block_count, config.thread_per_block, 0, d.stream(), - input_outer_dim_size, input_inner_dim_size, num_segments, - segment_ids.data(), data, output.data()); + TF_CHECK_OK(CudaLaunchKernel( + UnsortedSegmentCustomKernel, config.block_count, + config.thread_per_block, 0, d.stream(), input_outer_dim_size, + input_inner_dim_size, num_segments, segment_ids.data(), data, + output.data())); } }; diff --git a/tensorflow/core/kernels/softmax_op_gpu.cu.cc b/tensorflow/core/kernels/softmax_op_gpu.cu.cc index fb07399203..9b2f3a963b 100644 --- a/tensorflow/core/kernels/softmax_op_gpu.cu.cc +++ b/tensorflow/core/kernels/softmax_op_gpu.cu.cc @@ -180,12 +180,12 @@ class SoftmaxOpGPU : public OpKernel { context, const_cast(sum_probs.flat().data()), input_itr, rows, cols); - CudaLaunchKernel( + TF_CHECK_OK(CudaLaunchKernel( GenerateNormalizedProb, numBlocks, numThreads, 0, cu_stream, 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_); + const_cast(softmax_out->flat().data()), rows, cols, log_)); } } diff --git a/tensorflow/core/kernels/spacetobatch_functor_gpu.cu.cc b/tensorflow/core/kernels/spacetobatch_functor_gpu.cu.cc index dc1a7ac656..ea6e076909 100644 --- a/tensorflow/core/kernels/spacetobatch_functor_gpu.cu.cc +++ b/tensorflow/core/kernels/spacetobatch_functor_gpu.cu.cc @@ -141,12 +141,11 @@ struct SpaceToBatchFunctor { } CudaLaunchConfig config = GetCudaLaunchConfig(static_cast(total_count), d); - CudaLaunchKernel(S2B, config.block_count, - config.thread_per_block, 0, d.stream(), - config.virtual_thread_count, - const_cast(space_tensor.data()), args, - const_cast(batch_tensor.data())); - return Status::OK(); + return CudaLaunchKernel(S2B, config.block_count, + config.thread_per_block, 0, d.stream(), + config.virtual_thread_count, + const_cast(space_tensor.data()), args, + const_cast(batch_tensor.data())); } }; diff --git a/tensorflow/core/kernels/spacetodepth_op_gpu.cu.cc b/tensorflow/core/kernels/spacetodepth_op_gpu.cu.cc index 9fc76da26d..606ff89e74 100644 --- a/tensorflow/core/kernels/spacetodepth_op_gpu.cu.cc +++ b/tensorflow/core/kernels/spacetodepth_op_gpu.cu.cc @@ -158,11 +158,11 @@ struct SpaceToDepthOpFunctor { return; } CudaLaunchConfig config = GetCudaLaunchConfig(total_count, d); - CudaLaunchKernel(S2D_NHWC, config.block_count, config.thread_per_block, - 0, d.stream(), config.virtual_thread_count, input.data(), - block_size, batch_size, input_height, input_width, - input_depth, output_height, output_width, output_depth, - output.data()); + TF_CHECK_OK(CudaLaunchKernel( + S2D_NHWC, config.block_count, config.thread_per_block, 0, d.stream(), + config.virtual_thread_count, input.data(), block_size, batch_size, + input_height, input_width, input_depth, output_height, output_width, + output_depth, output.data())); } void operator()(const GPUDevice& d, typename TTypes::ConstTensor input, int block_size, typename TTypes::Tensor output) { @@ -194,23 +194,26 @@ struct SpaceToDepthOpFunctor { CudaLaunchConfig config = GetCudaLaunchConfig(total_count, d); switch (block_size) { case 2: - return CudaLaunchKernel(S2D_NCHW_LOOP, config.block_count, - config.thread_per_block, 0, d.stream(), - total_count, input.data(), output_width, - input_width, input_depth_by_output_area, - output_depth_by_output_area, output.data()); + TF_CHECK_OK(CudaLaunchKernel( + S2D_NCHW_LOOP, config.block_count, config.thread_per_block, + 0, d.stream(), total_count, input.data(), output_width, + input_width, input_depth_by_output_area, + output_depth_by_output_area, output.data())); + return; case 3: - return CudaLaunchKernel(S2D_NCHW_LOOP, config.block_count, - config.thread_per_block, 0, d.stream(), - total_count, input.data(), output_width, - input_width, input_depth_by_output_area, - output_depth_by_output_area, output.data()); + TF_CHECK_OK(CudaLaunchKernel( + S2D_NCHW_LOOP, config.block_count, config.thread_per_block, + 0, d.stream(), total_count, input.data(), output_width, + input_width, input_depth_by_output_area, + output_depth_by_output_area, output.data())); + return; case 4: - return CudaLaunchKernel(S2D_NCHW_LOOP, config.block_count, - config.thread_per_block, 0, d.stream(), - total_count, input.data(), output_width, - input_width, input_depth_by_output_area, - output_depth_by_output_area, output.data()); + TF_CHECK_OK(CudaLaunchKernel( + S2D_NCHW_LOOP, config.block_count, config.thread_per_block, + 0, d.stream(), total_count, input.data(), output_width, + input_width, input_depth_by_output_area, + output_depth_by_output_area, output.data())); + return; } } @@ -220,10 +223,10 @@ struct SpaceToDepthOpFunctor { return; } CudaLaunchConfig config = GetCudaLaunchConfig(total_count, d); - CudaLaunchKernel(S2D_NCHW, config.block_count, config.thread_per_block, - 0, d.stream(), config.virtual_thread_count, input.data(), - block_size, output_width, input_depth * output_height, - output.data()); + TF_CHECK_OK(CudaLaunchKernel( + S2D_NCHW, config.block_count, config.thread_per_block, 0, d.stream(), + config.virtual_thread_count, input.data(), block_size, output_width, + input_depth * output_height, output.data())); } void operator()(const GPUDevice& d, typename TTypes::ConstTensor input, int block_size, typename TTypes::Tensor output) { diff --git a/tensorflow/core/kernels/sparse_tensor_dense_matmul_op_gpu.cu.cc b/tensorflow/core/kernels/sparse_tensor_dense_matmul_op_gpu.cu.cc index ad9cc90605..f85f2a48a1 100644 --- a/tensorflow/core/kernels/sparse_tensor_dense_matmul_op_gpu.cu.cc +++ b/tensorflow/core/kernels/sparse_tensor_dense_matmul_op_gpu.cu.cc @@ -81,10 +81,11 @@ struct SparseTensorDenseMatMulFunctor { // out.size()? Perhaps p * nnz ? CudaLaunchConfig config = GetCudaLaunchConfig(p * nnz, d); - CudaLaunchKernel(SparseTensorDenseMatMulKernel, - config.block_count, config.thread_per_block, 0, d.stream(), - nnz, m, b_rows, b_cols, p, a_indices.data(), - a_values.data(), b.data(), out.data()); + TF_CHECK_OK(CudaLaunchKernel( + SparseTensorDenseMatMulKernel, + config.block_count, config.thread_per_block, 0, d.stream(), nnz, m, + b_rows, b_cols, p, a_indices.data(), a_values.data(), b.data(), + out.data())); return Status::OK(); } diff --git a/tensorflow/core/kernels/split_lib_gpu.cu.cc b/tensorflow/core/kernels/split_lib_gpu.cu.cc index 6106e70994..3d42f2dc70 100644 --- a/tensorflow/core/kernels/split_lib_gpu.cu.cc +++ b/tensorflow/core/kernels/split_lib_gpu.cu.cc @@ -199,10 +199,10 @@ struct SplitOpGPULaunch { CudaLaunchConfig config = GetCudaLaunchConfig( prefix_dim_size * split_dim_size * suffix_dim_size, d); - CudaLaunchKernel(SplitOpKernel, config.block_count, - config.thread_per_block, 0, d.stream(), input, - prefix_dim_size, split_dim_size, suffix_dim_size, - output_ptr_data); + TF_CHECK_OK(CudaLaunchKernel(SplitOpKernel, config.block_count, + config.thread_per_block, 0, d.stream(), input, + prefix_dim_size, split_dim_size, + suffix_dim_size, output_ptr_data)); } }; diff --git a/tensorflow/core/kernels/stateful_random_ops_gpu.cu.cc b/tensorflow/core/kernels/stateful_random_ops_gpu.cu.cc index b58d0b6088..6b20b1751b 100644 --- a/tensorflow/core/kernels/stateful_random_ops_gpu.cu.cc +++ b/tensorflow/core/kernels/stateful_random_ops_gpu.cu.cc @@ -74,9 +74,10 @@ void UpdateVariableAndFill_Philox::operator()( int zero = 0; cudaMemcpyToSymbol(thread_counter, &zero, sizeof(int)); - CudaLaunchKernel(FillKernel, cfg.block_count, - cfg.thread_per_block, 0, d.stream(), Distribution(), - state_size, output_size, state_data, output_data); + TF_CHECK_OK(CudaLaunchKernel(FillKernel, cfg.block_count, + cfg.thread_per_block, 0, d.stream(), + Distribution(), state_size, output_size, + state_data, output_data)); } // Explicit instantiation of the GPU distributions functors. diff --git a/tensorflow/core/kernels/tile_functor_gpu.h b/tensorflow/core/kernels/tile_functor_gpu.h index 726b3d5688..59bc2d3a00 100644 --- a/tensorflow/core/kernels/tile_functor_gpu.h +++ b/tensorflow/core/kernels/tile_functor_gpu.h @@ -75,9 +75,10 @@ void TileSimple(const Device& d, Tensor* out, const Tensor& in) { const T* p = in.flat().data(); T* q = out->flat().data(); CudaLaunchConfig cfg = GetCudaLaunchConfig(out_nelem, d); - CudaLaunchKernel(TileKernel, cfg.block_count, cfg.thread_per_block, 0, - d.stream(), cfg.virtual_thread_count, p, - reinterpret_cast(dev_buf), ndims, q); + TF_CHECK_OK( + CudaLaunchKernel(TileKernel, cfg.block_count, cfg.thread_per_block, 0, + d.stream(), cfg.virtual_thread_count, p, + reinterpret_cast(dev_buf), ndims, q)); // Safe to deallocate immediately after the kernel launch. d.deallocate(dev_buf); } diff --git a/tensorflow/core/kernels/topk_op_gpu.h b/tensorflow/core/kernels/topk_op_gpu.h index 27dd984314..1bcc0221b8 100644 --- a/tensorflow/core/kernels/topk_op_gpu.h +++ b/tensorflow/core/kernels/topk_op_gpu.h @@ -401,8 +401,9 @@ cudaError LaunchTopKKernel(const cudaStream_t& stream, int num_shards, // We are limited by the amount of shared memory we have per block. auto shared_memory_size = (num_shards + 1) * k * sizeof(Entry); - CudaLaunchKernel(TopKKernel, batch_size, num_shards, shared_memory_size, - stream, input, length, k, sorted, output, indices); + TF_CHECK_OK(CudaLaunchKernel(TopKKernel, batch_size, num_shards, + shared_memory_size, stream, input, length, k, + sorted, output, indices)); return cudaGetLastError(); } diff --git a/tensorflow/core/kernels/transpose_functor_gpu.cu.cc b/tensorflow/core/kernels/transpose_functor_gpu.cu.cc index 11da9c17ea..b4e5d0ae58 100644 --- a/tensorflow/core/kernels/transpose_functor_gpu.cu.cc +++ b/tensorflow/core/kernels/transpose_functor_gpu.cu.cc @@ -80,10 +80,10 @@ void TransposeSimple(const GPUDevice& d, const Tensor& in, const T* p = reinterpret_cast(in.tensor_data().data()); T* q = reinterpret_cast(const_cast((out->tensor_data().data()))); CudaLaunchConfig cfg = GetCudaLaunchConfig(nelem, d); - CudaLaunchKernel(TransposeKernel, cfg.block_count, - cfg.thread_per_block, 0, d.stream(), - cfg.virtual_thread_count, p, - reinterpret_cast(dev_buf), ndims, q); + TF_CHECK_OK(CudaLaunchKernel( + TransposeKernel, cfg.block_count, cfg.thread_per_block, 0, + d.stream(), cfg.virtual_thread_count, p, + reinterpret_cast(dev_buf), ndims, q)); // Safe to deallocate immediately after the kernel launch. d.deallocate(dev_buf); } diff --git a/tensorflow/core/kernels/where_op_gpu.cu.h b/tensorflow/core/kernels/where_op_gpu.cu.h index 72d109b9ff..da9434efcb 100644 --- a/tensorflow/core/kernels/where_op_gpu.cu.h +++ b/tensorflow/core/kernels/where_op_gpu.cu.h @@ -324,9 +324,10 @@ struct Where { CalculateStrides(input); const TIndex output_rows = output.dimension(0); CudaLaunchConfig config = GetCudaLaunchConfig(output_rows, d); - CudaLaunchKernel(PropagateWhereIndicesKernel, - config.block_count, config.thread_per_block, 0, d.stream(), - output_rows, strides, output.data()); + TF_CHECK_OK(CudaLaunchKernel(PropagateWhereIndicesKernel, + config.block_count, config.thread_per_block, 0, + d.stream(), output_rows, strides, + output.data())); return Status::OK(); } diff --git a/tensorflow/core/util/cuda_kernel_helper_test.cu.cc b/tensorflow/core/util/cuda_kernel_helper_test.cu.cc index f0fc0312ce..35f8d13f75 100644 --- a/tensorflow/core/util/cuda_kernel_helper_test.cu.cc +++ b/tensorflow/core/util/cuda_kernel_helper_test.cu.cc @@ -17,6 +17,7 @@ limitations under the License. #define EIGEN_USE_GPU #include +#include "tensorflow/core/lib/core/status_test_util.h" #include "tensorflow/core/platform/test.h" #include "tensorflow/core/util/cuda_kernel_helper.h" #include "tensorflow/core/util/cuda_launch_config.h" @@ -153,22 +154,24 @@ TEST_F(CudaLaunchConfigTest, GetCudaLaunchConfig) { // test valid inputs #define TEST_LAUNCH_PARAMETER(work_element_count) \ cfg = GetCudaLaunchConfig(bufsize, d); \ - CudaLaunchKernel(SetOutbufZero, cfg.block_count, cfg.thread_per_block, 0, \ - d.stream(), cfg, outbuf); \ + TF_CHECK_OK(CudaLaunchKernel(SetOutbufZero, cfg.block_count, \ + cfg.thread_per_block, 0, d.stream(), cfg, \ + outbuf)); \ CUDA_ASSERT_SUCCESS \ cfg = GetCudaLaunchConfig(work_element_count, d); \ - CudaLaunchKernel(Count1D, cfg.block_count, cfg.thread_per_block, 0, \ - d.stream(), cfg, bufsize, outbuf); \ + TF_CHECK_OK(CudaLaunchKernel(Count1D, cfg.block_count, cfg.thread_per_block, \ + 0, d.stream(), cfg, bufsize, outbuf)); \ CUDA_EXPECT_SUCCESS \ EXPECT_EQ(work_element_count, std::accumulate(outbuf, outbuf + bufsize, 0)); \ \ cfg = GetCudaLaunchConfig(bufsize, d, SetOutbufZero, 0, 0); \ - CudaLaunchKernel(SetOutbufZero, cfg.block_count, cfg.thread_per_block, 0, \ - d.stream(), cfg, outbuf); \ + TF_CHECK_OK(CudaLaunchKernel(SetOutbufZero, cfg.block_count, \ + cfg.thread_per_block, 0, d.stream(), cfg, \ + outbuf)); \ CUDA_ASSERT_SUCCESS \ cfg = GetCudaLaunchConfig(work_element_count, d, Count1D, 0, 0); \ - CudaLaunchKernel(Count1D, cfg.block_count, cfg.thread_per_block, 0, \ - d.stream(), cfg, bufsize, outbuf); \ + TF_CHECK_OK(CudaLaunchKernel(Count1D, cfg.block_count, cfg.thread_per_block, \ + 0, d.stream(), cfg, bufsize, outbuf)); \ CUDA_EXPECT_SUCCESS \ EXPECT_EQ(work_element_count, std::accumulate(outbuf, outbuf + bufsize, 0)) @@ -202,25 +205,29 @@ TEST_F(CudaLaunchConfigTest, GetCuda2DLaunchConfig) { CudaLaunchConfig cfg1d; // test valid inputs -#define TEST_LAUNCH_PARAMETER(dimx, dimy) \ - cfg1d = GetCudaLaunchConfig(bufsize, d); \ - CudaLaunchKernel(SetOutbufZero, cfg1d.block_count, cfg1d.thread_per_block, \ - 0, d.stream(), cfg1d, outbuf); \ - CUDA_ASSERT_SUCCESS \ - cfg = GetCuda2DLaunchConfig(dimx, dimy, d); \ - CudaLaunchKernel(Count2D, cfg.block_count, cfg.thread_per_block, 0, \ - d.stream(), cfg, bufsize, outbuf); \ - CUDA_EXPECT_SUCCESS \ - EXPECT_EQ(dimx* dimy, std::accumulate(outbuf, outbuf + bufsize, 0)); \ - \ - cfg1d = GetCudaLaunchConfig(bufsize, d, SetOutbufZero, 0, 0); \ - CudaLaunchKernel(SetOutbufZero, cfg1d.block_count, cfg1d.thread_per_block, \ - 0, d.stream(), cfg1d, outbuf); \ - CUDA_ASSERT_SUCCESS \ - cfg = GetCuda2DLaunchConfig(dimx, dimy, d, Count2D, 0, 0); \ - CudaLaunchKernel(Count2D, cfg.block_count, cfg.thread_per_block, 0, \ - d.stream(), cfg, bufsize, outbuf); \ - CUDA_EXPECT_SUCCESS \ +#define TEST_LAUNCH_PARAMETER(dimx, dimy) \ + cfg1d = GetCudaLaunchConfig(bufsize, d); \ + TF_EXPECT_OK(CudaLaunchKernel(SetOutbufZero, cfg1d.block_count, \ + cfg1d.thread_per_block, 0, d.stream(), cfg1d, \ + outbuf)); \ + CUDA_ASSERT_SUCCESS \ + cfg = GetCuda2DLaunchConfig(dimx, dimy, d); \ + TF_EXPECT_OK(CudaLaunchKernel(Count2D, cfg.block_count, \ + cfg.thread_per_block, 0, d.stream(), cfg, \ + bufsize, outbuf)); \ + CUDA_EXPECT_SUCCESS \ + EXPECT_EQ(dimx* dimy, std::accumulate(outbuf, outbuf + bufsize, 0)); \ + \ + cfg1d = GetCudaLaunchConfig(bufsize, d, SetOutbufZero, 0, 0); \ + TF_EXPECT_OK(CudaLaunchKernel(SetOutbufZero, cfg1d.block_count, \ + cfg1d.thread_per_block, 0, d.stream(), cfg1d, \ + outbuf)); \ + CUDA_ASSERT_SUCCESS \ + cfg = GetCuda2DLaunchConfig(dimx, dimy, d, Count2D, 0, 0); \ + TF_EXPECT_OK(CudaLaunchKernel(Count2D, cfg.block_count, \ + cfg.thread_per_block, 0, d.stream(), cfg, \ + bufsize, outbuf)); \ + CUDA_EXPECT_SUCCESS \ EXPECT_EQ(dimx* dimy, std::accumulate(outbuf, outbuf + bufsize, 0)) TEST_LAUNCH_PARAMETER(128, 128); @@ -242,15 +249,17 @@ TEST_F(CudaLaunchConfigTest, GetCuda3DLaunchConfig) { CudaLaunchConfig cfg1d; // test valid inputs -#define TEST_LAUNCH_PARAMETER(dimx, dimy, dimz) \ - cfg1d = GetCudaLaunchConfig(bufsize, d, SetOutbufZero, 0, 0); \ - CudaLaunchKernel(SetOutbufZero, cfg1d.block_count, cfg1d.thread_per_block, \ - 0, d.stream(), cfg1d, outbuf); \ - CUDA_ASSERT_SUCCESS \ - cfg = GetCuda3DLaunchConfig(dimx, dimy, dimz, d, Count3D, 0, 0); \ - CudaLaunchKernel(Count3D, cfg.block_count, cfg.thread_per_block, 0, \ - d.stream(), cfg, bufsize, outbuf); \ - CUDA_EXPECT_SUCCESS \ +#define TEST_LAUNCH_PARAMETER(dimx, dimy, dimz) \ + cfg1d = GetCudaLaunchConfig(bufsize, d, SetOutbufZero, 0, 0); \ + TF_EXPECT_OK(CudaLaunchKernel(SetOutbufZero, cfg1d.block_count, \ + cfg1d.thread_per_block, 0, d.stream(), cfg1d, \ + outbuf)); \ + CUDA_ASSERT_SUCCESS \ + cfg = GetCuda3DLaunchConfig(dimx, dimy, dimz, d, Count3D, 0, 0); \ + TF_EXPECT_OK(CudaLaunchKernel(Count3D, cfg.block_count, \ + cfg.thread_per_block, 0, d.stream(), cfg, \ + bufsize, outbuf)); \ + CUDA_EXPECT_SUCCESS \ EXPECT_EQ(dimx* dimy* dimz, std::accumulate(outbuf, outbuf + bufsize, 0)) TEST_LAUNCH_PARAMETER(128, 128, 128); @@ -272,7 +281,8 @@ TEST(CudaDeviceFunctionsTest, ShuffleGetSrcLane) { unsigned* failure_count; ASSERT_EQ(cudaMallocManaged(&failure_count, sizeof(unsigned)), cudaSuccess); *failure_count = 0; - CudaLaunchKernel(CudaShuffleGetSrcLaneTest, 1, 32, 0, nullptr, failure_count); + TF_EXPECT_OK(CudaLaunchKernel(CudaShuffleGetSrcLaneTest, 1, 32, 0, nullptr, + failure_count)); ASSERT_EQ(cudaDeviceSynchronize(), cudaSuccess); ASSERT_EQ(*failure_count, 0); cudaFree(failure_count); diff --git a/tensorflow/core/util/cuda_launch_config.h b/tensorflow/core/util/cuda_launch_config.h index 6a1bcb167b..a46bd72c93 100644 --- a/tensorflow/core/util/cuda_launch_config.h +++ b/tensorflow/core/util/cuda_launch_config.h @@ -328,9 +328,9 @@ constexpr bool NoneIsReference() { // // The kernel parameters 'Ts' must be constructible from the arguments 'Args'. template -void CudaLaunchKernel(void (*function)(Ts...), dim3 grid_dim, dim3 block_dim, - size_t shared_memory_size_bytes, cudaStream_t stream, - Args... arguments) { +Status CudaLaunchKernel(void (*function)(Ts...), dim3 grid_dim, dim3 block_dim, + size_t shared_memory_size_bytes, cudaStream_t stream, + Args... arguments) { static_assert(detail::NoneIsReference(), "Kernels with reference arguments have undefined behaviour."); // Cast arguments and forward them as an array of pointers. @@ -339,7 +339,10 @@ void CudaLaunchKernel(void (*function)(Ts...), dim3 grid_dim, dim3 block_dim, auto func_ptr = absl::bit_cast(function); auto result = cudaLaunchKernel(func_ptr, grid_dim, block_dim, arg_ptrs.data(), shared_memory_size_bytes, stream); - DCHECK_EQ(result, cudaSuccess) << cudaGetErrorString(result); + if (result != cudaSuccess) { + return errors::Internal(cudaGetErrorString(result)); + } + return Status::OK(); } } // namespace tensorflow diff --git a/tensorflow/examples/adding_an_op/cuda_op_kernel.cu.cc b/tensorflow/examples/adding_an_op/cuda_op_kernel.cu.cc index 418fb08298..721da2a0bd 100644 --- a/tensorflow/examples/adding_an_op/cuda_op_kernel.cu.cc +++ b/tensorflow/examples/adding_an_op/cuda_op_kernel.cu.cc @@ -26,7 +26,8 @@ __global__ void AddOneKernel(const int* in, const int N, int* out) { } void AddOneKernelLauncher(const int* in, const int N, int* out) { - ::tensorflow::CudaLaunchKernel(AddOneKernel, 32, 256, 0, nullptr, in, N, out); + TF_CHECK_OK(::tensorflow::CudaLaunchKernel(AddOneKernel, 32, 256, 0, nullptr, + in, N, out)); } #endif diff --git a/tensorflow/tools/ci_build/builds/user_ops/cuda_op_kernel.cu.cc b/tensorflow/tools/ci_build/builds/user_ops/cuda_op_kernel.cu.cc index 418fb08298..721da2a0bd 100644 --- a/tensorflow/tools/ci_build/builds/user_ops/cuda_op_kernel.cu.cc +++ b/tensorflow/tools/ci_build/builds/user_ops/cuda_op_kernel.cu.cc @@ -26,7 +26,8 @@ __global__ void AddOneKernel(const int* in, const int N, int* out) { } void AddOneKernelLauncher(const int* in, const int N, int* out) { - ::tensorflow::CudaLaunchKernel(AddOneKernel, 32, 256, 0, nullptr, in, N, out); + TF_CHECK_OK(::tensorflow::CudaLaunchKernel(AddOneKernel, 32, 256, 0, nullptr, + in, N, out)); } #endif -- GitLab From 2e1ea48763cac7b0db0a4b781195617e62d832e7 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 25 Feb 2019 05:09:28 -0800 Subject: [PATCH 444/766] Catching memory corruption when initializing ConvParameters. PiperOrigin-RevId: 235511159 --- tensorflow/core/kernels/conv_ops_gpu.h | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tensorflow/core/kernels/conv_ops_gpu.h b/tensorflow/core/kernels/conv_ops_gpu.h index 7a67658c4d..9aa395a0d8 100644 --- a/tensorflow/core/kernels/conv_ops_gpu.h +++ b/tensorflow/core/kernels/conv_ops_gpu.h @@ -92,12 +92,12 @@ class ConvParameters { : batch_(batch), in_depths_(in_depths), out_depths_(out_depths), - in_(in), + in_(CheckSpatialArraySize(in)), data_format_(data_format), - filter_(filter), - dilation_(dilation), - stride_(stride), - padding_(padding), + filter_(CheckSpatialArraySize(filter)), + dilation_(CheckSpatialArraySize(dilation)), + stride_(CheckSpatialArraySize(stride)), + padding_(CheckSpatialArraySize(padding)), dtype_(dtype), device_id_(device_id) { hash_code_ = batch; @@ -170,6 +170,11 @@ class ConvParameters { private: friend struct ConvParametersPeer; // For testing purposes. + static const SpatialArray& CheckSpatialArraySize(const SpatialArray& array) { + CHECK_LE(array.size(), 3); // Catch corruptions related to b/124313574. + return array; + } + template bool ShouldIncludeWinogradNonfusedAlgoPreCudnn7() const { int64 total_size = 16 * std::ceil(batch_ / 16.0) * -- GitLab From 8b5a12a14a42ea1805416a6b6982ec1624306213 Mon Sep 17 00:00:00 2001 From: Tom Hennigan Date: Mon, 25 Feb 2019 06:26:28 -0800 Subject: [PATCH 445/766] Make V1 and V2 VariableAggregation enums interchangeable. PiperOrigin-RevId: 235518682 --- .../python/distribute/mirrored_strategy.py | 10 ++++---- .../python/kernel_tests/variables_test.py | 24 +++++++++++++++++++ tensorflow/python/ops/variables.py | 14 +++++++++++ 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/tensorflow/python/distribute/mirrored_strategy.py b/tensorflow/python/distribute/mirrored_strategy.py index 96c7191652..70fb302f10 100644 --- a/tensorflow/python/distribute/mirrored_strategy.py +++ b/tensorflow/python/distribute/mirrored_strategy.py @@ -220,8 +220,9 @@ def _create_mirrored_variable(strategy, device_map, logical_device, # pylint: d # `AUTO` synchronization for `MirroredStrategy` is `ON_WRITE`. is_replica_local = False else: - raise ValueError("Invalid variable synchronization mode: " + - synchronization + " for variable: " + kwargs["name"]) + raise ValueError( + "Invalid variable synchronization mode: %s for variable: %s" % + (synchronization, kwargs["name"])) # Get aggregation value aggregation = kwargs.pop("aggregation", @@ -232,8 +233,9 @@ def _create_mirrored_variable(strategy, device_map, logical_device, # pylint: d variable_scope.VariableAggregation.MEAN, variable_scope.VariableAggregation.ONLY_FIRST_REPLICA ): - raise ValueError("Invalid variable aggregation mode: " + aggregation + - " for variable: " + kwargs["name"]) + raise ValueError( + "Invalid variable aggregation mode: %s for variable: %s" % + (aggregation, kwargs["name"])) # Ignore user-specified caching device, not needed for mirrored variables. kwargs.pop("caching_device", None) diff --git a/tensorflow/python/kernel_tests/variables_test.py b/tensorflow/python/kernel_tests/variables_test.py index 028ef11fc4..b3316b73ff 100644 --- a/tensorflow/python/kernel_tests/variables_test.py +++ b/tensorflow/python/kernel_tests/variables_test.py @@ -828,5 +828,29 @@ class VariableContainerTest(test.TestCase): self.assertEqual(compat.as_bytes(""), v4.op.get_attr("container")) +class AggregationModesTest(test.TestCase): + + def testV1V2Equal(self): + v1 = variables.VariableAggregation + v2 = variables.VariableAggregationV2 + + self.assertEqual(v1.NONE, v2.NONE) + self.assertEqual(v1.SUM, v2.SUM) + self.assertEqual(v1.MEAN, v2.MEAN) + self.assertEqual(v1.ONLY_FIRST_REPLICA, v2.ONLY_FIRST_REPLICA) + self.assertEqual(v1.ONLY_FIRST_TOWER, v2.ONLY_FIRST_REPLICA) + + self.assertEqual(v2.NONE, v1.NONE) + self.assertEqual(v2.SUM, v1.SUM) + self.assertEqual(v2.MEAN, v1.MEAN) + self.assertEqual(v2.ONLY_FIRST_REPLICA, v1.ONLY_FIRST_REPLICA) + self.assertEqual(v2.ONLY_FIRST_REPLICA, v1.ONLY_FIRST_TOWER) + + self.assertEqual(hash(v1.NONE), hash(v2.NONE)) + self.assertEqual(hash(v1.SUM), hash(v2.SUM)) + self.assertEqual(hash(v1.MEAN), hash(v2.MEAN)) + self.assertEqual(hash(v1.ONLY_FIRST_REPLICA), hash(v2.ONLY_FIRST_REPLICA)) + self.assertEqual(hash(v1.ONLY_FIRST_TOWER), hash(v2.ONLY_FIRST_REPLICA)) + if __name__ == "__main__": test.main() diff --git a/tensorflow/python/ops/variables.py b/tensorflow/python/ops/variables.py index 219ba7fbb2..412300772b 100644 --- a/tensorflow/python/ops/variables.py +++ b/tensorflow/python/ops/variables.py @@ -103,6 +103,17 @@ class VariableAggregationV2(enum.Enum): MEAN = 2 ONLY_FIRST_REPLICA = 3 + def __hash__(self): + return hash(self.value) + + def __eq__(self, other): + if self is other: + return True + elif isinstance(other, VariableAggregation): + return int(self.value) == int(other.value) + else: + return False + @tf_export(v1=["VariableAggregation"]) class VariableAggregation(enum.Enum): @@ -112,6 +123,9 @@ class VariableAggregation(enum.Enum): ONLY_FIRST_REPLICA = 3 ONLY_FIRST_TOWER = 3 # DEPRECATED + def __hash__(self): + return hash(self.value) + VariableAggregation.__doc__ = ( VariableAggregationV2.__doc__ + -- GitLab From 6fac047274daad84d93fea937de612f43e7d212e Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 25 Feb 2019 06:34:27 -0800 Subject: [PATCH 446/766] Add simple Mnist example on TPU. PiperOrigin-RevId: 235519570 --- .../contrib/distribute/python/examples/BUILD | 11 + .../python/examples/mnist_tf1_tpu.py | 188 ++++++++++++++++++ 2 files changed, 199 insertions(+) create mode 100644 tensorflow/contrib/distribute/python/examples/mnist_tf1_tpu.py diff --git a/tensorflow/contrib/distribute/python/examples/BUILD b/tensorflow/contrib/distribute/python/examples/BUILD index 5f89df5824..58bede801f 100644 --- a/tensorflow/contrib/distribute/python/examples/BUILD +++ b/tensorflow/contrib/distribute/python/examples/BUILD @@ -56,3 +56,14 @@ py_binary( "//third_party/py/numpy", ], ) + +py_binary( + name = "mnist_tf1_tpu", + srcs = [ + "mnist_tf1_tpu.py", + ], + deps = [ + "//tensorflow:tensorflow_py", + "//third_party/py/numpy", + ], +) diff --git a/tensorflow/contrib/distribute/python/examples/mnist_tf1_tpu.py b/tensorflow/contrib/distribute/python/examples/mnist_tf1_tpu.py new file mode 100644 index 0000000000..ae8194c576 --- /dev/null +++ b/tensorflow/contrib/distribute/python/examples/mnist_tf1_tpu.py @@ -0,0 +1,188 @@ +# Copyright 2019 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. +# ============================================================================== +"""Run MNIST on multiple GPUs on using MirroredStrategy with eager execution. + +By default, runs on all available GPUs, or CPU if no GPUs are available. + +NOTE: Currently, this takes more time than when running MNIST in eager without +MirroredStrategy because of a number overheads. Therefore, this is just a +proof of concept right now and cannot be used to actually scale up training. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from absl import app +from absl import flags +import numpy as np +import tensorflow as tf + + +flags.DEFINE_string("tpu", None, "Name of the TPU to use.") +flags.DEFINE_integer("batch_size", 64, + "What should be the size of each batch?") +flags.DEFINE_integer("num_epochs", 10, "How many epochs to run?") +flags.DEFINE_float("learning_rate", 0.01, "Learning Rate") +flags.DEFINE_float("momentum", 0.5, "SGD momentum") + +FLAGS = flags.FLAGS +NUM_TRAIN_IMAGES = 60000 + + +def create_model(): + max_pool = tf.keras.layers.MaxPooling2D((2, 2), (2, 2), padding="same") + # The model consists of a sequential chain of layers, so tf.keras.Sequential + # (a subclass of tf.keras.Model) makes for a compact description. + return tf.keras.Sequential([ + tf.keras.layers.Reshape( + target_shape=[28, 28, 1], + input_shape=(28, 28,)), + tf.keras.layers.Conv2D(2, 5, padding="same", activation=tf.nn.relu), + max_pool, + tf.keras.layers.Conv2D(4, 5, padding="same", activation=tf.nn.relu), + max_pool, + tf.keras.layers.Flatten(), + tf.keras.layers.Dense(32, activation=tf.nn.relu), + tf.keras.layers.Dropout(0.4), + tf.keras.layers.Dense(10)]) + + +def compute_loss(logits, labels): + loss = tf.reduce_sum( + tf.nn.sparse_softmax_cross_entropy_with_logits( + logits=logits, labels=labels)) + # Scale loss by global batch size. + return loss * (1. / FLAGS.batch_size) + + +def mnist_datasets(): + (x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data() + # Numpy defaults to dtype=float64; TF defaults to float32. Stick with float32. + x_train, x_test = x_train / np.float32(255), x_test / np.float32(255) + y_train, y_test = y_train.astype(np.int64), y_test.astype(np.int64) + # TODO(priyag): `strategy.make_numpy_iterator` can be used directly instead of + # converting to datasets. + train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train)) + test_dataset = tf.data.Dataset.from_tensor_slices((x_test, y_test)) + return train_dataset, test_dataset + + +def main(argv): + """Run a CNN model on MNIST data to demonstrate DistributedStrategies.""" + del argv # Unused. + tf.disable_v2_behavior() + + cluster_resolver = tf.distribute.cluster_resolver.TPUClusterResolver( + tpu=FLAGS.tpu) + strategy = tf.contrib.distribute.TPUStrategy(cluster_resolver) + + with strategy.scope(): + train_ds, test_ds = mnist_datasets() + train_ds = train_ds.shuffle(NUM_TRAIN_IMAGES).batch(FLAGS.batch_size) + test_ds = test_ds.batch(FLAGS.batch_size) + + model = create_model() + optimizer = tf.keras.optimizers.SGD(FLAGS.learning_rate, FLAGS.momentum) + training_loss = tf.keras.metrics.Mean("training_loss", dtype=tf.float32) + training_accuracy = tf.keras.metrics.SparseCategoricalAccuracy( + "training_accuracy", dtype=tf.float32) + test_loss = tf.keras.metrics.Mean("test_loss", dtype=tf.float32) + test_accuracy = tf.keras.metrics.SparseCategoricalAccuracy( + "test_accuracy", dtype=tf.float32) + + def train_step(inputs): # pylint: disable=missing-docstring + images, labels = inputs + with tf.GradientTape() as tape: + logits = model(images, training=True) + loss = compute_loss(logits, labels) + grads = tape.gradient(loss, model.variables) + update_vars = optimizer.apply_gradients(zip(grads, model.variables)) + update_loss = training_loss.update_state(loss) + update_accuracy = training_accuracy.update_state(labels, logits) + + with tf.control_dependencies([update_vars, update_loss, update_accuracy]): + return tf.identity(loss) + + def test_step(inputs): + images, labels = inputs + logits = model(images, training=False) + loss = compute_loss(logits, labels) + update_loss = test_loss.update_state(loss) + update_accuracy = test_accuracy.update_state(labels, logits) + + with tf.control_dependencies([update_loss, update_accuracy]): + return tf.identity(loss) + + train_iterator = strategy.make_dataset_iterator(train_ds) + test_iterator = strategy.make_dataset_iterator(test_ds) + + dist_train = strategy.unwrap( + strategy.experimental_run(train_step, train_iterator)) + dist_test = strategy.unwrap( + strategy.experimental_run(test_step, test_iterator)) + + training_loss_result = training_loss.result() + training_accuracy_result = training_accuracy.result() + test_loss_result = test_loss.result() + test_accuracy_result = test_accuracy.result() + + tf.contrib.distribute.initialize_tpu_system(cluster_resolver) + + train_iterator_init = train_iterator.initialize() + test_iterator_init = test_iterator.initialize() + + all_variables = ( + tf.global_variables() + + training_loss.variables + + training_accuracy.variables + + test_loss.variables + + test_accuracy.variables) + + with tf.Session(cluster_resolver.master()) as session: + session.run([v.initializer for v in all_variables]) + + for epoch in range(0, FLAGS.num_epochs): + # Train + print("Starting epoch {}".format(epoch)) + session.run(train_iterator_init) + while True: + try: + session.run(dist_train) + except tf.errors.OutOfRangeError: + break + print("Training loss: {:0.4f}, accuracy: {:0.2f}%".format( + session.run(training_loss_result), + session.run(training_accuracy_result) * 100)) + training_loss.reset_states() + training_accuracy.reset_states() + + # Test + session.run(test_iterator_init) + while True: + try: + session.run(dist_test) + except tf.errors.OutOfRangeError: + break + print("Test loss: {:0.4f}, accuracy: {:0.2f}%".format( + session.run(test_loss_result), + session.run(test_accuracy_result) * 100)) + test_loss.reset_states() + test_accuracy.reset_states() + + +if __name__ == "__main__": + flags.mark_flag_as_required("tpu") + app.run(main) -- GitLab From ebcc4f6887df1ad1a2ffdb2dc2da1f541e24e2af Mon Sep 17 00:00:00 2001 From: Andr? Susano Pinto Date: Mon, 25 Feb 2019 07:30:03 -0800 Subject: [PATCH 447/766] Replace usage of tf.saved_model.load() with the public symbol on saved model integration tests. PiperOrigin-RevId: 235526039 --- .../examples/saved_model/integration_tests/use_mnist_cnn.py | 2 -- .../integration_tests/use_model_in_sequential_keras.py | 5 ----- .../examples/saved_model/integration_tests/use_rnn_cell.py | 5 ----- .../saved_model/integration_tests/use_text_rnn_model.py | 5 ----- 4 files changed, 17 deletions(-) diff --git a/tensorflow/examples/saved_model/integration_tests/use_mnist_cnn.py b/tensorflow/examples/saved_model/integration_tests/use_mnist_cnn.py index 9e1ca33029..81fd4c2864 100644 --- a/tensorflow/examples/saved_model/integration_tests/use_mnist_cnn.py +++ b/tensorflow/examples/saved_model/integration_tests/use_mnist_cnn.py @@ -32,8 +32,6 @@ import tensorflow as tf from tensorflow.examples.saved_model.integration_tests import mnist_util from tensorflow.examples.saved_model.integration_tests import util -from tensorflow.python.saved_model import load as svmd_load -tf.saved_model.load = svmd_load.load FLAGS = flags.FLAGS diff --git a/tensorflow/examples/saved_model/integration_tests/use_model_in_sequential_keras.py b/tensorflow/examples/saved_model/integration_tests/use_model_in_sequential_keras.py index 6c8aab8e23..c54bd23591 100644 --- a/tensorflow/examples/saved_model/integration_tests/use_model_in_sequential_keras.py +++ b/tensorflow/examples/saved_model/integration_tests/use_model_in_sequential_keras.py @@ -22,13 +22,8 @@ from absl import app from absl import flags import numpy as np - import tensorflow as tf -# TODO(vbardiovsky): Remove when load symbol is public. from tensorflow.examples.saved_model.integration_tests import util -from tensorflow.python.saved_model.load import load - -tf.saved_model.load = load FLAGS = flags.FLAGS diff --git a/tensorflow/examples/saved_model/integration_tests/use_rnn_cell.py b/tensorflow/examples/saved_model/integration_tests/use_rnn_cell.py index 798033517c..2723b76fc8 100644 --- a/tensorflow/examples/saved_model/integration_tests/use_rnn_cell.py +++ b/tensorflow/examples/saved_model/integration_tests/use_rnn_cell.py @@ -21,12 +21,7 @@ from __future__ import print_function from absl import app from absl import flags import numpy as np - import tensorflow as tf -# TODO(vbardiovsky): Remove when load is available. -from tensorflow.python.saved_model.load import load - -tf.saved_model.load = load FLAGS = flags.FLAGS diff --git a/tensorflow/examples/saved_model/integration_tests/use_text_rnn_model.py b/tensorflow/examples/saved_model/integration_tests/use_text_rnn_model.py index 3f75c49a32..dc9ad54a6f 100644 --- a/tensorflow/examples/saved_model/integration_tests/use_text_rnn_model.py +++ b/tensorflow/examples/saved_model/integration_tests/use_text_rnn_model.py @@ -20,12 +20,7 @@ from __future__ import print_function from absl import app from absl import flags - import tensorflow as tf -# TODO(vbardiovsky): Remove when load is available. -from tensorflow.python.saved_model.load import load - -tf.saved_model.load = load FLAGS = flags.FLAGS -- GitLab From 72b4dc9de26eac52c267499ab15127c2af29706f Mon Sep 17 00:00:00 2001 From: Tom Hennigan Date: Mon, 25 Feb 2019 08:43:07 -0800 Subject: [PATCH 448/766] Rename ReplicaLocalVariable to SyncOnReadVariable. PiperOrigin-RevId: 235536186 --- .../python/mirrored_strategy_multigpu_test.py | 67 +++++++++---------- .../contrib/distribute/python/values_test.py | 8 +-- .../python/distribute/mirrored_strategy.py | 10 +-- tensorflow/python/distribute/values.py | 40 ++++++----- tensorflow/python/framework/tensor_util.py | 2 +- tensorflow/python/ops/metrics_impl.py | 6 +- 6 files changed, 64 insertions(+), 69 deletions(-) diff --git a/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py b/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py index 5ce731816c..e6db831ad6 100644 --- a/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py +++ b/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py @@ -430,7 +430,7 @@ class MirroredStrategyVariableCreationTest(test.TestCase): self.assertEqual("var0:0", v0.name) self.assertIsInstance(v1, values.MirroredVariable) self.assertEqual("common/var1:0", v1.name) - self.assertIsInstance(v2, values.ReplicaLocalVariable) + self.assertIsInstance(v2, values.SyncOnReadVariable) self.assertEqual("common/var2:0", v2.name) self.assertEqual(variable_scope.VariableAggregation.SUM, v2.aggregation) self.assertIsInstance(v3, values.MirroredVariable) @@ -467,7 +467,7 @@ class MirroredStrategyVariableCreationTest(test.TestCase): self.assertEqual("main/var0:0", v0.name) self.assertIsInstance(v1, values.MirroredVariable) self.assertEqual("main/common/var1:0", v1.name) - self.assertIsInstance(v2, values.ReplicaLocalVariable) + self.assertIsInstance(v2, values.SyncOnReadVariable) self.assertEqual("main/common/var2:0", v2.name) self.assertEqual(variable_scope.VariableAggregation.SUM, v2.aggregation) @@ -619,7 +619,7 @@ class MirroredStrategyVariableCreationTest(test.TestCase): with self.assertRaises(RuntimeError): _ = distribution.extended.call_for_each_replica(model_fn, args=(names,)) - def testReplicaLocalVariable(self, distribution): + def testSyncOnReadVariable(self, distribution): all_v_sum = {} all_v_mean = {} components_sum = {} @@ -635,8 +635,8 @@ class MirroredStrategyVariableCreationTest(test.TestCase): 4.0, synchronization=variable_scope.VariableSynchronization.ON_READ, aggregation=variable_scope.VariableAggregation.MEAN) - self.assertTrue(isinstance(v_sum, values.ReplicaLocalVariable)) - self.assertTrue(isinstance(v_mean, values.ReplicaLocalVariable)) + self.assertIsInstance(v_sum, values.SyncOnReadVariable) + self.assertIsInstance(v_mean, values.SyncOnReadVariable) updates = [v_sum.assign_add(2.0 + replica_id), v_mean.assign(6.0 * replica_id)] all_v_sum[replica_id] = v_sum @@ -650,7 +650,7 @@ class MirroredStrategyVariableCreationTest(test.TestCase): return updates, v_sum, v_mean, c_sum, c_mean with distribution.scope(): - # Create "sum" and "mean" versions of ReplicaLocalVariables. + # Create "sum" and "mean" versions of SyncOnReadVariables. ret_ops, ret_v_sum, ret_v_mean, regrouped_sum, regrouped_mean = ( distribution.extended.call_for_each_replica(model_fn)) # Should see the same wrapping instance in all replicas. @@ -716,13 +716,13 @@ class MirroredStrategyVariableCreationTest(test.TestCase): _, v1 = distribution.unwrap(v) self.assertStartsWith(v1._op.name, "replica_1/") - def testReplicaLocalVariableUpdate(self, distribution): + def testSyncOnReadVariableUpdate(self, distribution): def model_fn(): v_sum = variable_scope.variable( 1.0, synchronization=variable_scope.VariableSynchronization.ON_READ, aggregation=variable_scope.VariableAggregation.SUM) - self.assertTrue(isinstance(v_sum, values.ReplicaLocalVariable)) + self.assertIsInstance(v_sum, values.SyncOnReadVariable) return v_sum def update(var, value): @@ -733,7 +733,7 @@ class MirroredStrategyVariableCreationTest(test.TestCase): # Initialize variables. self.evaluate(variables.global_variables_initializer()) - # Assert that the aggregated value of the replica local vars is the sum + # Assert that the aggregated value of the sync on read var is the sum # of the individual values before running the update ops. self.assertEqual(1.0, self.evaluate(ret_v_sum.get( distribution.extended.worker_devices[0]).read_value())) @@ -743,7 +743,7 @@ class MirroredStrategyVariableCreationTest(test.TestCase): update_ops = distribution.extended.update( ret_v_sum, update, args=(5.0,), group=False) self.evaluate(update_ops) - # Assert that the aggregated value of the replica local vars is the sum + # Assert that the aggregated value of the sync on read vars is the sum # of the individual values after running the update ops. self.assertEqual(5.0, self.evaluate(ret_v_sum.get( distribution.extended.worker_devices[0]).read_value())) @@ -752,11 +752,11 @@ class MirroredStrategyVariableCreationTest(test.TestCase): def testVarDistributeStrategy(self, distribution): with distribution.scope(): mirrored = variable_scope.variable(1.0) - replica_local = variable_scope.variable( + sync_on_read = variable_scope.variable( 1.0, synchronization=variable_scope.VariableSynchronization.ON_READ) self.assertIs(distribution, mirrored.distribute_strategy) - self.assertIs(distribution, replica_local.distribute_strategy) + self.assertIs(distribution, sync_on_read.distribute_strategy) @combinations.generate(combinations.combine( @@ -1123,7 +1123,7 @@ class MirroredVariableUpdateTest(test.TestCase): combinations.mirrored_strategy_with_gpu_and_cpu, combinations.core_mirrored_strategy_with_gpu_and_cpu], mode=["graph", "eager"])) -class MirroredAndReplicaLocalVariableInitializerTest(test.TestCase): +class MirroredAndSyncOnReadVariableInitializerTest(test.TestCase): def testAssignMirroredVarInitializer(self, distribution): # This test is not eager compatible since in eager variables are initialized @@ -1149,17 +1149,16 @@ class MirroredAndReplicaLocalVariableInitializerTest(test.TestCase): 1.0, synchronization=variable_scope.VariableSynchronization.ON_READ, aggregation=variable_scope.VariableAggregation.SUM) - self.assertTrue(isinstance(v_sum, values.ReplicaLocalVariable)) + self.assertIsInstance(v_sum, values.SyncOnReadVariable) return v_sum with distribution.scope(): - replica_local_var = distribution.extended.call_for_each_replica( + sync_on_read_var = distribution.extended.call_for_each_replica( model_fn) - self.assertTrue(isinstance(replica_local_var, - values.ReplicaLocalVariable)) - self.assertFalse(self.evaluate(replica_local_var.is_initialized())) - self.evaluate(replica_local_var.initializer) - self.assertTrue(self.evaluate(replica_local_var.is_initialized())) + self.assertIsInstance(sync_on_read_var, values.SyncOnReadVariable) + self.assertFalse(self.evaluate(sync_on_read_var.is_initialized())) + self.evaluate(sync_on_read_var.initializer) + self.assertTrue(self.evaluate(sync_on_read_var.is_initialized())) @combinations.generate(combinations.combine( @@ -1167,7 +1166,7 @@ class MirroredAndReplicaLocalVariableInitializerTest(test.TestCase): combinations.mirrored_strategy_with_gpu_and_cpu, combinations.core_mirrored_strategy_with_gpu_and_cpu], mode=["graph", "eager"])) -class ReplicaLocalVariableAssignTest(test.TestCase): +class SyncOnReadVariableAssignTest(test.TestCase): def testAssignReplicaLocalVarSumAggregation(self, distribution): def model_fn(): @@ -1178,24 +1177,23 @@ class ReplicaLocalVariableAssignTest(test.TestCase): return v_sum with distribution.scope(): - replica_local_var = distribution.extended.call_for_each_replica(model_fn) - self.assertTrue(isinstance(replica_local_var, - values.ReplicaLocalVariable)) + sync_on_read_var = distribution.extended.call_for_each_replica(model_fn) + self.assertIsInstance(sync_on_read_var, values.SyncOnReadVariable) self.evaluate(variables.global_variables_initializer()) # Each replica has a value of 1.0 assigned to it in replica context. # When we read the value using `read_var` we should see the SUM of each of # values on each of the replicas. self.assertEqual(2.0, self.evaluate( - distribution.extended.read_var(replica_local_var))) + distribution.extended.read_var(sync_on_read_var))) # Assigning 6.0 in cross replica context will assign a value of # 6.0/num_replicas to each replica. - tlv_ops = replica_local_var.assign(6.0) + tlv_ops = sync_on_read_var.assign(6.0) self.evaluate(tlv_ops) - # On reading the replica local var we should get the assigned value back. + # On reading the sync on read var we should get the assigned value back. # The value on all the replicas are added before being returned by # `read_var`. self.assertEqual(6.0, self.evaluate( - distribution.extended.read_var(replica_local_var))) + distribution.extended.read_var(sync_on_read_var))) def testAssignReplicaLocalVarMeanAggregation(self, distribution): def model_fn(): @@ -1206,21 +1204,20 @@ class ReplicaLocalVariableAssignTest(test.TestCase): return v_sum with distribution.scope(): - replica_local_var = distribution.extended.call_for_each_replica(model_fn) - self.assertTrue(isinstance(replica_local_var, - values.ReplicaLocalVariable)) + sync_on_read_var = distribution.extended.call_for_each_replica(model_fn) + self.assertIsInstance(sync_on_read_var, values.SyncOnReadVariable) self.evaluate(variables.global_variables_initializer()) # Each replica has a value of 1.0 assigned to it in replica context. # When we read the value using `read_var` we should see the MEAN of values # on all replicas which is the value assigned in replica context. self.assertEqual(1.0, self.evaluate( - distribution.extended.read_var(replica_local_var))) - tlv_ops = replica_local_var.assign(6.0) + distribution.extended.read_var(sync_on_read_var))) + tlv_ops = sync_on_read_var.assign(6.0) self.evaluate(tlv_ops) - # On reading the replica local var we should get the MEAN of all values + # On reading the sync on read var we should get the MEAN of all values # which is equal to the value assigned. self.assertEqual(6.0, self.evaluate( - distribution.extended.read_var(replica_local_var))) + distribution.extended.read_var(sync_on_read_var))) class MockModel(object): diff --git a/tensorflow/contrib/distribute/python/values_test.py b/tensorflow/contrib/distribute/python/values_test.py index 9fd251175b..f9bd3078ef 100644 --- a/tensorflow/contrib/distribute/python/values_test.py +++ b/tensorflow/contrib/distribute/python/values_test.py @@ -522,11 +522,11 @@ def _make_replica_local(method, strategy=None): with ops.device(d): v.append(variable_scope.get_variable( name=n, initializer=init, use_resource=True)) - replica_local = values.ReplicaLocalVariable(strategy, device_map, v, method) + replica_local = values.SyncOnReadVariable(strategy, device_map, v, method) return v, replica_local -class ReplicaLocalVariablePropertiesTest(test.TestCase): +class SyncOnReadVariablePropertiesTest(test.TestCase): config = config_pb2.ConfigProto() config.allow_soft_placement = True @@ -549,7 +549,7 @@ class ReplicaLocalVariablePropertiesTest(test.TestCase): v = variable_scope.get_variable( name="v", initializer=[1.], use_resource=True) device_map = values.ReplicaDeviceMap(("/job:foo/device:CPU:0",)) - replica_local = values.ReplicaLocalVariable( + replica_local = values.SyncOnReadVariable( None, device_map, (v,), variable_scope.VariableAggregation.MEAN) self.assertEqual(v.name, replica_local.name) @@ -577,7 +577,7 @@ class ReplicaLocalVariablePropertiesTest(test.TestCase): combinations.mirrored_strategy_with_gpu_and_cpu, combinations.core_mirrored_strategy_with_gpu_and_cpu], mode=["graph", "eager"])) -class ReplicaLocalVariableTest(test.TestCase, parameterized.TestCase): +class SyncOnReadVariableTest(test.TestCase, parameterized.TestCase): def _assign_replica_local(self, devices, v, new): for d, var, n in zip(devices, v, new): diff --git a/tensorflow/python/distribute/mirrored_strategy.py b/tensorflow/python/distribute/mirrored_strategy.py index 70fb302f10..c171fa42ee 100644 --- a/tensorflow/python/distribute/mirrored_strategy.py +++ b/tensorflow/python/distribute/mirrored_strategy.py @@ -213,12 +213,12 @@ def _create_mirrored_variable(strategy, device_map, logical_device, # pylint: d kwargs["name"]) elif synchronization == variable_scope.VariableSynchronization.ON_READ: # Variables that are to be synced on read are replica local. - is_replica_local = True + is_sync_on_read = True kwargs["trainable"] = False elif (synchronization == variable_scope.VariableSynchronization.ON_WRITE or synchronization == variable_scope.VariableSynchronization.AUTO): # `AUTO` synchronization for `MirroredStrategy` is `ON_WRITE`. - is_replica_local = False + is_sync_on_read = False else: raise ValueError( "Invalid variable synchronization mode: %s for variable: %s" % @@ -247,8 +247,8 @@ def _create_mirrored_variable(strategy, device_map, logical_device, # pylint: d devices = device_map.logical_to_actual_devices(logical_device) value_list = real_mirrored_creator(devices, *args, **kwargs) - if is_replica_local: - result = values.ReplicaLocalVariable( + if is_sync_on_read: + result = values.SyncOnReadVariable( strategy, device_map, value_list, aggregation, logical_device=logical_device) else: @@ -719,7 +719,7 @@ class MirroredExtended(distribute_lib.DistributionStrategyExtended): def read_var(self, replica_local_var): """Read the aggregate value of a replica-local variable.""" - if isinstance(replica_local_var, values.ReplicaLocalVariable): + if isinstance(replica_local_var, values.SyncOnReadVariable): return replica_local_var._get_cross_replica() # pylint: disable=protected-access assert isinstance(replica_local_var, values.Mirrored) return array_ops.identity(replica_local_var.get()) diff --git a/tensorflow/python/distribute/values.py b/tensorflow/python/distribute/values.py index 40a9f9b012..374810e128 100644 --- a/tensorflow/python/distribute/values.py +++ b/tensorflow/python/distribute/values.py @@ -1173,28 +1173,28 @@ ops.register_tensor_conversion_function(TPUMirroredVariable, ops.register_dense_tensor_like_type(TPUMirroredVariable) -class _ReplicaLocalSaveable(saver.BaseSaverBuilder.SaveableObject): - """Class for defining how to restore a ReplicaLocalVariable.""" +class _SyncOnReadSaveable(saver.BaseSaverBuilder.SaveableObject): + """Class for defining how to restore a SyncOnReadVariable.""" - def __init__(self, replica_local_variable, name): - self._replica_local_variable = replica_local_variable + def __init__(self, sync_on_read_variable, name): + self._sync_on_read_variable = sync_on_read_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(): - strategy = replica_local_variable._distribute_strategy # pylint: disable=protected-access - return strategy.extended.read_var(replica_local_variable) + strategy = sync_on_read_variable._distribute_strategy # pylint: disable=protected-access + return strategy.extended.read_var(sync_on_read_variable) spec = saver.BaseSaverBuilder.SaveSpec( tensor=tensor, slice_spec="", name=name, - dtype=replica_local_variable.dtype) - super(_ReplicaLocalSaveable, self).__init__(tensor, [spec], name) + dtype=sync_on_read_variable.dtype) + super(_SyncOnReadSaveable, self).__init__(tensor, [spec], name) def restore(self, restored_tensors, restored_shapes): """Restore the same value into all variables.""" tensor, = restored_tensors - return self._replica_local_variable.assign(tensor) + return self._sync_on_read_variable.assign(tensor) def _assert_replica_context(strategy): @@ -1207,15 +1207,13 @@ def _assert_replica_context(strategy): "Replica-local variables may only be assigned in a replica context.") -# TODO(josh11b): Rename this to SyncOnReadVariable. -class ReplicaLocalVariable(DistributedVariable, PerReplica, - trackable.Trackable): +class SyncOnReadVariable(DistributedVariable, PerReplica, trackable.Trackable): """Holds a map from device to variables whose values are reduced on save.""" def __init__( self, strategy, device_map, values, aggregation, logical_device=None): self._aggregation = aggregation - super(ReplicaLocalVariable, self).__init__( + super(SyncOnReadVariable, self).__init__( strategy, device_map, values, logical_device=logical_device) def assign_sub(self, *args, **kwargs): @@ -1260,25 +1258,25 @@ class ReplicaLocalVariable(DistributedVariable, PerReplica, """Overrides Trackable method. This allows both name-based and object-based save and restore of - ReplicaLocalVariables. + `SyncOnReadVariable`s. Returns: A dictionary mapping attribute names to `SaveableObject` factories. """ def _saveable_factory(name=self._common_name): - return _ReplicaLocalSaveable(self, name) + return _SyncOnReadSaveable(self, name) return {trackable.VARIABLE_VALUE_KEY: _saveable_factory} -# Register a conversion function for ReplicaLocalVariable which allows as_ref to +# Register a conversion function for SyncOnReadVariable which allows as_ref to # be true. -def _tensor_conversion_replica_local(var, dtype=None, name=None, as_ref=False): +def _tensor_conversion_sync_on_read(var, dtype=None, name=None, as_ref=False): return ops.internal_convert_to_tensor( var.get(), dtype=dtype, name=name, as_ref=as_ref) -ops.register_tensor_conversion_function(ReplicaLocalVariable, - _tensor_conversion_replica_local) +ops.register_tensor_conversion_function(SyncOnReadVariable, + _tensor_conversion_sync_on_read) def regroup(device_map, values, wrap_class=PerReplica): @@ -1327,7 +1325,7 @@ def regroup(device_map, values, wrap_class=PerReplica): break # Consider three cases where same_id is true: # * If v0 is a DistributedVariable (a MirroredVariable or - # ReplicaLocalVariable, and same_id means it is the same across all + # SyncOnReadVariable, and same_id means it is the same across all # devices), we want to return it. We check DistributedVariable # specifically since it can look like it has a # _distributed_container member since its members do. @@ -1342,7 +1340,7 @@ def regroup(device_map, values, wrap_class=PerReplica): return v0 # Detect the case where each device has a parallel component of the - # same MirroredVariable (or ReplicaLocalVariable). In this case we + # same MirroredVariable (or SyncOnReadVariable). 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 diff --git a/tensorflow/python/framework/tensor_util.py b/tensorflow/python/framework/tensor_util.py index af943f09ab..fe09e76997 100644 --- a/tensorflow/python/framework/tensor_util.py +++ b/tensorflow/python/framework/tensor_util.py @@ -944,7 +944,7 @@ def is_tensor(x): # pylint: disable=invalid-name equivalent to calling `isinstance(x, (tf.Tensor, tf.SparseTensor, tf.RaggedTensor, tf.Variable))` and also checks if all the component variables of a MirroredVariable or a - ReplicaLocalVariable are tensors. + SyncOnReadVariable are tensors. Args: x: A python object to check. diff --git a/tensorflow/python/ops/metrics_impl.py b/tensorflow/python/ops/metrics_impl.py index e3cefb2e92..42ee158944 100644 --- a/tensorflow/python/ops/metrics_impl.py +++ b/tensorflow/python/ops/metrics_impl.py @@ -43,7 +43,7 @@ def metric_variable(shape, dtype, validate_shape=True, name=None): """Create variable in `GraphKeys.(LOCAL|METRIC_VARIABLES)` collections. If running in a `DistributionStrategy` context, the variable will be - "replica local". This means: + "sync on read". This means: * The returned object will be a container with separate variables per replica of the model. @@ -59,7 +59,7 @@ def metric_variable(shape, dtype, validate_shape=True, name=None): of the final result value inside `distribution_strategy_context.get_replica_context().merge_call(fn)`. Inside the `merge_call()`, ops are only added to the graph once - and access to a replica-local variable in a computation returns + and access to a sync on read variable in a computation returns the sum across all replicas. Args: @@ -71,7 +71,7 @@ def metric_variable(shape, dtype, validate_shape=True, name=None): Returns: A (non-trainable) variable initialized to zero, or if inside a - `DistributionStrategy` scope a replica-local variable container. + `DistributionStrategy` scope a sync on read variable container. """ # Note that synchronization "ON_READ" implies trainable=False. return variable_scope.variable( -- GitLab From ad76a4b373d72997584b46abc85af11840cf14db Mon Sep 17 00:00:00 2001 From: Benjamin Kramer Date: Mon, 25 Feb 2019 09:04:12 -0800 Subject: [PATCH 449/766] [XLA:TF] Enable argminmax test on CPU It's not clear what this comment refers to, but it's from 2017 and the test just works. PiperOrigin-RevId: 235539638 --- tensorflow/compiler/tests/BUILD | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tensorflow/compiler/tests/BUILD b/tensorflow/compiler/tests/BUILD index 7c1e0daf0b..adf91f100b 100644 --- a/tensorflow/compiler/tests/BUILD +++ b/tensorflow/compiler/tests/BUILD @@ -170,13 +170,6 @@ tf_xla_py_test( name = "argminmax_test", size = "small", srcs = ["argminmax_test.py"], - # ArgMax needs CustomCall on CPU, which is not available in normal - # (not precompiled) TensorFlow. The flag below excludes the CPU - # backend. - disabled_backends = [ - "cpu", - "cpu_ondemand", - ], deps = [ ":xla_test", "//tensorflow/python:array_ops", -- GitLab From e866995aff7d0e59a5506665e44e323465cbe17d Mon Sep 17 00:00:00 2001 From: Tom Hennigan Date: Mon, 25 Feb 2019 09:06:45 -0800 Subject: [PATCH 450/766] Remove implicit name scoping from `tf.Module`s. After attempting to integrate `tf.Module` into existing codebases (e.g. `tf.keras`) we've found that the automatic name scoping is too invasive (e.g. changing op and variable names) and it is desirable to disable it ~everywhere. We propose that name scoping for `tf.Module` becomes opt-in: >>> class MyModule(tf.Module): ... ... @tf.Module.with_name_scope ... def auto_name_scope(self, x): ... if not hasattr(self, 'w'): ... self.w = tf.Variable(1., name='w') ... return x * self.w ... ... def manual_name_scope(self, x): ... if not hasattr(self, 'w'): ... with self.name_scope: ... self.w = tf.Variable(1., name='w') ... return x * self.w ... ... def no_name_scope(self, x): ... if not hasattr(self, 'w'): ... self.w = tf.Variable(1., name='w') ... return x * self.w We will move opt-out name scoping into Sonnet: >>> class MyModule(snt.Module): ... ... def auto_name_scope(self, x): ... if not hasattr(self, 'w'): ... self.w = tf.Variable(1., name='w') ... return x * self.w ... ... @snt.no_name_scope ... def no_name_scope(self, x): ... if not hasattr(self, 'w'): ... self.w = tf.Variable(1., name='w') ... return x * self.w In TF2 name scopes are cosmetic and this should be less of a big deal. We might consider encouraging users who want to filter on names to instead use flatten to extract a state dictionary for their objects (c.f. https://github.com/tensorflow/community/pull/56#discussion_r255048762). I have moved the automatic name scoping logic (Metaclass etc) and associated tests into Sonnet 2. PiperOrigin-RevId: 235540184 --- tensorflow/python/module/module.py | 237 +++++------------- tensorflow/python/module/module_test.py | 99 ++------ .../api/golden/v1/tensorflow.-module.pbtxt | 2 +- .../tools/api/golden/v1/tensorflow.pbtxt | 2 +- .../api/golden/v2/tensorflow.-module.pbtxt | 2 +- .../tools/api/golden/v2/tensorflow.pbtxt | 2 +- 6 files changed, 76 insertions(+), 268 deletions(-) diff --git a/tensorflow/python/module/module.py b/tensorflow/python/module/module.py index 07455bd7f7..a015e9687d 100644 --- a/tensorflow/python/module/module.py +++ b/tensorflow/python/module/module.py @@ -18,160 +18,18 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -import abc import re -import sys -import six - -from tensorflow.python.eager import def_function from tensorflow.python.framework import ops from tensorflow.python.ops import variables from tensorflow.python.training.tracking import tracking from tensorflow.python.util import nest from tensorflow.python.util import tf_decorator -from tensorflow.python.util import tf_inspect from tensorflow.python.util.tf_export import tf_export -NO_MODULE_NAME_SCOPE = "__no_module_name_scope__" - - -class ModuleMetaclass(abc.ABCMeta): - """Metaclass for `tf.Module`.""" - - def __new__(mcs, name, bases, clsdict): - methods = [] - - for key, value in clsdict.items(): - if key == "name_scope": - continue - - elif key.startswith("__") and key != "__call__": - # Don't patch methods like `__getattr__` or `__del__`. - continue - - elif tf_inspect.isfunction(value): - # We defer patching methods until after the type is created such that we - # can trigger the descriptor binding them to the class. - methods.append(key) - - elif isinstance(value, property): - # TODO(tomhennigan) Preserve the type of property subclasses. - clsdict[key] = property( - value.fget if not value.fget else with_name_scope(value.fget), - value.fset if not value.fset else with_name_scope(value.fset), - value.fdel if not value.fdel else with_name_scope(value.fdel), - doc=value.__doc__) - - cls = super(ModuleMetaclass, mcs).__new__(mcs, name, bases, clsdict) - - for method_name in methods: - # Note: the below is quite subtle, we need to ensure that we're wrapping - # the method bound to the class. In some cases (e.g. `wrapt`) this is - # important since the method can trigger different behavior when it is - # bound (e.g. in wrapt `FunctionWrapper.__get__(None, cls)` produces a - # `BoundFunctionWrapper` which in turn populates the `instance` argument - # to decorator functions using args[0]). - # Equivalent to: `cls.__dict__[method_name].__get__(None, cls)` - method = getattr(cls, method_name) - method = with_name_scope(method) - setattr(cls, method_name, method) - - return cls - - def __call__(cls, *args, **kwargs): - # Call new such that we have an un-initialized module instance that we can - # still reference even if there is an exception during __init__. This is - # needed such that we can make sure the name_scope constructed in __init__ - # is closed even if there is an exception. - module = cls.__new__(cls, *args, **kwargs) - - # Now attempt to initialize the object. - try: - module.__init__(*args, **kwargs) - except: - # We must explicitly catch so that in Python 2 sys.exc_info() is populated - # before entering the finally block. - raise - - finally: - # The base Module constructor enters the modules name scope before - # returning such that other functionality in the ctor happens within the - # modules name scope. - scope = getattr(module, "_ctor_name_scope", None) - exc_info = sys.exc_info() - if scope is None: - if exc_info[0] is None: - raise ValueError( - "Constructing a tf.Module without calling the super constructor " - "is not supported. Add the following as the first line in your " - "__init__ method:\n\n" - "super(%s, self).__init__()" % cls.__name__) - else: - scope.__exit__(*exc_info) - del module._ctor_name_scope - - return module - - -def wrap_with_name_scope(unbound_method): - """Patches the given method so it enters the modules name scope.""" - def enter_name_scope(self, *args, **kwargs): - """Decorator that calls the given function in the module name scope. - - Args: - self: Module instance. - *args: Positional arguments to `unbound_method`. - **kwargs: Keyword arguments to `unbound_method`. - - Returns: - `with self.name_scope: return unbound_method(self, *args, **kwargs)` - """ - try: - module_name_scope = self.name_scope - except AttributeError as exc_value_from: - exc_value = AttributeError( - "The super constructor must be called before any other methods in " - "your constructor. If this is not possible then annotate all the " - "methods called with `@no_module_name_scope`.") - six.raise_from(exc_value, exc_value_from) - - with module_name_scope: - # tf.Module enters the module name scope for all methods. To disable this - # for a particular method annotate it with `@no_module_name_scope`. - return unbound_method(self, *args, **kwargs) - - return enter_name_scope - - -def wrap_with_name_scope_no_exception(unbound_method): - """Patches the given method so it enters the modules name scope.""" - def enter_name_scope(self, *args, **kwargs): - with self.name_scope: - # tf.Module enters the module name scope for all methods. To disable this - # for a particular method annotate it with `@no_module_name_scope`. - return unbound_method(self, *args, **kwargs) - return enter_name_scope - - -def with_name_scope(unbound_method): - """Patches the given method so it enters the modules name scope.""" - if getattr(unbound_method, NO_MODULE_NAME_SCOPE, False): - # The function has been annotated to say that no autoscoping should be - # applied, so do not patch it. - return unbound_method - - if isinstance(unbound_method, def_function.Function): - # Autograph cannot convert functions that have try/catch. - unbound_method._decorate(wrap_with_name_scope_no_exception) # pylint: disable=protected-access - return unbound_method - else: - return tf_decorator.make_decorator(unbound_method, - wrap_with_name_scope(unbound_method)) - @tf_export("Module") -class Module(six.with_metaclass(ModuleMetaclass, tracking.AutoTrackable)): +class Module(tracking.AutoTrackable): """Base neural network module class. A module is a named container for `tf.Variable`s, other `tf.Module`s and @@ -179,37 +37,54 @@ class Module(six.with_metaclass(ModuleMetaclass, tracking.AutoTrackable)): network might be implemented as a `tf.Module`: >>> class Dense(tf.Module): - ... def __init__(self, in_features, output_features): - ... super(Dense, self).__init__() + ... def __init__(self, in_features, output_features, name=None): + ... super(Dense, self).__init__(name=name) ... self.w = tf.Variable( ... tf.random_normal([input_features, output_features]), name='w') ... self.b = tf.Variable(tf.zeros([output_features]), name='b') ... ... def __call__(self, x): - ... x = tf.convert_to_tensor(x, name='x') ... y = tf.matmul(x, self.w) + self.b ... return tf.nn.relu(y) - You can use the dense layer as you would expect: + You can use the Dense layer as you would expect: >>> d = Dense(input_features=64, output_features=10) >>> d(tf.ones([100, 64])) - By subclassing `tf.Module` instead of `object` any variables created inside - the module are automatically created within the modules name scope: - - >>> d.w.name - "dense/w:0" - - In eager mode this is useful for debugging, and when used with `@tf.function` - the use of name scopes gives operations (e.g. matmul) useful names as well. - - As well as automatic naming, the Dense module inherits methods for tracking - its variables: + By subclassing `tf.Module` instead of `object` any `tf.Variable` or + `tf.Module` instances assigned to object properties can be collected using + the `variables`, `trainable_variables` or `submodules` property: >>> d.variables - (, ) + (, ) + + Subclasses of `tf.Module` can also take advantage of the `_flatten` method + which can be used to implement tracking of any other types. + + All `tf.Module` classes have an associated `tf.name_scope` which can be used + to group operations in TensorBoard and create hierarchies for variable names + which can help with debugging. We suggest using the name scope when creating + nested submodules/parameters or for forward methods whose graph you might want + to inspect in TensorBoard. You can enter the name scope explicitly using + `with self.name_scope:` or you can annotate methods (apart from `__init__`) + with `@tf.Module.with_name_scope`. + + >>> class MLP(tf.Module): + ... def __init__(self, input_size, sizes, name=None): + ... super(MLP, self).__init__(name=name) + ... self.layers = [] + ... with self.name_scope: + ... for size in sizes: + ... self.layers.append(Dense(input_size=size, output_size=size)) + ... input_size = size + ... + ... @tf.Module.with_name_scope + ... def __call__(self, x): + ... for layer in self.layers: + ... x = layer(x) + ... return x """ def __init__(self, name=None): @@ -225,12 +100,6 @@ class Module(six.with_metaclass(ModuleMetaclass, tracking.AutoTrackable)): with ops.name_scope(name) as scope_name: self._scope_name = scope_name - # Enter the name scope so subsequent code in the contructor (e.g. creating - # submodules) happens inside the modules name scope. This is exited when - # the subclass __init__ returns (this is implemented in ModuleMetaclass). - self._ctor_name_scope = self.name_scope - self._ctor_name_scope.__enter__() - @property def name(self): """Returns the name of this module as passed or determined in the ctor. @@ -360,27 +229,37 @@ class Module(six.with_metaclass(ModuleMetaclass, tracking.AutoTrackable)): with_path=with_path) @classmethod - def no_name_scope(cls, method): - """Decorator to wrap a method, preventing automatic name scope wrapping. + def with_name_scope(cls, method): + """Decorator to automatically enter the module name scope. + + >>> class MyModule(tf.Module): + ... @tf.Module.with_name_scope + ... def __call__(self, x): + ... if not hasattr(self, 'w'): + ... self.w = tf.Variable(tf.random.normal([x.shape[1], 64])) + ... return tf.matmul(x, self.w) - By default, any method on a module is considered as a forwards function, and - so any variables / modules created by the method will be scoped as belonging - to the module. In some cases this is undesirable, for example when - implementing .clone() / .transpose(), as in those cases we want the new - module to have the scope of wherever the .transpose() call is made. To - allow this, decorate any methods with `no_module_name_scope`. + Using the above module would produce `tf.Variable`s and `tf.Tensor`s whose + names included the module name: - This logic is tied to ModuleMetaclass.__new__, if anything is - changed here corresponding changes will be needed there. + >>> mod = MyModule() + >>> mod(tf.ones([8, 32])) + + >>> mod.w + Args: - method: the method to wrap. + method: The method to wrap. Returns: - The method, with a flag indicating no name scope wrapping should occur. + The original method wrapped such that it enters the module's name scope. """ - setattr(method, NO_MODULE_NAME_SCOPE, True) - return method + def method_with_name_scope(self, *args, **kwargs): + with self.name_scope: + return method(self, *args, **kwargs) + + return tf_decorator.make_decorator(method, method_with_name_scope) + _IS_VARIABLE = lambda o: isinstance(o, variables.Variable) _IS_TRAINABLE_VARIABLE = lambda o: (_IS_VARIABLE(o) and o.trainable) diff --git a/tensorflow/python/module/module_test.py b/tensorflow/python/module/module_test.py index 52bb97bc5b..17c0ad8832 100644 --- a/tensorflow/python/module/module_test.py +++ b/tensorflow/python/module/module_test.py @@ -227,24 +227,6 @@ class ModuleTrackingTest(test.TestCase): self.assertEqual(set(m.submodules), {leaf1, leaf2}) -class CommonErrorsTest(test.TestCase): - - def test_not_calling_super_constructor(self): - msg = ("Constructing a tf.Module without calling the super constructor is " - "not supported") - with self.assertRaisesRegexp(ValueError, msg): - DoesNotCallSuperConstructorModule() - - def test_calls_method_before_super(self): - msg = "super constructor must be called before any other methods" - with self.assertRaisesRegexp(AttributeError, msg): - CallsMethodBeforeSuperConstructorModule(allowed_method=False) - - def test_annotated_method_is_allowed(self): - self.assertIsNotNone( - CallsMethodBeforeSuperConstructorModule(allowed_method=True)) - - class ForwardMethodsTest(test.TestCase): def testFunctionType(self): @@ -307,10 +289,11 @@ class RecursiveModule(module.Module): def __init__(self, depth, trainable=True): super(RecursiveModule, self).__init__(name="badger") - self.child = None - if depth > 1: - self.child = RecursiveModule(depth - 1, trainable=trainable) - self.w = variables.Variable(1.0, trainable=trainable, name="mushroom") + with self.name_scope: + self.child = None + if depth > 1: + self.child = RecursiveModule(depth - 1, trainable=trainable) + self.w = variables.Variable(1.0, trainable=trainable, name="mushroom") @six.add_metaclass(abc.ABCMeta) @@ -323,6 +306,7 @@ class AbstractModule(module.Module): class ConcreteModule(AbstractModule): + @module.Module.with_name_scope def __call__(self, x): return x ** 2, get_name_scope() @@ -333,6 +317,7 @@ class TreeModule(module.Module): super(TreeModule, self).__init__(name=name) self._leaves = [] + @module.Module.with_name_scope def new_leaf(self, name=None): leaf = TreeModule(name=name) self._leaves.append(leaf) @@ -341,15 +326,18 @@ class TreeModule(module.Module): class ReturnsNameScopeModule(module.Module): + @module.Module.with_name_scope def alternative_forward(self): return get_name_scope() + @module.Module.with_name_scope def __call__(self): return get_name_scope() class SubclassedReturnsNameScopeModule(ReturnsNameScopeModule): + @module.Module.with_name_scope def alternative_alternative_forward(self): return get_name_scope() @@ -368,37 +356,15 @@ class ModuleOverridingNameScope(ReturnsNameScopeModule): return ops.name_scope("yolo/") -class DoesNotCallSuperConstructorModule(module.Module): - - def __init__(self): - # NOTE: Intentionally does not call super constructor. - pass - - -class CallsMethodBeforeSuperConstructorModule(module.Module): - - def __init__(self, allowed_method): - if allowed_method: - self.no_name_scope() - else: - self.with_name_scope() - super(CallsMethodBeforeSuperConstructorModule, self).__init__() - - @module.Module.no_name_scope - def no_name_scope(self): - pass - - def with_name_scope(self): - pass - - class ModuleWithFunctionAnnotatedCall(module.Module): @def_function.function(autograph=False) + @module.Module.with_name_scope def forward(self): return get_name_scope() @def_function.function(autograph=True) + @module.Module.with_name_scope def forward_ag(self): return get_name_scope() @@ -410,22 +376,22 @@ class PropertyModule(module.Module): self._setter_scope_name = None @property + @module.Module.with_name_scope def some_property(self): getter_scope_name = get_name_scope() return getter_scope_name, self._setter_scope_name @some_property.setter + @module.Module.with_name_scope def some_property(self, my_property): self._setter_scope_name = get_name_scope() @property - @module.Module.no_name_scope def no_name_scope_property(self): getter_scope_name = get_name_scope() return getter_scope_name, self._setter_scope_name @no_name_scope_property.setter - @module.Module.no_name_scope def no_name_scope_property(self, my_property): self._setter_scope_name = get_name_scope() @@ -514,43 +480,6 @@ class SimpleModule(module.Module): IS_MEMBER = lambda v: isinstance(v, MemberType) IS_MODULE = lambda v: isinstance(v, module.Module) - -class CustomMetaclass(type): - - TAG = "__custom_metaclass__" - - def __new__(mcs, name, bases, clsdict): - new_type = super(CustomMetaclass, mcs).__new__(mcs, name, bases, clsdict) - setattr(new_type, CustomMetaclass.TAG, True) - return new_type - - -class CombiningMetaclass(module.ModuleMetaclass, CustomMetaclass): - - TAG = "__combining_metaclass__" - - def __new__(mcs, name, bases, clsdict): - new_type = super(CombiningMetaclass, mcs).__new__(mcs, name, bases, clsdict) - setattr(new_type, CombiningMetaclass.TAG, True) - return new_type - - -@six.add_metaclass(CombiningMetaclass) -class ModuleWithCustomMetaclass(module.Module): - - def __init__(self): - super(ModuleWithCustomMetaclass, self).__init__() - self.init_name_scope = get_name_scope() - - -class CustomMetaclassTest(test.TestCase): - - def testSupportsCustomMetaclass(self): - m = ModuleWithCustomMetaclass() - self.assertEqual(m.init_name_scope, "module_with_custom_metaclass/") - self.assertTrue(getattr(ModuleWithCustomMetaclass, CombiningMetaclass.TAG)) - self.assertTrue(getattr(ModuleWithCustomMetaclass, CustomMetaclass.TAG)) - if __name__ == "__main__": v2_compat.enable_v2_behavior() test.main() diff --git a/tensorflow/tools/api/golden/v1/tensorflow.-module.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.-module.pbtxt index 8d599d73b8..8c3438e4d8 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.-module.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.-module.pbtxt @@ -29,7 +29,7 @@ tf_class { argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'None\'], " } member_method { - name: "no_name_scope" + name: "with_name_scope" argspec: "args=[\'cls\', \'method\'], varargs=None, keywords=None, defaults=None" } } diff --git a/tensorflow/tools/api/golden/v1/tensorflow.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.pbtxt index 103fdd0c1a..a7357e811e 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.pbtxt @@ -142,7 +142,7 @@ tf_module { } member { name: "Module" - mtype: "" + mtype: "" } member { name: "NameAttrList" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.-module.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.-module.pbtxt index 8d599d73b8..8c3438e4d8 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.-module.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.-module.pbtxt @@ -29,7 +29,7 @@ tf_class { argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'None\'], " } member_method { - name: "no_name_scope" + name: "with_name_scope" argspec: "args=[\'cls\', \'method\'], varargs=None, keywords=None, defaults=None" } } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.pbtxt index 534c7732f5..bff1fcd994 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.pbtxt @@ -26,7 +26,7 @@ tf_module { } member { name: "Module" - mtype: "" + mtype: "" } member { name: "Operation" -- GitLab From 32f7033c90d85834b5fd9e85bc23e33a3854f34a Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 25 Feb 2019 09:13:32 -0800 Subject: [PATCH 451/766] Fix tf.image.non_max_suppression docstrings. PiperOrigin-RevId: 235541426 --- tensorflow/python/ops/image_ops_impl.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tensorflow/python/ops/image_ops_impl.py b/tensorflow/python/ops/image_ops_impl.py index 44bd92f422..d99e10b7ac 100644 --- a/tensorflow/python/ops/image_ops_impl.py +++ b/tensorflow/python/ops/image_ops_impl.py @@ -2244,9 +2244,9 @@ def non_max_suppression(boxes, Prunes away boxes that have high intersection-over-union (IOU) overlap with previously selected boxes. Bounding boxes are supplied as - [y1, x1, y2, x2], where (y1, x1) and (y2, x2) are the coordinates of any + `[y1, x1, y2, x2]`, where `(y1, x1)` and `(y2, x2)` are the coordinates of any diagonal pair of box corners and the coordinates can be provided as normalized - (i.e., lying in the interval [0, 1]) or absolute. Note that this algorithm + (i.e., lying in the interval `[0, 1]`) or absolute. Note that this algorithm is agnostic to where the origin is in the coordinate system. Note that this algorithm is invariant to orthogonal transformations and translations of the coordinate system; thus translating or reflections of the coordinate @@ -2254,10 +2254,12 @@ def non_max_suppression(boxes, The output of this operation is a set of integers indexing into the input collection of bounding boxes representing the selected boxes. The bounding box coordinates corresponding to the selected indices can then be obtained - using the `tf.gather operation`. For example: + using the `tf.gather` operation. For example: + ```python selected_indices = tf.image.non_max_suppression( boxes, scores, max_output_size, iou_threshold) selected_boxes = tf.gather(boxes, selected_indices) + ``` Args: boxes: A 2-D float `Tensor` of shape `[num_boxes, 4]`. @@ -2301,12 +2303,14 @@ def non_max_suppression_padded(boxes, boxes and the number of valid indices in the index set. The bounding box coordinates corresponding to the selected indices can then be obtained using the `tf.slice` and `tf.gather` operations. For example: + ```python selected_indices_padded, num_valid = tf.image.non_max_suppression_padded( boxes, scores, max_output_size, iou_threshold, score_threshold, pad_to_max_output_size=True) selected_indices = tf.slice( selected_indices_padded, tf.constant([0]), num_valid) selected_boxes = tf.gather(boxes, selected_indices) + ``` Args: boxes: A 2-D float `Tensor` of shape `[num_boxes, 4]`. @@ -2355,10 +2359,12 @@ def non_max_suppression_with_overlaps(overlaps, The output of this operation is a set of integers indexing into the input collection of bounding boxes representing the selected boxes. The bounding box coordinates corresponding to the selected indices can then be obtained - using the `tf.gather operation`. For example: + using the `tf.gather` operation. For example: + ```python selected_indices = tf.image.non_max_suppression_overlaps( overlaps, scores, max_output_size, iou_threshold) selected_boxes = tf.gather(boxes, selected_indices) + ``` Args: overlaps: A 2-D float `Tensor` of shape `[num_boxes, num_boxes]`. -- GitLab From 99c2a2ee39a94966605af520cd5a5af36d2b3977 Mon Sep 17 00:00:00 2001 From: Nupur Garg Date: Mon, 25 Feb 2019 09:32:35 -0800 Subject: [PATCH 452/766] Expose TFLiteConverter for TensorFlow 2.0. PiperOrigin-RevId: 235544520 --- tensorflow/lite/python/lite.py | 39 ++++++++++++++----- .../v1/tensorflow.lite.-target-spec.pbtxt | 9 +++++ .../tools/api/golden/v1/tensorflow.lite.pbtxt | 4 ++ .../tensorflow.lite.-t-f-lite-converter.pbtxt | 24 ++---------- .../v2/tensorflow.lite.-target-spec.pbtxt | 9 +++++ .../tools/api/golden/v2/tensorflow.lite.pbtxt | 4 ++ 6 files changed, 59 insertions(+), 30 deletions(-) create mode 100644 tensorflow/tools/api/golden/v1/tensorflow.lite.-target-spec.pbtxt create mode 100644 tensorflow/tools/api/golden/v2/tensorflow.lite.-target-spec.pbtxt diff --git a/tensorflow/lite/python/lite.py b/tensorflow/lite/python/lite.py index c8254518bd..6d2a247361 100644 --- a/tensorflow/lite/python/lite.py +++ b/tensorflow/lite/python/lite.py @@ -150,6 +150,25 @@ class RepresentativeDataset(object): self.output_gen = output_gen +@_tf_export("lite.TargetSpec") +class TargetSpec(object): + """Specification of target device. + + Details about target device. Converter optimizes the generated model for + specific device. + + Attributes: + supported_ops: Experimental flag, subject to change. Set of OpsSet options + supported by the device. (default set([OpsSet.TFLITE_BUILTINS])) + """ + + def __init__(self, supported_ops=None): + if supported_ops is None: + supported_ops = set([OpsSet.TFLITE_BUILTINS]) + self.supported_ops = supported_ops + + +@_tf_export("lite.TFLiteConverter", v1=[]) class TFLiteConverterV2(object): """Converts a TensorFlow model into TensorFlow Lite model. @@ -159,8 +178,8 @@ class TFLiteConverterV2(object): created for any op that is unknown. The developer will need to provide these to the TensorFlow Lite runtime with a custom resolver. (default False) - target_ops: Experimental flag, subject to change. Set of OpsSet options - indicating which converter to use. (default set([OpsSet.TFLITE_BUILTINS])) + target_spec: Experimental flag, subject to change. Specification of target + device. optimizations: Experimental flag, subject to change, A list of optimizations to apply when converting the model. The converter applies the optimizations by giving priority to the optimizations specified earlier in @@ -168,7 +187,7 @@ class TFLiteConverterV2(object): optimize.OPTIMIZE_FOR_LATENCY]` requires the converter to do both size and latency optimizations giving priority to size optimizations over latency optimizations. - representative_dataset: a representative dataset that can be used to + representative_dataset: A representative dataset that can be used to generate input and output samples for the model. The converter can use the dataset to evaluate different optimizations. @@ -190,7 +209,7 @@ class TFLiteConverterV2(object): """ self._func = func self.allow_custom_ops = False - self.target_ops = set([OpsSet.TFLITE_BUILTINS]) + self.target_spec = TargetSpec() self.representative_dataset = None self.optimizations = [] @@ -248,11 +267,11 @@ class TFLiteConverterV2(object): if self.representative_dataset: if not isinstance(self.representative_dataset, RepresentativeDataset): - raise TypeError("representative_dataset must be an instance of " - "RepresentativeDataset") + raise TypeError("`representative_dataset` must be an instance of " + "`RepresentativeDataset`") if self.representative_dataset.input_gen is None: raise ValueError( - "Provide an input generator for representative_dataset") + "Provide an input generator for `representative_dataset`") # TODO(shashishekhar): For now use optimizations order is ignored. # Both size and latency optimizations decide whether to apply post @@ -269,7 +288,7 @@ class TFLiteConverterV2(object): "input_format": constants.TENSORFLOW_GRAPHDEF, "allow_custom_ops": self.allow_custom_ops, "post_training_quantize": weights_only_quantize_flag, - "target_ops": self.target_ops, + "target_ops": self.target_spec.supported_ops, } # Converts model. @@ -287,7 +306,7 @@ class TFLiteConverterV2(object): return result -@_tf_export("lite.TFLiteConverter") +@_tf_export(v1=["lite.TFLiteConverter"]) class TFLiteConverter(object): """Convert a TensorFlow model into `output_format` using TOCO. @@ -349,7 +368,7 @@ class TFLiteConverter(object): `[optimize.OPTIMIZE_FOR_SIZE, optimize.OPTIMIZE_FOR_LATENCY]` requires the converter to do both size and latency optimizations giving priority to size optimizations over latency optimizations. - representative_dataset: a representative dataset that can be used to + representative_dataset: A representative dataset that can be used to generate input and output samples for the model. The converter can use the dataset to evaluate different optimizations. diff --git a/tensorflow/tools/api/golden/v1/tensorflow.lite.-target-spec.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.lite.-target-spec.pbtxt new file mode 100644 index 0000000000..966fb69cbe --- /dev/null +++ b/tensorflow/tools/api/golden/v1/tensorflow.lite.-target-spec.pbtxt @@ -0,0 +1,9 @@ +path: "tensorflow.lite.TargetSpec" +tf_class { + is_instance: "" + is_instance: "" + member_method { + name: "__init__" + argspec: "args=[\'self\', \'supported_ops\'], varargs=None, keywords=None, defaults=[\'None\'], " + } +} diff --git a/tensorflow/tools/api/golden/v1/tensorflow.lite.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.lite.pbtxt index b508c3255e..97722ad559 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.lite.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.lite.pbtxt @@ -24,6 +24,10 @@ tf_module { name: "TFLiteConverter" mtype: "" } + member { + name: "TargetSpec" + mtype: "" + } member { name: "TocoConverter" mtype: "" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.lite.-t-f-lite-converter.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.lite.-t-f-lite-converter.pbtxt index c955b1a04a..eca1508564 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.lite.-t-f-lite-converter.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.lite.-t-f-lite-converter.pbtxt @@ -1,33 +1,17 @@ path: "tensorflow.lite.TFLiteConverter" tf_class { - is_instance: "" + is_instance: "" is_instance: "" member_method { name: "__init__" - argspec: "args=[\'self\', \'graph_def\', \'input_tensors\', \'output_tensors\', \'input_arrays_with_shape\', \'output_arrays\'], varargs=None, keywords=None, defaults=[\'None\', \'None\'], " + argspec: "args=[\'self\', \'func\'], varargs=None, keywords=None, defaults=None" } member_method { name: "convert" argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" } member_method { - name: "from_frozen_graph" - argspec: "args=[\'cls\', \'graph_def_file\', \'input_arrays\', \'output_arrays\', \'input_shapes\'], varargs=None, keywords=None, defaults=[\'None\'], " - } - member_method { - name: "from_keras_model_file" - argspec: "args=[\'cls\', \'model_file\', \'input_arrays\', \'input_shapes\', \'output_arrays\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'None\'], " - } - member_method { - name: "from_saved_model" - argspec: "args=[\'cls\', \'saved_model_dir\', \'input_arrays\', \'input_shapes\', \'output_arrays\', \'tag_set\', \'signature_key\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'None\', \'None\', \'None\'], " - } - member_method { - name: "from_session" - argspec: "args=[\'cls\', \'sess\', \'input_tensors\', \'output_tensors\'], varargs=None, keywords=None, defaults=None" - } - member_method { - name: "get_input_arrays" - argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + name: "from_concrete_function" + argspec: "args=[\'cls\', \'func\'], varargs=None, keywords=None, defaults=None" } } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.lite.-target-spec.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.lite.-target-spec.pbtxt new file mode 100644 index 0000000000..966fb69cbe --- /dev/null +++ b/tensorflow/tools/api/golden/v2/tensorflow.lite.-target-spec.pbtxt @@ -0,0 +1,9 @@ +path: "tensorflow.lite.TargetSpec" +tf_class { + is_instance: "" + is_instance: "" + member_method { + name: "__init__" + argspec: "args=[\'self\', \'supported_ops\'], varargs=None, keywords=None, defaults=[\'None\'], " + } +} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.lite.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.lite.pbtxt index 5fccb608ab..4e80f432e7 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.lite.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.lite.pbtxt @@ -24,6 +24,10 @@ tf_module { name: "TFLiteConverter" mtype: "" } + member { + name: "TargetSpec" + mtype: "" + } member { name: "constants" mtype: "" -- GitLab From b7e8735015f8d232b7c24615f69ba9812e8448f5 Mon Sep 17 00:00:00 2001 From: Taylor Robie Date: Mon, 25 Feb 2019 09:49:17 -0800 Subject: [PATCH 453/766] Make the backend constant utility create EagerTensors when the outer scope is eager. When creating EagerTensors under a graph scope, they will be captured by the graph and treated as constants. PiperOrigin-RevId: 235547535 --- tensorflow/python/keras/BUILD | 3 +- tensorflow/python/keras/backend.py | 8 +++ .../python/keras/engine/base_layer_test.py | 64 +++++++++++++++++++ .../python/keras/layers/normalization.py | 4 +- 4 files changed, 76 insertions(+), 3 deletions(-) diff --git a/tensorflow/python/keras/BUILD b/tensorflow/python/keras/BUILD index 926f354450..98052166ce 100755 --- a/tensorflow/python/keras/BUILD +++ b/tensorflow/python/keras/BUILD @@ -1221,7 +1221,7 @@ tf_py_test( tf_py_test( name = "base_layer_test", - size = "small", + size = "medium", srcs = ["engine/base_layer_test.py"], additional_deps = [ ":keras", @@ -1229,6 +1229,7 @@ tf_py_test( "//third_party/py/numpy", "//tensorflow/python:client_testlib", ], + shard_count = 8, tags = ["no_rocm"], ) diff --git a/tensorflow/python/keras/backend.py b/tensorflow/python/keras/backend.py index 0a8c5bb19f..b63127c26f 100644 --- a/tensorflow/python/keras/backend.py +++ b/tensorflow/python/keras/backend.py @@ -784,6 +784,14 @@ def constant(value, dtype=None, shape=None, name=None): """ if dtype is None: dtype = floatx() + + # If the outer context is eager but we are executing under the keras + # FuncGraph, we create EagerTensors and use them as constants. + if (ops.executing_eagerly_outside_functions() and + getattr(get_graph(), 'name', '') == 'keras_graph'): + with ops.init_scope(): + return constant_op.constant(value, dtype=dtype, shape=shape, name=name) + return constant_op.constant(value, dtype=dtype, shape=shape, name=name) diff --git a/tensorflow/python/keras/engine/base_layer_test.py b/tensorflow/python/keras/engine/base_layer_test.py index b4748d1301..aa3c53306b 100644 --- a/tensorflow/python/keras/engine/base_layer_test.py +++ b/tensorflow/python/keras/engine/base_layer_test.py @@ -18,6 +18,8 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +import collections +import itertools as it import sys import traceback from absl.testing import parameterized @@ -574,6 +576,68 @@ class NameScopingTest(keras_parameterized.TestCase): self.assertEqual(layer.kernel.name, 'MyName3/kernel:0') +_LAYERS_TO_TEST = [ + (keras.layers.Dense, (1,), collections.OrderedDict(units=[1])), + (keras.layers.Activation, (2, 2), + collections.OrderedDict(activation=['relu'])), + (keras.layers.Dropout, (16,), collections.OrderedDict(rate=[0.25])), + (keras.layers.BatchNormalization, (8, 8, 3), collections.OrderedDict( + axis=[3], center=[True, False], scale=[True, False])), + (keras.layers.Conv1D, (8, 8), collections.OrderedDict( + filters=[1], kernel_size=[1, 3], strides=[1, 2], + padding=['valid', 'same'], use_bias=[True, False], + kernel_regularizer=[None, 'l2'])), + (keras.layers.Conv2D, (8, 8, 3), collections.OrderedDict( + filters=[1], kernel_size=[1, 3], strides=[1, 2], + padding=['valid', 'same'], use_bias=[True, False], + kernel_regularizer=[None, 'l2'])), + (keras.layers.LSTM, (8, 8), collections.OrderedDict( + units=[1], + activation=[None, 'relu'], + kernel_regularizer=[None, 'l2'], + dropout=[0, 0.5], + stateful=[True, False], + unroll=[True, False])), +] + +OUTPUT_TEST_CASES = [] +for layer_type, inp_shape, arg_dict in _LAYERS_TO_TEST: + arg_combinations = [[(k, i) for i in v] for k, v in arg_dict.items()] # pylint: disable=g-complex-comprehension + for args in it.product(*arg_combinations): + name = '_{}_{}'.format( + layer_type.__name__, '_'.join('{}_{}'.format(k, v) for k, v in args)) + OUTPUT_TEST_CASES.append( + (name, layer_type, inp_shape, {k: v for k, v in args})) + + +class OutputTypeTest(keras_parameterized.TestCase): + """Test that layers and models produce the correct tensor types.""" + + # In v1 graph there are only symbolic tensors. + @keras_parameterized.run_all_keras_modes(always_skip_v1=True) + @parameterized.named_parameters(*OUTPUT_TEST_CASES) + def test_layer_outputs(self, layer_to_test, input_shape, layer_kwargs): + layer = layer_to_test(**layer_kwargs) + + input_data = np.ones(shape=(2,) + input_shape, dtype=np.float32) + layer_result = layer(input_data) + + inp = keras.layers.Input(shape=input_shape, batch_size=2) + model = keras.models.Model(inp, layer_to_test(**layer_kwargs)(inp)) + model_result = model(input_data) + + for x in [layer_result, model_result]: + if not isinstance(x, ops.Tensor): + raise ValueError('Tensor or EagerTensor expected, got type {}' + .format(type(x))) + + if isinstance(x, ops.EagerTensor) != context.executing_eagerly(): + expected_type = (ops.EagerTensor if context.executing_eagerly() + else ops.Tensor) + raise ValueError('Expected type {}, got type {}' + .format(expected_type, type(x))) + + if __name__ == '__main__': ops.enable_eager_execution() test.main() diff --git a/tensorflow/python/keras/layers/normalization.py b/tensorflow/python/keras/layers/normalization.py index 5008bd7714..02efefcc1f 100644 --- a/tensorflow/python/keras/layers/normalization.py +++ b/tensorflow/python/keras/layers/normalization.py @@ -329,7 +329,7 @@ class BatchNormalizationV2(Layer): else: self.gamma = None if self.fused: - self._gamma_const = array_ops.constant( + self._gamma_const = K.constant( 1.0, dtype=param_dtype, shape=param_shape) if self.center: @@ -344,7 +344,7 @@ class BatchNormalizationV2(Layer): else: self.beta = None if self.fused: - self._beta_const = array_ops.constant( + self._beta_const = K.constant( 0.0, dtype=param_dtype, shape=param_shape) try: -- GitLab From d21b5f6d08b0e63fb12db0ecf06546874e356d19 Mon Sep 17 00:00:00 2001 From: Benjamin Kramer Date: Mon, 25 Feb 2019 09:53:04 -0800 Subject: [PATCH 454/766] [XLA:CPU] Enable F64 convolutions There's no good reason for disabling it completely. Right now this falls back to the slow generic implementation. We could make it use Eigen if we want to. PiperOrigin-RevId: 235548224 --- tensorflow/compiler/xla/service/cpu/ir_emission_utils.cc | 3 ++- tensorflow/compiler/xla/service/cpu/ir_emitter.cc | 2 +- tensorflow/compiler/xla/tests/convolution_test.cc | 8 ++++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/tensorflow/compiler/xla/service/cpu/ir_emission_utils.cc b/tensorflow/compiler/xla/service/cpu/ir_emission_utils.cc index a8b139aec9..2cc618e430 100644 --- a/tensorflow/compiler/xla/service/cpu/ir_emission_utils.cc +++ b/tensorflow/compiler/xla/service/cpu/ir_emission_utils.cc @@ -72,7 +72,8 @@ bool PotentiallyImplementedAsEigenConvolution( CHECK( ShapeUtil::SameElementTypeIgnoringFpPrecision(input_shape, kernel_shape)); // TODO(b/65408531): Explore using Eigen dot for complex64 type. - if (ShapeUtil::ElementIsComplex(input_shape)) { + PrimitiveType primitive_type = input_shape.element_type(); + if (primitive_type != F16 && primitive_type != F32) { return false; } if (window_util::HasWindowReversal(convolution.window())) { diff --git a/tensorflow/compiler/xla/service/cpu/ir_emitter.cc b/tensorflow/compiler/xla/service/cpu/ir_emitter.cc index 2418d96440..47041eb348 100644 --- a/tensorflow/compiler/xla/service/cpu/ir_emitter.cc +++ b/tensorflow/compiler/xla/service/cpu/ir_emitter.cc @@ -1090,7 +1090,7 @@ Status IrEmitter::HandleConvolution(HloInstruction* convolution) { auto rhs = convolution->operand(1); TF_RETURN_IF_ERROR(ElementTypesSameAndSupported( /*instruction=*/*convolution, /*operands=*/{lhs, rhs}, - /*supported_types=*/{F16, F32, C64, C128})); + /*supported_types=*/{F16, F32, F64, C64, C128})); // TODO(tonywy): Add PotentiallyImplementedAsMKLCovolution to support // different data layouts. diff --git a/tensorflow/compiler/xla/tests/convolution_test.cc b/tensorflow/compiler/xla/tests/convolution_test.cc index 9db9f2563b..cfee9c0f8a 100644 --- a/tensorflow/compiler/xla/tests/convolution_test.cc +++ b/tensorflow/compiler/xla/tests/convolution_test.cc @@ -1945,7 +1945,7 @@ XLA_TEST_F(ConvolutionTest, ConvolveF32BackwardInputGroupedConvolution) { class ConvolutionHloTest : public HloTestBase {}; -XLA_TEST_F(ConvolutionHloTest, DISABLED_ON_CPU(ConvolveF64Forward)) { +XLA_TEST_F(ConvolutionHloTest, ConvolveF64Forward) { constexpr char kHlo[] = R"( HloModule TestModule @@ -1957,7 +1957,7 @@ ENTRY Test { EXPECT_TRUE(RunAndCompare(kHlo, ErrorSpec{0.001})); } -XLA_TEST_F(ConvolutionHloTest, DISABLED_ON_CPU(ConvolveF32ForwardReversed)) { +XLA_TEST_F(ConvolutionHloTest, ConvolveF32ForwardReversed) { constexpr char kHlo[] = R"( HloModule TestModule @@ -1969,7 +1969,7 @@ ENTRY Test { EXPECT_TRUE(RunAndCompare(kHlo, ErrorSpec{0.001})); } -XLA_TEST_F(ConvolutionHloTest, DISABLED_ON_CPU(ConvolveF64BackwardFilter)) { +XLA_TEST_F(ConvolutionHloTest, ConvolveF64BackwardFilter) { constexpr char kHlo[] = R"( HloModule TestModule @@ -1981,7 +1981,7 @@ ENTRY Test { EXPECT_TRUE(RunAndCompare(kHlo, ErrorSpec{0.001})); } -XLA_TEST_F(ConvolutionHloTest, DISABLED_ON_CPU(ConvolveF64BackwardInput)) { +XLA_TEST_F(ConvolutionHloTest, ConvolveF64BackwardInput) { constexpr char kHlo[] = R"( HloModule TestModule -- GitLab From d813ad7dfce22489dfdddad1dea8160ad3d712bf Mon Sep 17 00:00:00 2001 From: Michael Banfield Date: Mon, 25 Feb 2019 09:59:28 -0800 Subject: [PATCH 455/766] Remove health check from TpuClusterResolver. PiperOrigin-RevId: 235549420 --- .../cluster_resolver/tpu_cluster_resolver.py | 4 ---- .../tpu_cluster_resolver_test.py | 23 ------------------- 2 files changed, 27 deletions(-) diff --git a/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver.py b/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver.py index 364e7d715e..9a36dc77ae 100644 --- a/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver.py +++ b/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver.py @@ -419,10 +419,6 @@ class TPUClusterResolver(ClusterResolver): raise RuntimeError('TPU "%s" is not yet ready; state: "%s"' % (compat.as_text(self._tpu), response['state'])) - if 'health' in response and response['health'] != 'HEALTHY': - raise RuntimeError('TPU "%s" is unhealthy: "%s"' % - (compat.as_text(self._tpu), response['health'])) - if 'networkEndpoints' in response: worker_list = [ '%s:%s' % (endpoint['ipAddress'], endpoint['port']) diff --git a/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver_test.py b/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver_test.py index 48119e63b1..816a2614f7 100644 --- a/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver_test.py +++ b/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver_test.py @@ -208,29 +208,6 @@ class TPUClusterResolverTest(test.TestCase): self._verifyClusterSpecEquality(actual_cluster_spec, expected_proto) self.assertEqual(resolver.master(), 'grpc://10.1.2.3:8470') - @mock.patch.object(TPUClusterResolver, - '_requestComputeMetadata', - mock_request_compute_metadata) - def testUnhealthyCloudTpu(self): - tpu_map = { - 'projects/test-project/locations/us-central1-c/nodes/test-tpu-1': { - 'ipAddress': '10.1.2.3', - 'port': '8470', - 'health': 'UNHEALTHY' - } - } - - resolver = TPUClusterResolver( - project=None, - zone=None, - tpu='test-tpu-1', - coordinator_name=None, - credentials=None, - service=self.mock_service_client(tpu_map=tpu_map)) - - with self.assertRaises(RuntimeError): - resolver.cluster_spec() - @mock.patch.object(TPUClusterResolver, '_requestComputeMetadata', mock_request_compute_metadata) -- GitLab From 6e946b40f4cf14cb5858757340384a1d1340cad9 Mon Sep 17 00:00:00 2001 From: Benjamin Kramer Date: Mon, 25 Feb 2019 10:01:32 -0800 Subject: [PATCH 456/766] [XLA:CPU] Make EmitElementalDot handle batch dots This is as simple as correcting the RHS index so it doesn't lose the batch dimensions. Fused elemental dots only occur for small operands on CPU. PiperOrigin-RevId: 235549797 --- .../xla/service/elemental_ir_emitter.cc | 6 ++++- .../xla/service/elemental_ir_emitter_test.cc | 25 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/tensorflow/compiler/xla/service/elemental_ir_emitter.cc b/tensorflow/compiler/xla/service/elemental_ir_emitter.cc index 926e673d12..f07925ca93 100644 --- a/tensorflow/compiler/xla/service/elemental_ir_emitter.cc +++ b/tensorflow/compiler/xla/service/elemental_ir_emitter.cc @@ -2169,7 +2169,11 @@ StatusOr ElementalIrEmitter::EmitElementalDot( } lhs_index.InsertAt(lhs_contracting_dim, inner_loop->GetIndVarValue()); - for (int64 i = 0; i < rhs_dims - 1; i++) { + int64 num_batch_dims = dim_numbers.rhs_batch_dimensions_size(); + for (int64 i = 0; i < num_batch_dims; i++) { + rhs_index.push_back(dot_result_index[dim_numbers.rhs_batch_dimensions(i)]); + } + for (int64 i = 0; i < rhs_dims - 1 - num_batch_dims; i++) { rhs_index.push_back(dot_result_index[lhs_dims - 1 + i]); } rhs_index.InsertAt(rhs_contracting_dim, inner_loop->GetIndVarValue()); diff --git a/tensorflow/compiler/xla/service/elemental_ir_emitter_test.cc b/tensorflow/compiler/xla/service/elemental_ir_emitter_test.cc index 852f34e06d..9e9d3daf25 100644 --- a/tensorflow/compiler/xla/service/elemental_ir_emitter_test.cc +++ b/tensorflow/compiler/xla/service/elemental_ir_emitter_test.cc @@ -60,5 +60,30 @@ ENTRY main { Literal rhs = LiteralUtil::CreateR3({{{3}, {4}}}); RunTest(hlo_text, {&lhs, &rhs}); } + +XLA_TEST_F(ElementalIrEmitterExecutionTest, BatchDot) { + const char* hlo_text = R"( +HloModule BatchDot + +fused_computation.1 { + param_0 = f64[1,1,8]{2,1,0} parameter(0) + r.1 = f64[2,4]{1,0} reshape(param_0) + param_1 = f64[1,2,2,2,1]{4,3,2,1,0} parameter(1) + r.2 = f64[2,4,1]{2,1,0} reshape(param_1) + ROOT dot = f64[2,1]{1,0} dot(r.1, r.2), lhs_batch_dims={0}, + lhs_contracting_dims={1}, + rhs_batch_dims={0}, + rhs_contracting_dims={1} +} + +ENTRY resampler_Resampler.49 { + p0 = f64[1,1,8]{2,1,0} parameter(0) + p1 = f64[1,2,2,2,1]{4,3,2,1,0} parameter(1) + ROOT f = f64[2,1]{1,0} fusion(p0, p1), kind=kLoop, calls=fused_computation.1 +} +)"; + + EXPECT_TRUE(RunAndCompare(hlo_text, ErrorSpec{4e-3, 4e-3})); +} } // namespace } // namespace xla -- GitLab From ab47f01278db3da0856f6f20da410ac617b203eb Mon Sep 17 00:00:00 2001 From: Nupur Garg Date: Mon, 25 Feb 2019 10:13:08 -0800 Subject: [PATCH 457/766] Internal change. PiperOrigin-RevId: 235552415 --- tensorflow/lite/build_def.bzl | 7 +- .../model_coverage/model_coverage_lib.py | 94 +++++++++++++++++++ 2 files changed, 98 insertions(+), 3 deletions(-) diff --git a/tensorflow/lite/build_def.bzl b/tensorflow/lite/build_def.bzl index 80c0c83e5a..764af75ab9 100644 --- a/tensorflow/lite/build_def.bzl +++ b/tensorflow/lite/build_def.bzl @@ -452,10 +452,11 @@ def flex_dep(target_op_sets): else: return [] -def gen_model_coverage_test(model_name, data, failure_type, tags): +def gen_model_coverage_test(src, model_name, data, failure_type, tags): """Generates Python test targets for testing TFLite models. Args: + src: Main source file. model_name: Name of the model to test (must be also listed in the 'data' dependencies) data: List of BUILD targets linking the data. @@ -472,9 +473,9 @@ def gen_model_coverage_test(model_name, data, failure_type, tags): i = i + 1 native.py_test( name = "model_coverage_test_%s_%s" % (model_name, target_op_sets.lower().replace(",", "_")), - srcs = ["model_coverage_test.py"], + srcs = [src], + main = src, size = "large", - main = "model_coverage_test.py", args = [ "--model_name=%s" % model_name, "--target_ops=%s" % target_op_sets, diff --git a/tensorflow/lite/testing/model_coverage/model_coverage_lib.py b/tensorflow/lite/testing/model_coverage/model_coverage_lib.py index 804e328d9d..92b5f41869 100644 --- a/tensorflow/lite/testing/model_coverage/model_coverage_lib.py +++ b/tensorflow/lite/testing/model_coverage/model_coverage_lib.py @@ -18,6 +18,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +import os import numpy as np from tensorflow.core.framework import graph_pb2 as _graph_pb2 @@ -25,13 +26,50 @@ from tensorflow.lite.python import convert_saved_model as _convert_saved_model from tensorflow.lite.python import lite as _lite from tensorflow.python import keras as _keras from tensorflow.python.client import session as _session +from tensorflow.python.framework import constant_op from tensorflow.python.framework.importer import import_graph_def as _import_graph_def +from tensorflow.python.keras.preprocessing import image from tensorflow.python.lib.io import file_io as _file_io +from tensorflow.python.platform import resource_loader as _resource_loader +from tensorflow.python.saved_model import load as _load from tensorflow.python.saved_model import loader as _loader from tensorflow.python.saved_model import signature_constants as _signature_constants from tensorflow.python.saved_model import tag_constants as _tag_constants +def get_filepath(filename, base_dir=None): + """Returns the full path of the filename. + + Args: + filename: Subdirectory and name of the model file. + base_dir: Base directory containing model file. + + Returns: + str. + """ + if base_dir is None: + base_dir = "learning/brain/mobile/tflite_compat_models" + return os.path.join(_resource_loader.get_root_dir_with_all_resources(), + base_dir, filename) + + +def get_image(size): + """Returns an image loaded into an np.ndarray with dims [1, size, size, 3]. + + Args: + size: Size of image. + + Returns: + np.ndarray. + """ + img_filename = _resource_loader.get_path_to_datafile( + "testdata/grace_hopper.jpg") + img = image.load_img(img_filename, target_size=(size, size)) + img_array = image.img_to_array(img) + img_array = np.expand_dims(img_array, axis=0) + return img_array + + def _convert(converter, **kwargs): """Converts the model. @@ -186,6 +224,32 @@ def compare_models(tflite_model, tf_eval_func, input_data=None, tolerance=5): np.testing.assert_almost_equal(tf_result, tflite_result, tolerance) +def compare_models_v2(tflite_model, concrete_func, input_data=None, + tolerance=5): + """Compares TensorFlow and TFLite models for TensorFlow 2.0. + + Unless the input data is provided, the models are compared with random data. + Currently only 1 input and 1 output are supported by this function. + + Args: + tflite_model: Serialized TensorFlow Lite model. + concrete_func: TensorFlow ConcreteFunction. + input_data: np.ndarray to pass into models during inference. (default None) + tolerance: Decimal place to check accuracy to. (default 5) + """ + if input_data is None: + input_data = _generate_random_input_data(tflite_model) + input_data_func = constant_op.constant(input_data[0]) + + # Gets the TensorFlow results as a map from the output names to outputs. + # Converts the map into a list that is equivalent to the TFLite list. + tf_results_map = concrete_func(input_1=input_data_func) + tf_results = [tf_results_map[tf_results_map.keys()[0]]] + tflite_results = _evaluate_tflite_model(tflite_model, input_data) + for tf_result, tflite_result in zip(tf_results, tflite_results): + np.testing.assert_almost_equal(tf_result, tflite_result, tolerance) + + def test_frozen_graph_quant(filename, input_arrays, output_arrays, @@ -315,6 +379,36 @@ def test_saved_model(directory, compare_models(tflite_model, tf_eval_func, input_data=input_data) +# TODO(nupurgarg): Remove input_shape parameter after bug with shapes is fixed. +def test_saved_model_v2(directory, + input_shape=None, + signature_key=None, + input_data=None, + **kwargs): + """Validates the TensorFlow SavedModel converts to a TFLite model. + + Converts the TensorFlow SavedModel to TFLite and checks the accuracy of the + model on random data. + + Args: + directory: SavedModel directory to convert. + input_shape: Input shape for the single input array as a list of integers. + signature_key: Key identifying SignatureDef containing inputs and outputs. + input_data: np.ndarray to pass into models during inference. (default None) + **kwargs: Additional arguments to be passed into the converter. + """ + model = _load.load(directory) + if not signature_key: + signature_key = _signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY + concrete_func = model.signatures[signature_key] + concrete_func.inputs[0].set_shape(input_shape) + + converter = _lite.TFLiteConverterV2.from_concrete_function(concrete_func) + tflite_model = _convert(converter, **kwargs) + + compare_models_v2(tflite_model, concrete_func, input_data=input_data) + + def test_keras_model(filename, input_arrays=None, input_shapes=None, -- GitLab From 557604f03a8171015124573cfdb4ddda9a79b0b4 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 25 Feb 2019 10:15:19 -0800 Subject: [PATCH 458/766] [xla] fix Python client bug in AllToAll PiperOrigin-RevId: 235552811 --- tensorflow/compiler/xla/python/local_computation_builder.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tensorflow/compiler/xla/python/local_computation_builder.cc b/tensorflow/compiler/xla/python/local_computation_builder.cc index 9104b27888..8f7b105eef 100644 --- a/tensorflow/compiler/xla/python/local_computation_builder.cc +++ b/tensorflow/compiler/xla/python/local_computation_builder.cc @@ -498,7 +498,8 @@ LocalOp ComputationBuilder::Collapse(const LocalOp& operand, LocalOp ComputationBuilder::AllToAll( const LocalOp& operand, int64 split_dimension, int64 concat_dimension, int64 split_count, absl::Span replica_groups) { - std::vector rg(replica_groups.size()); + std::vector rg; + rg.reserve(replica_groups.size()); for (int i = 0; i < replica_groups.size(); ++i) { rg.push_back(replica_groups[i]); } -- GitLab From 452952e289084468d06431db433ce5fbd031dfac Mon Sep 17 00:00:00 2001 From: Stephan Lee Date: Mon, 25 Feb 2019 10:15:49 -0800 Subject: [PATCH 459/766] Automated rollback of commit 31df1ce7dee077a5acaba2ddd43959665a8ae323 PiperOrigin-RevId: 235552900 --- tensorflow/python/keras/callbacks.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tensorflow/python/keras/callbacks.py b/tensorflow/python/keras/callbacks.py index 49c3971c19..bbbc53fce9 100644 --- a/tensorflow/python/keras/callbacks.py +++ b/tensorflow/python/keras/callbacks.py @@ -1222,8 +1222,6 @@ class TensorBoard(Callback): with self._train_writer.as_default(): with summary_ops_v2.always_record_summaries(): summary_ops_v2.graph(K.get_graph()) - if self.model._is_graph_network: # pylint: disable=protected-access - summary_ops_v2.keras_model('keras', self.model, step=0) def _close_writers(self): """Close all remaining open file writers owned by this callback. -- GitLab From e5e6c6d9b075ac738777a7fd376a59dba22e4b76 Mon Sep 17 00:00:00 2001 From: Akshay Modi Date: Mon, 25 Feb 2019 10:35:00 -0800 Subject: [PATCH 460/766] Place functions on ops where it has most resources. This is not the best heuristic (though not necessarily worse than the previous one - which just selected the first input), but attempts to make function placement agnostic to capture ordering. PiperOrigin-RevId: 235556736 --- .../core/common_runtime/eager/execute.cc | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tensorflow/core/common_runtime/eager/execute.cc b/tensorflow/core/common_runtime/eager/execute.cc index f2af6267bf..d6219aa8c7 100644 --- a/tensorflow/core/common_runtime/eager/execute.cc +++ b/tensorflow/core/common_runtime/eager/execute.cc @@ -775,6 +775,44 @@ bool IsPinnableOp(const string& op_type) { !absl::StartsWith(op_type, "XRT"); } +Status MaybeUpdateFunctionOpDevice(EagerOperation* op) { + gtl::FlatMap device_counts; + Device* op_device = + op->Device() == nullptr ? op->EagerContext()->HostCPU() : op->Device(); + for (int i = 0; i < op->Inputs().size(); ++i) { + TensorHandle* tensor_handle = op->Inputs()[i]; + if (tensor_handle->dtype == DT_RESOURCE) { + Device* resource_device = tensor_handle->resource_device(); + device_counts[resource_device]++; + VLOG(2) << "for op " << op->Name() << " input " << i << " " + << DataTypeString(tensor_handle->dtype) + << " input device = " << resource_device->name() + << ", op device = " << op_device->name(); + } + } + + Device* target_device = nullptr; + int target_device_count = 0; + + for (const auto& kv : device_counts) { + if (kv.second > target_device_count) { + target_device_count = kv.second; + target_device = kv.first; + } + } + + if (target_device != nullptr && + (target_device != op_device || op->Device() == nullptr)) { + VLOG(1) << (target_device != op_device ? "Changing " : "Setting ") + << "device of operation " << op->Name() << " to " + << target_device->name() << " because most inputs are resources on" + << " this device."; + op->SetDevice(target_device); + } + + return Status::OK(); +} + // The Op device may be updated if: // - A resource touching input is specified: all resource-touching ops run in // the device the resource is, regardless of anything else that has been @@ -784,6 +822,9 @@ bool IsPinnableOp(const string& op_type) { // (int32/int64). This can be disabled by setting the environment variable // "TF_EAGER_ENABLE_SMALL_TENSOR_CPU_PINNING" to "0" or "false". Status MaybeUpdateOpDevice(EagerOperation* op) { + if (op->is_function()) { + return MaybeUpdateFunctionOpDevice(op); + } EagerContext* ctx = op->EagerContext(); bool all_inputs_eligible_for_cpu_pinning = ctx->PinSmallOpsToCPU() && !op->is_function() && IsPinnableOp(op->Name()); -- GitLab From 72b6b348f91999379b158699410e782e23c90601 Mon Sep 17 00:00:00 2001 From: Eugene Zhulenev Date: Mon, 25 Feb 2019 10:48:25 -0800 Subject: [PATCH 461/766] [Grappler] Use control outputs in function inlining. PiperOrigin-RevId: 235559353 --- tensorflow/core/grappler/optimizers/BUILD | 1 + .../grappler/optimizers/function_optimizer.cc | 172 ++++++++++++------ .../optimizers/function_optimizer_test.cc | 40 ++-- tensorflow/python/eager/function_test.py | 19 ++ 4 files changed, 157 insertions(+), 75 deletions(-) diff --git a/tensorflow/core/grappler/optimizers/BUILD b/tensorflow/core/grappler/optimizers/BUILD index 670e0f04dc..7b52e7c288 100644 --- a/tensorflow/core/grappler/optimizers/BUILD +++ b/tensorflow/core/grappler/optimizers/BUILD @@ -154,6 +154,7 @@ cc_library( "//tensorflow/core/grappler:op_types", "//tensorflow/core/grappler:utils", "//tensorflow/core/grappler/utils:functions", + "//tensorflow/core/grappler/utils:topological_sort", "//tensorflow/core/grappler/utils:traversal", "@com_google_absl//absl/algorithm:container", "@com_google_absl//absl/container:flat_hash_map", diff --git a/tensorflow/core/grappler/optimizers/function_optimizer.cc b/tensorflow/core/grappler/optimizers/function_optimizer.cc index b722e6b332..cf5402a5c9 100644 --- a/tensorflow/core/grappler/optimizers/function_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/function_optimizer.cc @@ -47,6 +47,7 @@ limitations under the License. #include "tensorflow/core/grappler/op_types.h" #include "tensorflow/core/grappler/utils.h" #include "tensorflow/core/grappler/utils/functions.h" +#include "tensorflow/core/grappler/utils/topological_sort.h" #include "tensorflow/core/grappler/utils/traversal.h" #include "tensorflow/core/lib/gtl/map_util.h" @@ -74,9 +75,11 @@ constexpr char kNoInlineAttr[] = "_noinline"; // also used as a new destination for incoming control edges. constexpr char kInputsReadyNodeName[] = "inputs_ready"; -// Name of the node that will have control edges from function output nodes, and -// also used as a new source of outgoing control edges. -constexpr char kOutputsReadyNodeName[] = "outputs_ready"; +// Name of the node that will have control edges from function control output +// nodes, and also used as a new source of outgoing control edges. This node +// will guarantee that all side-effects inside function body will be executed +// after function inlining. +constexpr char kSideEffectsExecutedNodeName[] = "side_effects_executed"; bool AttrIsTrue(const FunctionDef& func, const string& attr) { return func.attr().count(attr) != 0 && func.attr().at(attr).b(); @@ -325,6 +328,12 @@ class FunctionOptimizerContext { }); } + bool IsKeepOp(const string& node_name) const { + return absl::c_any_of(item_->keep_ops, [&](const string& keep_node) { + return keep_node == node_name; + }); + } + bool IsTrulyConst(const string& name) const { return TrulyConstNode(name) != nullptr; } @@ -370,6 +379,9 @@ class FunctionOptimizerContext { void AddControlOverrides(const NodeDef& func_node, const std::vector& control_overrides) { + VLOG(4) << "Add control overrides: from=" << func_node.name() << " to: [" + << absl::StrJoin(control_overrides, ", ") << "]"; + control_overrides_[func_node.name()].reserve(control_overrides.size()); for (const string& control_override : control_overrides) { control_overrides_[func_node.name()].push_back(control_override); @@ -1396,12 +1408,12 @@ Status IsInlinableIndirectFunctionCall(const FunctionOptimizerContext& ctx, } // Checks that all side-effects will be executed in well defined order. We do it -// by checking if there is a path from stateful/dataset ops to one of the output -// nodes. +// by checking if there is a path from stateful/dataset ops to one of the +// control output nodes. Status CheckThatSideEffectsWillExecute( const FunctionOptimizerContext& ctx, const GraphTopologyView& graph_topo_view, - const absl::flat_hash_set output_nodes) { + const absl::flat_hash_set control_output_nodes) { // In aggressive mode we just print a warning for side-effectful nodes that // might not be executed after inlining. const bool aggressive = ctx.opt_level() == RewriterConfig::AGGRESSIVE; @@ -1424,8 +1436,8 @@ Status CheckThatSideEffectsWillExecute( // Check if we reached one of the output nodes. const auto callbacks = DfsCallbacks::PreOrder([&](const NodeDef* node) { - if (output_nodes.contains(node->name())) { - VLOG(4) << "Found a path to output node: " << node->name(); + if (control_output_nodes.contains(node->name())) { + VLOG(4) << "Found a path to control output node: " << node->name(); will_execute = true; } }); @@ -1610,7 +1622,7 @@ Status InlineIndirectFunctionCall(const NodeDef& func_node, VLOG(3) << "Happens after set (size = " << happens_after.size() << "): " << absl::StrJoin(happens_after, ", "); - // Regular (positional) inputs to the function call. + // Regular (data) inputs to the function call. std::vector inputs; for (const string& input : func_node.input()) { SafeTensorId tensor_id = ParseTensorName(input); @@ -1671,6 +1683,10 @@ Status InlineIndirectFunctionCall(const NodeDef& func_node, // to it from all input nodes. Inlined function arguments (Identity nodes) // will have a control dependency on it. // + // TODO(ezhulenev): We do not need to provide this guarantee for ALL nodes in + // the function body. We must only ensure that we do not generate observable + // side effects. + // // If the function call node has incoming control edges, we will update them // to use this node as destination, to ensure side-effects execution order. NodeDef* inputs_ready_node = nullptr; @@ -1680,17 +1696,29 @@ Status InlineIndirectFunctionCall(const NodeDef& func_node, inputs_ready_node->set_name(kInputsReadyNodeName); } - // All nodes that have control edge from the function call node, will be - // updated to have a control edge from 'outputs_ready_node`. This node will - // have control edges from all function outputs. This a "barrier" that - // guarantees that all function side effects were executed, and it will also - // allow to propagate deadness flag (if there is a deadness mismatch between - // output nodes). - NodeDef* outputs_ready_node = nullptr; - if (item.output_size() > 0 || !happens_after.empty()) { - outputs_ready_node = item.graph.add_node(); - outputs_ready_node->set_op("NoOp"); - outputs_ready_node->set_name(kOutputsReadyNodeName); + // All nodes that have a control edge from the function call node, will be + // updated to have a control edge from 'side_effects_executed_node`. This node + // will have control edges from all function control outputs (see + // `control_ret` in FunctionDef). This a "barrier" that guarantees that all + // ops with side effects in the function body were executed + // + // If the function call node has no outgoing control edges, it means that no + // one is interested in the function side-effect affecting captured resources. + // + // If node is in keep_ops set, it means that it must execute. This could + // happen if the graph is an instantiation of a function with control output. + NodeDef* side_effects_executed_node = nullptr; + if (!happens_after.empty() || ctx->IsKeepOp(func_node.name())) { + side_effects_executed_node = item.graph.add_node(); + side_effects_executed_node->set_op("NoOp"); + side_effects_executed_node->set_name(kSideEffectsExecutedNodeName); + } + + // If function executed only for the regular data outputs, it's totally safe + // to prune side-effects. If side-effects order is important, it must be + // captured at graph construction time via control edges. + if (item.control_output_size() > 0 && happens_after.empty()) { + VLOG(2) << "Function has control outputs and empty happens after set."; } // ------------------------------------------------------------------------ // @@ -1760,9 +1788,12 @@ Status InlineIndirectFunctionCall(const NodeDef& func_node, // function call has no regular and control inputs, we will not add // inputs_ready node to the function body graph. node_name != kInputsReadyNodeName && - // Outputs ready node might not have any inputs (in case function has - // no outputs), so we must make sure it's executed in correct frame. - (node_name != kOutputsReadyNodeName || item.output_size() == 0); + // The node acting as a return barrier for execution of side effects + // might not have any inputs (in case function has no control outputs, + // but we still added it because of non-empty happens-after set), so + // we must make sure it's executed in correct frame. + (node_name != kSideEffectsExecutedNodeName || + item.control_output_size() == 0); if (node_needs_empty_inputs_hook) { *func_body_node.add_input() = @@ -1799,12 +1830,19 @@ Status InlineIndirectFunctionCall(const NodeDef& func_node, return inlined_output_nodes.find(node.name()) != inlined_output_nodes.end(); }; + // Names of the inlined control output nodes. + absl::flat_hash_set inlined_control_output_nodes; + for (const ControlOutput& control_output : item.control_outputs()) { + inlined_control_output_nodes.insert( + inlined_node_name(control_output.node_name)); + } + // Construct a graph topology view for DFS traversals (skip invalid edges for // input nodes connected to nodes in the optimized graph). GraphTopologyView placed_topo_view(/*skip_invalid_edges=*/true); TF_RETURN_IF_ERROR(placed_topo_view.InitializeFromGraph(placed_graph_def)); - TF_RETURN_IF_ERROR(CheckThatSideEffectsWillExecute(*ctx, placed_topo_view, - inlined_output_nodes)); + TF_RETURN_IF_ERROR(CheckThatSideEffectsWillExecute( + *ctx, placed_topo_view, inlined_control_output_nodes)); // ------------------------------------------------------------------------ // // Move all the nodes to the optimized graph after successful preprocessing. @@ -1815,7 +1853,15 @@ Status InlineIndirectFunctionCall(const NodeDef& func_node, absl::flat_hash_set input_nodes; for (const string& input : func_node.input()) { - const SafeTensorId tensor = ParseTensorName(input); + SafeTensorId tensor = ParseTensorName(input); + + // Input node might have been a function call that was already inlined. + auto it = ctx->tensor_mapping().find(tensor); + while (it != ctx->tensor_mapping().end()) { + tensor = it->second; + it = ctx->tensor_mapping().find(tensor); + } + if (input_nodes.insert(tensor.node()).second) { placed_graph_def.mutable_node(*node_idx)->add_input( AsControlDependency(tensor.node())); @@ -1823,14 +1869,14 @@ Status InlineIndirectFunctionCall(const NodeDef& func_node, } } - if (outputs_ready_node != nullptr) { - string inlined_node = inlined_node_name(outputs_ready_node->name()); + if (side_effects_executed_node != nullptr) { + string inlined_node = inlined_node_name(side_effects_executed_node->name()); absl::optional node_idx = placed_topo_view.GetNodeIndex(inlined_node); - // Add control edges from all nodes producing output tensors. - for (const string& node_name : output_tensors_nodes) { + // Add control edges from all control output nodes. + for (const string& node_name : inlined_control_output_nodes) { placed_graph_def.mutable_node(*node_idx)->add_input( - AsControlDependency(inlined_node_name(node_name))); + AsControlDependency(node_name)); } // Forward all control dependencies in the optimized graph to the new node. @@ -1864,6 +1910,21 @@ Status InlineIndirectFunctionCall(const NodeDef& func_node, } } + // If function call node was in keep_ops set, it means that we need to keep a + // node with the same name in the optimized graph. We forward all data + // consumers to inlined nodes, and we verify that the node is not in a fetch + // set, so it's safe to assume that the function call node is only required + // for a control edge source. + if (ctx->IsKeepOp(func_node.name())) { + VLOG(4) << "Add NoOp for inlined function in keep ops set."; + NodeDef* keep_func_node = optimized_graph->add_node(); + keep_func_node->set_op("NoOp"); + keep_func_node->set_name(func_node.name()); + keep_func_node->set_device(func_node.device()); + keep_func_node->add_input( + AsControlDependency(inlined_node_name(kSideEffectsExecutedNodeName))); + } + VLOG(3) << "Successfully inlined indirect function call: " << SummarizeNodeDef(func_node); @@ -1959,7 +2020,12 @@ Status FunctionOptimizer::RunFunctionOptimizerPass( bool inline_func = options_.enable_function_inlining; bool specialize_func = options_.enable_function_specialization; - for (const NodeDef& node : graph.node()) { + // We will process all the nodes in topological order, to correctly handle + // inlining of function call chains. + std::vector topo_ordered_nodes; + TF_RETURN_IF_ERROR(ComputeTopologicalOrder(graph, &topo_ordered_nodes)); + + for (const NodeDef* node : topo_ordered_nodes) { // Each node optimization can modify optimized graph only by adding new // nodes, we can check node size to make sure that graph was not modified. const int num_nodes_before = optimized_graph->node_size(); @@ -1970,12 +2036,12 @@ Status FunctionOptimizer::RunFunctionOptimizerPass( }; // Copy node from the `graph` to the `optimized_graph`. - const auto copy_node = [&]() { *optimized_graph->add_node() = node; }; + const auto copy_node = [&]() { *optimized_graph->add_node() = *node; }; // If we already failed to optimize this node during one of the previous // passes, we just give up, and do not try on more time. - if (skip_nodes->find(node.name()) != skip_nodes->end()) { - VLOG(3) << "Skip optimization for node: " << node.name(); + if (skip_nodes->find(node->name()) != skip_nodes->end()) { + VLOG(3) << "Skip optimization for node: " << node->name(); copy_node(); continue; } @@ -1988,7 +2054,7 @@ Status FunctionOptimizer::RunFunctionOptimizerPass( return _status; \ if (TF_PREDICT_FALSE(!_status.ok() && !is_graph_modified())) { \ VLOG(3) << "Skip error: " << _status.error_message(); \ - skip_nodes->insert(node.name()); \ + skip_nodes->insert(node->name()); \ copy_node(); \ } \ } while (0) @@ -1997,19 +2063,19 @@ Status FunctionOptimizer::RunFunctionOptimizerPass( // 1. Inline symbolic gradients into the optimized graph. // // ---------------------------------------------------------------------- // - if (IsSymbolicGradient(node) && inline_gradients) { + if (IsSymbolicGradient(*node) && inline_gradients) { // Inline symbolic gradients only if the corresponding function is not // marked as `_noinline`. - const auto* f_attr = gtl::FindOrNull(node.attr(), "f"); + const auto* f_attr = gtl::FindOrNull(node->attr(), "f"); const string f_name = f_attr != nullptr ? f_attr->func().name() : ""; const FunctionDef* func = ctx.function_library().Find(f_name); if (func && !MarkedNoInline(*func)) { TF_SKIP_ERROR_IF_GRAPH_UNMODIFIED( - InlineSymbolicGradient(node, &ctx, optimized_graph)); + InlineSymbolicGradient(*node, &ctx, optimized_graph)); continue; } else { VLOG(2) << "Skip SymbolicGradient inlining: function=" << f_name; - skip_nodes->insert(node.name()); + skip_nodes->insert(node->name()); } } @@ -2018,45 +2084,45 @@ Status FunctionOptimizer::RunFunctionOptimizerPass( // ---------------------------------------------------------------------- // // Find if a node is a function call (direct or indirect). - const FunctionDef* func = FindFunctionCall(ctx, node); + const FunctionDef* func = FindFunctionCall(ctx, *node); if (func != nullptr) { const string& func_name = func->signature().name(); - const bool is_direct_func = IsDirectFunctionCall(*func, node); - const bool is_indirect_func = IsIndirectFunctionCall(*func, node); + const bool is_direct_func = IsDirectFunctionCall(*func, *node); + const bool is_indirect_func = IsIndirectFunctionCall(*func, *node); // 2a. Inline direct function call if it's inlinable. if (inline_func && is_direct_func) { - Status inlinable = IsInlinableDirectFunctionCall(ctx, *func, node); + Status inlinable = IsInlinableDirectFunctionCall(ctx, *func, *node); if (inlinable.ok()) { TF_SKIP_ERROR_IF_GRAPH_UNMODIFIED( - InlineDirectFunctionCall(node, *func, ctx, optimized_graph)); + InlineDirectFunctionCall(*node, *func, ctx, optimized_graph)); continue; } else { VLOG(2) << inlinable.error_message(); - skip_nodes->insert(node.name()); + skip_nodes->insert(node->name()); } } // 2b. Inline indirect function call if it's inlinable. if (inline_func && is_indirect_func) { - Status inlinable = IsInlinableIndirectFunctionCall(ctx, *func, node); + Status inlinable = IsInlinableIndirectFunctionCall(ctx, *func, *node); if (inlinable.ok()) { TF_SKIP_ERROR_IF_GRAPH_UNMODIFIED( - InlineIndirectFunctionCall(node, *func, &ctx, optimized_graph)); + InlineIndirectFunctionCall(*node, *func, &ctx, optimized_graph)); continue; } else { VLOG(2) << inlinable.error_message(); - skip_nodes->insert(node.name()); + skip_nodes->insert(node->name()); } } // 2c. Specialize it to its instantiation context if can't be inlined, // and it has something worth specializing. bool specialization_worthy = IsParametrized(*func) || - HasTrulyConstInputs(node, ctx) || - HasUnusedOutputs(node, *func, ctx); + HasTrulyConstInputs(*node, ctx) || + HasUnusedOutputs(*node, *func, ctx); // Do not specialize if function has custom gradient. const string grad_func = ctx.function_library().FindGradient(func_name); @@ -2065,11 +2131,11 @@ Status FunctionOptimizer::RunFunctionOptimizerPass( // TODO(ezhulenev): Specialize function call if input has a known shape. // Specialize function body for its instantiation attributes and inputs. TF_SKIP_ERROR_IF_GRAPH_UNMODIFIED( - SpecializeFunction(node, *func, &ctx, optimized_graph)); + SpecializeFunction(*node, *func, &ctx, optimized_graph)); continue; } else { VLOG(2) << "Skip function specialization: " << func->signature().name(); - skip_nodes->insert(node.name()); + skip_nodes->insert(node->name()); } } diff --git a/tensorflow/core/grappler/optimizers/function_optimizer_test.cc b/tensorflow/core/grappler/optimizers/function_optimizer_test.cc index 2787d9d525..8ea49dc3dd 100644 --- a/tensorflow/core/grappler/optimizers/function_optimizer_test.cc +++ b/tensorflow/core/grappler/optimizers/function_optimizer_test.cc @@ -742,7 +742,6 @@ TEST_F(FunctionOptimizerTest, InlineIndirectFunctionSimpleFunction) { NDef("c/y", "Identity", {"b:0", "^c/inputs_ready"}, {{"T", DT_FLOAT}}, kDevice), NDef("c/mul", "Mul", {"c/x", "c/y"}, {{"T", DT_FLOAT}}, kDevice), - NDef("c/outputs_ready", "NoOp", {"^c/mul"}, {}, kDevice), NDef("d", "Identity", {"c/mul:0"}, {{"T", DT_FLOAT}}, kDevice)}, // Function library. @@ -766,7 +765,7 @@ TEST_F(FunctionOptimizerTest, InlineIndirectFunctionWithControlDependencies) { using test::function::NDef; using FDH = FunctionDefHelper; - FunctionOptimizer optimizer(RewriterConfig::AGGRESSIVE); + FunctionOptimizer optimizer(RewriterConfig::ON); const Tensor kOne = test::AsScalar(1.0); const Tensor kTwo = test::AsScalar(2.0); @@ -780,9 +779,11 @@ TEST_F(FunctionOptimizerTest, InlineIndirectFunctionWithControlDependencies) { "AssignAddVariableOp", {"v", "one:output:0"}, {{"dtype", DT_FLOAT}}}, - {{"mul"}, "Mul", {"x", "y", "^add"}, {{"T", "$T"}}}}, + {{"mul"}, "Mul", {"x", "y"}, {{"T", "$T"}}}}, /* Mapping between function returns and function node outputs. */ - {{"z", "mul:z:0"}}); + {{"z", "mul:z:0"}}, + /* Control output to ensure that side effects will be executed. */ + {{"size_effects", "add"}}); // Build a graph to compute: // a = Placeholder @@ -851,16 +852,15 @@ TEST_F(FunctionOptimizerTest, InlineIndirectFunctionWithControlDependencies) { {{"dtype", DT_FLOAT}, {"value", kOne}}, kDevice), NDef("f1/add", "AssignAddVariableOp", {"f1/v", "f1/one"}, {{"dtype", DT_FLOAT}}, kDevice), - NDef("f1/mul", "Mul", {"f1/x", "f1/y", "^f1/add"}, {{"T", DT_FLOAT}}, - kDevice), + NDef("f1/mul", "Mul", {"f1/x", "f1/y"}, {{"T", DT_FLOAT}}, kDevice), - NDef("f1/outputs_ready", "NoOp", {"^f1/mul"}, {}, kDevice), + NDef("f1/side_effects_executed", "NoOp", {"^f1/add"}, {}, kDevice), // Function body of a second function call also inlined into the graph, // and input nodes read directly from the inlined nodes of the first // function call. - NDef("f2/inputs_ready", "NoOp", {"^v", "^f1/outputs_ready"}, {}, - kDevice), + NDef("f2/inputs_ready", "NoOp", + {"^v", "^f1/mul", "^f1/side_effects_executed"}, {}, kDevice), NDef("f2/x", "Identity", {"f1/mul:0", "^f2/inputs_ready"}, {{"T", DT_FLOAT}}, kDevice), @@ -873,15 +873,14 @@ TEST_F(FunctionOptimizerTest, InlineIndirectFunctionWithControlDependencies) { {{"dtype", DT_FLOAT}, {"value", kOne}}, kDevice), NDef("f2/add", "AssignAddVariableOp", {"f2/v", "f2/one"}, {{"dtype", DT_FLOAT}}, kDevice), - NDef("f2/mul", "Mul", {"f2/x", "f2/y", "^f2/add"}, {{"T", DT_FLOAT}}, - kDevice), + NDef("f2/mul", "Mul", {"f2/x", "f2/y"}, {{"T", DT_FLOAT}}, kDevice), - NDef("f2/outputs_ready", "NoOp", {"^f2/mul"}, {}, kDevice), + NDef("f2/side_effects_executed", "NoOp", {"^f2/add"}, {}, kDevice), // Return values read directly from inlined nodes. NDef("out_1", "Identity", {"f2/mul:0"}, {{"T", DT_FLOAT}}, kDevice), NDef("out_2", "ReadVariableOp", - {"v", "^f1/outputs_ready", "^f2/outputs_ready"}, + {"v", "^f1/side_effects_executed", "^f2/side_effects_executed"}, {{"dtype", DT_FLOAT}}, kDevice)}, // Function library. @@ -953,7 +952,6 @@ TEST_F(FunctionOptimizerTest, InlineIndirectFunctionWithDevicePlacement) { NDef("c/y", "Identity", {"b:0", "^c/inputs_ready"}, {{"T", DT_FLOAT}}, cpu1), NDef("c/mul", "Mul", {"c/x", "c/y"}, {{"T", DT_FLOAT}}, cpu1), - NDef("c/outputs_ready", "NoOp", {"^c/mul"}, {}, cpu0), NDef("d", "Identity", {"c/mul:0"}, {{"T", DT_FLOAT}}, cpu0)}, // Function library. @@ -1027,18 +1025,21 @@ TEST_F(FunctionOptimizerTest, NDef("f1/y", "Identity", {"b:0", "^f1/inputs_ready"}, {{"T", DT_FLOAT}}, kDevice), NDef("f1/mul", "Mul", {"f1/x", "f1/y"}, {{"T", DT_FLOAT}}, kDevice), - NDef("f1/outputs_ready", "NoOp", {"^f1/mul"}, {}, kDevice), + // Control input from `inputs_ready` node is added to ensure correct + // frame execution. + NDef("f1/side_effects_executed", "NoOp", {"^f1/inputs_ready"}, {}, + kDevice), // Function body of a second function call also inlined into the graph, // and input nodes read directly from the inlined nodes of the first // function call, and control dependency edge removed. - NDef("f2/inputs_ready", "NoOp", {"^f1/outputs_ready"}, {}, kDevice), + NDef("f2/inputs_ready", "NoOp", {"^f1/mul", "^f1/side_effects_executed"}, + {}, kDevice), NDef("f2/x", "Identity", {"f1/mul:0", "^f2/inputs_ready"}, {{"T", DT_FLOAT}}, kDevice), NDef("f2/y", "Identity", {"f1/mul:0", "^f2/inputs_ready"}, {{"T", DT_FLOAT}}, kDevice), NDef("f2/mul", "Mul", {"f2/x", "f2/y"}, {{"T", DT_FLOAT}}, kDevice), - NDef("f2/outputs_ready", "NoOp", {"^f2/mul"}, {}, kDevice), // Return directly from inlined node of f2. NDef("out", "Identity", {"f2/mul:0"}, {{"T", DT_FLOAT}}, kDevice)}, @@ -1196,7 +1197,6 @@ TEST_F(FunctionOptimizerTest, InlineIndirectFunctionWithMergedDeadTensors) { kDevice), NDef("fn/merge", "Merge", {"fn/if_false:0", "fn/if_true:0"}, {{"T", DT_FLOAT}, {"N", 2}}, kDevice), - NDef("fn/outputs_ready", "NoOp", {"^fn/merge"}, {}, kDevice), // Return directly from inlined node. NDef("out", "Identity", {"fn/merge:0"}, {{"T", DT_FLOAT}}, kDevice)}, @@ -1283,10 +1283,6 @@ TEST_F(FunctionOptimizerTest, InlineIndirectFunctionWithNestedFunctionCall) { NDef("b/square/mul", "Mul", {"b/square/x", "b/square/y"}, {{"T", DT_FLOAT}}, kDevice), - NDef("b/square/outputs_ready", "NoOp", {"^b/square/mul"}, {}, kDevice), - NDef("b/outputs_ready", "NoOp", {"^b/square/outputs_ready"}, {}, - kDevice), - NDef("c", "Identity", {"b/square/mul:0"}, {{"T", DT_FLOAT}}, kDevice)}, // Function library. {mul_func}); diff --git a/tensorflow/python/eager/function_test.py b/tensorflow/python/eager/function_test.py index 7a17eb6ee2..270e420fa8 100644 --- a/tensorflow/python/eager/function_test.py +++ b/tensorflow/python/eager/function_test.py @@ -1831,6 +1831,25 @@ class FunctionTest(test.TestCase, parameterized.TestCase): side_effecting_function.python_function() self.assertAllEqual(state, [0, 0]) + def testFunctionWithNestedFunctionCallAndSideEffects(self): + v1 = variables.Variable(1.0) + v2 = variables.Variable(1.0) + + @def_function.function + def add_one(a): + a.assign_add(1.0) + + # Grappler will inline calls to `add_one` into the function body, we check + # that all side-effects were executed. + @def_function.function + def side_effecting_function(a, b): + add_one(a) + add_one(b) + return a + b + + result = side_effecting_function(v1, v2) + self.assertEqual(result.numpy(), 4.0) + def testFunctionWithExtraAttributes(self): @function.defun_with_attributes(attributes={'experimental_1': 'value1', 'experimental_2': 2}) -- GitLab From e89959206b1ca58b6f0921c9375d4cd6704055f8 Mon Sep 17 00:00:00 2001 From: Peter Hawkins Date: Mon, 25 Feb 2019 10:55:17 -0800 Subject: [PATCH 462/766] [XLA:Python] Remove use of protocol buffers in XLA Python client. The XLA Python extension is packaged separately as "jaxlib", but XLA itself is part of TensorFlow. Some of the same basic protocol buffers are used by both (e.g., xla_data.proto), leading to a conflict if a proto is imported twice into the same Python interpreter via different routes (e.g., https://github.com/google/jax/issues/349), since a single global C++ protocol buffer registry exists for the entire interpreter. The simplest solution, short of a significant refactoring of the TensorFlow->XLA's dependency structure, seems to be to change xla_client.py not to depend on any XLA protocol buffers. A few other possible alternatives are discussed in https://github.com/google/jax/issues/349. Fortunately, we don't use protocol buffers in any essential ways in the XLA client, mostly for objects such as convolution dimension numbers. Instead, create Python objects that play the same role and that duck type as protocol buffers well enough to keep the SWIG bindings happy. Remove a couple an unused function OpMetadataToProto. Change Computation.GetProto() to Computation.GetSerializedProto(). In passing, remove duplicated comment between xla_data.i and local_computation_builder.i. PiperOrigin-RevId: 235560841 --- .../xla/python/local_computation_builder.i | 86 ---- tensorflow/compiler/xla/python/xla_client.py | 376 +++++++++++------- .../compiler/xla/python/xla_client_test.py | 52 +-- tensorflow/compiler/xla/python/xla_data.i | 16 +- 4 files changed, 274 insertions(+), 256 deletions(-) diff --git a/tensorflow/compiler/xla/python/local_computation_builder.i b/tensorflow/compiler/xla/python/local_computation_builder.i index 2f60387e7b..9fcb4822c7 100644 --- a/tensorflow/compiler/xla/python/local_computation_builder.i +++ b/tensorflow/compiler/xla/python/local_computation_builder.i @@ -16,92 +16,6 @@ limitations under the License. // SWIG typemaps and declarations for building, compiling, and // executing XLA computations, wrapping most of what is declared in // local_computation_builder.h. -// -// The typemaps below implement/assert the following correspondences -// (with elaborations below): -// -// C++ Python -// -------------------------------------+--------------------------------------- -// Span <- sequence of int -// vector -> sequence of int -// Span <- sequence of LocalOp -// Literal <-> (nested tuple of) numpy ndarray -// std::vector <- sequence of (nested tuple of) ndarray -// Shape -> pair holding (dtype, dimensions) -// <- object duck-typed as xla_client.Shape -// ProgramShape -> pair of ([arg_shapes], ret_shape) -// std::vector <- sequence of xla_client.Shape objects -// PrimitiveType <- int -// Span> <- sequence of int pairs -// PaddingConfig proto <- corresponding Python proto -// ConvolutionDimensionNumbers proto <- corresponding Python proto -// DotDimensionNumbers proto <- corresponding Python proto -// GatherDimensionNumbers proto <- corresponding Python proto -// ScatterDimensionNumbers proto <- corresponding Python proto -// Span <- sequence of ReplicaGroup Python proto -// -// Arrows indicate whether a conversion only ever occurs in one -// direction, or whether it is maintained bidirectionally. -// -// The Python objects corresponding to C++ Literals have the type: -// -// T = ndarray | (T, ...) -// -// where a terminal numpy ndarray translates to a Literal with a -// non-tuple Shape, an XLA primitive element type corresponding to the -// ndarray's dtype. Meanwhile, a non-terminal "tuple of T" translates -// to a tuple-shaped Literal whose tuple components are translated -// recursively. For example, if x is a numpy ndarray in Python, with -// shape (2, 3) and dtype of dtype('float32'), then x translates to a -// Literal with rank 2, dimension 2 and 3, and XLA primitive type -// F32. Meanwhile, -// -// (x, (x, x), (x,)), -// -// translates to a tuple-shaped XLA Literal, whose component subshapes -// are a 2x3 F32-shaped literal followed by two tuple-shaped literals. -// -// Shapes output by C++ become Python objects with the type: -// -// T = (dtype, S) -// S = DIMENSIONS | TUPLE_SHAPES -// DIMENSIONS = (int, ...) -// TUPLE_SHAPES = (T, ...) -// -// In the pair described by the T rule, the terminal dtype determines -// whether S expands as DIMENSIONS or TUPLE_SHAPES. Namely if it is -// dtype('O'), numpy's object dtype, the structure represents a tuple -// shape and the expansion of the non-terminal S is -// TUPLE_SHAPES. Otherwise, dtype describes a primitive element type -// and S expands into DIMENSIONS giving dimension sizes. For example: -// -// (dtype('float32'), (3, 5, 7)) -// -// describes a 3x5x7 array of F32s, and -// -// (dtype('O'), ((dtype('float32'), (2, 3)), -// (dtype('float64'), (4, 5)))) -// -// describes a tuple shape with two subshapes: the first a 2x3 F32, -// and the other a 4x5 F64. -// -// The Python int corresponding to a PrimitiveType enum must be valid -// per xla_data.proto (e.g. xla_data.PRED, xla_data.F32). -// -// The SWIG object wrappers generated by this file are not intended -// for end use, but rather for internal use in the Python XLA client, -// xla_client.py. -// -// One central reason for the Python-side indirection is that the -// Python-side objects produced by the typemaps in this file are -// further packaged up by xla_client before being passed on. For -// instance, the Python pair produced for a C++ Shape is further -// wrapped in a Python class (xla_client.Shape) so as not to expose -// the raw pair externally. -// -// Other SWIG object wrappers (e.g. of Computation) are further -// wrapped by xla_client in order to set up a custom destructor that -// triggers memory deallocation on the C++ side. %module(threads="1") local_computation_builder diff --git a/tensorflow/compiler/xla/python/xla_client.py b/tensorflow/compiler/xla/python/xla_client.py index 5ae7fddbc6..ccea134894 100644 --- a/tensorflow/compiler/xla/python/xla_client.py +++ b/tensorflow/compiler/xla/python/xla_client.py @@ -30,9 +30,12 @@ import numpy as np import six from six.moves import xrange -from tensorflow.compiler.xla import xla_data_pb2 +# Note this module does *not* depend on any Python protocol buffers. The XLA +# Python bindings are currently packaged both as part of jaxlib and as part +# of TensorFlow. If we use protocol buffers here, then importing both jaxlib +# and TensorFlow may fail with duplicate protocol buffer message definitions. + from tensorflow.compiler.xla.python import pywrap_xla as c_api -from tensorflow.compiler.xla.service import hlo_pb2 # Import the XRT backend, if available. try: @@ -228,15 +231,6 @@ def BackendSpec(backend, target): raise ValueError('Unknown backend {}'.format(backend)) -def OpMetadataToProto(pyobj): - proto = xla_data_pb2.OpMetadata() - for field in _OP_METADATA_FIELDS: - attr = getattr(pyobj, field) - if attr is not None: - setattr(proto, field, attr) - return proto - - def CurrentSourceInfoMetadata(op_type=None, op_name=None, skip_frames=1): """Helper for use in source mapping that returns an OpMetadata object.""" full_filename, lineno = inspect.stack()[skip_frames][1:3] @@ -276,8 +270,7 @@ def _convert_padding_type_to_pad_values(padding_type, lhs_dims, rhs_dims, pad_sizes = [max((out_size - 1) * stride + filter_size - in_size, 0) for out_size, stride, filter_size, in_size in zip(out_shape, window_strides, rhs_dims, lhs_dims)] - return [(pad_size // 2, pad_size - pad_size // 2) - for pad_size in pad_sizes] + return [(pad_size // 2, pad_size - pad_size // 2) for pad_size in pad_sizes] else: msg = 'Unexpected PaddingType value: {}' raise ValueError(msg.format(padding_type)) @@ -349,29 +342,56 @@ _BINARY_OPS = [ ] +class PrimitiveType(enum.IntEnum): + """Python copy of the XLA PrimitiveType enum. + + Must match the corresponding protocol buffer. + """ + PRIMITIVE_TYPE_INVALID = 0 + PRED = 1 + S8 = 2 + S16 = 3 + S32 = 4 + S64 = 5 + U8 = 6 + U16 = 7 + U32 = 8 + U64 = 9 + BF16 = 16 + F16 = 10 + F32 = 11 + F64 = 12 + C64 = 15 + C128 = 18 + TUPLE = 13 + OPAQUE = 14 + TOKEN = 17 + + XLA_ELEMENT_TYPE_TO_DTYPE = { - xla_data_pb2.PRED: np.dtype('bool'), - xla_data_pb2.S8: np.dtype('int8'), - xla_data_pb2.S16: np.dtype('int16'), - xla_data_pb2.S32: np.dtype('int32'), - xla_data_pb2.S64: np.dtype('int64'), - xla_data_pb2.U8: np.dtype('uint8'), - xla_data_pb2.U16: np.dtype('uint16'), - xla_data_pb2.U32: np.dtype('uint32'), - xla_data_pb2.U64: np.dtype('uint64'), - xla_data_pb2.F16: np.dtype('float16'), - xla_data_pb2.F32: np.dtype('float32'), - xla_data_pb2.F64: np.dtype('float64'), - xla_data_pb2.C64: np.dtype('complex64'), - xla_data_pb2.C128: np.dtype('complex128'), - xla_data_pb2.TUPLE: np.dtype(np.object), + PrimitiveType.PRED: np.dtype('bool'), + PrimitiveType.S8: np.dtype('int8'), + PrimitiveType.S16: np.dtype('int16'), + PrimitiveType.S32: np.dtype('int32'), + PrimitiveType.S64: np.dtype('int64'), + PrimitiveType.U8: np.dtype('uint8'), + PrimitiveType.U16: np.dtype('uint16'), + PrimitiveType.U32: np.dtype('uint32'), + PrimitiveType.U64: np.dtype('uint64'), + PrimitiveType.F16: np.dtype('float16'), + PrimitiveType.F32: np.dtype('float32'), + PrimitiveType.F64: np.dtype('float64'), + PrimitiveType.C64: np.dtype('complex64'), + PrimitiveType.C128: np.dtype('complex128'), + PrimitiveType.TUPLE: np.dtype(np.object), } # Note the conversion on the key. Numpy has a known issue wherein dtype hashing # doesn't work as expected (https://github.com/numpy/numpy/issues/7242). Thus, # when keying by dtype in this dict, we use the string form of dtypes. -DTYPE_TO_XLA_ELEMENT_TYPE = {str(dt): et - for et, dt in XLA_ELEMENT_TYPE_TO_DTYPE.items()} +DTYPE_TO_XLA_ELEMENT_TYPE = { + str(dt): et for et, dt in XLA_ELEMENT_TYPE_TO_DTYPE.items() +} def dtype_to_etype(dtype): @@ -430,6 +450,13 @@ class LocalBuffer(object): self.delete() +class Format(enum.IntEnum): + """Python copy of the Format protocol buffer enum.""" + INVALID_FORMAT = 0 + DENSE = 1 + SPARSE = 2 + + class Shape(object): """Represents an XLA shape. @@ -459,8 +486,8 @@ class Shape(object): if (not isinstance(dimensions, tuple) or not all(isinstance(i, int) for i in dimensions)): dimensions = tuple(int(i) for i in dimensions) - return Shape(dimensions, np.dtype(element_type), - minor_to_major=minor_to_major) + return Shape( + dimensions, np.dtype(element_type), minor_to_major=minor_to_major) @staticmethod def from_pyval(pyval): @@ -539,8 +566,8 @@ class Shape(object): """Map f over each leaf-level array subshape. Args: - f: The function to apply. Whenever f returns None, the identity is - applied instead. + f: The function to apply. Whenever f returns None, the identity is applied + instead. Returns: A new Shape with the mapped leaves. @@ -565,8 +592,8 @@ class Shape(object): raise ValueError('not an array shape') if not isinstance(minor_to_major, tuple): raise TypeError('minor_to_major must be a tuple') - updated = Shape.array_shape( - self.element_type(), self.dimensions(), minor_to_major) + updated = Shape.array_shape(self.element_type(), self.dimensions(), + minor_to_major) updated._check_minor_to_major() # pylint: disable=protected-access return updated @@ -583,7 +610,7 @@ class Shape(object): def serialize(self, proto): """Serializes 'shape' into proto.""" if self.is_tuple(): - proto.element_type = xla_data_pb2.TUPLE + proto.element_type = PrimitiveType.TUPLE for shape in self.tuple_shapes(): shape.serialize(proto.tuple_shapes.add()) else: @@ -591,7 +618,7 @@ class Shape(object): proto.dimensions.extend(self.dimensions()) proto.is_dynamic_dimension.extend([False for _ in self.dimensions()]) if self.minor_to_major(): - proto.layout.format = xla_data_pb2.DENSE + proto.layout.format = Format.DENSE proto.layout.minor_to_major.extend(self.minor_to_major()) @@ -602,7 +629,7 @@ ProgramShape = collections.namedtuple('ProgramShape', def _wrap_shape(shape_info): dtype, dims = shape_info element_type = DTYPE_TO_XLA_ELEMENT_TYPE[str(dtype)] - if element_type == xla_data_pb2.TUPLE: + if element_type == PrimitiveType.TUPLE: shapes = tuple(_wrap_shape(subshape_info) for subshape_info in dims) return Shape.tuple_shape(shapes) else: @@ -690,15 +717,14 @@ class Computation(object): def computation(self): return self._c_computation - def GetProto(self): - """Get the HloModuleProto proto object in this computation. + def GetSerializedProto(self): + """Gets the serialized HloModuleProto proto object in this computation. Returns: - An HloModuleProto proto object that has the whole-graph information. + A string containing a serialized HloModuleProto proto containing the + computation and its dependencies. """ - serialized = self.computation.GetSerializedProto() - proto = hlo_pb2.HloModuleProto.FromString(serialized) - return proto + return self.computation.GetSerializedProto() def GetHloText(self): """Get the textual HLO representation of this computation. @@ -867,12 +893,6 @@ class Executable(object): self._backend.delete_executable(self._c_executable) -def _make_replica_group_proto(replica_group): - replica_group_proto = xla_data_pb2.ReplicaGroup() - replica_group_proto.replica_ids.extend(replica_group) - return replica_group_proto - - class ComputationBuilder(object): """XLA computation builder. @@ -899,6 +919,7 @@ class ComputationBuilder(object): root: if not None, the operator containing the return value of the computation. backend: deprecated. Pass a `backend` to `Computation.Compile` instead. + Returns: A `Computation`. """ @@ -938,8 +959,8 @@ class ComputationBuilder(object): """Enqueues a constant op onto the computation. Args: - value: value for the constant, as a np.array with an explicit dtype set - to one of the supported types. + value: value for the constant, as a np.array with an explicit dtype set to + one of the supported types. Returns: A LocalOp. @@ -1008,9 +1029,9 @@ class ComputationBuilder(object): Args: shape: the parameter's shape as a Shape object. name: optional string name for the parameter. - parameter_num: parameter number in the computation function. If None, - the next linear parameter number is used. The default value capability - can be used for auto-numbering. If you're using auto-numbering for some + parameter_num: parameter number in the computation function. If None, the + next linear parameter number is used. The default value capability can + be used for auto-numbering. If you're using auto-numbering for some parameters, use it for *all* parameters to avoid clashes. Returns: @@ -1027,8 +1048,8 @@ class ComputationBuilder(object): """Enqueues a Parameter op onto the computation. Args: - value: a Numpy array, or a nested tuple thereof, from which the - shape is inferred. + value: a Numpy array, or a nested tuple thereof, from which the shape is + inferred. name: as in ParameterWithShape. parameter_num: as in ParameterWithShape. @@ -1083,8 +1104,8 @@ class ComputationBuilder(object): Args: operand: the operand LocalOp to broadcast. shape: tuple of integers, the expected output shape. - broadcast_dimensions: tuple of integers identifying which dimensions - of the output are to be broadcast into. + broadcast_dimensions: tuple of integers identifying which dimensions of + the output are to be broadcast into. Returns: A LocalOp representing the added broadcast-in-dimensions op. @@ -1150,14 +1171,14 @@ class ComputationBuilder(object): Args: operand: LocalOp representing the array to pad. padding_value: LocalOp representing the scalar pad value. - padding_config: either an xla_data_pb2.PaddingConfig or a list of integer - triples (edge_padding_low, edge_padding_high, interior_padding) - representing the configuration of the padding operation. + padding_config: either a PaddingConfig or a list of integer triples + (edge_padding_low, edge_padding_high, interior_padding) representing the + configuration of the padding operation. Returns: A LocalOp representing the added Pad op. """ - if not isinstance(padding_config, xla_data_pb2.PaddingConfig): + if isinstance(padding_config, tuple) or isinstance(padding_config, list): padding_config = GetPaddingConfigFromTriples(padding_config) return self._client.Pad(operand, padding_value, padding_config) @@ -1203,7 +1224,8 @@ class ComputationBuilder(object): else: replica_groups = list(replica_groups) replica_groups_protos = [ - _make_replica_group_proto(group) for group in replica_groups] + _make_replica_group_proto(group) for group in replica_groups + ] if not replica_groups: split_count = get_replica_count() else: @@ -1230,7 +1252,8 @@ class ComputationBuilder(object): replica_groups = [] # special value for XLA API else: replica_groups = [ - _make_replica_group_proto(group) for group in replica_groups] + _make_replica_group_proto(group) for group in replica_groups + ] return self._client.CrossReplicaSum(operand, replica_groups) def Collapse(self, operand, dimensions): @@ -1258,8 +1281,8 @@ class ComputationBuilder(object): """Select and scatter op, used by the gradient of ReduceWindow. Args: - operand: LocalOp for array of dimension N and type T over - which the windows slide. + operand: LocalOp for array of dimension N and type T over which the + windows slide. select: Computation of type (T, T) -> Pred to apply to the elements of each window to indicate which element is selected. window_dimensions: sequence of N integers for dimensions of the window. @@ -1274,8 +1297,8 @@ class ComputationBuilder(object): A LocalOp representing the added SelectAndScatter op. """ pads = _convert_padding_type_to_pad_values( - padding, self.GetShape(operand).dimensions(), - window_dimensions, window_strides) + padding, self.GetShape(operand).dimensions(), window_dimensions, + window_strides) return self._client.SelectAndScatterWithGeneralPadding( operand, select.computation, window_dimensions, window_strides, pads, source, init_value, scatter.computation) @@ -1329,8 +1352,8 @@ class ComputationBuilder(object): Args: operand: LocalOp for the N dimensional array to be sliced. - start_indices: LocalOp for the 1D array of N integers - containing the starting indices of the slice. + start_indices: LocalOp for the 1D array of N integers containing the + starting indices of the slice. slice_sizes: iterable of N integers containing the slice sizes in each dimension. @@ -1347,6 +1370,7 @@ class ComputationBuilder(object): update: N dimensional array comprising the slice update. start_indices: Rank-1 array of N integers comprising the starting indices of the slice along each dimension. + Returns: A LocalOp representing the added DynamicUpdateSlice op. """ @@ -1380,8 +1404,8 @@ class ComputationBuilder(object): Args: computation_to_apply: a Computation object. - operands: an iterable of LocalOp. The number and types of - operands must match the arity of computation_to_apply. + operands: an iterable of LocalOp. The number and types of operands must + match the arity of computation_to_apply. Returns: A LocalOp representing the added call op. @@ -1458,8 +1482,8 @@ class ComputationBuilder(object): A LocalOp representing the added ReduceWindow op. """ pads = _convert_padding_type_to_pad_values( - padding, self.GetShape(operand).dimensions(), window_dimensions, - window_strides) + padding, + self.GetShape(operand).dimensions(), window_dimensions, window_strides) return self._client.ReduceWindowWithGeneralPadding( operand, init_value, computation_to_apply.computation, window_dimensions, window_strides, (), (), pads) @@ -1492,10 +1516,8 @@ class ComputationBuilder(object): Args: mu: A LocalOp to an F32 scalar specifying the mean. - sigma: A LocalOp to an F32 scalar specifying the standard - deviation. + sigma: A LocalOp to an F32 scalar specifying the standard deviation. dims: A 1D array-like of nonnegative integers specifying the dimensions. - Returns: a LocalOp to the generated array of F32 values. """ shape = Shape.array_shape(self.GetShape(mu).element_type(), dims) @@ -1505,16 +1527,15 @@ class ComputationBuilder(object): """Enqueues an RngUniform operation onto the computation. Args: - a: a LocalOp to an F32, S32, or U32 scalar (consistent with - the type of b) specifying the low end of the interval [a, b) over which - values are generated. - b: a LocalOp to an F32, S32, or U32 scalar (consistent with - the type of a) specifying the high end of the interval [a, b) over which - values are generated. + a: a LocalOp to an F32, S32, or U32 scalar (consistent with the type of b) + specifying the low end of the interval [a, b) over which values are + generated. + b: a LocalOp to an F32, S32, or U32 scalar (consistent with the type of a) + specifying the high end of the interval [a, b) over which values are + generated. dims: A 1D array-like of nonnegative integers specifying the dimensions. - - Returns: a LocalOp to the generated array of values with the - same numeric type (F32, S32, or U32) as the arguments a and b. + Returns: a LocalOp to the generated array of values with the same numeric + type (F32, S32, or U32) as the arguments a and b. """ shape = Shape.array_shape(self.GetShape(a).element_type(), dims) return self._client.RngUniform(a, b, shape) @@ -1526,7 +1547,6 @@ class ComputationBuilder(object): cond: a Computation for the loop condition, which has type T -> PRED body: a Computation for the loop body, which has type T -> T init: a LocalOp for the initial parameter, which has type T - Returns: a LocalOp representing the While operation. """ return self._client.While(cond.computation, body.computation, init) @@ -1541,19 +1561,17 @@ class ComputationBuilder(object): true_computation: a Computation to apply to true_operand, type T_0 -> S false_operand: a ComputationDatahandle of type T_1 false_computation: a Computation to apply to false_operand, type T_1 -> S - Returns: a LocalOp representing the Conditional operation. """ - return self._client.Conditional( - pred, true_operand, true_computation.computation, false_operand, - false_computation.computation) + return self._client.Conditional(pred, true_operand, + true_computation.computation, false_operand, + false_computation.computation) def IsConstant(self, operand): """Checks whether the given operand is a compile-time constant. Args: operand: a ComputationDataHandle to test. - Returns: bool indicating whether `operand` is a compile-time constant, meaning its value does not depend on any parametersor, or on stateful operators such as `RngNormal` or `Infeed`. @@ -1576,7 +1594,6 @@ class ComputationBuilder(object): Args: lhs: LocalOp for the rank 1 or rank 2 left-hand-side array. rhs: LocalOp for the rank 1 or rank 2 right-hand-side array. - Returns: a LocalOp representing the Dot operation. """ return self._client.Dot(lhs, rhs) @@ -1587,14 +1604,13 @@ class ComputationBuilder(object): Args: lhs: LocalOp for the left-hand-side array. rhs: LocalOp for the right-hand-side array. - dimension_numbers: either an xla_data_pb2.DotDimensionNumbers or a nested - tuple ((lhs_contract, rhs_contract), (lhs_batch, rhs_batch)) of lists of + dimension_numbers: either a DotDimensionNumbers or a nested tuple + ((lhs_contract, rhs_contract), (lhs_batch, rhs_batch)) of lists of integers representing the dimensions to treat as contracting dimensions and batch dimensions on each input operand. - Returns: a LocalOp representing the DotGeneral operation. """ - if not isinstance(dimension_numbers, xla_data_pb2.DotDimensionNumbers): + if isinstance(dimension_numbers, tuple): dimension_numbers = GetDotDimensionsFromLists(dimension_numbers) return self._client.DotGeneral(lhs, rhs, dimension_numbers) @@ -1607,15 +1623,15 @@ class ComputationBuilder(object): window_strides: length-N array-like of integer kernel strides. padding: PaddingType representing either 'SAME' or 'VALID' padding. feature_group_count: number of feature groups for grouped convolution. - Returns: a LocalOp representing the Conv operation. """ pads = _convert_padding_type_to_pad_values( - padding, self.GetShape(lhs).dimensions()[2:], + padding, + self.GetShape(lhs).dimensions()[2:], self.GetShape(rhs).dimensions()[2:], window_strides) return self.ConvGeneralDilated( - lhs, rhs, window_strides, pads, (), (), - dimension_numbers=None, feature_group_count=feature_group_count) + lhs, rhs, window_strides, pads, (), (), dimension_numbers=None, + feature_group_count=feature_group_count) def ConvWithGeneralPadding(self, lhs, rhs, window_strides, padding, lhs_dilation, rhs_dilation, feature_group_count=1): @@ -1640,7 +1656,7 @@ class ComputationBuilder(object): def _GetConvDimensionNumbers(self, num_spatial_dims): """Create ConvolutionDimensionNumbers proto for convolutions.""" nd = num_spatial_dims - dimension_numbers = xla_data_pb2.ConvolutionDimensionNumbers() + dimension_numbers = ConvolutionDimensionNumbers() dimension_numbers.input_batch_dimension = 0 dimension_numbers.input_feature_dimension = 1 dimension_numbers.output_batch_dimension = 0 @@ -1664,35 +1680,33 @@ class ComputationBuilder(object): padding: length-N array-like of pairs of integers of (low, high) padding. lhs_dilation: length-N array-like of integer dilation factors. rhs_dilation: length-N array-like of integer dilation factors. - dimension_numbers: optional, either an - xla_data_pb2.ConvolutionDimensionNumbers proto instance or a tuple - (lhs_spec, rhs_spec, out_spec) where each element is a string of length - N+2 identifying by position (1) batch dimensions in lhs, rhs, and the - output with the character 'N', (2) feature dimensions in lhs and the - output with the character 'C', (3) input and output feature dimensions - in rhs with the characters 'I' and 'O' respectively, and (4) spatial - dimension correspondences between lhs, rhs, and the output using any - distinct characters. For example, to indicate dimension numbers - consistent with the Conv operation with two spatial dimensions, one - could use ('NCHW', 'OIHW', 'NCHW'). As another example, to indicate - dimension numbers consistent with the TensorFlow Conv2D operation, one - could use ('NHWC', 'HWIO', 'NHWC'). When using the latter form of - convolution dimension specification, window strides are associated with - spatial dimension character labels according to the order in which the - labels appear in the rhs_spec string, so that window_strides[0] is - matched with the dimension corresponding to the first character - appearing in rhs_spec that is not 'I' or 'O'. By default, use the same - dimension numbering as Conv and ConvWithGeneralPadding. + dimension_numbers: optional, either a ConvolutionDimensionNumbers object + or a tuple (lhs_spec, rhs_spec, out_spec). Each element is a string of + length N+2 identifying by position: (1) batch dimensions in lhs, rhs, + and the output with the character 'N', (2) feature dimensions in lhs + and the output with the character 'C', (3) input and output feature + dimensions in rhs with the characters 'I' and 'O' respectively, and + (4) spatial dimension correspondences between lhs, rhs, and the output + using any distinct characters. For example, to indicate dimension + numbers consistent with the Conv operation with two spatial + dimensions, one could use ('NCHW', 'OIHW', 'NCHW'). As another + example, to indicate dimension numbers consistent with the TensorFlow + Conv2D operation, one could use ('NHWC', 'HWIO', 'NHWC'). When using + the latter form of convolution dimension specification, window strides + are associated with spatial dimension character labels according to + the order in which the labels appear in the rhs_spec string, so that + window_strides[0] is matched with the dimension corresponding to the + first character appearing in rhs_spec that is not 'I' or 'O'. By + default, use the same dimension numbering as Conv and + ConvWithGeneralPadding. feature_group_count: number of feature groups for grouped convolution. - Returns: a LocalOp representing the ConvGenralDilated operation. """ if dimension_numbers is None: dimension_numbers = self._GetConvDimensionNumbers(len(window_strides)) - elif not isinstance(dimension_numbers, - xla_data_pb2.ConvolutionDimensionNumbers): + elif isinstance(dimension_numbers, tuple): lhs_spec, rhs_spec, out_spec = dimension_numbers - dimension_numbers = xla_data_pb2.ConvolutionDimensionNumbers() + dimension_numbers = ConvolutionDimensionNumbers() dimension_numbers.input_batch_dimension = lhs_spec.index('N') dimension_numbers.input_feature_dimension = lhs_spec.index('C') @@ -1709,10 +1723,9 @@ class ComputationBuilder(object): dimension_numbers.output_spatial_dimensions.extend( sorted((i for i, c in enumerate(out_spec) if c not in {'N', 'C'}), key=lambda i: rhs_spec.index(out_spec[i]))) - return self._client.ConvGeneralDilated(lhs, rhs, window_strides, padding, - lhs_dilation, rhs_dilation, - dimension_numbers, - feature_group_count) + return self._client.ConvGeneralDilated( + lhs, rhs, window_strides, padding, lhs_dilation, rhs_dilation, + dimension_numbers, feature_group_count) def Sort(self, operand, dimension=-1): """Enqueues a sort operation onto the computation.""" @@ -1750,15 +1763,14 @@ class ComputationBuilder(object): def Gather(self, a, start_indices, dimension_numbers, slice_sizes): """Enqueues a Gather operation onto the computation.""" - return self._client.Gather(a, start_indices, dimension_numbers, - slice_sizes) + return self._client.Gather(a, start_indices, dimension_numbers, slice_sizes) def Scatter(self, a, scatter_indices, updates, update_computation, dimension_numbers): """Enqueues a Scatter operation onto the computation.""" return self._client.Scatter( a, scatter_indices, updates, update_computation.computation, - dimension_numbers,) + dimension_numbers) def _forward_methods_to_local_builder(): @@ -1799,7 +1811,6 @@ def _forward_methods_to_local_builder(): _forward_methods_to_local_builder() - _default_replica_count = 1 @@ -1852,22 +1863,111 @@ def register_cpu_custom_call_target(name, fn): c_api.RegisterCpuCustomCallTarget(name, fn) +class PaddingConfigDimension(object): + """Python representation of a xla.PaddingConfigDimension protobuf.""" + __slots__ = ('edge_padding_low', 'edge_padding_high', 'interior_padding') + + def __init__(self): + self.edge_padding_low = [] + self.edge_padding_high = [] + self.interior_padding = [] + + +class PaddingConfig(object): + """Python representation of a xla.PaddingConfig protobuf.""" + __slots__ = ('dimensions',) + + def __init__(self): + self.dimensions = [] + + def GetPaddingConfigFromTriples(triples): """Create PaddingConfig proto from list of triples of integers.""" - padding_config = xla_data_pb2.PaddingConfig() + padding_config = PaddingConfig() for lo, hi, interior in triples: - dimension = padding_config.dimensions.add() + dimension = PaddingConfigDimension() dimension.edge_padding_low = lo dimension.edge_padding_high = hi dimension.interior_padding = interior + padding_config.dimensions.append(dimension) return padding_config +class DotDimensionNumbers(object): + """Python representation of a xla.DotDimensionNumbers protobuf.""" + __slots__ = ('lhs_contracting_dimensions', 'rhs_contracting_dimensions', + 'lhs_batch_dimensions', 'rhs_batch_dimensions') + + def __init__(self): + self.lhs_contracting_dimensions = [] + self.rhs_contracting_dimensions = [] + self.lhs_batch_dimensions = [] + self.rhs_batch_dimensions = [] + + def GetDotDimensionsFromLists(dimension_numbers): (lhs_contract, rhs_contract), (lhs_batch, rhs_batch) = dimension_numbers - dot_dims_proto = xla_data_pb2.DotDimensionNumbers() + dot_dims_proto = DotDimensionNumbers() dot_dims_proto.lhs_contracting_dimensions.extend(lhs_contract) dot_dims_proto.rhs_contracting_dimensions.extend(rhs_contract) dot_dims_proto.lhs_batch_dimensions.extend(lhs_batch) dot_dims_proto.rhs_batch_dimensions.extend(rhs_batch) return dot_dims_proto + + +class ConvolutionDimensionNumbers(object): + """Python representation of a xla.ConvolutionDimensionNumbers protobuf.""" + __slots__ = ('input_batch_dimension', 'input_feature_dimension', + 'input_spatial_dimensions', 'kernel_input_feature_dimension', + 'kernel_output_feature_dimension', 'kernel_spatial_dimensions', + 'output_batch_dimension', 'output_feature_dimension', + 'output_spatial_dimensions') + + def __init__(self): + self.input_batch_dimension = 0 + self.input_feature_dimension = 0 + self.input_spatial_dimensions = [] + self.kernel_input_feature_dimension = 0 + self.kernel_output_feature_dimension = 0 + self.kernel_spatial_dimensions = [] + self.output_batch_dimension = 0 + self.output_feature_dimension = 0 + self.output_spatial_dimensions = [] + + +class GatherDimensionNumbers(object): + """Python representation of a xla.GatherDimensionNumbers protobuf.""" + __slots__ = ('offset_dims', 'collapsed_slice_dims', 'start_index_map', + 'index_vector_dim') + + def __init__(self): + self.offset_dims = [] + self.collapsed_slice_dims = [] + self.start_index_map = [] + self.index_vector_dim = 0 + + +class ScatterDimensionNumbers(object): + """Python representation of a xla.ScatterDimensionNumbers protobuf.""" + __slots__ = ('update_window_dims', 'inserted_window_dims', + 'scatter_dims_to_operand_dims', 'index_vector_dim') + + def __init__(self): + self.update_window_dims = [] + self.inserted_window_dims = [] + self.scatter_dims_to_operand_dims = [] + self.index_vector_dim = 0 + + +class ReplicaGroup(object): + """Python representation of a xla.ReplicaGroup protobuf.""" + __slots__ = ('replica_ids',) + + def __init__(self): + self.replica_ids = [] + + +def _make_replica_group_proto(replica_group): + replica_group_proto = ReplicaGroup() + replica_group_proto.replica_ids.extend(replica_group) + return replica_group_proto diff --git a/tensorflow/compiler/xla/python/xla_client_test.py b/tensorflow/compiler/xla/python/xla_client_test.py index 3094d48fb0..bd789efaee 100644 --- a/tensorflow/compiler/xla/python/xla_client_test.py +++ b/tensorflow/compiler/xla/python/xla_client_test.py @@ -24,11 +24,24 @@ import threading import numpy as np +from tensorflow.compiler.xla import xla_data_pb2 from tensorflow.compiler.xla.python import custom_call_for_test from tensorflow.compiler.xla.python import xla_client import unittest +class EnumTest(unittest.TestCase): + """Verifies Python enumerations match their protocol buffer equivalents.""" + + def testPrimitiveType(self): + for name, value in xla_client.PrimitiveType.__members__.items(): + self.assertEqual(value, getattr(xla_data_pb2, name)) + + def testFormat(self): + for name, value in xla_client.Format.__members__.items(): + self.assertEqual(value, getattr(xla_data_pb2, name)) + + class ComputationTest(unittest.TestCase): """Base class for running an XLA Computation through the local client.""" @@ -230,16 +243,6 @@ class ComputationsWithConstantsTest(ComputationTest): c.Constant(NumpyArrayS32([1]))) self._ExecuteAndCompareClose(c, expected=[2**31 - 1]) - def testGetProto(self): - c = self._NewComputation() - c.Add( - c.Constant(NumpyArrayF32([[1, 2, 3], [4, 5, 6]])), - c.Constant(NumpyArrayF32([[1, -1, 1], [-1, 1, -1]]))) - built = c.Build() - proto = built.GetProto() # HloModuleProto - self.assertTrue(len(proto.computations) == 1) - self.assertTrue(len(proto.computations[0].instructions) == 3) - def testSum2DF64(self): c = self._NewComputation() c.Add( @@ -528,11 +531,11 @@ class SingleOpTest(ComputationTest): def testConvertElementType(self): xla_types = { - np.bool: xla_client.xla_data_pb2.PRED, - np.int32: xla_client.xla_data_pb2.S32, - np.int64: xla_client.xla_data_pb2.S64, - np.float32: xla_client.xla_data_pb2.F32, - np.float64: xla_client.xla_data_pb2.F64, + np.bool: xla_client.PrimitiveType.PRED, + np.int32: xla_client.PrimitiveType.S32, + np.int64: xla_client.PrimitiveType.S64, + np.float32: xla_client.PrimitiveType.F32, + np.float64: xla_client.PrimitiveType.F64, } def _ConvertAndTest(template, src_dtype, dst_dtype): @@ -553,13 +556,13 @@ class SingleOpTest(ComputationTest): def testBitcastConvertType(self): xla_x32_types = { - np.int32: xla_client.xla_data_pb2.S32, - np.float32: xla_client.xla_data_pb2.F32, + np.int32: xla_client.PrimitiveType.S32, + np.float32: xla_client.PrimitiveType.F32, } xla_x64_types = { - np.int64: xla_client.xla_data_pb2.S64, - np.float64: xla_client.xla_data_pb2.F64, + np.int64: xla_client.PrimitiveType.S64, + np.float64: xla_client.PrimitiveType.F64, } def _ConvertAndTest(template, src_dtype, dst_dtype, dst_etype): @@ -663,7 +666,7 @@ class SingleOpTest(ComputationTest): lhs = NumpyArrayF32(rng.randn(10, 3, 4)) rhs = NumpyArrayF32(rng.randn(10, 4, 5)) - dimension_numbers = xla_client.xla_data_pb2.DotDimensionNumbers() + dimension_numbers = xla_client.DotDimensionNumbers() dimension_numbers.lhs_contracting_dimensions.append(2) dimension_numbers.rhs_contracting_dimensions.append(1) dimension_numbers.lhs_batch_dimensions.append(0) @@ -975,12 +978,13 @@ class SingleOpTest(ComputationTest): def testPadWithPaddingConfig(self): c = self._NewComputation() - padding_config = xla_client.xla_data_pb2.PaddingConfig() + padding_config = xla_client.PaddingConfig() for lo, hi, interior in [(1, 2, 1), (0, 1, 0)]: - dimension = padding_config.dimensions.add() + dimension = xla_client.PaddingConfigDimension() dimension.edge_padding_low = lo dimension.edge_padding_high = hi dimension.interior_padding = interior + padding_config.dimensions.append(dimension) c.Pad( c.Constant(NumpyArrayF32([[1.0, 2.0], [3.0, 4.0]])), c.Constant(NumpyArrayF32(0.0)), @@ -1192,7 +1196,7 @@ class SingleOpTest(ComputationTest): def testGather(self): a = np.arange(9).astype(np.int32).reshape((3, 3)) indices = np.array([[[0, 2], [2, 1]], [[1, 2], [2, 0]]], dtype=np.int32) - dnums = xla_client.xla_data_pb2.GatherDimensionNumbers() + dnums = xla_client.GatherDimensionNumbers() dnums.offset_dims.append(1) dnums.offset_dims.append(2) dnums.start_index_map.append(0) @@ -1656,7 +1660,7 @@ class EmbeddedComputationsTest(ComputationTest): scatter_indices = np.array([0, 2], dtype=np.int32) updates = np.array([[10, 20, 30], [70, 80, 90]], dtype=np.int32) - dnums = xla_client.xla_data_pb2.ScatterDimensionNumbers() + dnums = xla_client.ScatterDimensionNumbers() dnums.update_window_dims.append(1) dnums.inserted_window_dims.append(0) dnums.scatter_dims_to_operand_dims.append(0) diff --git a/tensorflow/compiler/xla/python/xla_data.i b/tensorflow/compiler/xla/python/xla_data.i index 974f314af2..b18583c64d 100644 --- a/tensorflow/compiler/xla/python/xla_data.i +++ b/tensorflow/compiler/xla/python/xla_data.i @@ -13,9 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -// SWIG typemaps and declarations for building, compiling, and -// executing XLA computations, wrapping most of what is declared in -// xla_data.h. +// SWIG typemaps for building, compiling, and executing XLA computations. // // The typemaps below implement/assert the following correspondences // (with elaborations below): @@ -33,11 +31,11 @@ limitations under the License. // std::vector <- sequence of xla_client.Shape objects // PrimitiveType <- int // Span> <- sequence of int pairs -// PaddingConfig proto <- corresponding Python proto -// ConvolutionDimensionNumbers proto <- corresponding Python proto -// DotDimensionNumbers proto <- corresponding Python proto -// GatherDimensionNumbers proto <- corresponding Python proto -// ScatterDimensionNumbers proto <- corresponding Python proto +// PaddingConfig proto <- ducktyped Python proto +// ConvolutionDimensionNumbers proto <- ducktyped Python proto +// DotDimensionNumbers proto <- ducktyped Python proto +// GatherDimensionNumbers proto <- ducktyped Python proto +// ScatterDimensionNumbers proto <- ducktyped Python proto // Span <- sequence of ReplicaGroup Python proto // // Arrows indicate whether a conversion only ever occurs in one @@ -102,6 +100,8 @@ limitations under the License. // Other SWIG object wrappers (e.g. of Computation) are further // wrapped by xla_client in order to set up a custom destructor that // triggers memory deallocation on the C++ side. +// + %module(threads="1") xla_data -- GitLab From 64ff7c16ce5a154eaf9fbcd8a919f851080fe44d Mon Sep 17 00:00:00 2001 From: Gaurav Jain Date: Mon, 25 Feb 2019 11:03:22 -0800 Subject: [PATCH 463/766] Make inplace and output args mutually exclusive. This avoids the need for the user to re-specify the output path. In addition, we make the inplace arg a bit more friendly by making it a boolean toggle vs requiring the user to write "True" as its value. PiperOrigin-RevId: 235562748 --- tensorflow/tools/compatibility/ast_edits.py | 10 ++------ .../tools/compatibility/tf_upgrade_v2_main.py | 23 ++++++++++++------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/tensorflow/tools/compatibility/ast_edits.py b/tensorflow/tools/compatibility/ast_edits.py index 3d421f6704..25c0a2d5c1 100644 --- a/tensorflow/tools/compatibility/ast_edits.py +++ b/tensorflow/tools/compatibility/ast_edits.py @@ -555,7 +555,7 @@ class ASTCodeUpgrader(object): process_errors) def process_tree(self, root_directory, output_root_directory, - copy_other_files, in_place): + copy_other_files): """Processes upgrades on an entire tree of python files in place. Note that only Python files. If you have custom code in other languages, @@ -565,7 +565,6 @@ class ASTCodeUpgrader(object): root_directory: Directory to walk and process. output_root_directory: Directory to use as base. copy_other_files: Copy files that are not touched by this converter. - in_place: Allow the conversion of an entire directory in place. Returns: A tuple of files processed, the report string for all files, and a dict @@ -573,12 +572,7 @@ class ASTCodeUpgrader(object): """ if output_root_directory == root_directory: - if in_place: - return self.process_tree_inplace(root_directory) - else: - print("In order to copy a directory in place the `--inplace` input " - "arg must be set to `True`.") - sys.exit(1) + return self.process_tree_inplace(root_directory) # make sure output directory doesn't exist if output_root_directory and os.path.exists(output_root_directory): diff --git a/tensorflow/tools/compatibility/tf_upgrade_v2_main.py b/tensorflow/tools/compatibility/tf_upgrade_v2_main.py index 75a91609a7..36e30f559e 100644 --- a/tensorflow/tools/compatibility/tf_upgrade_v2_main.py +++ b/tensorflow/tools/compatibility/tf_upgrade_v2_main.py @@ -80,11 +80,10 @@ Simple usage: parser.add_argument( "--inplace", dest="in_place", - help=("If converting a whole tree of files, whether to " + help=("If converting a set of files, whether to " "allow the conversion to be performed on the " - "files in the input tree."), - type=bool, - default=False) + "input files."), + action="store_true") parser.add_argument( "--reportfile", dest="report_filename", @@ -99,21 +98,29 @@ Simple usage: report_filename = args.report_filename files_processed = 0 if args.input_file: - if not args.output_file: + if not args.in_place and not args.output_file: raise ValueError( "--outfile= argument is required when converting a " "single file.") + if args.in_place and args.output_file: + raise ValueError( + "--outfile argument is invalid when when converting in place") + output_file = args.input_file if args.in_place else args.output_file files_processed, report_text, errors = process_file( - args.input_file, args.output_file, upgrade) + args.input_file, output_file, upgrade) errors = {args.input_file: errors} files_processed = 1 elif args.input_tree: - if not args.output_tree: + if not args.in_place and not args.output_tree: raise ValueError( "--outtree= argument is required when converting a " "file tree.") + if args.in_place and args.output_tree: + raise ValueError( + "--outtree argument is invalid when when converting in place") + output_tree = args.input_tree if args.in_place else args.output_tree files_processed, report_text, errors = upgrade.process_tree( - args.input_tree, args.output_tree, args.copy_other_files, args.in_place) + args.input_tree, output_tree, args.copy_other_files) else: parser.print_help() if report_text: -- GitLab From 83966daa2d56bad262b15c6d92017149bd99167b Mon Sep 17 00:00:00 2001 From: Stephan Lee Date: Mon, 25 Feb 2019 11:04:46 -0800 Subject: [PATCH 464/766] Summary API for trace. The Trace API allows user to trace execution and collect graph or profile information. PiperOrigin-RevId: 235563123 --- tensorflow/python/BUILD | 1 + tensorflow/python/eager/profiler.py | 17 +++- tensorflow/python/eager/profiler_test.py | 4 +- tensorflow/python/kernel_tests/BUILD | 1 + .../python/kernel_tests/summary_ops_test.py | 74 ++++++++++++++ tensorflow/python/ops/summary_ops_v2.py | 97 +++++++++++++++++++ .../api/golden/v2/tensorflow.summary.pbtxt | 12 +++ 7 files changed, 200 insertions(+), 6 deletions(-) diff --git a/tensorflow/python/BUILD b/tensorflow/python/BUILD index e6ea4fd36b..ab75a4c106 100644 --- a/tensorflow/python/BUILD +++ b/tensorflow/python/BUILD @@ -3308,6 +3308,7 @@ py_library( ":util", "//tensorflow/core:protos_all_py", "//tensorflow/python/eager:context", + "//tensorflow/python/eager:profiler", "@six_archive//:six", ], ) diff --git a/tensorflow/python/eager/profiler.py b/tensorflow/python/eager/profiler.py index 0c8e9796f5..c7aba1d2bf 100644 --- a/tensorflow/python/eager/profiler.py +++ b/tensorflow/python/eager/profiler.py @@ -51,16 +51,24 @@ _profiler_lock = threading.Lock() _run_num = 0 +class ProfilerAlreadyRunningError(Exception): + pass + + +class ProfilerNotRunningError(Exception): + pass + + def start(): """Start profiling. Raises: - AssertionError: If another profiling session is running. + ProfilerAlreadyRunningError: If another profiling session is running. """ global _profiler with _profiler_lock: if _profiler is not None: - raise AssertionError('Another profiler is running.') + raise ProfilerAlreadyRunningError('Another profiler is running.') profiler_context = pywrap_tensorflow.TFE_NewProfilerContext() if context.default_execution_mode == context.EAGER_MODE: pywrap_tensorflow.TFE_ProfilerContextSetEagerContext( @@ -82,13 +90,14 @@ def stop(): to file for offline analysis by tensorboard. Raises: - AssertionError: If there is no active profiling session. + ProfilerNotRunningError: If there is no active profiling session. """ global _profiler global _run_num with _profiler_lock: if _profiler is None: - raise AssertionError('Cannot stop profiling. No profiler is running.') + raise ProfilerNotRunningError( + 'Cannot stop profiling. No profiler is running.') with c_api_util.tf_buffer() as buffer_: pywrap_tensorflow.TFE_ProfilerSerializeToString( context.context()._handle, # pylint: disable=protected-access diff --git a/tensorflow/python/eager/profiler_test.py b/tensorflow/python/eager/profiler_test.py index ba19e17e96..fea46b0014 100644 --- a/tensorflow/python/eager/profiler_test.py +++ b/tensorflow/python/eager/profiler_test.py @@ -33,7 +33,7 @@ class ProfilerTest(test_util.TensorFlowTestCase): five = constant_op.constant(5) product = three * five self.assertAllEqual(15, product) - with self.assertRaises(AssertionError): + with self.assertRaises(profiler.ProfilerAlreadyRunningError): profiler.start() profile_result = profiler.stop() @@ -41,7 +41,7 @@ class ProfilerTest(test_util.TensorFlowTestCase): profile_pb.ParseFromString(profile_result) profile_pb_str = '%s' % profile_pb self.assertTrue('Mul' in profile_pb_str) - with self.assertRaises(AssertionError): + with self.assertRaises(profiler.ProfilerNotRunningError): profiler.stop() diff --git a/tensorflow/python/kernel_tests/BUILD b/tensorflow/python/kernel_tests/BUILD index e1861ca669..1a83ca1e49 100644 --- a/tensorflow/python/kernel_tests/BUILD +++ b/tensorflow/python/kernel_tests/BUILD @@ -1113,6 +1113,7 @@ cuda_py_test( "//tensorflow/python:math_ops", "//tensorflow/python:platform", "//tensorflow/python:summary_ops_v2", + "@six_archive//:six", "//tensorflow/python:tensor_spec", "//tensorflow/python:tensor_util", "//tensorflow/python:variables", diff --git a/tensorflow/python/kernel_tests/summary_ops_test.py b/tensorflow/python/kernel_tests/summary_ops_test.py index 0b4d58f608..dead337cfb 100644 --- a/tensorflow/python/kernel_tests/summary_ops_test.py +++ b/tensorflow/python/kernel_tests/summary_ops_test.py @@ -21,6 +21,8 @@ from __future__ import print_function import os import unittest +import six + from tensorflow.core.framework import graph_pb2 from tensorflow.core.framework import node_def_pb2 from tensorflow.core.framework import step_stats_pb2 @@ -45,6 +47,7 @@ from tensorflow.python.ops import summary_ops_v2 as summary_ops from tensorflow.python.ops import variables from tensorflow.python.platform import gfile from tensorflow.python.platform import test +from tensorflow.python.platform import tf_logging as logging class SummaryOpsCoreTest(test_util.TensorFlowTestCase): @@ -571,6 +574,9 @@ class SummaryWriterTest(test_util.TensorFlowTestCase): class SummaryOpsTest(test_util.TensorFlowTestCase): + def tearDown(self): + summary_ops.disable_trace() + def run_metadata(self, *args, **kwargs): assert context.executing_eagerly() logdir = self.get_temp_dir() @@ -616,6 +622,18 @@ class SummaryOpsTest(test_util.TensorFlowTestCase): # the second event. return events[1].summary + def run_trace(self, f): + assert context.executing_eagerly() + logdir = self.get_temp_dir() + writer = summary_ops.create_file_writer(logdir) + summary_ops.enable_trace(graph=True, profiler=False) + with writer.as_default(): + f() + summary_ops.export_trace(name='foo', step=1) + writer.close() + events = events_from_logdir(logdir) + return events[1].summary + @test_util.run_v2_only def testRunMetadata_usesNameAsTag(self): meta = config_pb2.RunMetadata() @@ -718,6 +736,62 @@ class SummaryOpsTest(test_util.TensorFlowTestCase): first_val = summary.value[0] self.assertEqual(model.to_json(), first_val.tensor.string_val[0]) + @test_util.run_v2_only + def testTrace(self): + + @def_function.function + def f(): + x = constant_op.constant(2) + y = constant_op.constant(3) + return x**y + + summary = self.run_trace(f) + + first_val = summary.value[0] + actual_run_metadata = config_pb2.RunMetadata.FromString( + first_val.tensor.string_val[0]) + + # Content of function_graphs is large and, for instance, device can change. + self.assertTrue(hasattr(actual_run_metadata, 'function_graphs')) + + @test_util.run_v2_only + def testTrace_cannotEnableTraceInFunction(self): + + @def_function.function + def f(): + summary_ops.enable_trace(graph=True, profiler=False) + x = constant_op.constant(2) + y = constant_op.constant(3) + return x**y + + with test.mock.patch.object(logging, 'warn') as mock_log: + f() + self.assertRegexpMatches( + str(mock_log.call_args), 'Must enable trace in eager mode.') + + @test_util.run_v2_only + def testTrace_cannotExportTraceWithoutTrace(self): + with six.assertRaisesRegex(self, ValueError, + 'Must enable trace before export.'): + summary_ops.export_trace(name='foo', step=1) + + @test_util.run_v2_only + def testTrace_cannotExportTraceInFunction(self): + summary_ops.enable_trace(graph=True, profiler=False) + + @def_function.function + def f(): + x = constant_op.constant(2) + y = constant_op.constant(3) + summary_ops.export_trace(name='foo', step=1) + return x**y + + with test.mock.patch.object(logging, 'warn') as mock_log: + f() + self.assertRegexpMatches( + str(mock_log.call_args), + 'Can only export trace while executing eagerly.') + def events_from_file(filepath): """Returns all events in a single event file. diff --git a/tensorflow/python/ops/summary_ops_v2.py b/tensorflow/python/ops/summary_ops_v2.py index bb570bafee..4ed2bd6b7d 100644 --- a/tensorflow/python/ops/summary_ops_v2.py +++ b/tensorflow/python/ops/summary_ops_v2.py @@ -20,10 +20,12 @@ from __future__ import division from __future__ import print_function import abc +import collections import functools import getpass import os import re +import threading import time import six @@ -32,6 +34,7 @@ from tensorflow.core.framework import graph_pb2 from tensorflow.core.framework import summary_pb2 from tensorflow.core.protobuf import config_pb2 from tensorflow.python.eager import context +from tensorflow.python.eager import profiler as _profiler from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes from tensorflow.python.framework import ops @@ -974,3 +977,97 @@ def keras_model(name, data, step): tensor=constant_op.constant(json_string, dtype=dtypes.string), step=step, metadata=summary_metadata) + + +_TraceContext = collections.namedtuple("TraceContext", ("graph", "profiler")) +_current_trace_context_lock = threading.Lock() +_current_trace_context = None + + +@tf_export("summary.enable_trace", v1=[]) +def enable_trace(graph=True, profiler=False): + """Enables execution trace. + + Args: + graph: whether to collect graphs used in execution + profiler: whether to enable profiler. + + Returns: + None + """ + if not context.context().executing_eagerly(): + logging.warn("Must enable trace in eager mode.") + return + + global _current_trace_context + with _current_trace_context_lock: + if _current_trace_context: + logging.warn("Trace already enabled") + return + + if graph and not profiler: + context.context().enable_graph_collection() + if profiler: + context.context().enable_run_metadata() + _profiler.start() + + _current_trace_context = _TraceContext(graph=graph, profiler=profiler) + + +@tf_export("summary.export_trace", v1=[]) +def export_trace(name, step, profiler_outdir=None): + """Exports trace as a Summary and/or profile file. + + Args: + name: A name for the summary to be written. + step: Required `int64`-castable monotonic step value. + profiler_outdir: Output directory for profiler. It is required when profiler + is enabled when trace was started. Otherwise, it is ignored. + + Returns: + None + """ + # TODO(stephanlee): See if we can remove profiler_outdir and infer it from + # the SummaryWriter's logdir. + global _current_trace_context + + if not context.context().executing_eagerly(): + logging.warn("Can only export trace while executing eagerly.") + return + + with _current_trace_context_lock: + if _current_trace_context is None: + raise ValueError("Must enable trace before export.") + graph, profiler = _current_trace_context + if profiler and profiler_outdir is None: + raise ValueError("Required profiler_outdir is not specified") + + run_meta = context.context().export_run_metadata() + + if graph and not profiler: + run_metadata_graphs(name, run_meta, step) + else: + run_metadata(name, run_meta, step) + + if profiler: + _profiler.save(profiler_outdir, _profiler.stop()) + + disable_trace() + + +@tf_export("summary.disable_trace", v1=[]) +def disable_trace(): + """Disables and resets the trace state.""" + global _current_trace_context + with _current_trace_context_lock: + _current_trace_context = None + + # Disabling run_metadata disables graph collection as well. + context.context().disable_run_metadata() + + # profiler only has start and stop. One needs to stop in order to export + # and stopping when it is not running will raise an error. + try: + _profiler.stop() + except _profiler.ProfilerNotRunningError: + pass diff --git a/tensorflow/tools/api/golden/v2/tensorflow.summary.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.summary.pbtxt index bdc9c83553..d8650bd4ce 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.summary.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.summary.pbtxt @@ -16,6 +16,18 @@ tf_module { name: "create_noop_writer" argspec: "args=[], varargs=None, keywords=None, defaults=None" } + member_method { + name: "disable_trace" + argspec: "args=[], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "enable_trace" + argspec: "args=[\'graph\', \'profiler\'], varargs=None, keywords=None, defaults=[\'True\', \'False\'], " + } + member_method { + name: "export_trace" + argspec: "args=[\'name\', \'step\', \'profiler_outdir\'], varargs=None, keywords=None, defaults=[\'None\'], " + } member_method { name: "flush" argspec: "args=[\'writer\', \'name\'], varargs=None, keywords=None, defaults=[\'None\', \'None\'], " -- GitLab From b6ed9186089de852c933244a7d772f836cc3eb27 Mon Sep 17 00:00:00 2001 From: Nick Felt Date: Mon, 25 Feb 2019 11:06:03 -0800 Subject: [PATCH 465/766] Update tensorboard dependency to 1.13.x TensorBoard release: https://pypi.org/project/tensorboard/1.13.0/ PiperOrigin-RevId: 235563447 --- 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 83081a1ff3..72dde83aa7 100644 --- a/tensorflow/tools/pip_package/setup.py +++ b/tensorflow/tools/pip_package/setup.py @@ -57,7 +57,7 @@ REQUIRED_PACKAGES = [ 'numpy >= 1.14.5, < 2.0', 'six >= 1.10.0', 'protobuf >= 3.6.1', - 'tensorboard >= 1.12.0, < 1.13.0', + 'tensorboard >= 1.13.0, < 1.14.0', 'tensorflow_estimator >= 1.13.0rc0, < 1.14.0rc0', 'termcolor >= 1.1.0', ] -- GitLab From 55511f64f43e2053a5c7e55448cdda3b9e2a655f Mon Sep 17 00:00:00 2001 From: David Majnemer Date: Mon, 25 Feb 2019 11:10:01 -0800 Subject: [PATCH 466/766] [XLA] Clarify a comment regarding complex power when the base is zero PiperOrigin-RevId: 235564208 --- tensorflow/compiler/xla/service/elemental_ir_emitter.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/compiler/xla/service/elemental_ir_emitter.cc b/tensorflow/compiler/xla/service/elemental_ir_emitter.cc index f07925ca93..dffa6cc199 100644 --- a/tensorflow/compiler/xla/service/elemental_ir_emitter.cc +++ b/tensorflow/compiler/xla/service/elemental_ir_emitter.cc @@ -784,7 +784,7 @@ StatusOr ElementalIrEmitter::EmitComplexPower( auto q = FAdd(FMul(c, arg_lhs), FMul(half_d, ln_aa_p_bb)); TF_ASSIGN_OR_RETURN(auto cos_q, EmitCos(component_type, q)); TF_ASSIGN_OR_RETURN(auto sin_q, EmitSin(component_type, q)); - // 0^c is 0 if d is 0 and c > 0. 0^0 is defined to be 1.0, see + // d^c is 0 if d is 0 and c > 0. 0^0 is defined to be 1.0, see // Branch Cuts for Complex Elementary Functions or Much Ado About // Nothing's Sign Bit, W. Kahan, Section 10. return Select( -- GitLab From ba3815f1053555baf1c9cf767d4275d3b3590408 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 25 Feb 2019 11:45:28 -0800 Subject: [PATCH 467/766] Change mutex for cuDNN access to static because it is not safe to call cuDNN from different threads with different handles. PiperOrigin-RevId: 235571743 --- tensorflow/stream_executor/cuda/cuda_dnn.cc | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tensorflow/stream_executor/cuda/cuda_dnn.cc b/tensorflow/stream_executor/cuda/cuda_dnn.cc index 7e80367ef9..75bd63a742 100644 --- a/tensorflow/stream_executor/cuda/cuda_dnn.cc +++ b/tensorflow/stream_executor/cuda/cuda_dnn.cc @@ -163,7 +163,7 @@ class CudnnAccess { explicit CudnnAccess(cudnnHandle_t handle) : handle_(handle) {} ~CudnnAccess() { - mutex_lock lock(mutex_); + mutex_lock lock(*mutex_); cudnnDestroy(handle_); } @@ -184,7 +184,7 @@ class CudnnAccess { // therefore a bad idea (performance wise) to call any cuDNN APIs that // enqueue work in the stream. CudnnHandle GetHandle(GpuExecutor* executor, Stream* stream) { - mutex_lock lock(mutex_); + mutex_lock lock(*mutex_); gpu::ScopedActivateExecutorContext context(executor); CUstream cu_stream = stream ? AsGpuStreamValue(stream) : cudaStreamLegacy; auto status = cudnnSetStream(handle_, cu_stream); @@ -194,12 +194,19 @@ class CudnnAccess { private: // Guards the enqueueing of cuDNN operations via the handle_ below. - mutex mutex_; + // + // The mutex is static to work around b/124313574: calling cuDNN concurrently + // with different handles is not thread safe, even though the documentation + // suggests it is: + // https://docs.nvidia.com/deeplearning/sdk/cudnn-developer-guide/index.html#thread-safety. + static mutex* mutex_; // cuDNN library handle. - cudnnHandle_t handle_ GUARDED_BY(mutex_); // Owned. + cudnnHandle_t handle_ GUARDED_BY(*mutex_); // Owned. }; +mutex* CudnnAccess::mutex_ = new mutex; + namespace { // A helper function to return the internal compute type for -- GitLab From b5a312423d4e78ae950ec1970cf1b16d679ac326 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 25 Feb 2019 11:53:01 -0800 Subject: [PATCH 468/766] Add the decorator tpu_function.on_device_training_loop to the eval method as well. PiperOrigin-RevId: 235573331 --- tensorflow/python/tpu/tpu_estimator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/python/tpu/tpu_estimator.py b/tensorflow/python/tpu/tpu_estimator.py index ea6b297ac3..00e6f5b3a4 100644 --- a/tensorflow/python/tpu/tpu_estimator.py +++ b/tensorflow/python/tpu/tpu_estimator.py @@ -3132,6 +3132,7 @@ def _eval_on_tpu_system(ctx, model_fn_wrapper, dequeue_fn): (single_tpu_eval_step, host_calls, captured_scaffold_fn, captured_eval_hooks ) = model_fn_wrapper.convert_to_single_tpu_eval_step(dequeue_fn) + @tpu_function.on_device_training_loop def multi_tpu_eval_steps_on_single_shard(): return training_loop.repeat(iterations_per_loop_var, single_tpu_eval_step, [_ZERO_LOSS]) -- GitLab From b8e9ed9315b93a0e7211bdbd25ba2266ef4886c6 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 25 Feb 2019 11:54:20 -0800 Subject: [PATCH 469/766] std::map -> absl::flat_hash_map in fast parse sequence example The ordering property of the std::map is never used, it isn't really necessary. A flat hash map will provide faster lookups and better memory locality. PiperOrigin-RevId: 235573638 --- tensorflow/core/BUILD | 1 + .../core/util/example_proto_fast_parsing.cc | 33 +++++++++++-------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD index 40800809e8..64aed375b2 100644 --- a/tensorflow/core/BUILD +++ b/tensorflow/core/BUILD @@ -2791,6 +2791,7 @@ tf_cuda_library( ":stats_calculator_portable", ":version_lib", "@com_google_absl//absl/base", + "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/strings", "//tensorflow/core/platform/default/build_config:platformlib", diff --git a/tensorflow/core/util/example_proto_fast_parsing.cc b/tensorflow/core/util/example_proto_fast_parsing.cc index 8877cd0d3f..4c29bd582e 100644 --- a/tensorflow/core/util/example_proto_fast_parsing.cc +++ b/tensorflow/core/util/example_proto_fast_parsing.cc @@ -17,6 +17,7 @@ limitations under the License. #include #include "absl/base/casts.h" +#include "absl/container/flat_hash_map.h" #include "tensorflow/core/example/example.pb.h" #include "tensorflow/core/example/feature.pb_text.h" #include "tensorflow/core/framework/numeric_op.h" @@ -1763,9 +1764,13 @@ Status FastParseSequenceExample( DCHECK(context_result != nullptr); DCHECK(feature_list_result != nullptr); DCHECK(dense_feature_lengths != nullptr); - std::map context_is_sparse; - std::map> + size_t num_context_features = + context_config.sparse.size() + context_config.dense.size(); + absl::flat_hash_map context_is_sparse; + context_is_sparse.reserve(num_context_features); + absl::flat_hash_map> context_feature_type_and_lengths; + context_feature_type_and_lengths.reserve(num_context_features); if (!example_names.empty() && example_names.size() != num_examples) { return errors::InvalidArgument( "example_names must be empty or have the correct number of elements"); @@ -1793,11 +1798,14 @@ Status FastParseSequenceExample( " but expected ", c.shape.DebugString()); } } - context_is_sparse[c.feature_name] = false; } - std::map sequence_is_sparse; - std::map> + size_t num_sequence_features = + feature_list_config.sparse.size() + feature_list_config.dense.size(); + absl::flat_hash_map sequence_is_sparse; + sequence_is_sparse.reserve(num_sequence_features); + absl::flat_hash_map> sequence_feature_type_and_lengths; + sequence_feature_type_and_lengths.reserve(num_sequence_features); for (auto& c : feature_list_config.sparse) { TF_RETURN_IF_ERROR(CheckConfigDataType(c.dtype)); sequence_feature_type_and_lengths[c.feature_name] = @@ -1812,13 +1820,12 @@ Status FastParseSequenceExample( TF_RETURN_IF_ERROR(CheckConfigDataType(c.dtype)); sequence_feature_type_and_lengths[c.feature_name] = std::make_pair(c.dtype, 0); - sequence_is_sparse[c.feature_name] = false; } - std::vector> all_context_features( - num_examples); - std::vector> all_sequence_features( - num_examples); + std::vector> + all_context_features(num_examples); + std::vector> + all_sequence_features(num_examples); const string kUnknown = ""; for (int d = 0; d < num_examples; d++) { const string& example = serialized[d]; @@ -1834,9 +1841,9 @@ Status FastParseSequenceExample( // Extract pointers to all features within this serialized example. while (!stream.ExpectAtEnd()) { - std::map* features = nullptr; - const std::map>* config = - nullptr; + absl::flat_hash_map* features = nullptr; + const absl::flat_hash_map>* + config = nullptr; if (stream.ExpectTag(kDelimitedTag(1))) { // Context features = context_features; -- GitLab From c715e350caa2f5de063c2e403ed56e8f387f7a75 Mon Sep 17 00:00:00 2001 From: Akshay Modi Date: Mon, 25 Feb 2019 12:05:38 -0800 Subject: [PATCH 470/766] Automated rollback of commit 082a349b260c3ceeab7c616700fbe19810191216 PiperOrigin-RevId: 235576212 --- tensorflow/python/keras/BUILD | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tensorflow/python/keras/BUILD b/tensorflow/python/keras/BUILD index 98052166ce..b854088e40 100755 --- a/tensorflow/python/keras/BUILD +++ b/tensorflow/python/keras/BUILD @@ -585,10 +585,6 @@ cuda_py_test( "//tensorflow/python:client_testlib", ], shard_count = 8, - tags = [ - "manual", # b/124471597 - "notap", # b/124471597 - ], xla_enable_strict_auto_jit = True, ) -- GitLab From 1c6f10152fe850463422108f03b6b022b8f24ccc Mon Sep 17 00:00:00 2001 From: James Keeling Date: Mon, 25 Feb 2019 12:08:41 -0800 Subject: [PATCH 471/766] Split up conv_ops_fused kernels. This improves build times by allowing the double, float, and half implementations to build in parallel. PiperOrigin-RevId: 235576953 --- tensorflow/contrib/makefile/tf_op_files.txt | 4 +- tensorflow/core/kernels/BUILD | 5 ++- .../core/kernels/conv_ops_fused_double.cc | 39 ++++++++++++++++++ .../core/kernels/conv_ops_fused_float.cc | 39 ++++++++++++++++++ .../core/kernels/conv_ops_fused_half.cc | 29 ++++++++++++++ ...onv_ops_fused.cc => conv_ops_fused_impl.h} | 40 +++++-------------- 6 files changed, 123 insertions(+), 33 deletions(-) create mode 100644 tensorflow/core/kernels/conv_ops_fused_double.cc create mode 100644 tensorflow/core/kernels/conv_ops_fused_float.cc create mode 100644 tensorflow/core/kernels/conv_ops_fused_half.cc rename tensorflow/core/kernels/{conv_ops_fused.cc => conv_ops_fused_impl.h} (97%) diff --git a/tensorflow/contrib/makefile/tf_op_files.txt b/tensorflow/contrib/makefile/tf_op_files.txt index 2cd7d6d519..0ed87544ce 100644 --- a/tensorflow/contrib/makefile/tf_op_files.txt +++ b/tensorflow/contrib/makefile/tf_op_files.txt @@ -43,7 +43,9 @@ tensorflow/core/kernels/conv_grad_input_ops.cc tensorflow/core/kernels/conv_grad_ops.cc tensorflow/core/kernels/conv_ops.cc tensorflow/core/kernels/conv_ops_3d.cc -tensorflow/core/kernels/conv_ops_fused.cc +tensorflow/core/kernels/conv_ops_fused_double.cc +tensorflow/core/kernels/conv_ops_fused_float.cc +tensorflow/core/kernels/conv_ops_fused_half.cc tensorflow/core/kernels/conv_ops_using_gemm.cc tensorflow/core/kernels/crop_and_resize_op.cc tensorflow/core/kernels/ctc_decoder_ops.cc diff --git a/tensorflow/core/kernels/BUILD b/tensorflow/core/kernels/BUILD index d176d3eca1..3b3ce6cd22 100644 --- a/tensorflow/core/kernels/BUILD +++ b/tensorflow/core/kernels/BUILD @@ -5624,7 +5624,10 @@ filegroup( "conv_grad_ops.h", "conv_ops.cc", "conv_ops_3d.cc", - "conv_ops_fused.cc", + "conv_ops_fused_double.cc", + "conv_ops_fused_float.cc", + "conv_ops_fused_half.cc", + "conv_ops_fused_impl.h", "conv_ops_using_gemm.cc", "crop_and_resize_op.cc", "crop_and_resize_op.h", diff --git a/tensorflow/core/kernels/conv_ops_fused_double.cc b/tensorflow/core/kernels/conv_ops_fused_double.cc new file mode 100644 index 0000000000..ae5dcb2400 --- /dev/null +++ b/tensorflow/core/kernels/conv_ops_fused_double.cc @@ -0,0 +1,39 @@ +/* Copyright 2019 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/register_types.h" +#include "tensorflow/core/kernels/conv_ops_fused_impl.h" + +namespace tensorflow { + +// If we're using the alternative GEMM-based implementation of Conv2D for the +// CPU implementation, don't register this EigenTensor-based version. +// TODO(b/119765980): Upgrade upstream Eigen to set `m_can_use_xsmm=false` for +// contractions with non-default contraction output kernels. +#if !defined(USE_GEMM_FOR_CONV) && !defined(EIGEN_USE_LIBXSMM) +TF_CALL_double(REGISTER_FUSED_CPU_CONV2D); +#endif // !USE_GEMM_FOR_CONV + +#if GOOGLE_CUDA + +namespace functor { +DECLARE_FUNCTOR_GPU_SPEC(double); +} // namespace functor + +TF_CALL_double(REGISTER_FUSED_GPU_CONV2D); + +#endif // GOOGLE_CUDA + +} // namespace tensorflow diff --git a/tensorflow/core/kernels/conv_ops_fused_float.cc b/tensorflow/core/kernels/conv_ops_fused_float.cc new file mode 100644 index 0000000000..979dcc5aa2 --- /dev/null +++ b/tensorflow/core/kernels/conv_ops_fused_float.cc @@ -0,0 +1,39 @@ +/* Copyright 2019 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/register_types.h" +#include "tensorflow/core/kernels/conv_ops_fused_impl.h" + +namespace tensorflow { + +// If we're using the alternative GEMM-based implementation of Conv2D for the +// CPU implementation, don't register this EigenTensor-based version. +// TODO(b/119765980): Upgrade upstream Eigen to set `m_can_use_xsmm=false` for +// contractions with non-default contraction output kernels. +#if !defined(USE_GEMM_FOR_CONV) && !defined(EIGEN_USE_LIBXSMM) +TF_CALL_float(REGISTER_FUSED_CPU_CONV2D); +#endif // !USE_GEMM_FOR_CONV + +#if GOOGLE_CUDA + +namespace functor { +DECLARE_FUNCTOR_GPU_SPEC(float); +} // namespace functor + +TF_CALL_float(REGISTER_FUSED_GPU_CONV2D); + +#endif // GOOGLE_CUDA + +} // namespace tensorflow diff --git a/tensorflow/core/kernels/conv_ops_fused_half.cc b/tensorflow/core/kernels/conv_ops_fused_half.cc new file mode 100644 index 0000000000..5a7d360fad --- /dev/null +++ b/tensorflow/core/kernels/conv_ops_fused_half.cc @@ -0,0 +1,29 @@ +/* Copyright 2019 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/register_types.h" +#include "tensorflow/core/kernels/conv_ops_fused_impl.h" + +namespace tensorflow { + +#if GOOGLE_CUDA + +namespace functor { +DECLARE_FUNCTOR_GPU_SPEC(Eigen::half); +} // namespace functor + +#endif // GOOGLE_CUDA + +} // namespace tensorflow diff --git a/tensorflow/core/kernels/conv_ops_fused.cc b/tensorflow/core/kernels/conv_ops_fused_impl.h similarity index 97% rename from tensorflow/core/kernels/conv_ops_fused.cc rename to tensorflow/core/kernels/conv_ops_fused_impl.h index 9c807c3375..bc0039497b 100644 --- a/tensorflow/core/kernels/conv_ops_fused.cc +++ b/tensorflow/core/kernels/conv_ops_fused_impl.h @@ -28,6 +28,9 @@ limitations under the License. // // NOTE: GPU only supports fusion of Conv2D + BiasAdd + . +#ifndef TENSORFLOW_CORE_KERNELS_CONV_OPS_FUSED_IMPL_H_ +#define TENSORFLOW_CORE_KERNELS_CONV_OPS_FUSED_IMPL_H_ + #define USE_EIGEN_TENSOR #define EIGEN_USE_THREADS @@ -63,7 +66,6 @@ namespace tensorflow { typedef Eigen::ThreadPoolDevice CPUDevice; typedef Eigen::GpuDevice GPUDevice; -namespace { // Supported Conv2D fusions. Not all of them supported on all type of devices. enum class FusedComputationType { // NOTE(ezhulenev): CuDNN `cudnnConvolutionBiasActivationForward` supports @@ -463,12 +465,12 @@ class FusedConvParameters : public ConvParameters { se::dnn::ActivationMode activation_mode_; }; -bool operator==(const FusedConvParameters& lhs, +inline bool operator==(const FusedConvParameters& lhs, const FusedConvParameters& rhs) { return lhs.get_data_as_tuple() == rhs.get_data_as_tuple(); } -bool operator!=(const FusedConvParameters& lhs, +inline bool operator!=(const FusedConvParameters& lhs, const FusedConvParameters& rhs) { return !(lhs == rhs); } @@ -482,7 +484,7 @@ using AutoTuneFusedConv = AutoTuneSingleton; -int64 ConvolveScratchSize() { +inline int64 ConvolveScratchSize() { static int64 convolve_scratch_size = GetDnnWorkspaceLimit( // default value is in bytes despite the name of the environment variable "TF_CUDNN_WORKSPACE_LIMIT_IN_MB", 1LL << 32 // 4GB @@ -822,8 +824,6 @@ struct LaunchFusedConv2DOp { #endif // GOOGLE_CUDA -} // namespace - template class FusedConv2DOp : public OpKernel { public: @@ -962,22 +962,9 @@ class FusedConv2DOp : public OpKernel { Name("_FusedConv2D").Device(DEVICE_CPU).TypeConstraint("T"), \ FusedConv2DOp); -// If we're using the alternative GEMM-based implementation of Conv2D for the -// CPU implementation, don't register this EigenTensor-based version. -// TODO(b/119765980): Upgrade upstream Eigen to set `m_can_use_xsmm=false` for -// contractions with non-default contraction output kernels. -#if !defined(USE_GEMM_FOR_CONV) && !defined(EIGEN_USE_LIBXSMM) -TF_CALL_float(REGISTER_FUSED_CPU_CONV2D); -TF_CALL_double(REGISTER_FUSED_CPU_CONV2D); -#endif // !USE_GEMM_FOR_CONV - -#undef REGISTER_FUSED_CPU_CONV2D - #if GOOGLE_CUDA -// Forward declarations of the functor specializations for GPU. -namespace functor { -#define DECLARE_GPU_SPEC(T) \ +#define DECLARE_FUNCTOR_GPU_SPEC(T) \ template <> \ void TransformFilter::operator()( \ const GPUDevice& d, FilterTensorFormat dst_filter_format, \ @@ -992,23 +979,14 @@ namespace functor { typename TTypes::Tensor out, TensorFormat data_format); \ extern template struct PadInput -DECLARE_GPU_SPEC(float); -DECLARE_GPU_SPEC(Eigen::half); -DECLARE_GPU_SPEC(double); -#undef DECLARE_GPU_SPEC -} // namespace functor - // Registration of the GPU implementations. #define REGISTER_FUSED_GPU_CONV2D(T) \ REGISTER_KERNEL_BUILDER( \ Name("_FusedConv2D").Device(DEVICE_GPU).TypeConstraint("T"), \ FusedConv2DOp); -TF_CALL_float(REGISTER_FUSED_GPU_CONV2D); -TF_CALL_double(REGISTER_FUSED_GPU_CONV2D); - -#undef REGISTER_FUSED_GPU_CONV2D - #endif // GOOGLE_CUDA } // namespace tensorflow + +#endif // TENSORFLOW_CORE_KERNELS_CONV_OPS_FUSED_IMPL_H_ -- GitLab From 4d6f8114b0c917046d47ad6faf8a0b79d00197e0 Mon Sep 17 00:00:00 2001 From: Allen Lavoie Date: Mon, 25 Feb 2019 12:21:39 -0800 Subject: [PATCH 472/766] Adds tf.train.experimental.PythonState Allows users to hook into TF's object-based checkpointing with arbitrary Python state. Doing this now for the TF Agents migration. This is a reasonable place to cutoff and expose a public API; the NumPy stuff can be copied out of contrib. Experimental for now pending Trackable being exposed, since it may make more sense to combine the Variable/Tensor saving API with the non-Tensor Python state API. PiperOrigin-RevId: 235579652 --- tensorflow/contrib/checkpoint/__init__.py | 3 +- .../contrib/checkpoint/python/python_state.py | 33 +-- tensorflow/python/BUILD | 1 + .../tools/api/generator/api_init_files.bzl | 1 + .../tools/api/generator/api_init_files_v1.bzl | 1 + tensorflow/python/training/tracking/BUILD | 20 ++ .../python/training/tracking/python_state.py | 92 +++++++ .../training/tracking/python_state_test.py | 244 ++++++++++++++++++ tensorflow/python/training/training.py | 2 + ...low.train.experimental.-python-state.pbtxt | 17 ++ .../v1/tensorflow.train.experimental.pbtxt | 7 + .../api/golden/v1/tensorflow.train.pbtxt | 4 + ...low.train.experimental.-python-state.pbtxt | 17 ++ .../v2/tensorflow.train.experimental.pbtxt | 7 + .../api/golden/v2/tensorflow.train.pbtxt | 4 + 15 files changed, 423 insertions(+), 30 deletions(-) create mode 100644 tensorflow/python/training/tracking/python_state.py create mode 100644 tensorflow/python/training/tracking/python_state_test.py create mode 100644 tensorflow/tools/api/golden/v1/tensorflow.train.experimental.-python-state.pbtxt create mode 100644 tensorflow/tools/api/golden/v1/tensorflow.train.experimental.pbtxt create mode 100644 tensorflow/tools/api/golden/v2/tensorflow.train.experimental.-python-state.pbtxt create mode 100644 tensorflow/tools/api/golden/v2/tensorflow.train.experimental.pbtxt diff --git a/tensorflow/contrib/checkpoint/__init__.py b/tensorflow/contrib/checkpoint/__init__.py index 7b3df96254..a416588691 100644 --- a/tensorflow/contrib/checkpoint/__init__.py +++ b/tensorflow/contrib/checkpoint/__init__.py @@ -46,7 +46,6 @@ from __future__ import print_function from tensorflow.contrib.checkpoint.python.containers import UniqueNameTracker from tensorflow.contrib.checkpoint.python.python_state import NumpyState -from tensorflow.contrib.checkpoint.python.python_state import PythonStateWrapper from tensorflow.contrib.checkpoint.python.split_dependency import split_dependency from tensorflow.contrib.checkpoint.python.visualize import dot_graph_from_checkpoint from tensorflow.core.protobuf.trackable_object_graph_pb2 import TrackableObjectGraph as CheckpointableObjectGraph @@ -55,6 +54,7 @@ from tensorflow.python.training.tracking.base import Trackable as Checkpointable from tensorflow.python.training.tracking.data_structures import List from tensorflow.python.training.tracking.data_structures import Mapping from tensorflow.python.training.tracking.data_structures import NoDependency +from tensorflow.python.training.tracking.python_state import PythonState as PythonStateWrapper from tensorflow.python.training.tracking.tracking import AutoTrackable as Checkpointable from tensorflow.python.training.tracking.util import capture_dependencies from tensorflow.python.training.tracking.util import list_objects @@ -62,3 +62,4 @@ from tensorflow.python.training.tracking.util import object_metadata from tensorflow.python.util.all_util import remove_undocumented remove_undocumented(module_name=__name__) + diff --git a/tensorflow/contrib/checkpoint/python/python_state.py b/tensorflow/contrib/checkpoint/python/python_state.py index 737a6c30c1..1ada05227b 100644 --- a/tensorflow/contrib/checkpoint/python/python_state.py +++ b/tensorflow/contrib/checkpoint/python/python_state.py @@ -17,13 +17,10 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -import abc -import functools -import six - import numpy from tensorflow.python.training.tracking import base +from tensorflow.python.training.tracking import python_state as core_python_state # pylint: disable=g-import-not-at-top try: @@ -129,29 +126,7 @@ class NumpyState(base.Trackable): super(NumpyState, self).__setattr__(name, value) -@six.add_metaclass(abc.ABCMeta) -class PythonStateWrapper(base.Trackable): - """Wraps a Python object for storage in an object-based checkpoint.""" - - @abc.abstractmethod - def _serialize(self): - """Callback for `PythonStringStateSaveable` to serialize the object.""" - - @abc.abstractmethod - def _deserialize(self, string_value): - """Callback for `PythonStringStateSaveable` to deserialize the object.""" - - def _gather_saveables_for_checkpoint(self): - """Specify callbacks for saving and restoring `array`.""" - return { - "py_state": functools.partial( - base.PythonStringStateSaveable, - state_callback=self._serialize, - restore_callback=self._deserialize) - } - - -class _NumpyWrapper(PythonStateWrapper): +class _NumpyWrapper(core_python_state.PythonState): """Wraps a NumPy array for storage in an object-based checkpoint.""" def __init__(self, array): @@ -162,7 +137,7 @@ class _NumpyWrapper(PythonStateWrapper): """ self.array = array - def _serialize(self): + def serialize(self): """Callback to serialize the array.""" string_file = BytesIO() try: @@ -172,7 +147,7 @@ class _NumpyWrapper(PythonStateWrapper): string_file.close() return serialized - def _deserialize(self, string_value): + def deserialize(self, string_value): """Callback to deserialize the array.""" string_file = BytesIO(string_value) try: diff --git a/tensorflow/python/BUILD b/tensorflow/python/BUILD index ab75a4c106..7a0e5ee7eb 100644 --- a/tensorflow/python/BUILD +++ b/tensorflow/python/BUILD @@ -3816,6 +3816,7 @@ py_library( "//tensorflow/python/keras/optimizer_v2:learning_rate_schedule", "//tensorflow/python/ops/losses", "//tensorflow/python/training/tracking:base", + "//tensorflow/python/training/tracking:python_state", "//tensorflow/python/training/tracking:util", "//third_party/py/numpy", "@six_archive//:six", diff --git a/tensorflow/python/tools/api/generator/api_init_files.bzl b/tensorflow/python/tools/api/generator/api_init_files.bzl index a1dd37f42f..5a5bb501e9 100644 --- a/tensorflow/python/tools/api/generator/api_init_files.bzl +++ b/tensorflow/python/tools/api/generator/api_init_files.bzl @@ -48,6 +48,7 @@ TENSORFLOW_API_INIT_FILES = [ "sysconfig/__init__.py", "test/__init__.py", "train/__init__.py", + "train/experimental/__init__.py", "version/__init__.py", # END GENERATED FILES ] diff --git a/tensorflow/python/tools/api/generator/api_init_files_v1.bzl b/tensorflow/python/tools/api/generator/api_init_files_v1.bzl index 43ef09b9fa..236115f2c3 100644 --- a/tensorflow/python/tools/api/generator/api_init_files_v1.bzl +++ b/tensorflow/python/tools/api/generator/api_init_files_v1.bzl @@ -70,6 +70,7 @@ TENSORFLOW_API_INIT_FILES_V1 = [ "sysconfig/__init__.py", "test/__init__.py", "train/__init__.py", + "train/experimental/__init__.py", "train/queue_runner/__init__.py", "user_ops/__init__.py", "version/__init__.py", diff --git a/tensorflow/python/training/tracking/BUILD b/tensorflow/python/training/tracking/BUILD index 9d49d67816..be53cdd22b 100644 --- a/tensorflow/python/training/tracking/BUILD +++ b/tensorflow/python/training/tracking/BUILD @@ -257,3 +257,23 @@ tf_py_test( "notsan", # b/74395663 ], ) + +py_library( + name = "python_state", + srcs = ["python_state.py"], + srcs_version = "PY2AND3", + deps = [ + ":base", + ], +) + +tf_py_test( + name = "python_state_test", + srcs = ["python_state_test.py"], + additional_deps = [ + ":base", + ":util", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework_test_lib", + ], +) diff --git a/tensorflow/python/training/tracking/python_state.py b/tensorflow/python/training/tracking/python_state.py new file mode 100644 index 0000000000..f05ca91a21 --- /dev/null +++ b/tensorflow/python/training/tracking/python_state.py @@ -0,0 +1,92 @@ +"""Utilities for including Python state in TensorFlow checkpoints.""" +# 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. +# ============================================================================== +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import abc +import functools + +import six + +from tensorflow.python.training.tracking import base +from tensorflow.python.util.tf_export import tf_export + + +@tf_export("train.experimental.PythonState") +@six.add_metaclass(abc.ABCMeta) +class PythonState(base.Trackable): + """A mixin for putting Python state in an object-based checkpoint. + + This is an abstract class which allows extensions to TensorFlow's object-based + checkpointing (see `tf.train.Checkpoint`). For example a wrapper for NumPy + arrays: + + ```python + import io + import numpy + + class NumpyWrapper(tf.train.experimental.PythonState): + + def __init__(self, array): + self.array = array + + def serialize(self): + string_file = io.BytesIO() + try: + numpy.save(string_file, self.array, allow_pickle=False) + serialized = string_file.getvalue() + finally: + string_file.close() + return serialized + + def deserialize(self, string_value): + string_file = io.BytesIO(string_value) + try: + self.array = numpy.load(string_file, allow_pickle=False) + finally: + string_file.close() + ``` + + Instances of `NumpyWrapper` are checkpointable objects, and will be saved and + restored from checkpoints along with TensorFlow state like variables. + + ```python + root = tf.train.Checkpoint(numpy=NumpyWrapper(numpy.array([1.]))) + save_path = root.save(prefix) + root.numpy.array *= 2. + assert [2.] == root.numpy.array + root.restore(save_path) + assert [1.] == root.numpy.array + ``` + """ + + @abc.abstractmethod + def serialize(self): + """Callback to serialize the object. Returns a string.""" + + @abc.abstractmethod + def deserialize(self, string_value): + """Callback to deserialize the object.""" + + def _gather_saveables_for_checkpoint(self): + """Specify callbacks for saving and restoring `array`.""" + return { + "py_state": functools.partial( + base.PythonStringStateSaveable, + state_callback=self.serialize, + restore_callback=self.deserialize) + } diff --git a/tensorflow/python/training/tracking/python_state_test.py b/tensorflow/python/training/tracking/python_state_test.py new file mode 100644 index 0000000000..038e2d1650 --- /dev/null +++ b/tensorflow/python/training/tracking/python_state_test.py @@ -0,0 +1,244 @@ +# 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. +# ============================================================================== +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import io +import os + +import numpy + +from tensorflow.python.client import session +from tensorflow.python.framework import ops +from tensorflow.python.framework import test_util +from tensorflow.python.ops import variables +from tensorflow.python.platform import test +from tensorflow.python.training.tracking import base +from tensorflow.python.training.tracking import python_state +from tensorflow.python.training.tracking import util + + +class _NumpyState(base.Trackable): + """A checkpointable object whose NumPy array attributes are saved/restored. + + Example usage: + + ```python + arrays = _NumpyState() + checkpoint = tf.train.Checkpoint(numpy_arrays=arrays) + arrays.x = numpy.zeros([3, 4]) + save_path = checkpoint.save("/tmp/ckpt") + arrays.x[1, 1] = 4. + checkpoint.restore(save_path) + assert (arrays.x == numpy.zeros([3, 4])).all() + + second_checkpoint = tf.train.Checkpoint( + numpy_arrays=_NumpyState()) + # Attributes of NumpyState objects are created automatically by restore() + second_checkpoint.restore(save_path) + assert (second_checkpoint.numpy_arrays.x == numpy.zeros([3, 4])).all() + ``` + + Note that `NumpyState` objects re-create the attributes of the previously + saved object on `restore()`. This is in contrast to TensorFlow variables, for + which a `Variable` object must be created and assigned to an attribute. + + This snippet works both when graph building and when executing eagerly. On + save, the NumPy array(s) are fed as strings to be saved in the checkpoint (via + a placeholder when graph building, or as a string constant when executing + eagerly). When restoring they skip the TensorFlow graph entirely, and so no + restore ops need be run. This means that restoration always happens eagerly, + rather than waiting for `checkpoint.restore(...).run_restore_ops()` like + TensorFlow variables when graph building. + """ + + def _lookup_dependency(self, name): + """Create placeholder NumPy arrays for to-be-restored attributes. + + Typically `_lookup_dependency` is used to check by name whether a dependency + exists. We cheat slightly by creating a checkpointable object for `name` if + we don't already have one, giving us attribute re-creation behavior when + loading a checkpoint. + + Args: + name: The name of the dependency being checked. + Returns: + An existing dependency if one exists, or a new `_NumpyWrapper` placeholder + dependency (which will generally be restored immediately). + """ + value = super(_NumpyState, self)._lookup_dependency(name) + if value is None: + value = _NumpyWrapper(numpy.array([])) + new_reference = base.TrackableReference(name=name, ref=value) + self._unconditional_checkpoint_dependencies.append(new_reference) + self._unconditional_dependency_names[name] = value + super(_NumpyState, self).__setattr__(name, value) + return value + + def __getattribute__(self, name): + """Un-wrap `_NumpyWrapper` objects when accessing attributes.""" + value = super(_NumpyState, self).__getattribute__(name) + if isinstance(value, _NumpyWrapper): + return value.array + return value + + def __setattr__(self, name, value): + """Automatically wrap NumPy arrays assigned to attributes.""" + # TODO(allenl): Consider supporting lists/tuples, either ad-hoc or by making + # ndarrays checkpointable natively and using standard checkpointable list + # tracking. + if isinstance(value, (numpy.ndarray, numpy.generic)): + try: + existing = super(_NumpyState, self).__getattribute__(name) + existing.array = value + return + except AttributeError: + value = _NumpyWrapper(value) + self._track_trackable(value, name=name, overwrite=True) + elif (name not in ("_setattr_tracking", "_update_uid") + and getattr(self, "_setattr_tracking", True)): + # Mixing restore()-created attributes with user-added checkpointable + # objects is tricky, since we can't use the `_lookup_dependency` trick to + # re-create attributes (we might accidentally steal the restoration for + # another checkpointable object). For now `_NumpyState` objects must be + # leaf nodes. Theoretically we could add some extra arguments to + # `_lookup_dependency` to figure out whether we should create a NumPy + # array for the attribute or not. + raise NotImplementedError( + ("Assigned %s to the %s property of %s, which is not a NumPy array. " + "Currently mixing NumPy arrays and other checkpointable objects is " + "not supported. File a feature request if this limitation bothers " + "you.") + % (value, name, self)) + super(_NumpyState, self).__setattr__(name, value) + + +class _NumpyWrapper(python_state.PythonState): + """Wraps a NumPy array for storage in an object-based checkpoint.""" + + def __init__(self, array): + """Specify a NumPy array to wrap. + + Args: + array: The NumPy array to save and restore (may be overwritten). + """ + self.array = array + + def serialize(self): + """Callback to serialize the array.""" + string_file = io.BytesIO() + try: + numpy.save(string_file, self.array, allow_pickle=False) + serialized = string_file.getvalue() + finally: + string_file.close() + return serialized + + def deserialize(self, string_value): + """Callback to deserialize the array.""" + string_file = io.BytesIO(string_value) + try: + self.array = numpy.load(string_file, allow_pickle=False) + finally: + string_file.close() + + +class NumpyStateTests(test.TestCase): + + def testWrapper(self): + directory = self.get_temp_dir() + prefix = os.path.join(directory, "ckpt") + root = util.Checkpoint(numpy=_NumpyWrapper(numpy.array([1.]))) + save_path = root.save(prefix) + root.numpy.array *= 2. + self.assertEqual([2.], root.numpy.array) + root.restore(save_path) + self.assertEqual([1.], root.numpy.array) + + @test_util.run_in_graph_and_eager_modes + def testSaveRestoreNumpyState(self): + directory = self.get_temp_dir() + prefix = os.path.join(directory, "ckpt") + save_state = _NumpyState() + saver = util.Checkpoint(numpy=save_state) + save_state.a = numpy.ones([2, 2]) + save_state.b = numpy.ones([2, 2]) + save_state.b = numpy.zeros([2, 2]) + save_state.c = numpy.int64(3) + self.assertAllEqual(numpy.ones([2, 2]), save_state.a) + self.assertAllEqual(numpy.zeros([2, 2]), save_state.b) + self.assertEqual(3, save_state.c) + first_save_path = saver.save(prefix) + save_state.a[1, 1] = 2. + save_state.c = numpy.int64(4) + second_save_path = saver.save(prefix) + + load_state = _NumpyState() + loader = util.Checkpoint(numpy=load_state) + loader.restore(first_save_path).initialize_or_restore() + self.assertAllEqual(numpy.ones([2, 2]), load_state.a) + self.assertAllEqual(numpy.zeros([2, 2]), load_state.b) + self.assertEqual(3, load_state.c) + load_state.a[0, 0] = 42. + self.assertAllEqual([[42., 1.], [1., 1.]], load_state.a) + loader.restore(first_save_path).run_restore_ops() + self.assertAllEqual(numpy.ones([2, 2]), load_state.a) + loader.restore(second_save_path).run_restore_ops() + self.assertAllEqual([[1., 1.], [1., 2.]], load_state.a) + self.assertAllEqual(numpy.zeros([2, 2]), load_state.b) + self.assertEqual(4, load_state.c) + + def testNoGraphPollution(self): + graph = ops.Graph() + with graph.as_default(), session.Session(): + directory = self.get_temp_dir() + prefix = os.path.join(directory, "ckpt") + save_state = _NumpyState() + saver = util.Checkpoint(numpy=save_state) + save_state.a = numpy.ones([2, 2]) + save_path = saver.save(prefix) + saver.restore(save_path) + graph.finalize() + saver.save(prefix) + save_state.a = numpy.zeros([2, 2]) + saver.save(prefix) + saver.restore(save_path) + + @test_util.run_in_graph_and_eager_modes + def testNoMixedNumpyStateTF(self): + save_state = _NumpyState() + save_state.a = numpy.ones([2, 2]) + with self.assertRaises(NotImplementedError): + save_state.v = variables.Variable(1.) + + @test_util.run_in_graph_and_eager_modes + def testDocstringExample(self): + arrays = _NumpyState() + checkpoint = util.Checkpoint(numpy_arrays=arrays) + arrays.x = numpy.zeros([3, 4]) + save_path = checkpoint.save(os.path.join(self.get_temp_dir(), "ckpt")) + arrays.x[1, 1] = 4. + checkpoint.restore(save_path) + self.assertAllEqual(numpy.zeros([3, 4]), arrays.x) + + second_checkpoint = util.Checkpoint(numpy_arrays=_NumpyState()) + second_checkpoint.restore(save_path) + self.assertAllEqual(numpy.zeros([3, 4]), second_checkpoint.numpy_arrays.x) + + +if __name__ == "__main__": + ops.enable_eager_execution() + test.main() diff --git a/tensorflow/python/training/training.py b/tensorflow/python/training/training.py index 5a15278889..9f509ae0a3 100644 --- a/tensorflow/python/training/training.py +++ b/tensorflow/python/training/training.py @@ -68,6 +68,7 @@ from tensorflow.python.training.basic_session_run_hooks import FinalOpsHook from tensorflow.python.training.basic_session_run_hooks import FeedFnHook from tensorflow.python.training.basic_session_run_hooks import ProfilerHook from tensorflow.python.training.basic_loops import basic_train_loop +from tensorflow.python.training.tracking.python_state import PythonState from tensorflow.python.training.tracking.util import Checkpoint from tensorflow.python.training.checkpoint_utils import init_from_checkpoint from tensorflow.python.training.checkpoint_utils import list_variables @@ -142,3 +143,4 @@ tf_export(v1=["train.SaverDef"])(SaverDef) tf_export("train.SequenceExample")(SequenceExample) tf_export("train.ServerDef")(ServerDef) # pylint: enable=undefined-variable + diff --git a/tensorflow/tools/api/golden/v1/tensorflow.train.experimental.-python-state.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.train.experimental.-python-state.pbtxt new file mode 100644 index 0000000000..59bc0179ae --- /dev/null +++ b/tensorflow/tools/api/golden/v1/tensorflow.train.experimental.-python-state.pbtxt @@ -0,0 +1,17 @@ +path: "tensorflow.train.experimental.PythonState" +tf_class { + is_instance: "" + is_instance: "" + is_instance: "" + member_method { + name: "__init__" + } + member_method { + name: "deserialize" + argspec: "args=[\'self\', \'string_value\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "serialize" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/v1/tensorflow.train.experimental.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.train.experimental.pbtxt new file mode 100644 index 0000000000..2761b489b9 --- /dev/null +++ b/tensorflow/tools/api/golden/v1/tensorflow.train.experimental.pbtxt @@ -0,0 +1,7 @@ +path: "tensorflow.train.experimental" +tf_module { + member { + name: "PythonState" + mtype: "" + } +} diff --git a/tensorflow/tools/api/golden/v1/tensorflow.train.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.train.pbtxt index ab7b199d80..551fda2eac 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.train.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.train.pbtxt @@ -240,6 +240,10 @@ tf_module { name: "WorkerSessionCreator" mtype: "" } + member { + name: "experimental" + mtype: "" + } member { name: "queue_runner" mtype: "" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.train.experimental.-python-state.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.train.experimental.-python-state.pbtxt new file mode 100644 index 0000000000..59bc0179ae --- /dev/null +++ b/tensorflow/tools/api/golden/v2/tensorflow.train.experimental.-python-state.pbtxt @@ -0,0 +1,17 @@ +path: "tensorflow.train.experimental.PythonState" +tf_class { + is_instance: "" + is_instance: "" + is_instance: "" + member_method { + name: "__init__" + } + member_method { + name: "deserialize" + argspec: "args=[\'self\', \'string_value\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "serialize" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.train.experimental.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.train.experimental.pbtxt new file mode 100644 index 0000000000..2761b489b9 --- /dev/null +++ b/tensorflow/tools/api/golden/v2/tensorflow.train.experimental.pbtxt @@ -0,0 +1,7 @@ +path: "tensorflow.train.experimental" +tf_module { + member { + name: "PythonState" + mtype: "" + } +} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.train.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.train.pbtxt index 4f293fb40d..59c7c17c52 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.train.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.train.pbtxt @@ -68,6 +68,10 @@ tf_module { name: "ServerDef" mtype: "" } + member { + name: "experimental" + mtype: "" + } member_method { name: "get_checkpoint_state" argspec: "args=[\'checkpoint_dir\', \'latest_filename\'], varargs=None, keywords=None, defaults=[\'None\'], " -- GitLab From e19d945009bdb44cfab63b344373258084c4bf8b Mon Sep 17 00:00:00 2001 From: William Chargin Date: Mon, 25 Feb 2019 13:04:07 -0800 Subject: [PATCH 473/766] Fix profile plugin output directory on Windows Windows tests are (correctly, not spuriously) failing with tensorflow.python.framework.errors_impl.InvalidArgumentError: Failed to create a directory: T:\tmp\\tmp4vh90go6\tb/plugins/profile\2019-02-22_15:46:14; Invalid argument Because the profiler plugin?s director is hard-coded to use a `/` separator rather than the platform-specific separator. The root cause is <#26021>. The astute reader will note that there are _two_ forward slashes in the above log: `?\tb/plugins/profile/?`. This patch fixes the second, but whence the first? In fact, it is but a trick of the light. The calling code actually asks to create `?\tb\plugins/profile/?`, but after the implementation of `CreateRecursiveDirectory` creates the `?\tb` directory, it adjoins `/plugins` (hard-coded forward slash) to the running path to form the next directory to be created. This is harmless; it just makes the log message a bit subtle to understand. Furthermore, the colon in the last component of the directory name is an invalid path character on Windows. This patch also changes the colons to hyphens. Tested: This fixes the `callbacks_test` and `callbacks_v1_test` test cases on the Windows Kokoro build. (Other tests on that build still fail.) PiperOrigin-RevId: 235587998 --- tensorflow/python/eager/profiler.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tensorflow/python/eager/profiler.py b/tensorflow/python/eager/profiler.py index c7aba1d2bf..5f0d770f1f 100644 --- a/tensorflow/python/eager/profiler.py +++ b/tensorflow/python/eager/profiler.py @@ -44,7 +44,6 @@ from tensorflow.python.framework import c_api_util from tensorflow.python.platform import gfile from tensorflow.python.platform import tf_logging as logging -LOGDIR_PLUGIN = 'plugins/profile' _profiler = None _profiler_lock = threading.Lock() @@ -118,8 +117,8 @@ def save(logdir, result): result: profiling result returned by stop(). """ plugin_dir = os.path.join( - logdir, LOGDIR_PLUGIN, - datetime.datetime.now().strftime('%Y-%m-%d_%H:%M:%S')) + logdir, 'plugins', 'profile', + datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')) gfile.MakeDirs(plugin_dir) with gfile.Open(os.path.join(plugin_dir, 'local.trace'), 'wb') as f: f.write(result) -- GitLab From b29b3f506ae547309a54ccb2c014db853b876114 Mon Sep 17 00:00:00 2001 From: Gaurav Jain Date: Mon, 25 Feb 2019 13:06:40 -0800 Subject: [PATCH 474/766] New configuration mechanism for eager mode. Expose setting various ConfigProto options as individual functions. We choose to expose these as individual functions as they become easier to maintain and deprecate if needed. Most of these options are required to be set at program startup. However, in the future we can enable setting some of these options at runtime. Fixes #25138 PiperOrigin-RevId: 235588442 --- .../contrib/distribute/python/tpu_strategy.py | 2 +- .../contrib/optimizer_v2/optimizer_v2.py | 4 +- .../core/common_runtime/eager/context.h | 3 +- tensorflow/python/BUILD | 25 ++ .../collective_all_reduce_strategy.py | 2 +- .../python/distribute/cross_device_ops.py | 2 +- .../python/distribute/mirrored_strategy.py | 4 +- tensorflow/python/eager/BUILD | 1 + tensorflow/python/eager/context.py | 211 +++++++++++--- tensorflow/python/eager/core_test.py | 18 +- tensorflow/python/eager/ops_test.py | 12 +- tensorflow/python/framework/config.py | 259 ++++++++++++++++++ tensorflow/python/framework/config_test.py | 215 +++++++++++++++ .../api/golden/v1/tensorflow.debugging.pbtxt | 8 + .../api/golden/v2/tensorflow.debugging.pbtxt | 8 + 15 files changed, 707 insertions(+), 67 deletions(-) create mode 100644 tensorflow/python/framework/config.py create mode 100644 tensorflow/python/framework/config_test.py diff --git a/tensorflow/contrib/distribute/python/tpu_strategy.py b/tensorflow/contrib/distribute/python/tpu_strategy.py index 2d9d221f42..051a3890d7 100644 --- a/tensorflow/contrib/distribute/python/tpu_strategy.py +++ b/tensorflow/contrib/distribute/python/tpu_strategy.py @@ -545,7 +545,7 @@ class TPUExtended(distribute_lib.DistributionStrategyExtended): with ops.device(device): return array_ops.identity(value_list[0].initial_value) kwargs["initial_value"] = initial_value_fn - with context.context().device_policy(context.DEVICE_PLACEMENT_SILENT): + with context.device_policy(context.DEVICE_PLACEMENT_SILENT): v = next_creator(*args, **kwargs) assert not isinstance(v, values.TPUMirroredVariable) value_list.append(v) diff --git a/tensorflow/contrib/optimizer_v2/optimizer_v2.py b/tensorflow/contrib/optimizer_v2/optimizer_v2.py index a7f978634e..436ece79a7 100644 --- a/tensorflow/contrib/optimizer_v2/optimizer_v2.py +++ b/tensorflow/contrib/optimizer_v2/optimizer_v2.py @@ -965,7 +965,7 @@ class OptimizerV2(optimizer_v1.Optimizer): # `update_op`. # TODO(josh11b): Make different state objects for each device to # avoid needing to set the device_policy. - device_policy = context.context().device_policy( + device_policy = context.device_policy( context.DEVICE_PLACEMENT_SILENT) with ops.name_scope("update_" + scope_name), device_policy: return processor.update_op(self, g, state) @@ -981,7 +981,7 @@ class OptimizerV2(optimizer_v1.Optimizer): 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): + with context.device_policy(context.DEVICE_PLACEMENT_SILENT): return self._finish(state) update_ops = control_flow_ops.group(update_ops) diff --git a/tensorflow/core/common_runtime/eager/context.h b/tensorflow/core/common_runtime/eager/context.h index 330936e233..73f4b350d3 100644 --- a/tensorflow/core/common_runtime/eager/context.h +++ b/tensorflow/core/common_runtime/eager/context.h @@ -63,8 +63,7 @@ enum ContextDevicePlacementPolicy { // 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. + // Placement policy which silently copies int32 tensors but not other dtypes. DEVICE_PLACEMENT_SILENT_FOR_INT32 = 3, }; diff --git a/tensorflow/python/BUILD b/tensorflow/python/BUILD index 7a0e5ee7eb..52fd243500 100644 --- a/tensorflow/python/BUILD +++ b/tensorflow/python/BUILD @@ -121,6 +121,7 @@ py_library( ":clustering_ops", ":collective_ops", ":cond_v2", + ":config", ":confusion_matrix", ":control_flow_ops", ":cudnn_rnn_ops_gen", @@ -996,6 +997,30 @@ tf_py_test( ], ) +py_library( + name = "config", + srcs = ["framework/config.py"], + srcs_version = "PY2AND3", + deps = [ + ":framework_ops", + ":util", + "//tensorflow/python/eager:context", + ], +) + +tf_py_test( + name = "config_test", + size = "small", + srcs = ["framework/config_test.py"], + additional_deps = [ + ":config", + ":constant_op", + ":client_testlib", + ":platform", + ":util", + ], +) + py_library( name = "random_seed", srcs = ["framework/random_seed.py"], diff --git a/tensorflow/python/distribute/collective_all_reduce_strategy.py b/tensorflow/python/distribute/collective_all_reduce_strategy.py index 2cf22d29bc..0fc67efc17 100644 --- a/tensorflow/python/distribute/collective_all_reduce_strategy.py +++ b/tensorflow/python/distribute/collective_all_reduce_strategy.py @@ -246,7 +246,7 @@ class CollectiveAllReduceExtended(mirrored_strategy.MirroredExtended): return array_ops.identity(value_list[0].initial_value) kwargs["initial_value"] = _overridden_initial_value_fn - with context.context().device_policy(context.DEVICE_PLACEMENT_SILENT): + with context.device_policy(context.DEVICE_PLACEMENT_SILENT): # Don't record operations (e.g. other variable reads) during # variable creation. with tape.stop_recording(): diff --git a/tensorflow/python/distribute/cross_device_ops.py b/tensorflow/python/distribute/cross_device_ops.py index afb7a35c0a..6f16b4d290 100644 --- a/tensorflow/python/distribute/cross_device_ops.py +++ b/tensorflow/python/distribute/cross_device_ops.py @@ -209,7 +209,7 @@ def _simple_reduce(per_replica_value, reduce_to_device, accumulation_fn, count = len(all_values) with ops.device(reduce_to_device): - with context.context().device_policy(context.DEVICE_PLACEMENT_SILENT): + with context.device_policy(context.DEVICE_PLACEMENT_SILENT): reduced = cross_device_utils.aggregate_tensors_or_indexed_slices( all_values, accumulation_fn) if reduce_op == reduce_util.ReduceOp.MEAN: diff --git a/tensorflow/python/distribute/mirrored_strategy.py b/tensorflow/python/distribute/mirrored_strategy.py index c171fa42ee..44bfd8718b 100644 --- a/tensorflow/python/distribute/mirrored_strategy.py +++ b/tensorflow/python/distribute/mirrored_strategy.py @@ -533,7 +533,7 @@ class MirroredExtended(distribute_lib.DistributionStrategyExtended): init_value = value_list[0].initial_value return array_ops.identity(init_value) kwargs["initial_value"] = initial_value_fn - with context.context().device_policy(context.DEVICE_PLACEMENT_SILENT): + with context.device_policy(context.DEVICE_PLACEMENT_SILENT): # Don't record operations (e.g. other variable reads) during # variable creation. with tape.stop_recording(): @@ -853,7 +853,7 @@ class _MirroredReplicaThread(threading.Thread): _enter_graph(self._init_graph, self._init_in_eager), \ _enter_graph(self.graph, self.in_eager, self._variable_creator_stack), \ - context.context().device_policy(self.context_device_policy), \ + context.device_policy(self.context_device_policy), \ MirroredReplicaContext(self.distribution, constant_op.constant( self.replica_id, dtypes.int32)), \ ops.device(self.device_map.logical_to_actual_devices(0)[ diff --git a/tensorflow/python/eager/BUILD b/tensorflow/python/eager/BUILD index 45e2682aac..98b1551986 100644 --- a/tensorflow/python/eager/BUILD +++ b/tensorflow/python/eager/BUILD @@ -436,6 +436,7 @@ cuda_py_test( ":test", "//third_party/py/numpy", "//tensorflow/python:array_ops", + "//tensorflow/python:config", "//tensorflow/python:control_flow_ops", "//tensorflow/python:dtypes", "//tensorflow/python:errors", diff --git a/tensorflow/python/eager/context.py b/tensorflow/python/eager/context.py index 4364b49258..e25243c7bb 100644 --- a/tensorflow/python/eager/context.py +++ b/tensorflow/python/eager/context.py @@ -83,6 +83,7 @@ class _EagerTensorCache(object): class FunctionCallOptions(object): """Options applied at call sites of eager functions. + Eager functions are functions decorated with tf.contrib.eager.defun. """ @@ -251,27 +252,27 @@ class Context(object): options for the Context. Note that a lot of these options may be currently unimplemented or irrelevant when eager execution is enabled. device_policy: (Optional.) What policy to use when trying to run an - operation on a device with inputs which are not on that device. - When set to None, an appropriate value will be picked automatically. - The value picked may change between TensorFlow releases. - - Defaults to tf.contrib.eager.DEVICE_PLACEMENT_SILENT_FOR_INT32. - Valid values: - - tfe.DEVICE_PLACEMENT_EXPLICIT: raises an error if the placement is - not correct. - - tfe.DEVICE_PLACEMENT_WARN: copies the tensors which are not on the - right device but raises a warning. - - tfe.DEVICE_PLACEMENT_SILENT: silently copies the tensors. This might - hide performance problems. - - tfe.DEVICE_PLACEMENT_SILENT_FOR_INT32: silently copies int32 tensors, - raising errors on the other ones. + operation on a device with inputs which are not on that device. + When set to None, an appropriate value will be picked automatically. + The value picked may change between TensorFlow releases. + + Defaults to DEVICE_PLACEMENT_SILENT. + Valid values: + - DEVICE_PLACEMENT_EXPLICIT: raises an error if the placement is + not correct. + - DEVICE_PLACEMENT_WARN: copies the tensors which are not on the + right device but raises a warning. + - DEVICE_PLACEMENT_SILENT: silently copies the tensors. This might + hide performance problems. + - DEVICE_PLACEMENT_SILENT_FOR_INT32: silently copies int32 tensors, + raising errors on the other ones. execution_mode: (Optional.) Policy controlling how operations dispatched are actually executed. When set to None, an appropriate value will be picked automatically. The value picked may change between TensorFlow releases. Valid values: - - tf.contrib.eager.SYNC: executes each operation synchronously. - - tf.contrib.eager.ASYNC: executes each operation asynchronously. These + - SYNC: executes each operation synchronously. + - ASYNC: executes each operation asynchronously. These operations may return "non-ready" handles. server_def: (Optional.) A tensorflow::ServerDef proto. Enables execution on remote devices. GrpcServers need to be started by @@ -287,9 +288,13 @@ class Context(object): self._context_handle = None self._context_devices = None self._post_execution_callbacks = [] + if config is None: + config = config_pb2.ConfigProto() self._config = config self._seed = None self._initialize_lock = threading.Lock() + if device_policy is None: + device_policy = DEVICE_PLACEMENT_SILENT self._device_policy = device_policy if execution_mode not in (None, SYNC, ASYNC): raise ValueError( @@ -594,31 +599,35 @@ class Context(object): """List of the names of devices available to execute operations.""" return self._devices - def get_execution_mode(self): + @property + def execution_mode(self): + """Gets execution mode for current thread.""" + # Only get the execution mode from the context if it has already been + # initialized + if self._context_handle is None: + return self._execution_mode + mode = self._eager_context.execution_mode if mode is None: mode = self._execution_mode return mode - def set_execution_mode(self, mode): + @execution_mode.setter + def execution_mode(self, mode): """Sets execution mode for current thread.""" if mode not in (None, SYNC, ASYNC): raise ValueError( "Execution mode should be None/SYNC/ASYNC. Got %s" % mode) if mode is None: mode = SYNC - self._eager_context.execution_mode = mode - pywrap_tensorflow.TFE_ContextSetAsyncForThread(self._handle, mode == ASYNC) - @tf_contextlib.contextmanager - def execution_mode(self, mode): - """Context manager for setting execution mode for current thread.""" - old_mode = self.get_execution_mode() - try: - self.set_execution_mode(mode) - yield - finally: - self.set_execution_mode(old_mode) + if self._execution_mode != mode: + self._execution_mode = mode + # Only set the execution mode if the context has already been initialized + if self._context_handle is not None: + self._eager_context.execution_mode = mode + pywrap_tensorflow.TFE_ContextSetAsyncForThread( + self._handle, mode == ASYNC) def get_function_call_options(self): """Returns function call options for current thread. @@ -725,17 +734,98 @@ class Context(object): """Get the list of post-execution callbacks added to the context.""" return self._post_execution_callbacks - @tf_contextlib.contextmanager + @property + def gpu_per_process_memory_fraction(self): + return self._config.gpu_options.per_process_gpu_memory_fraction + + @gpu_per_process_memory_fraction.setter + def gpu_per_process_memory_fraction(self, fraction): + if self._context_handle is not None: + raise RuntimeError( + "GPU options must be set at program startup") + + self._config.gpu_options.per_process_gpu_memory_fraction = fraction + + @property + def gpu_per_process_memory_growth(self): + return self._config.gpu_options.allow_growth + + @gpu_per_process_memory_growth.setter + def gpu_per_process_memory_growth(self, enabled): + if self._context_handle is not None: + raise RuntimeError( + "GPU options must be set at program startup") + + self._config.gpu_options.allow_growth = enabled + + @property + def intra_op_parallelism_threads(self): + return self._config.intra_op_parallelism_threads + + @intra_op_parallelism_threads.setter + def intra_op_parallelism_threads(self, num_threads): + if self._context_handle is not None: + raise RuntimeError( + "Intra op parallelism must be set at program startup") + + self._config.intra_op_parallelism_threads = num_threads + + @property + def inter_op_parallelism_threads(self): + return self._config.inter_op_parallelism_threads + + @inter_op_parallelism_threads.setter + def inter_op_parallelism_threads(self, num_threads): + if self._context_handle is not None: + raise RuntimeError( + "Inter op parallelism must be set at program startup") + + self._config.inter_op_parallelism_threads = num_threads + + @property + def soft_device_placement(self): + return self._config.allow_soft_placement + + @soft_device_placement.setter + def soft_device_placement(self, enabled): + if self._context_handle is not None: + raise RuntimeError( + "Soft placement must be set at program startup") + + self._config.allow_soft_placement = enabled + + @property + def log_device_placement(self): + return self._config.log_device_placement + + @log_device_placement.setter + def log_device_placement(self, enabled): + if self._context_handle is not None: + raise RuntimeError( + "Device placement logging must be set at program startup") + + self._config.log_device_placement = enabled + + @property + def device_policy(self): + # Only get the policy from the context if it has already been initialized + if self._context_handle is not None: + return pywrap_tensorflow.TFE_ContextGetDevicePlacementPolicy(self._handle) + + return self._device_policy + + @device_policy.setter def device_policy(self, policy): - handle = self._handle - old = pywrap_tensorflow.TFE_ContextGetDevicePlacementPolicy(handle) - pywrap_tensorflow.TFE_ContextSetThreadLocalDevicePlacementPolicy( - handle, policy) - try: - yield - finally: - pywrap_tensorflow.TFE_ContextSetThreadLocalDevicePlacementPolicy( - handle, old) + if policy is None: + policy = DEVICE_PLACEMENT_SILENT + + if self._device_policy != policy: + self._device_policy = policy + + # Only set the policy if the context has already been initialized + if self._context_handle is not None: + pywrap_tensorflow.TFE_ContextSetThreadLocalDevicePlacementPolicy( + self._handle, self._device_policy) def enable_run_metadata(self): """Enables tracing of op execution via RunMetadata. @@ -931,14 +1021,53 @@ def list_devices(): return context().devices() +@tf_export("debugging.get_log_device_placement") +def get_log_device_placement(): + """Get if device placements are logged. + + Returns: + If device placements are logged. + """ + return context().log_device_placement + + +@tf_export("debugging.set_log_device_placement") +def set_log_device_placement(enabled): + """Set if device placements should be logged. + + Args: + enabled: Whether to enabled device placement logging. + """ + context().log_device_placement = enabled + + +@tf_contextlib.contextmanager +def device_policy(policy): + """Context manager for setting device placement policy for current thread.""" + ctx = context() + old_policy = ctx.device_policy + try: + ctx.device_policy = policy + yield + finally: + ctx.device_policy = old_policy + + def set_execution_mode(mode): """Sets execution mode for the current thread.""" - context().set_execution_mode(mode) + context().execution_mode = mode +@tf_contextlib.contextmanager def execution_mode(mode): """Context manager for setting execution mode for current thread.""" - return context().execution_mode(mode) + ctx = context() + old_mode = ctx.execution_mode + try: + ctx.execution_mode = mode + yield + finally: + ctx.execution_mode = old_mode @tf_export("experimental.function_executor_type") diff --git a/tensorflow/python/eager/core_test.py b/tensorflow/python/eager/core_test.py index 5432abab60..753c43314e 100644 --- a/tensorflow/python/eager/core_test.py +++ b/tensorflow/python/eager/core_test.py @@ -65,15 +65,11 @@ class TFETest(test_util.TensorFlowTestCase): ctx.scope_name = 'foo' self.assertEqual('foo', ctx.scope_name) - self.assertEqual(context.SYNC, ctx.get_execution_mode()) - ctx.set_execution_mode(context.ASYNC) - self.assertEqual(context.ASYNC, ctx.get_execution_mode()) - ctx.set_execution_mode(context.SYNC) - self.assertEqual(context.SYNC, ctx.get_execution_mode()) - with ctx.execution_mode(context.ASYNC): - self.assertEqual(context.ASYNC, ctx.get_execution_mode()) - ctx.set_execution_mode(context.SYNC) - self.assertEqual(context.SYNC, ctx.get_execution_mode()) + self.assertEqual(context.SYNC, ctx.execution_mode) + ctx.execution_mode = context.ASYNC + self.assertEqual(context.ASYNC, ctx.execution_mode) + ctx.execution_mode = context.SYNC + self.assertEqual(context.SYNC, ctx.execution_mode) self.assertIsNone(ctx.summary_writer_resource) ctx.summary_writer_resource = 'mock' @@ -259,7 +255,7 @@ class TFETest(test_util.TensorFlowTestCase): self.skipTest('No GPUs found') constant = constant_op.constant(1.0) with ops.device('gpu:0'): - with context.context().device_policy(context.DEVICE_PLACEMENT_SILENT): + with context.device_policy(context.DEVICE_PLACEMENT_SILENT): c = constant + 1.0 self.assertAllEqual(c, 2.0) @@ -315,7 +311,7 @@ class TFETest(test_util.TensorFlowTestCase): three.dtype.as_datatype_enum)) context.async_wait() context.async_clear_error() - context.set_execution_mode(context.SYNC) + context.execution_mode = context.SYNC def testExecuteTooManyNumOutputs(self): # num_outputs provided is 50, but only one output is produced. diff --git a/tensorflow/python/eager/ops_test.py b/tensorflow/python/eager/ops_test.py index ab4bdaa601..0eb0e6cfbe 100644 --- a/tensorflow/python/eager/ops_test.py +++ b/tensorflow/python/eager/ops_test.py @@ -22,10 +22,10 @@ import weakref import numpy as np -from tensorflow.core.protobuf import config_pb2 from tensorflow.python.eager import context from tensorflow.python.eager import execute from tensorflow.python.eager import test +from tensorflow.python.framework import config from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes from tensorflow.python.framework import ops @@ -274,9 +274,9 @@ class OpsTest(test_util.TensorFlowTestCase): # Temporarily replace the context # pylint: disable=protected-access del context._context + context._context = context.Context() try: - context._context = context.Context( - device_policy=context.DEVICE_PLACEMENT_SILENT) + config.set_device_policy('silent') cpu_tensor = constant_op.constant(1.0) gpu_tensor = cpu_tensor.gpu() self.assertAllEqual(cpu_tensor + gpu_tensor, 2.0) @@ -291,10 +291,10 @@ class OpsTest(test_util.TensorFlowTestCase): # Temporarily replace the context # pylint: disable=protected-access del context._context + context._context = context.Context() try: - context._context = context.Context( - device_policy=context.DEVICE_PLACEMENT_SILENT, - config=config_pb2.ConfigProto(allow_soft_placement=True)) + config.set_device_policy('silent') + config.set_soft_device_placement(True) cpu_tensor = constant_op.constant(1.0) result = cpu_tensor + cpu_tensor self.assertEqual(result.device, diff --git a/tensorflow/python/framework/config.py b/tensorflow/python/framework/config.py new file mode 100644 index 0000000000..de79ca3b2a --- /dev/null +++ b/tensorflow/python/framework/config.py @@ -0,0 +1,259 @@ +# Copyright 2019 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 for configuring TensorFlow execution.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.python.eager import context +from tensorflow.python.util.tf_export import tf_export + + +@tf_export('config.gpu.get_per_process_memory_fraction') +def get_gpu_per_process_memory_fraction(): + """Get fraction of the available GPU memory to allocate for each process. + + 1.0 means to allocate all of the GPU memory, 0.5 means the process allocates + up to half of the available GPU memory. + + Returns: + Current GPU per process memory fraction + """ + return context.context().gpu_per_process_memory_fraction + + +@tf_export('config.gpu.set_per_process_memory_fraction') +def set_gpu_per_process_memory_fraction(fraction): + """Set fraction of the available GPU memory to allocate for each process. + + 1.0 means to allocate all of the GPU memory, 0.5 means the process allocates + up to half of the available GPU memory. + + Args: + fraction: Fraction of GPU memory to allocate + """ + context.context().gpu_per_process_memory_fraction = fraction + + +@tf_export('config.gpu.get_per_process_memory_growth') +def get_gpu_per_process_memory_growth(): + """Get if GPU memory should be pre-allocated or allowed to grow. + + Returns: + If GPU memory growth should be enabled + """ + return context.context().gpu_per_process_memory_growth + + +@tf_export('config.gpu.set_per_process_memory_growth') +def set_gpu_per_process_memory_growth(enabled): + """Set if GPU memory should be pre-allocated or allowed to grow. + + Args: + enabled: Indicates if GPU memory growth should be enabled + """ + context.context().gpu_per_process_memory_growth = enabled + + +@tf_export('config.threading.intra_op_parallelism_threads') +def get_intra_op_parallelism_threads(): + """Get number of threads used within an individual op for parallelism. + + Certain operations like matrix multiplication and reductions can utilize + parellel threads for speed ups. A value of 0 means the system picks an + appropriate number. + + Returns: + Number of parallel threads + """ + return context.context().intra_op_parallelism_threads + + +@tf_export('config.threading.set_intra_op_parallelism_threads') +def set_intra_op_parallelism_threads(num_threads): + """Set number of threads used within an individual op for parallelism. + + Certain operations like matrix multiplication and reductions can utilize + parellel threads for speed ups. A value of 0 means the system picks an + appropriate number. + + Args: + num_threads: Number of parallel threads + """ + context.context().intra_op_parallelism_threads = num_threads + + +@tf_export('config.threading.inter_op_parallelism_threads') +def get_inter_op_parallelism_threads(): + """Get number of threads used for parallelism between independent operations. + + Determines the number of threads used by independent non-blokcing operations. + 0 means the system picks an appropriate number. + + Returns: + Number of parallel threads + """ + return context.context().inter_op_parallelism_threads + + +@tf_export('config.threading.set_inter_op_parallelism_threads') +def set_inter_op_parallelism_threads(num_threads): + """Set number of threads used for parallelism between independent operations. + + Determines the number of threads used by independent non-blokcing operations. + 0 means the system picks an appropriate number. + + Args: + num_threads: Number of parallel threads + """ + context.context().inter_op_parallelism_threads = num_threads + + +@tf_export('config.get_soft_device_placement') +def get_soft_device_placement(): + """Get if soft device placement is enabled. + + If enabled, an op will be placed on CPU if any of the following are true + 1. there's no GPU implementation for the OP + 2. no GPU devices are known or registered + 3. need to co-locate with reftype input(s) which are from CPU + + Returns: + If soft placement is enabled. + """ + return context.context().soft_device_placement + + +@tf_export('config.set_soft_device_placement') +def set_soft_device_placement(enabled): + """Set if soft device placement is enabled. + + If enabled, an op will be placed on CPU if any of the following are true + 1. there's no GPU implementation for the OP + 2. no GPU devices are known or registered + 3. need to co-locate with reftype input(s) which are from CPU + + Args: + enabled: Whether to enabled soft placement. + """ + context.context().soft_device_placement = enabled + + +@tf_export('config.experimental.get_device_policy') +def get_device_policy(): + """Gets the current device policy. + + The device policy controls how operations requiring inputs on a specific + device (e.g., on GPU:0) handle inputs on a different device (e.g. GPU:1). + + This function only gets the device policy for the current thread. Any + subsequently started thread will again use the default policy. + + Returns: + Current thread device policy + """ + device_policy = context.context().device_policy + if device_policy == context.DEVICE_PLACEMENT_SILENT: + return 'silent' + elif device_policy == context.DEVICE_PLACEMENT_SILENT_FOR_INT32: + return 'silent_for_int32' + elif device_policy == context.DEVICE_PLACEMENT_WARN: + return 'warn' + elif device_policy == context.DEVICE_PLACEMENT_EXPLICIT: + return 'explicit' + else: + raise ValueError('Not a valid device policy: %r' % device_policy) + + +@tf_export('config.experimental.set_device_policy') +def set_device_policy(device_policy): + """Sets the current thread device policy. + + The device policy controls how operations requiring inputs on a specific + device (e.g., on GPU:0) handle inputs on a different device (e.g. GPU:1). + + When using the default, an appropriate policy will be picked automatically. + The default policy may change over time. + + This function only sets the device policy for the current thread. Any + subsequently started thread will again use the default policy. + + Args: + device_policy: A device policy. + Valid values: + - None: Switch to a system default. + - 'warn': Copies the tensors which are not on the right device and logs + a warning. + - 'explicit': Raises an error if the placement is not as required. + - 'silent': Silently copies the tensors. Note that this may hide + performance problems as there is no notification provided when + operations are blocked on the tensor being copied between devices. + - 'silent_for_int32': silently copies `int32` tensors, raising errors on + the other ones. + + Raises: + ValueError: If an invalid `device_policy` is passed. + """ + if device_policy == 'silent': + context.context().device_policy = context.DEVICE_PLACEMENT_SILENT + elif device_policy == 'silent_for_int32': + context.context().device_policy = context.DEVICE_PLACEMENT_SILENT_FOR_INT32 + elif device_policy == 'warn': + context.context().device_policy = context.DEVICE_PLACEMENT_WARN + elif device_policy == 'explicit': + context.context().device_policy = context.DEVICE_PLACEMENT_EXPLICIT + elif device_policy is None: + context.context().device_policy = None + else: + raise ValueError('Not a valid device policy: %r' % device_policy) + + +@tf_export('config.experimental.get_synchronous_execution') +def get_synchronous_execution(): + """Gets whether operations are executed synchronously or asynchronously. + + TensorFlow can execute operations synchronously or asynchronously. If + asynchronous execution is enabled, operations may return "non-ready" handles. + + Returns: + Current thread execution mode + """ + return context.context().execution_mode == context.SYNC + + +@tf_export('config.experimental.set_synchronous_execution') +def set_synchronous_execution(enable): + """Specifies whether operations are executed synchronously or asynchronously. + + TensorFlow can execute operations synchronously or asynchronously. If + asynchronous execution is enabled, operations may return "non-ready" handles. + + When `enable` is set to None, an appropriate value will be picked + automatically. The value picked may change between TensorFlow releases. + + Args: + enable: Whether operations should be dispatched synchronously. + Valid values: + - None: sets the system default. + - True: executes each operation synchronously. + - False: executes each operation asynchronously. + """ + if enable is None: + context.context().execution_mode = None + elif enable: + context.context().execution_mode = context.SYNC + else: + context.context().execution_mode = context.ASYNC diff --git a/tensorflow/python/framework/config_test.py b/tensorflow/python/framework/config_test.py new file mode 100644 index 0000000000..e1efe2ac4a --- /dev/null +++ b/tensorflow/python/framework/config_test.py @@ -0,0 +1,215 @@ +# Copyright 2019 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 that the system configuration methods work properly.""" + +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 config +from tensorflow.python.framework import constant_op +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import errors +from tensorflow.python.framework import ops +from tensorflow.python.framework import test_util +from tensorflow.python.platform import test + + +def reset_eager(fn): + def wrapper(*args, **kwargs): + try: + return fn(*args, **kwargs) + finally: + del context._context + context._context = context.Context() + ops.enable_eager_execution() + + return wrapper + + +class ConfigTest(test.TestCase): + + @test_util.run_gpu_only + @reset_eager + def testDevicePolicy(self): + self.assertEqual(context.DEVICE_PLACEMENT_SILENT, + context.context().device_policy) + + # If no op has been executed we should be able to set the device policy as + # well as any init-time configs. + config.set_intra_op_parallelism_threads(1) + config.set_device_policy('silent') + config.set_intra_op_parallelism_threads(2) + + # Excute a dummy op to ensure that the context has been initialized + constant_op.constant(1) + + def copy_tensor(dtype=dtypes.int32): + cpu_tensor = constant_op.constant(1, dtype=dtype) + gpu_tensor = cpu_tensor.gpu() + self.assertAllEqual(cpu_tensor + gpu_tensor, 2.0) + + config.set_device_policy('silent') + self.assertEqual(config.get_device_policy(), 'silent') + self.assertEqual(context.DEVICE_PLACEMENT_SILENT, + context.context().device_policy) + copy_tensor() + + config.set_device_policy('silent_for_int32') + self.assertEqual(config.get_device_policy(), 'silent_for_int32') + self.assertEqual(context.DEVICE_PLACEMENT_SILENT_FOR_INT32, + context.context().device_policy) + with self.assertRaisesRegexp(errors.InvalidArgumentError, + 'Tensors on conflicting devices'): + copy_tensor(dtypes.float32) + copy_tensor() + + config.set_device_policy('warn') + self.assertEqual(config.get_device_policy(), 'warn') + self.assertEqual(context.DEVICE_PLACEMENT_WARN, + context.context().device_policy) + copy_tensor() + + config.set_device_policy('explicit') + self.assertEqual(config.get_device_policy(), 'explicit') + self.assertEqual(context.DEVICE_PLACEMENT_EXPLICIT, + context.context().device_policy) + with self.assertRaisesRegexp(errors.InvalidArgumentError, + 'Tensors on conflicting devices'): + copy_tensor() + + config.set_device_policy(None) + self.assertEqual(config.get_device_policy(), 'silent') + + @reset_eager + def testExecutionMode(self): + self.assertTrue(config.get_synchronous_execution()) + self.assertEqual(context.SYNC, context.context().execution_mode) + + # If no op has been executed we should be able to set the execution mode as + # well as any init-time configs. + config.set_intra_op_parallelism_threads(1) + config.set_synchronous_execution(False) + config.set_intra_op_parallelism_threads(2) + + config.set_synchronous_execution(True) + self.assertTrue(config.get_synchronous_execution()) + self.assertEqual(context.SYNC, context.context().execution_mode) + config.set_synchronous_execution(False) + self.assertFalse(config.get_synchronous_execution()) + self.assertEqual(context.ASYNC, context.context().execution_mode) + + @reset_eager + def testGpuPerProcessMemoryFraction(self): + config.set_gpu_per_process_memory_fraction(0.5) + self.assertEqual( + config.get_gpu_per_process_memory_fraction(), + context.context().gpu_per_process_memory_fraction) + + constant_op.constant(1) + with self.assertRaises(RuntimeError): + config.set_gpu_per_process_memory_fraction(0.5) + + @reset_eager + def testGpuPerProcessMemoryGrowth(self): + self.assertFalse(config.get_gpu_per_process_memory_growth()) + + config.set_gpu_per_process_memory_growth(True) + self.assertTrue(config.get_gpu_per_process_memory_growth()) + self.assertEqual( + config.get_gpu_per_process_memory_growth(), + context.context().gpu_per_process_memory_growth) + + config.set_gpu_per_process_memory_growth(False) + self.assertFalse(config.get_gpu_per_process_memory_growth()) + self.assertEqual( + config.get_gpu_per_process_memory_growth(), + context.context().gpu_per_process_memory_growth) + + constant_op.constant(1) + with self.assertRaises(RuntimeError): + config.set_gpu_per_process_memory_growth(True) + + @reset_eager + def testIntraOpParallelismThreads(self): + config.set_intra_op_parallelism_threads(10) + self.assertEqual( + config.get_intra_op_parallelism_threads(), + context.context().intra_op_parallelism_threads) + + constant_op.constant(1) + with self.assertRaises(RuntimeError): + config.set_intra_op_parallelism_threads(1) + + @reset_eager + def testInterOpParallelismThreads(self): + config.set_inter_op_parallelism_threads(10) + self.assertEqual( + config.get_inter_op_parallelism_threads(), + context.context().inter_op_parallelism_threads) + + constant_op.constant(1) + with self.assertRaises(RuntimeError): + config.set_inter_op_parallelism_threads(1) + + @reset_eager + def testEnableSoftPlacement(self): + self.assertEqual(config.get_soft_device_placement(), False) + + config.set_soft_device_placement(True) + self.assertEqual(config.get_soft_device_placement(), True) + self.assertEqual( + config.get_soft_device_placement(), + context.context().soft_device_placement) + + config.set_soft_device_placement(False) + self.assertEqual(config.get_soft_device_placement(), False) + self.assertEqual( + config.get_soft_device_placement(), + context.context().soft_device_placement) + + constant_op.constant(1) + with self.assertRaises(RuntimeError): + config.set_soft_device_placement(True) + with self.assertRaises(RuntimeError): + config.set_soft_device_placement(False) + + @reset_eager + def testLogDevicePlacement(self): + self.assertEqual(context.get_log_device_placement(), False) + + context.set_log_device_placement(True) + self.assertEqual(context.get_log_device_placement(), True) + self.assertEqual( + context.get_log_device_placement(), + context.context().log_device_placement) + + context.set_log_device_placement(False) + self.assertEqual(context.get_log_device_placement(), False) + self.assertEqual( + context.get_log_device_placement(), + context.context().log_device_placement) + + constant_op.constant(1) + with self.assertRaises(RuntimeError): + context.set_log_device_placement(True) + with self.assertRaises(RuntimeError): + context.set_log_device_placement(False) + + +if __name__ == '__main__': + ops.enable_eager_execution() + test.main() diff --git a/tensorflow/tools/api/golden/v1/tensorflow.debugging.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.debugging.pbtxt index bf6816a0ab..07684dd92e 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.debugging.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.debugging.pbtxt @@ -88,6 +88,10 @@ tf_module { name: "check_numerics" argspec: "args=[\'tensor\', \'message\', \'name\'], varargs=None, keywords=None, defaults=[\'None\'], " } + member_method { + name: "get_log_device_placement" + argspec: "args=[], varargs=None, keywords=None, defaults=None" + } member_method { name: "is_finite" argspec: "args=[\'x\', \'name\'], varargs=None, keywords=None, defaults=[\'None\'], " @@ -112,4 +116,8 @@ tf_module { name: "is_strictly_increasing" argspec: "args=[\'x\', \'name\'], varargs=None, keywords=None, defaults=[\'None\'], " } + member_method { + name: "set_log_device_placement" + argspec: "args=[\'enabled\'], varargs=None, keywords=None, defaults=None" + } } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.debugging.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.debugging.pbtxt index 314aedda90..f47cd12d2a 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.debugging.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.debugging.pbtxt @@ -88,8 +88,16 @@ tf_module { name: "check_numerics" argspec: "args=[\'tensor\', \'message\', \'name\'], varargs=None, keywords=None, defaults=[\'None\'], " } + member_method { + name: "get_log_device_placement" + argspec: "args=[], varargs=None, keywords=None, defaults=None" + } member_method { name: "is_numeric_tensor" argspec: "args=[\'tensor\'], varargs=None, keywords=None, defaults=None" } + member_method { + name: "set_log_device_placement" + argspec: "args=[\'enabled\'], varargs=None, keywords=None, defaults=None" + } } -- GitLab From d75b5505ac5e393d08aef7bf07e6ccecbd6773a9 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 25 Feb 2019 13:27:46 -0800 Subject: [PATCH 475/766] This CL allows users to pass an arbitrary list or tuple of tensors or lists to the RaggedTensor from_tensor() method. Doing this allows users to densify and un-densify arbitrarily deep nested tensors. PiperOrigin-RevId: 235591802 --- tensorflow/python/ops/ragged/BUILD | 3 + .../ops/ragged/ragged_from_tensor_op_test.py | 56 ++++++++++++++- tensorflow/python/ops/ragged/ragged_tensor.py | 70 +++++++++++++------ 3 files changed, 105 insertions(+), 24 deletions(-) diff --git a/tensorflow/python/ops/ragged/BUILD b/tensorflow/python/ops/ragged/BUILD index 4f29fcc41d..b980efe77c 100644 --- a/tensorflow/python/ops/ragged/BUILD +++ b/tensorflow/python/ops/ragged/BUILD @@ -599,12 +599,15 @@ py_test( srcs = ["ragged_from_tensor_op_test.py"], srcs_version = "PY2AND3", deps = [ + ":ragged_string_ops", ":ragged_tensor", ":ragged_test_util", "//tensorflow/python:array_ops", "//tensorflow/python:constant_op", + "//tensorflow/python:dtypes", "//tensorflow/python:framework_test_lib", "//tensorflow/python:math_ops", + "//tensorflow/python:nn_ops", "//tensorflow/python:platform_test", "@absl_py//absl/testing:parameterized", ], diff --git a/tensorflow/python/ops/ragged/ragged_from_tensor_op_test.py b/tensorflow/python/ops/ragged/ragged_from_tensor_op_test.py index 68d3953f4c..d4e1faf888 100644 --- a/tensorflow/python/ops/ragged/ragged_from_tensor_op_test.py +++ b/tensorflow/python/ops/ragged/ragged_from_tensor_op_test.py @@ -45,6 +45,13 @@ class RaggedTensorToSparseOpTest(ragged_test_util.RaggedTensorTestCase, self.assertRaggedEqual( RaggedTensor.from_tensor(dt, padding=0), [[5, 7], [0, 3], [6]]) + dt_3d = constant_op.constant([[[5, 0], [7, 0], [0, 0]], + [[0, 0], [3, 0], [0, 0]], + [[6, 0], [0, 0], [0, 0]]]) + self.assertRaggedEqual( + RaggedTensor.from_tensor(dt_3d, lengths=([2, 0, 3], [1, 1, 2, 0, 1])), + [[[5], [7]], [], [[6, 0], [], [0]]]) + @parameterized.parameters( # 2D test cases, no length or padding. { @@ -251,6 +258,24 @@ class RaggedTensorToSparseOpTest(ragged_test_util.RaggedTensorTestCase, 'expected': [[[[1, 0], [2, 3]], [[0, 0], [4, 0]]], [[[5, 6], [7, 0]], [[0, 8]]]] }, + { + 'tensor': [[[[1, 0], [2, 3]], [[0, 0], [4, 0]]], + [[[5, 6], [7, 0]], [[0, 8], [0, 0]]]], + 'lengths': ([2, 2], [1, 2, 2, 1]), + 'expected': [[[[1, 0]], [[0, 0], [4, 0]]], + [[[5, 6], [7, 0]], [[0, 8]]]], + 'ragged_rank': 2, + 'use_ragged_rank': False + }, + { + 'tensor': [[[[1, 0], [2, 3]], [[0, 0], [4, 0]]], + [[[5, 6], [7, 0]], [[0, 8], [0, 0]]]], + 'lengths': [[2, 2], [1, 2, 2, 1]], + 'expected': [[[[1, 0]], [[0, 0], [4, 0]]], + [[[5, 6], [7, 0]], [[0, 8]]]], + 'ragged_rank': 2, + 'use_ragged_rank': False + }, { 'tensor': [[[[1, 0], [2, 3]], [[0, 0], [4, 0]]], [[[5, 6], [7, 0]], [[0, 8], [0, 0]]]], @@ -259,15 +284,37 @@ class RaggedTensorToSparseOpTest(ragged_test_util.RaggedTensorTestCase, 'expected': [[[[1], [2, 3]], [[], [4]]], [[[5, 6], [7]], [[0, 8], []]]] }, + { + 'tensor': [[[[1, 0], [2, 3]], [[0, 0], [4, 0]]], + [[[5, 6], [7, 0]], [[0, 8], [0, 0]]]], + 'lengths': ([2, 2], [2, 2, 2, 2], [1, 2, 0, 1, 2, 1, 2, 0]), + 'expected': [[[[1], [2, 3]], [[], [4]]], + [[[5, 6], [7]], [[0, 8], []]]], + 'ragged_rank': 3, + 'use_ragged_rank': False + }, + { + 'tensor': [[[[1, 0], [2, 3]], [[0, 0], [4, 0]]], + [[[5, 6], [7, 0]], [[0, 8], [0, 0]]]], + 'lengths': [[2, 2], [2, 2, 2, 2], [1, 2, 0, 1, 2, 1, 2, 0]], + 'expected': [[[[1], [2, 3]], [[], [4]]], + [[[5, 6], [7]], [[0, 8], []]]], + 'ragged_rank': 3, + 'use_ragged_rank': False + }, ) # pyformat: disable def testRaggedFromTensor(self, tensor, expected, lengths=None, padding=None, - ragged_rank=1): + ragged_rank=1, + use_ragged_rank=True): dt = constant_op.constant(tensor) - rt = RaggedTensor.from_tensor(dt, lengths, padding, ragged_rank) + if use_ragged_rank: + rt = RaggedTensor.from_tensor(dt, lengths, padding, ragged_rank) + else: + rt = RaggedTensor.from_tensor(dt, lengths, padding) self.assertEqual(type(rt), RaggedTensor) self.assertEqual(rt.ragged_rank, ragged_rank) self.assertTrue( @@ -411,6 +458,11 @@ class RaggedTensorToSparseOpTest(ragged_test_util.RaggedTensorTestCase, 'lengths': [0.5], 'error': (TypeError, 'lengths must be an integer tensor') }, + { + 'tensor': [[1, 2, 3]], + 'lengths': [[1], [1]], + 'error': (ValueError, r'Shape \(1, 3\) must have rank at least 3') + }, { 'tensor': [[1]], 'padding': 'a', diff --git a/tensorflow/python/ops/ragged/ragged_tensor.py b/tensorflow/python/ops/ragged/ragged_tensor.py index 8fb3c1f44c..27438ff6bd 100644 --- a/tensorflow/python/ops/ragged/ragged_tensor.py +++ b/tensorflow/python/ops/ragged/ragged_tensor.py @@ -1053,11 +1053,12 @@ class RaggedTensor(composite_tensor.CompositeTensor): The set of absent/default values may be specified using a vector of lengths or a padding value (but not both). If `lengths` is specified, then the - output tensor will satisfy `output[row] = tensor[row][:lengths[row]]`. - If `padding` is specified, then any row *suffix* consisting entirely of - `padding` will be excluded from the returned `RaggedTensor`. If neither - `lengths` nor `padding` is specified, then the returned `RaggedTensor` will - have no absent/default values. + output tensor will satisfy `output[row] = tensor[row][:lengths[row]]`. If + 'lengths' is a list of lists or tuple of lists, those lists will be used + as nested row lengths. If `padding` is specified, then any row *suffix* + consisting entirely of `padding` will be excluded from the returned + `RaggedTensor`. If neither `lengths` nor `padding` is specified, then the + returned `RaggedTensor` will have no absent/default values. Examples: @@ -1065,10 +1066,17 @@ class RaggedTensor(composite_tensor.CompositeTensor): >>> dt = tf.constant([[5, 7, 0], [0, 3, 0], [6, 0, 0]]) >>> tf.RaggedTensor.from_tensor(dt) - >>> tf.RaggedTensor.from_tensor(dt, lengths=[2, 0, 3]) - + >>> tf.RaggedTensor.from_tensor(dt, lengths=[1, 0, 3]) + + >>> tf.RaggedTensor.from_tensor(dt, padding=0) + + >>> dt = tf.constant([[[5, 0], [7, 0], [0, 0]], + [[0, 0], [3, 0], [0, 0]], + [[6, 0], [0, 0], [0, 0]]]) + >>> tf.RaggedTensor.from_tensor(dt, lengths=([2, 0, 3], [1, 1, 2, 0, 1])) + ``` Args: @@ -1077,7 +1085,10 @@ class RaggedTensor(composite_tensor.CompositeTensor): lengths: An optional set of row lengths, specified using a 1-D integer `Tensor` whose length is equal to `tensor.shape[0]` (the number of rows in `tensor`). If specified, then `output[row]` will contain - `tensor[row][:lengths[row]]`. Negative lengths are treated as zero. + `tensor[row][:lengths[row]]`. Negative lengths are treated as zero. You + may optionally pass a list or tuple of lengths to this argument, which + will be used as nested row lengths to construct a ragged tensor with + multiple ragged dimensions. padding: An optional padding value. If specified, then any row suffix consisting entirely of `padding` will be excluded from the returned RaggedTensor. `padding` is a `Tensor` with the same dtype as `tensor` @@ -1167,21 +1178,36 @@ class RaggedTensor(composite_tensor.CompositeTensor): math_ops.range(1, ncols + 1), 0)) lengths = math_ops.reduce_max(length_for_nondefault_value, axis=1) - # If we have lengths (either directly supplied, or computed from - # paddings), then use those to construct splits; and then use masking - # to get the corresponding values. if lengths is not None: - lengths = ragged_util.convert_to_int_tensor(lengths, "lengths", - dtypes.int64) - lengths.shape.assert_has_rank(1) - lengths = math_ops.minimum(lengths, ncols) - lengths = math_ops.maximum(lengths, 0) - limits = math_ops.cumsum(lengths) - splits = array_ops.concat([array_ops.zeros([1], dtypes.int64), limits], - axis=0) - mask = array_ops.sequence_mask(lengths, maxlen=ncols) - values = array_ops.boolean_mask(tensor, mask) - return cls.from_row_splits(values, splits) + if isinstance(lengths, + (list, tuple)) and len(lengths) and not isinstance( + lengths[0], (int, float)): + # In this case, we've been given nested row lengths. Rather than + # reconstructing the tensor mask directly, we can recreate it as + # a boolean RaggedTensor, then densify that and use that as the + # mask to clear out the unused data in the passed tensor. + tensor.shape.with_rank_at_least(len(lengths) + 1) + num_tokens = math_ops.reduce_sum(lengths[-1]) + ones_mask = array_ops.ones([num_tokens], dtype=dtypes.bool) + ragged_mask = cls.from_nested_row_lengths(ones_mask, lengths) + dense_ragged_mask = ragged_mask.to_tensor(default_value=False) + masked_data = array_ops.boolean_mask(tensor, dense_ragged_mask) + return cls.from_nested_row_lengths(masked_data, lengths) + else: + # If we have lengths (either directly supplied, or computed from + # paddings), then use those to construct splits; and then use masking + # to get the corresponding values. + lengths = ragged_util.convert_to_int_tensor(lengths, "lengths", + dtypes.int64) + lengths.shape.assert_has_rank(1) + lengths = math_ops.minimum(lengths, ncols) + lengths = math_ops.maximum(lengths, 0) + limits = math_ops.cumsum(lengths) + splits = array_ops.concat( + [array_ops.zeros([1], dtypes.int64), limits], axis=0) + mask = array_ops.sequence_mask(lengths, maxlen=ncols) + values = array_ops.boolean_mask(tensor, mask) + return cls.from_row_splits(values, splits) # If neither padding nor lengths were specified, then create a splits # vector that contains no default values, and reshape the input tensor -- GitLab From a526c6d195190cc635fbd79fc1084958c1eaaeaf Mon Sep 17 00:00:00 2001 From: Sanjoy Das Date: Mon, 25 Feb 2019 13:37:34 -0800 Subject: [PATCH 476/766] [TF:XLA] Bump open source llvm revision to r354791 PiperOrigin-RevId: 235593700 --- tensorflow/workspace.bzl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl index 091f1371cf..68d680abcd 100755 --- a/tensorflow/workspace.bzl +++ b/tensorflow/workspace.bzl @@ -513,11 +513,11 @@ def tf_workspace(path_prefix = "", tf_repo_name = ""): tf_http_archive( name = "llvm", build_file = clean_dep("//third_party/llvm:llvm.autogenerated.BUILD"), - sha256 = "dd88ca5853dde6200d205fc01f6fb2b2f30ef31d62ceb579e0c4081b2e6af399", - strip_prefix = "llvm-1d53cf7ef3397d91f5a1310e0c4542e7b8c4b682", + sha256 = "cf564d5b14db5e2a66b098d8eb1e6e288e7ea7f8aea1a8d1bbf3f1c8a6cca37f", + strip_prefix = "llvm-78f7736cbd364bcf143ced306d3219fa1dc77ad1", urls = [ - "https://mirror.bazel.build/github.com/llvm-mirror/llvm/archive/1d53cf7ef3397d91f5a1310e0c4542e7b8c4b682.tar.gz", - "https://github.com/llvm-mirror/llvm/archive/1d53cf7ef3397d91f5a1310e0c4542e7b8c4b682.tar.gz", + "https://mirror.bazel.build/github.com/llvm-mirror/llvm/archive/78f7736cbd364bcf143ced306d3219fa1dc77ad1.tar.gz", + "https://github.com/llvm-mirror/llvm/archive/78f7736cbd364bcf143ced306d3219fa1dc77ad1.tar.gz", ], ) -- GitLab From 507ffd071f0a2af8a9f5678dae225e50479b44c9 Mon Sep 17 00:00:00 2001 From: Nick Kreeger Date: Mon, 25 Feb 2019 13:39:14 -0800 Subject: [PATCH 477/766] Add eager header files to the Windows libtensorflow GPU builds. PiperOrigin-RevId: 235594017 --- tensorflow/tools/ci_build/windows/libtensorflow_gpu.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tensorflow/tools/ci_build/windows/libtensorflow_gpu.sh b/tensorflow/tools/ci_build/windows/libtensorflow_gpu.sh index 9c05db974b..11730d4788 100644 --- a/tensorflow/tools/ci_build/windows/libtensorflow_gpu.sh +++ b/tensorflow/tools/ci_build/windows/libtensorflow_gpu.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 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. @@ -60,13 +60,16 @@ rm -f ${DIR}/tensorflow_jni.dll # Zip up the .dll, LICENSE and include files for the C library. mkdir -p ${DIR}/include/tensorflow/c +mkdir -p ${DIR}/include/tensorflow/c/eager mkdir -p ${DIR}/lib cp bazel-bin/tensorflow/libtensorflow.so ${DIR}/lib/tensorflow.dll cp tensorflow/c/c_api.h ${DIR}/include/tensorflow/c +cp tensorflow/c/eager/c_api.h ${DIR}/include/tensorflow/c/eager cp bazel-genfiles/tensorflow/tools/lib_package/include/tensorflow/c/LICENSE ${DIR}/include/tensorflow/c cd ${DIR} zip -j libtensorflow-gpu-windows-$(uname -m).zip \ lib/tensorflow.dll \ + include/tensorflow/c/eager/c_api.h \ include/tensorflow/c/c_api.h \ include/tensorflow/c/LICENSE rm -rf lib include -- GitLab From 65a9d18257d8bb8533dc43c9f8bdd9b0e1367306 Mon Sep 17 00:00:00 2001 From: Akshay Modi Date: Mon, 25 Feb 2019 13:45:59 -0800 Subject: [PATCH 478/766] Order the captures in the same order as the graph being lifted from as far as possible PiperOrigin-RevId: 235595330 --- tensorflow/python/eager/BUILD | 12 ++++ tensorflow/python/eager/lift_to_graph.py | 21 ++++++- tensorflow/python/eager/lift_to_graph_test.py | 56 +++++++++++++++++++ 3 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 tensorflow/python/eager/lift_to_graph_test.py diff --git a/tensorflow/python/eager/BUILD b/tensorflow/python/eager/BUILD index 98b1551986..d0afee3a41 100644 --- a/tensorflow/python/eager/BUILD +++ b/tensorflow/python/eager/BUILD @@ -524,6 +524,18 @@ py_library( deps = [ ":context", "//tensorflow/python:framework_ops", + "@six_archive//:six", + ], +) + +tf_py_test( + name = "lift_to_graph_test", + size = "medium", + srcs = ["lift_to_graph_test.py"], + additional_deps = [ + "lift_to_graph", + "//tensorflow/python/eager:test", + "//tensorflow/python:framework_ops", ], ) diff --git a/tensorflow/python/eager/lift_to_graph.py b/tensorflow/python/eager/lift_to_graph.py index 2ed2d5882e..949ff5ef49 100644 --- a/tensorflow/python/eager/lift_to_graph.py +++ b/tensorflow/python/eager/lift_to_graph.py @@ -20,6 +20,7 @@ from __future__ import division from __future__ import print_function import collections +import six from tensorflow.python.framework import func_graph from tensorflow.python.framework import ops @@ -287,11 +288,25 @@ def lift_to_graph(init_tensors, graph, sources=None, with graph.as_default(): op_map = {i: i for i in variable_init_tensors} # Pass through variables. source_ops = set() + # Add the sources in the same order as the original graph. + for s in six.itervalues(captures): + if s in sources: + sources.remove(s) + source_ops.add(s.op) + _copy_source( + s=s, + graph=graph, + op_map=op_map, + handle_captures=handle_captures, + inverse_captures=inverse_captures) for s in sources: source_ops.add(s.op) - _copy_source(s=s, graph=graph, op_map=op_map, - handle_captures=handle_captures, - inverse_captures=inverse_captures) + _copy_source( + s=s, + graph=graph, + op_map=op_map, + handle_captures=handle_captures, + inverse_captures=inverse_captures) for op in reversed(ops_to_copy): if op in source_ops: diff --git a/tensorflow/python/eager/lift_to_graph_test.py b/tensorflow/python/eager/lift_to_graph_test.py new file mode 100644 index 0000000000..27bcfd1852 --- /dev/null +++ b/tensorflow/python/eager/lift_to_graph_test.py @@ -0,0 +1,56 @@ +# Copyright 2019 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 lift_to_graph.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.python.eager import def_function +from tensorflow.python.eager import lift_to_graph +from tensorflow.python.eager import test +from tensorflow.python.framework import func_graph +from tensorflow.python.ops import resource_variable_ops + + +class LiftToGraphTest(test.TestCase): + + def testCaptureOrdering(self): + v1 = resource_variable_ops.ResourceVariable(1.0) + v2 = resource_variable_ops.ResourceVariable(2.0) + v3 = resource_variable_ops.ResourceVariable(3.0) + + @def_function.function + def fn(): + return v1 + v2 + v3 + + concrete_fn = fn.get_concrete_function() + original_captures = concrete_fn.graph.captures + outputs = concrete_fn.graph.outputs + + for _ in range(100): + g = func_graph.FuncGraph('lifted') + + lift_to_graph.lift_to_graph( + outputs, g, add_sources=True, handle_captures=True) + lifted_captures = g.captures + self.assertLen(lifted_captures, 3) + for original_capture, lifted_capture in zip(original_captures.values(), + lifted_captures.values()): + self.assertEqual(original_capture.name, lifted_capture.name) + + +if __name__ == '__main__': + test.main() -- GitLab From f3249127cb5ebe7fe7ed51d1bfc61d01828140ab Mon Sep 17 00:00:00 2001 From: Akshay Modi Date: Mon, 25 Feb 2019 13:47:54 -0800 Subject: [PATCH 479/766] Automated rollback of commit de7d814b50ebf74bda4fe560afe22d120428b1fd PiperOrigin-RevId: 235595623 --- tensorflow/python/eager/tensor_test.py | 8 ------ tensorflow/python/lib/core/py_seq_tensor.cc | 29 --------------------- 2 files changed, 37 deletions(-) diff --git a/tensorflow/python/eager/tensor_test.py b/tensorflow/python/eager/tensor_test.py index 4e6a12b897..23fb983767 100644 --- a/tensorflow/python/eager/tensor_test.py +++ b/tensorflow/python/eager/tensor_test.py @@ -364,14 +364,6 @@ class TFETensorTest(test_util.TensorFlowTestCase): "Provided value.*Requested dtype.*"): _ = ops.convert_to_tensor(1., dtype=dtypes.int32) - def testEagerLargeConstant(self): - self.assertEqual( - constant_op.constant(dtypes.uint64.max, dtype=dtypes.uint64).numpy(), - dtypes.uint64.max) - self.assertEqual( - constant_op.constant(dtypes.uint32.max, dtype=dtypes.uint32).numpy(), - dtypes.uint32.max) - class TFETensorUtilTest(test_util.TensorFlowTestCase): diff --git a/tensorflow/python/lib/core/py_seq_tensor.cc b/tensorflow/python/lib/core/py_seq_tensor.cc index 726701eb8f..77fbfd51bb 100644 --- a/tensorflow/python/lib/core/py_seq_tensor.cc +++ b/tensorflow/python/lib/core/py_seq_tensor.cc @@ -316,31 +316,6 @@ const char* ConvertOneInt64(PyObject* v, int64* out) { DEFINE_HELPER(ConvertInt64, int64, DT_INT64, ConvertOneInt64); -const char* ConvertOneUint64(PyObject* v, uint64* out) { -#if PY_MAJOR_VERSION < 3 - if (TF_PREDICT_TRUE(PyInt_Check(v))) { - *out = PyInt_AsUnsignedLongMask(v); - return nullptr; - } -#endif - if (TF_PREDICT_TRUE(PyLong_Check(v) || IsPyDimension(v))) { - *out = PyLong_AsUnsignedLong(v); - return nullptr; - } - if (PyIsInstance(v, &PyIntegerArrType_Type)) { // NumPy integers -#if PY_MAJOR_VERSION < 3 - Safe_PyObjectPtr as_int = make_safe(PyNumber_Int(v)); -#else - Safe_PyObjectPtr as_int = make_safe(PyNumber_Long(v)); -#endif - return ConvertOneUint64(as_int.get(), out); - } - if (IsPyFloat(v)) return ErrorFoundFloat; - return ErrorMixedTypes; -} - -DEFINE_HELPER(ConvertUint64, uint64, DT_UINT64, ConvertOneUint64); - const char* ConvertOneInt32(PyObject* v, int32* out) { int64 i; #if PY_MAJOR_VERSION < 3 @@ -550,10 +525,6 @@ Status PySeqToTensor(PyObject* obj, PyObject* dtype, Tensor* ret) { if (ConvertInt32(obj, shape, ret) == nullptr) return Status::OK(); break; - case DT_UINT64: - if (ConvertUint64(obj, shape, ret) == nullptr) return Status::OK(); - break; - case DT_COMPLEX128: if (ConvertComplex(obj, shape, ret) == nullptr) return Status::OK(); break; -- GitLab From 17ebf99b8e3719262f0a32c67cf25a0ec2026994 Mon Sep 17 00:00:00 2001 From: Pete Warden Date: Mon, 25 Feb 2019 14:43:17 -0800 Subject: [PATCH 480/766] Add support for SparkFun Edge board PiperOrigin-RevId: 235606016 --- .../micro/examples/micro_speech/Makefile.inc | 13 +- .../sparkfun_edge/audio_provider.cc | 358 ++++++++++++++++++ .../micro/tools/make/download_dependencies.sh | 7 +- .../make/targets/apollo3evb_makefile.inc | 14 +- 4 files changed, 385 insertions(+), 7 deletions(-) create mode 100644 tensorflow/lite/experimental/micro/examples/micro_speech/sparkfun_edge/audio_provider.cc diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/Makefile.inc b/tensorflow/lite/experimental/micro/examples/micro_speech/Makefile.inc index c4e0f0e6ca..41a313f5aa 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/Makefile.inc +++ b/tensorflow/lite/experimental/micro/examples/micro_speech/Makefile.inc @@ -2,6 +2,9 @@ INCLUDES += \ -I$(MAKEFILE_DIR)/downloads/kissfft +GENERATED_PROJECT_INCLUDES += \ +-I./third_party/kissfft + PROJECT_INCLUDES += \ third_party/kissfft @@ -15,6 +18,12 @@ $(MAKEFILE_DIR)/downloads/kissfft/kiss_fft.h \ $(MAKEFILE_DIR)/downloads/kissfft/_kiss_fft_guts.h \ $(MAKEFILE_DIR)/downloads/kissfft/tools/kiss_fftr.h +THIRD_PARTY_CC_HDRS += \ +third_party/kissfft/COPYING \ +third_party/kissfft/kiss_fft.h \ +third_party/kissfft/_kiss_fft_guts.h \ +third_party/kissfft/tools/kiss_fftr.h + MICRO_SPEECH_TEST_SRCS := \ tensorflow/lite/experimental/micro/examples/micro_speech/micro_speech_test.cc \ tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.cc \ @@ -37,8 +46,8 @@ tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/yes_pow SIMPLE_FEATURES_GENERATOR_TEST_HDRS := \ tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/simple_model_settings.h \ tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/simple_features_generator.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/no_30ms_sample_data.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/yes_30ms_sample_data.h \ +tensorflow/lite/experimental/micro/examples/micro_speech/no_30ms_sample_data.h \ +tensorflow/lite/experimental/micro/examples/micro_speech/yes_30ms_sample_data.h \ tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/no_power_spectrum_data.h \ tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/yes_power_spectrum_data.h diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/sparkfun_edge/audio_provider.cc b/tensorflow/lite/experimental/micro/examples/micro_speech/sparkfun_edge/audio_provider.cc new file mode 100644 index 0000000000..520a46ef59 --- /dev/null +++ b/tensorflow/lite/experimental/micro/examples/micro_speech/sparkfun_edge/audio_provider.cc @@ -0,0 +1,358 @@ +/* Copyright 2019 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/lite/experimental/micro/examples/micro_speech/audio_provider.h" + +#include + +// These are headers from Ambiq's Apollo3 SDK. +#include "am_bsp.h" // NOLINT +#include "am_mcu_apollo.h" // NOLINT +#include "am_util.h" // NOLINT + +#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_model_settings.h" + +namespace { + +// These are the raw buffers that are filled by the ADC during DMA +constexpr int kAdcNumSlots = 2; +constexpr int kAdcSamplesPerSlot = 1024; +constexpr int kAdcSampleBufferSize = (kAdcNumSlots * kAdcSamplesPerSlot); +uint32_t g_ui32ADCSampleBuffer0[kAdcSampleBufferSize]; +uint32_t g_ui32ADCSampleBuffer1[kAdcSampleBufferSize]; +// Controls the double buffering between the two DMA buffers. +int g_dma_destination_index = 0; +// ADC Device Handle. +static void* g_adc_handle; +// ADC DMA error flag. +volatile bool g_adc_dma_error; +// So the interrupt can use the passed-in error handler to report issues. +tflite::ErrorReporter* g_adc_dma_error_reporter = nullptr; + +// Holds a longer history of audio samples in a ring buffer. +constexpr int kAudioCaptureBufferSize = 16000; +int16_t g_audio_capture_buffer[kAudioCaptureBufferSize] = {}; +int g_audio_capture_buffer_start = 0; +int64_t g_total_samples_captured = 0; +int32_t g_latest_audio_timestamp = 0; + +// Copy of audio samples returned to the caller. +int16_t g_audio_output_buffer[kMaxAudioSampleSize]; +bool g_is_audio_initialized = false; + +// Start the DMA fetch of ADC samples. +void adc_start_dma(tflite::ErrorReporter* error_reporter) { + am_hal_adc_dma_config_t ADCDMAConfig; + + // Configure the ADC to use DMA for the sample transfer. + ADCDMAConfig.bDynamicPriority = true; + ADCDMAConfig.ePriority = AM_HAL_ADC_PRIOR_SERVICE_IMMED; + ADCDMAConfig.bDMAEnable = true; + ADCDMAConfig.ui32SampleCount = kAdcSampleBufferSize; + if (g_dma_destination_index == 0) { + ADCDMAConfig.ui32TargetAddress = (uint32_t)g_ui32ADCSampleBuffer0; + } else { + ADCDMAConfig.ui32TargetAddress = (uint32_t)g_ui32ADCSampleBuffer1; + } + if (AM_HAL_STATUS_SUCCESS != + am_hal_adc_configure_dma(g_adc_handle, &ADCDMAConfig)) { + error_reporter->Report("Error - configuring ADC DMA failed."); + } + + // Reset the ADC DMA flags. + g_adc_dma_error = false; + g_adc_dma_error_reporter = error_reporter; +} + +// Configure the ADC. +void adc_config0(tflite::ErrorReporter* error_reporter) { + am_hal_adc_config_t ADCConfig; + am_hal_adc_slot_config_t ADCSlotConfig; + + // Initialize the ADC and get the handle. + if (AM_HAL_STATUS_SUCCESS != am_hal_adc_initialize(0, &g_adc_handle)) { + error_reporter->Report("Error - reservation of the ADC0 instance failed."); + } + + // Power on the ADC. + if (AM_HAL_STATUS_SUCCESS != + am_hal_adc_power_control(g_adc_handle, AM_HAL_SYSCTRL_WAKE, false)) { + error_reporter->Report("Error - ADC0 power on failed."); + } + + // Set up the ADC configuration parameters. These settings are reasonable + // for accurate measurements at a low sample rate. + ADCConfig.eClock = AM_HAL_ADC_CLKSEL_HFRC_DIV2; + ADCConfig.ePolarity = AM_HAL_ADC_TRIGPOL_RISING; + ADCConfig.eTrigger = AM_HAL_ADC_TRIGSEL_SOFTWARE; + ADCConfig.eReference = + AM_HAL_ADC_REFSEL_INT_2P0; // AM_HAL_ADC_REFSEL_INT_1P5; + ADCConfig.eClockMode = AM_HAL_ADC_CLKMODE_LOW_LATENCY; + ADCConfig.ePowerMode = AM_HAL_ADC_LPMODE0; + ADCConfig.eRepeat = AM_HAL_ADC_REPEATING_SCAN; + if (AM_HAL_STATUS_SUCCESS != am_hal_adc_configure(g_adc_handle, &ADCConfig)) { + error_reporter->Report("Error - configuring ADC0 failed."); + } + + // Set up an ADC slot (2) + ADCSlotConfig.eMeasToAvg = AM_HAL_ADC_SLOT_AVG_1; + ADCSlotConfig.ePrecisionMode = AM_HAL_ADC_SLOT_14BIT; + ADCSlotConfig.eChannel = AM_HAL_ADC_SLOT_CHSEL_SE2; + ADCSlotConfig.bWindowCompare = false; + ADCSlotConfig.bEnabled = true; + if (AM_HAL_STATUS_SUCCESS != + am_hal_adc_configure_slot(g_adc_handle, 2, &ADCSlotConfig)) { + error_reporter->Report("Error - configuring ADC Slot 2 failed."); + } + + // Set up an ADC slot (1) + ADCSlotConfig.eMeasToAvg = AM_HAL_ADC_SLOT_AVG_1; + ADCSlotConfig.ePrecisionMode = AM_HAL_ADC_SLOT_14BIT; + ADCSlotConfig.eChannel = AM_HAL_ADC_SLOT_CHSEL_SE1; + ADCSlotConfig.bWindowCompare = false; + ADCSlotConfig.bEnabled = true; + if (AM_HAL_STATUS_SUCCESS != + am_hal_adc_configure_slot(g_adc_handle, 1, &ADCSlotConfig)) { + error_reporter->Report("Error - configuring ADC Slot 1 failed."); + } + + // Configure the ADC to use DMA for the sample transfer. + adc_start_dma(error_reporter); + + // For this example, the samples will be coming in slowly. This means we + // can afford to wake up for every conversion. + am_hal_adc_interrupt_enable(g_adc_handle, + AM_HAL_ADC_INT_DERR | AM_HAL_ADC_INT_DCMP); + + // Enable the ADC. + if (AM_HAL_STATUS_SUCCESS != am_hal_adc_enable(g_adc_handle)) { + error_reporter->Report("Error - enabling ADC0 failed."); + } +} + +// Initialize the ADC repetitive sample timer A3. +void init_timerA3_for_ADC() { + // Start a timer to trigger the ADC periodically (1 second). + am_hal_ctimer_config_single(3, AM_HAL_CTIMER_TIMERA, + AM_HAL_CTIMER_HFRC_12MHZ | + AM_HAL_CTIMER_FN_REPEAT | + AM_HAL_CTIMER_INT_ENABLE); + + am_hal_ctimer_int_enable(AM_HAL_CTIMER_INT_TIMERA3); + + // 750 = 12,000,000 (clock rate) / 16,000 (desired sample rate). + am_hal_ctimer_period_set(3, AM_HAL_CTIMER_TIMERA, 750, 374); + + // Enable the timer A3 to trigger the ADC directly + am_hal_ctimer_adc_trigger_enable(); + + // Start the timer. + am_hal_ctimer_start(3, AM_HAL_CTIMER_TIMERA); +} + +// Make sure the CPU is running as fast as possible. +void enable_burst_mode(tflite::ErrorReporter* error_reporter) { + am_hal_burst_avail_e eBurstModeAvailable; + am_hal_burst_mode_e eBurstMode; + + // Check that the Burst Feature is available. + if (AM_HAL_STATUS_SUCCESS == + am_hal_burst_mode_initialize(&eBurstModeAvailable)) { + if (AM_HAL_BURST_AVAIL == eBurstModeAvailable) { + error_reporter->Report("Apollo3 Burst Mode is Available\n"); + } else { + error_reporter->Report("Apollo3 Burst Mode is Not Available\n"); + } + } else { + error_reporter->Report("Failed to Initialize for Burst Mode operation\n"); + } + + // Put the MCU into "Burst" mode. + if (AM_HAL_STATUS_SUCCESS == am_hal_burst_mode_enable(&eBurstMode)) { + if (AM_HAL_BURST_MODE == eBurstMode) { + error_reporter->Report("Apollo3 operating in Burst Mode (96MHz)\n"); + } + } else { + error_reporter->Report("Failed to Enable Burst Mode operation\n"); + } +} + +} // namespace + +// Interrupt handler for the ADC. +extern "C" void am_adc_isr(void) { + uint32_t ui32IntMask; + + // Read the interrupt status. + if (AM_HAL_STATUS_SUCCESS != + am_hal_adc_interrupt_status(g_adc_handle, &ui32IntMask, false)) { + g_adc_dma_error_reporter->Report("Error reading ADC0 interrupt status."); + } + + // Clear the ADC interrupt. + if (AM_HAL_STATUS_SUCCESS != + am_hal_adc_interrupt_clear(g_adc_handle, ui32IntMask)) { + g_adc_dma_error_reporter->Report("Error clearing ADC0 interrupt status."); + } + + // If we got a DMA complete, set the flag. + if (ui32IntMask & AM_HAL_ADC_INT_DCMP) { + uint32_t* source_buffer; + if (g_dma_destination_index == 0) { + source_buffer = g_ui32ADCSampleBuffer0; + g_dma_destination_index = 1; + } else { + source_buffer = g_ui32ADCSampleBuffer1; + g_dma_destination_index = 0; + } + adc_start_dma(g_adc_dma_error_reporter); + + // For slot 1: + uint32_t slotCount = 0; + for (uint32_t indi = 0; indi < kAdcSampleBufferSize; indi++) { + am_hal_adc_sample_t temp; + + temp.ui32Slot = AM_HAL_ADC_FIFO_SLOT(source_buffer[indi]); + temp.ui32Sample = AM_HAL_ADC_FIFO_SAMPLE(source_buffer[indi]); + + if (temp.ui32Slot == 1) { + g_audio_capture_buffer[g_audio_capture_buffer_start] = temp.ui32Sample; + g_audio_capture_buffer_start = + (g_audio_capture_buffer_start + 1) % kAudioCaptureBufferSize; + slotCount++; + } + } + + g_total_samples_captured += slotCount; + g_latest_audio_timestamp = + (g_total_samples_captured / (kAudioSampleFrequency / 1000)); + } + + // If we got a DMA error, set the flag. + if (ui32IntMask & AM_HAL_ADC_INT_DERR) { + g_adc_dma_error = true; + } +} + +TfLiteStatus InitAudioRecording(tflite::ErrorReporter* error_reporter) { + // Set the clock frequency. + if (AM_HAL_STATUS_SUCCESS != + am_hal_clkgen_control(AM_HAL_CLKGEN_CONTROL_SYSCLK_MAX, 0)) { + error_reporter->Report("Error - configuring the system clock failed."); + return kTfLiteError; + } + + // Set the default cache configuration and enable it. + if (AM_HAL_STATUS_SUCCESS != + am_hal_cachectrl_config(&am_hal_cachectrl_defaults)) { + error_reporter->Report("Error - configuring the system cache failed."); + return kTfLiteError; + } + if (AM_HAL_STATUS_SUCCESS != am_hal_cachectrl_enable()) { + error_reporter->Report("Error - enabling the system cache failed."); + return kTfLiteError; + } + + // Ensure the CPU is running as fast as possible. + enable_burst_mode(error_reporter); + + // Start the CTIMER A3 for timer-based ADC measurements. + init_timerA3_for_ADC(); + + // Enable interrupts. + NVIC_EnableIRQ(ADC_IRQn); + am_hal_interrupt_master_enable(); + + // Edge Board Pin Definitions + constexpr int kSfEdgePinMic0 = 11; + const am_hal_gpio_pincfg_t g_sf_edge_pin_mic0 = { + .uFuncSel = AM_HAL_PIN_11_ADCSE2, + }; + constexpr int kSfEdgePinMic1 = 29; + const am_hal_gpio_pincfg_t g_sf_edge_pin_mic1 = { + .uFuncSel = AM_HAL_PIN_29_ADCSE1, + }; + + // Set pins to act as our ADC input + am_hal_gpio_pinconfig(kSfEdgePinMic0, g_sf_edge_pin_mic0); + am_hal_gpio_pinconfig(kSfEdgePinMic1, g_sf_edge_pin_mic1); + + // Configure the ADC + adc_config0(error_reporter); + + // Trigger the ADC sampling for the first time manually. + if (AM_HAL_STATUS_SUCCESS != am_hal_adc_sw_trigger(g_adc_handle)) { + error_reporter->Report("Error - triggering the ADC0 failed."); + return kTfLiteError; + } + + // Enable the LED outputs. + am_hal_gpio_pinconfig(AM_BSP_GPIO_LED_RED, g_AM_HAL_GPIO_OUTPUT_12); + am_hal_gpio_pinconfig(AM_BSP_GPIO_LED_YELLOW, g_AM_HAL_GPIO_OUTPUT_12); + + am_hal_gpio_output_set(AM_BSP_GPIO_LED_RED); + am_hal_gpio_output_set(AM_BSP_GPIO_LED_YELLOW); + + return kTfLiteOk; +} + +TfLiteStatus GetAudioSamples(tflite::ErrorReporter* error_reporter, + int start_ms, int duration_ms, + int* audio_samples_size, int16_t** audio_samples) { + if (!g_is_audio_initialized) { + TfLiteStatus init_status = InitAudioRecording(error_reporter); + if (init_status != kTfLiteOk) { + return init_status; + } + g_is_audio_initialized = true; + } + + // This is the 'zero' level of the microphone when no audio is present, and + // should be recalibrated if the hardware configuration ever changes. It was + // generated experimentally by averaging some samples captured on a board. + const int16_t kAdcSampleDC = 6003; + + // Temporary gain emulation to deal with too-quiet audio on prototype boards. + const int16_t kAdcSampleGain = 10; + + // This should only be called when the main thread notices that the latest + // audio sample data timestamp has changed, so that there's new data in the + // capture ring buffer. The ring buffer will eventually wrap around and + // overwrite the data, but the assumption is that the main thread is checking + // often enough and the buffer is large enough that this call will be made + // before that happens. + const int start_offset = start_ms * (kAudioSampleFrequency / 1000); + const int duration_sample_count = + duration_ms * (kAudioSampleFrequency / 1000); + for (int i = 0; i < duration_sample_count; ++i) { + const int capture_index = (start_offset + i) % kAudioCaptureBufferSize; + const int32_t capture_value = g_audio_capture_buffer[capture_index]; + int32_t output_value = capture_value - kAdcSampleDC; + output_value *= kAdcSampleGain; + if (output_value < std::numeric_limits::min()) { + output_value = std::numeric_limits::min(); + } + if (output_value > std::numeric_limits::max()) { + output_value = std::numeric_limits::max(); + } + g_audio_output_buffer[i] = output_value; + } + + *audio_samples_size = kMaxAudioSampleSize; + *audio_samples = g_audio_output_buffer; + return kTfLiteOk; +} + +int32_t LatestAudioTimestamp() { return g_latest_audio_timestamp; } diff --git a/tensorflow/lite/experimental/micro/tools/make/download_dependencies.sh b/tensorflow/lite/experimental/micro/tools/make/download_dependencies.sh index 639f002c3b..6e66ad3a84 100755 --- a/tensorflow/lite/experimental/micro/tools/make/download_dependencies.sh +++ b/tensorflow/lite/experimental/micro/tools/make/download_dependencies.sh @@ -40,6 +40,7 @@ AP3_URL="https://github.com/AmbiqMicro/TFLiteMicro_Apollo3/archive/dfbcef9a57276 CUST_CMSIS_URL="https://github.com/AmbiqMicro/TFLiteMicro_CustCMSIS/archive/8f63966c5692e6a3a83956efd2e4aed77c4c9949.zip" GCC_EMBEDDED_URL="https://developer.arm.com/-/media/Files/downloads/gnu-rm/7-2018q2/gcc-arm-none-eabi-7-2018-q2-update-linux.tar.bz2" KISSFFT_URL="http://downloads.sourceforge.net/project/kissfft/kissfft/v1_3_0/kiss_fft130.zip" +SPARKFUN_EDGE_BSP_URL="https://github.com/sparkfun/SparkFun_Edge_BSP/archive/620f5f7a69fc69e38cda8132b69302d9c28ba0dd.zip" download_and_extract() { local usage="Usage: download_and_extract URL DIR" @@ -91,13 +92,16 @@ patch_am_sdk() { cp "${src_dir}/hello_world.ld" "${dest_dir}/apollo3evb.ld" sed -i -e '114s/1024/1024\*20/g' "${dest_dir}/startup_gcc.c" - sed -i -e 's/main/_main/g' "${dest_dir}/startup_gcc.c" + #sed -i -e 's/main/_main/g' "${dest_dir}/startup_gcc.c" sed -i -e '3s/hello_world.ld/apollo3evb.ld/g' "${dest_dir}/apollo3evb.ld" sed -i -e '3s/startup_gnu/startup_gcc/g' "${dest_dir}/apollo3evb.ld" sed -i -e '22s/\*(.text\*)/\*(.text\*)\n\n\t\/\* These are the C++ global constructors. Stick them all here and\n\t \* then walk through the array in main() calling them all.\n\t \*\/\n\t_init_array_start = .;\n\tKEEP (\*(SORT(.init_array\*)))\n\t_init_array_end = .;\n\n\t\/\* XXX Currently not doing anything for global destructors. \*\/\n/g' "${dest_dir}/apollo3evb.ld" sed -i -e "70s/} > SRAM/} > SRAM\n \/\* Add this to satisfy reference to symbol 'end' from libnosys.a(sbrk.o)\n \* to denote the HEAP start.\n \*\/\n end = .;/g" "${dest_dir}/apollo3evb.ld" + # Workaround for bug in 2.0.0 SDK, remove once that's fixed. + sed -i -e 's/#ifndef AM_HAL_GPIO_H/#ifdef __cplusplus\nextern "C" {\n#endif\n#ifndef AM_HAL_GPIO_H/g' ${am_dir}/mcu/apollo3/hal/am_hal_gpio.h + echo "Finished preparing Apollo3 files" } @@ -123,5 +127,6 @@ download_and_extract "${CUST_CMSIS_URL}" "${DOWNLOADS_DIR}/CMSIS_ext" download_and_extract "${GCC_EMBEDDED_URL}" "${DOWNLOADS_DIR}/gcc_embedded" download_and_extract "${KISSFFT_URL}" "${DOWNLOADS_DIR}/kissfft" patch_kissfft "${DOWNLOADS_DIR}/kissfft" +download_and_extract "${SPARKFUN_EDGE_BSP_URL}" "${DOWNLOADS_DIR}/AmbiqSuite-Rel2.0.0/boards/SparkFun_TensorFlow_Apollo3_BSP" echo "download_dependencies.sh completed successfully." >&2 diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/apollo3evb_makefile.inc b/tensorflow/lite/experimental/micro/tools/make/targets/apollo3evb_makefile.inc index fa9dc9c99b..4b3e79849e 100644 --- a/tensorflow/lite/experimental/micro/tools/make/targets/apollo3evb_makefile.inc +++ b/tensorflow/lite/experimental/micro/tools/make/targets/apollo3evb_makefile.inc @@ -1,5 +1,5 @@ -# Settings for apollo3 evb platforms. -ifeq ($(TARGET), apollo3evb) +# Settings for apollo3 evb and SparkFun Edge platforms. +ifeq ($(TARGET),$(filter $(TARGET),apollo3evb sparkfun_edge)) export PATH := $(MAKEFILE_DIR)/downloads/gcc_embedded/bin/:$(PATH) TARGET_ARCH := cortex-m4 TARGET_TOOLCHAIN_PREFIX := arm-none-eabi- @@ -58,8 +58,14 @@ ifeq ($(TARGET), apollo3evb) -Wl,-T,$(APOLLO3_SDK)/boards/apollo3_evb/examples/hello_world/gcc_patched/apollo3evb.ld \ -Wl,-Map=$(MAKEFILE_DIR)/gen/$(TARGET).map,--cref BUILD_TYPE := micro + ifeq ($(TARGET), apollo3evb) + BOARD_BSP_PATH := $(APOLLO3_SDK)/boards/apollo3_evb/bsp + endif + ifeq ($(TARGET), sparkfun_edge) + BOARD_BSP_PATH := $(APOLLO3_SDK)/boards/SparkFun_TensorFlow_Apollo3_BSP/bsp + endif MICROLITE_LIBS := \ - $(APOLLO3_SDK)/boards/apollo3_evb/bsp/gcc/bin/libam_bsp.a \ + $(BOARD_BSP_PATH)/gcc/bin/libam_bsp.a \ $(APOLLO3_SDK)/mcu/apollo3/hal/gcc/bin/libam_hal.a \ $(GCC_ARM)/lib/gcc/arm-none-eabi/7.3.1/thumb/v7e-m/fpv4-sp/hard/crtbegin.o \ -lm @@ -70,7 +76,7 @@ ifeq ($(TARGET), apollo3evb) -I$(GCC_ARM)/arm-none-eabi/ \ -I$(APOLLO3_SDK)/mcu/apollo3/ \ -I$(APOLLO3_SDK)/CMSIS/AmbiqMicro/Include/ \ - -I$(APOLLO3_SDK)/boards/apollo3_evb/bsp/ \ + -I$(BOARD_BSP_PATH) \ -I$(APOLLO3_SDK)/devices/ \ -I$(APOLLO3_SDK)/utils/ -- GitLab From fd7bcb4eb75c7cc6a28bdceecadfd282857ff1b9 Mon Sep 17 00:00:00 2001 From: Brandon Carter Date: Mon, 25 Feb 2019 18:04:03 -0500 Subject: [PATCH 481/766] Fix reference in HParams docstring. In the tf.contrib.training.HParams.HParams class docstring, `tf.HParams` should be `tf.contrib.training.HParams`. (I don't think there currently exists a `tf.HParams` alias.) --- tensorflow/contrib/training/python/training/hparam.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tensorflow/contrib/training/python/training/hparam.py b/tensorflow/contrib/training/python/training/hparam.py index 27f0d9b2e3..cb0a25f333 100644 --- a/tensorflow/contrib/training/python/training/hparam.py +++ b/tensorflow/contrib/training/python/training/hparam.py @@ -353,8 +353,10 @@ class HParams(object): def my_program(): # Create a HParams object specifying the names and values of the # model hyperparameters: - hparams = tf.HParams(learning_rate=0.1, num_hidden_units=100, - activations=['relu', 'tanh']) + hparams = tf.contrib.training.HParams( + learning_rate=0.1, + num_hidden_units=100, + activations=['relu', 'tanh']) # Override hyperparameters values by parsing the command line hparams.parse(args.hparams) @@ -387,7 +389,7 @@ class HParams(object): # Define 3 hyperparameters: 'learning_rate' is a float parameter, # 'num_hidden_units' an integer parameter, and 'activation' a string # parameter. - hparams = tf.HParams( + hparams = tf.contrib.training.HParams( learning_rate=0.1, num_hidden_units=100, activation='relu') hparams.activation ==> 'relu' -- GitLab From c28a24b5f40a2ba887981e55b8f449633a71ccda Mon Sep 17 00:00:00 2001 From: Shashi Shekhar Date: Mon, 25 Feb 2019 14:48:19 -0800 Subject: [PATCH 482/766] PR #23829: [TFLite] Add options for benchmark_model and label_image to allow fp16 for fp32 models Please approve this CL. It will be submitted automatically, and its GitHub pull request will be marked as merged. Imported from GitHub PR #23829 Add options for benchmark_model and label_image to allow running fp32 models with fp16. Useful when testing NNPAI accelerators with fp16 capability. Copybara import of the project: - b5a99e2174f115f87c0cb3de9d2a2479a0193db2 add cmdline option to allow running fp32 models with fp16 by Koan-Sin Tan - dc4b9745b41ca7b2a00efb142bb0ab1e9895b4e0 add option for label_image to allow running fp32 models w... by Koan-Sin Tan - ab7b554405dc4bf5b023522095a9b4a318327e0b rebase and run clang-format by Koan-Sin Tan - 3042e7a029047d9190a3d37b24771f82017dbbfd Merge ab7b554405dc4bf5b023522095a9b4a318327e0b into 150f7... by freedom" Koan-Sin Tan PiperOrigin-RevId: 235606880 --- .../lite/examples/label_image/label_image.cc | 34 ++++++++++++------- .../lite/examples/label_image/label_image.h | 1 + .../lite/tools/benchmark/benchmark_test.cc | 1 + .../tools/benchmark/benchmark_tflite_model.cc | 10 +++++- 4 files changed, 32 insertions(+), 14 deletions(-) diff --git a/tensorflow/lite/examples/label_image/label_image.cc b/tensorflow/lite/examples/label_image/label_image.cc index b8dc2840df..340fbab5c6 100644 --- a/tensorflow/lite/examples/label_image/label_image.cc +++ b/tensorflow/lite/examples/label_image/label_image.cc @@ -113,6 +113,7 @@ void RunInference(Settings* s) { } interpreter->UseNNAPI(s->accel); + interpreter->SetAllowFp16PrecisionForFp32(s->allow_fp16); if (s->verbose) { LOG(INFO) << "tensors size: " << interpreter->tensors_size() << "\n"; @@ -251,19 +252,21 @@ void RunInference(Settings* s) { } void display_usage() { - LOG(INFO) << "label_image\n" - << "--accelerated, -a: [0|1], use Android NNAPI or not\n" - << "--count, -c: loop interpreter->Invoke() for certain times\n" - << "--input_mean, -b: input mean\n" - << "--input_std, -s: input standard deviation\n" - << "--image, -i: image_name.bmp\n" - << "--labels, -l: labels for the model\n" - << "--tflite_model, -m: model_name.tflite\n" - << "--profiling, -p: [0|1], profiling or not\n" - << "--num_results, -r: number of results to show\n" - << "--threads, -t: number of threads\n" - << "--verbose, -v: [0|1] print more information\n" - << "\n"; + LOG(INFO) + << "label_image\n" + << "--accelerated, -a: [0|1], use Android NNAPI or not\n" + << "--allow_fp16, -f: [0|1], allow running fp32 models with fp16 not\n" + << "--count, -c: loop interpreter->Invoke() for certain times\n" + << "--input_mean, -b: input mean\n" + << "--input_std, -s: input standard deviation\n" + << "--image, -i: image_name.bmp\n" + << "--labels, -l: labels for the model\n" + << "--tflite_model, -m: model_name.tflite\n" + << "--profiling, -p: [0|1], profiling or not\n" + << "--num_results, -r: number of results to show\n" + << "--threads, -t: number of threads\n" + << "--verbose, -v: [0|1] print more information\n" + << "\n"; } int Main(int argc, char** argv) { @@ -273,6 +276,7 @@ int Main(int argc, char** argv) { while (1) { static struct option long_options[] = { {"accelerated", required_argument, nullptr, 'a'}, + {"allow_fp16", required_argument, nullptr, 'f'}, {"count", required_argument, nullptr, 'c'}, {"verbose", required_argument, nullptr, 'v'}, {"image", required_argument, nullptr, 'i'}, @@ -305,6 +309,10 @@ int Main(int argc, char** argv) { s.loop_count = strtol(optarg, nullptr, 10); // NOLINT(runtime/deprecated_fn) break; + case 'f': + s.allow_fp16 = + strtol(optarg, nullptr, 10); // NOLINT(runtime/deprecated_fn) + break; case 'i': s.input_bmp_name = optarg; break; diff --git a/tensorflow/lite/examples/label_image/label_image.h b/tensorflow/lite/examples/label_image/label_image.h index 88b047fecc..cc46e56b64 100644 --- a/tensorflow/lite/examples/label_image/label_image.h +++ b/tensorflow/lite/examples/label_image/label_image.h @@ -26,6 +26,7 @@ struct Settings { bool accel = false; bool input_floating = false; bool profiling = false; + bool allow_fp16 = false; int loop_count = 1; float input_mean = 127.5f; float input_std = 127.5f; diff --git a/tensorflow/lite/tools/benchmark/benchmark_test.cc b/tensorflow/lite/tools/benchmark/benchmark_test.cc index a4f830122f..473e588ec9 100644 --- a/tensorflow/lite/tools/benchmark/benchmark_test.cc +++ b/tensorflow/lite/tools/benchmark/benchmark_test.cc @@ -44,6 +44,7 @@ BenchmarkParams CreateParams() { params.AddParam("input_layer", BenchmarkParam::Create("")); params.AddParam("input_layer_shape", BenchmarkParam::Create("")); params.AddParam("use_nnapi", BenchmarkParam::Create(false)); + params.AddParam("allow_fp16", BenchmarkParam::Create(false)); params.AddParam("warmup_min_secs", BenchmarkParam::Create(0.5f)); return params; } diff --git a/tensorflow/lite/tools/benchmark/benchmark_tflite_model.cc b/tensorflow/lite/tools/benchmark/benchmark_tflite_model.cc index 83e0ff1f87..190c5a9c4c 100644 --- a/tensorflow/lite/tools/benchmark/benchmark_tflite_model.cc +++ b/tensorflow/lite/tools/benchmark/benchmark_tflite_model.cc @@ -200,6 +200,7 @@ BenchmarkParams BenchmarkTfLiteModel::DefaultParams() { default_params.AddParam("input_layer_shape", BenchmarkParam::Create("")); default_params.AddParam("use_nnapi", BenchmarkParam::Create(false)); + default_params.AddParam("allow_fp16", BenchmarkParam::Create(false)); return default_params; } @@ -219,7 +220,8 @@ std::vector BenchmarkTfLiteModel::GetFlags() { CreateFlag("input_layer", ¶ms_, "input layer names"), CreateFlag("input_layer_shape", ¶ms_, "input layer shape"), - CreateFlag("use_nnapi", ¶ms_, "use nnapi api")}; + CreateFlag("use_nnapi", ¶ms_, "use nnapi api"), + CreateFlag("allow_fp16", ¶ms_, "allow fp16")}; flags.insert(flags.end(), specific_flags.begin(), specific_flags.end()); return flags; @@ -233,6 +235,8 @@ void BenchmarkTfLiteModel::LogParams() { TFLITE_LOG(INFO) << "Input shapes: [" << params_.Get("input_layer_shape") << "]"; TFLITE_LOG(INFO) << "Use nnapi : [" << params_.Get("use_nnapi") << "]"; + TFLITE_LOG(INFO) << "Allow fp16 : [" << params_.Get("allow_fp16") + << "]"; } bool BenchmarkTfLiteModel::ValidateParams() { @@ -328,6 +332,10 @@ void BenchmarkTfLiteModel::Init() { interpreter->UseNNAPI(use_nnapi); ApplyDelegates(); + bool allow_fp16 = params_.Get("allow_fp16"); + + interpreter->SetAllowFp16PrecisionForFp32(allow_fp16); + auto interpreter_inputs = interpreter->inputs(); if (!inputs.empty()) { -- GitLab From 1ee0db9cae8d95fa0ff51f6866fb85717adc1bc8 Mon Sep 17 00:00:00 2001 From: Rohan Jain Date: Mon, 25 Feb 2019 15:03:07 -0800 Subject: [PATCH 483/766] Making create_resource and initialize methods on TrackableResource private. PiperOrigin-RevId: 235609556 --- .../boosted_trees/python/ops/model_ops.py | 6 ++-- .../boosted_trees/python/ops/quantile_ops.py | 10 +++--- .../python/ops/stats_accumulator_ops.py | 10 +++--- tensorflow/contrib/lookup/lookup_ops.py | 2 +- .../tensor_forest/python/ops/model_ops.py | 8 ++--- .../tensor_forest/python/ops/stats_ops.py | 8 ++--- tensorflow/python/ops/boosted_trees_ops.py | 20 +++++------ tensorflow/python/ops/lookup_ops.py | 34 +++++++++---------- tensorflow/python/saved_model/load.py | 10 +++--- .../python/saved_model/load_v1_in_v2.py | 6 ++-- tensorflow/python/saved_model/save.py | 4 +-- .../python/training/tracking/tracking.py | 14 ++++---- .../python/training/tracking/tracking_test.py | 2 +- 13 files changed, 67 insertions(+), 67 deletions(-) diff --git a/tensorflow/contrib/boosted_trees/python/ops/model_ops.py b/tensorflow/contrib/boosted_trees/python/ops/model_ops.py index ad6ff0a861..f994595981 100644 --- a/tensorflow/contrib/boosted_trees/python/ops/model_ops.py +++ b/tensorflow/contrib/boosted_trees/python/ops/model_ops.py @@ -96,18 +96,18 @@ class TreeEnsembleVariable(tracking.TrackableResource): self._init_op = None super(TreeEnsembleVariable, self).__init__() - def create_resource(self): + def _create_resource(self): return gen_model_ops.decision_tree_ensemble_resource_handle_op( self._container, shared_name=self._name, name=self._name) - def initialize(self): + def _initialize(self): return gen_model_ops.create_tree_ensemble_variable( self.resource_handle, self._stamp_token, self._tree_ensemble_config) @property def initializer(self): if self._init_op is None: - self._init_op = self.initialize() + self._init_op = self._initialize() return self._init_op def is_initialized(self): diff --git a/tensorflow/contrib/boosted_trees/python/ops/quantile_ops.py b/tensorflow/contrib/boosted_trees/python/ops/quantile_ops.py index aff7105e94..82f9b17b33 100644 --- a/tensorflow/contrib/boosted_trees/python/ops/quantile_ops.py +++ b/tensorflow/contrib/boosted_trees/python/ops/quantile_ops.py @@ -120,8 +120,8 @@ class QuantileAccumulator(tracking.TrackableResource): name = _PATTERN.sub("", name) with ops.name_scope(name, "QuantileAccumulator") as name: self._name = name - self._resource_handle = self.create_resource() - self._init_op = self.initialize() + self._resource_handle = self._create_resource() + self._init_op = self._initialize() is_initialized_op = self.is_initialized() resources.register_resource(self.resource_handle, self._init_op, is_initialized_op) @@ -129,11 +129,11 @@ class QuantileAccumulator(tracking.TrackableResource): self._init_op, name) ops.add_to_collection(ops.GraphKeys.SAVEABLE_OBJECTS, self._saveable) - def create_resource(self): + def _create_resource(self): return gen_quantile_ops.quantile_stream_resource_handle_op( container=self._container, shared_name=self._name, name=self._name) - def initialize(self): + def _initialize(self): return gen_quantile_ops.create_quantile_accumulator( self.resource_handle, self._init_stamp_token, @@ -145,7 +145,7 @@ class QuantileAccumulator(tracking.TrackableResource): @property def initializer(self): if self._init_op is None: - self._init_op = self.initialize() + self._init_op = self._initialize() return self._init_op def is_initialized(self): diff --git a/tensorflow/contrib/boosted_trees/python/ops/stats_accumulator_ops.py b/tensorflow/contrib/boosted_trees/python/ops/stats_accumulator_ops.py index 2a0a206d97..1f6bbbf574 100644 --- a/tensorflow/contrib/boosted_trees/python/ops/stats_accumulator_ops.py +++ b/tensorflow/contrib/boosted_trees/python/ops/stats_accumulator_ops.py @@ -144,8 +144,8 @@ class StatsAccumulator(tracking.TrackableResource): name = _PATTERN.sub("", name) with ops.name_scope(name, "StatsAccumulator") as name: self._name = name - self._resource_handle = self.create_resource() - self._init_op = self.initialize() + self._resource_handle = self._create_resource() + self._init_op = self._initialize() is_initialized_op = self.is_initialized() resources.register_resource(self.resource_handle, self.initializer, is_initialized_op) @@ -153,7 +153,7 @@ class StatsAccumulator(tracking.TrackableResource): self.resource_handle, self.initializer, self._is_scalar, name) ops.add_to_collection(ops.GraphKeys.SAVEABLE_OBJECTS, self._saveable) - def create_resource(self): + def _create_resource(self): if self._is_scalar: return ( gen_stats_accumulator_ops.stats_accumulator_scalar_resource_handle_op( @@ -163,7 +163,7 @@ class StatsAccumulator(tracking.TrackableResource): gen_stats_accumulator_ops.stats_accumulator_tensor_resource_handle_op( self._container, self._name, name=self._name)) - def initialize(self): + def _initialize(self): if self._is_scalar: return gen_stats_accumulator_ops.create_stats_accumulator_scalar( self.resource_handle, self._stamp_token) @@ -175,7 +175,7 @@ class StatsAccumulator(tracking.TrackableResource): @property def initializer(self): if self._init_op is None: - self._init_op = self.initialize() + self._init_op = self._initialize() return self._init_op def is_initialized(self): diff --git a/tensorflow/contrib/lookup/lookup_ops.py b/tensorflow/contrib/lookup/lookup_ops.py index 2a8e674ab3..454168c1cf 100644 --- a/tensorflow/contrib/lookup/lookup_ops.py +++ b/tensorflow/contrib/lookup/lookup_ops.py @@ -325,7 +325,7 @@ class HashTable(InitializableLookupTableBase): super(HashTable, self).__init__(default_value, initializer) self._value_shape = self._default_value.get_shape() - def create_resource(self): + def _create_resource(self): table_ref = gen_lookup_ops.hash_table_v2( shared_name=self._shared_name, key_dtype=self._initializer.key_dtype, diff --git a/tensorflow/contrib/tensor_forest/python/ops/model_ops.py b/tensorflow/contrib/tensor_forest/python/ops/model_ops.py index 40bf7081a3..d36d0eb0c4 100644 --- a/tensorflow/contrib/tensor_forest/python/ops/model_ops.py +++ b/tensorflow/contrib/tensor_forest/python/ops/model_ops.py @@ -103,9 +103,9 @@ class TreeVariable(tracking.TrackableResource): self._container = container self._init_op = None super(TreeVariable, self).__init__() - self._resource_handle = self.create_resource() + self._resource_handle = self._create_resource() - def create_resource(self): + def _create_resource(self): if context.executing_eagerly(): # TODO(allenl): This will leak memory due to kernel caching by the # shared_name attribute value (but is better than the alternative of @@ -117,7 +117,7 @@ class TreeVariable(tracking.TrackableResource): return gen_model_ops.decision_tree_resource_handle_op( self._container, shared_name=shared_name, name=self._name) - def initialize(self): + def _initialize(self): return gen_model_ops.create_tree_variable( self.resource_handle, self._tree_config, @@ -126,7 +126,7 @@ class TreeVariable(tracking.TrackableResource): @property def initializer(self): if self._init_op is None: - self._init_op = self.initialize() + self._init_op = self._initialize() return self._init_op def is_initialized(self): diff --git a/tensorflow/contrib/tensor_forest/python/ops/stats_ops.py b/tensorflow/contrib/tensor_forest/python/ops/stats_ops.py index 80afcfb251..7ac68fed20 100644 --- a/tensorflow/contrib/tensor_forest/python/ops/stats_ops.py +++ b/tensorflow/contrib/tensor_forest/python/ops/stats_ops.py @@ -98,9 +98,9 @@ class FertileStatsVariable(tracking.TrackableResource): self._container = container self._init_op = None super(FertileStatsVariable, self).__init__() - self._resource_handle = self.create_resource() + self._resource_handle = self._create_resource() - def create_resource(self): + def _create_resource(self): if context.executing_eagerly(): # TODO(allenl): This will leak memory due to kernel caching by the # shared_name attribute value (but is better than the alternative of @@ -112,7 +112,7 @@ class FertileStatsVariable(tracking.TrackableResource): return gen_stats_ops.fertile_stats_resource_handle_op( self._container, shared_name=shared_name, name=self._name) - def initialize(self): + def _initialize(self): return gen_stats_ops.create_fertile_stats_variable( self.resource_handle, self._stats_config, @@ -121,7 +121,7 @@ class FertileStatsVariable(tracking.TrackableResource): @property def initializer(self): if self._init_op is None: - self._init_op = self.initialize() + self._init_op = self._initialize() return self._init_op def is_initialized(self): diff --git a/tensorflow/python/ops/boosted_trees_ops.py b/tensorflow/python/ops/boosted_trees_ops.py index 362c17ec6f..9e0011194d 100644 --- a/tensorflow/python/ops/boosted_trees_ops.py +++ b/tensorflow/python/ops/boosted_trees_ops.py @@ -109,8 +109,8 @@ class QuantileAccumulator(tracking.TrackableResource): with ops.name_scope(name, 'QuantileAccumulator') as name: self._name = name - self._resource_handle = self.create_resource() - self._init_op = self.initialize() + self._resource_handle = self._create_resource() + self._init_op = self._initialize() is_initialized_op = self.is_initialized() resources.register_resource(self.resource_handle, self._init_op, is_initialized_op) @@ -119,18 +119,18 @@ class QuantileAccumulator(tracking.TrackableResource): self.resource_handle.name) ops.add_to_collection(ops.GraphKeys.SAVEABLE_OBJECTS, self._saveable) - def create_resource(self): + def _create_resource(self): return quantile_resource_handle_op( container='', shared_name=self._name, name=self._name) - def initialize(self): + def _initialize(self): return create_quantile_stream_resource(self.resource_handle, self._eps, self._num_streams) @property def initializer(self): if self._init_op is None: - self._init_op = self.initialize() + self._init_op = self._initialize() return self._init_op def is_initialized(self): @@ -210,8 +210,8 @@ class TreeEnsemble(tracking.TrackableResource): self._is_local = is_local with ops.name_scope(name, 'TreeEnsemble') as name: self._name = name - self._resource_handle = self.create_resource() - self._init_op = self.initialize() + self._resource_handle = self._create_resource() + self._init_op = self._initialize() is_initialized_op = self.is_initialized() # Adds the variable to the savable list. if not is_local: @@ -224,11 +224,11 @@ class TreeEnsemble(tracking.TrackableResource): is_initialized_op, is_shared=not is_local) - def create_resource(self): + def _create_resource(self): return gen_boosted_trees_ops.boosted_trees_ensemble_resource_handle_op( container='', shared_name=self._name, name=self._name) - def initialize(self): + def _initialize(self): return gen_boosted_trees_ops.boosted_trees_create_ensemble( self.resource_handle, self._stamp_token, @@ -237,7 +237,7 @@ class TreeEnsemble(tracking.TrackableResource): @property def initializer(self): if self._init_op is None: - self._init_op = self.initialize() + self._init_op = self._initialize() return self._init_op def is_initialized(self): diff --git a/tensorflow/python/ops/lookup_ops.py b/tensorflow/python/ops/lookup_ops.py index a1d29fbf47..98b6c77db2 100644 --- a/tensorflow/python/ops/lookup_ops.py +++ b/tensorflow/python/ops/lookup_ops.py @@ -116,7 +116,7 @@ class LookupInterface(trackable.TrackableResource): self._value_dtype = dtypes.as_dtype(value_dtype) super(LookupInterface, self).__init__() - def create_resource(self): + def _create_resource(self): raise NotImplementedError @property @@ -168,10 +168,10 @@ class InitializableLookupTableBase(LookupInterface): if isinstance(initializer, trackable_base.Trackable): self._initializer = self._track_trackable( initializer, "_initializer") - self._resource_handle = self.create_resource() - self._init_op = self.initialize() + self._resource_handle = self._create_resource() + self._init_op = self._initialize() - def initialize(self): + def _initialize(self): return self._initializer.initialize(self) @property @@ -276,7 +276,7 @@ class StaticHashTable(InitializableLookupTableBase): super(StaticHashTable, self).__init__(default_value, initializer) self._value_shape = self._default_value.get_shape() - def create_resource(self): + def _create_resource(self): table_ref = gen_lookup_ops.hash_table_v2( shared_name=self._shared_name, key_dtype=self._initializer.key_dtype, @@ -849,14 +849,14 @@ class IdTableWithHashBuckets(LookupInterface): self._table_name = None super(IdTableWithHashBuckets, self).__init__(key_dtype, dtypes.int64) - def create_resource(self): + def _create_resource(self): if self._table is not None: - return self._table.create_resource() + return self._table._create_resource() # pylint: disable=protected-access return None - def initialize(self): + def _initialize(self): if self._table is not None: - return self._table.initialize() + return self._table._initialize() # pylint: disable=protected-access with ops.name_scope(None, "init"): return control_flow_ops.no_op() @@ -1052,14 +1052,14 @@ class StaticVocabularyTable(LookupInterface): self._table_name = name.split("/")[-1] super(StaticVocabularyTable, self).__init__(lookup_key_dtype, dtypes.int64) - def create_resource(self): + def _create_resource(self): if self._table is not None: - return self._table.create_resource() + return self._table._create_resource() # pylint: disable=protected-access return None - def initialize(self): + def _initialize(self): if self._table is not None: - return self._table.initialize() + return self._table._initialize() # pylint: disable=protected-access with ops.name_scope(None, "init"): return control_flow_ops.no_op() @@ -1563,13 +1563,13 @@ class MutableHashTable(LookupInterface): self._shared_name = "table_%d" % (ops.uid(),) super(MutableHashTable, self).__init__(key_dtype, value_dtype) - self._resource_handle = self.create_resource() + self._resource_handle = self._create_resource() if checkpoint: saveable = MutableHashTable._Saveable(self, name) if not context.executing_eagerly(): ops.add_to_collection(ops.GraphKeys.SAVEABLE_OBJECTS, saveable) - def create_resource(self): + def _create_resource(self): # The table must be shared if checkpointing is requested for multi-worker # training to work correctly. Use the node name if no shared_name has been # explicitly specified. @@ -1820,13 +1820,13 @@ class DenseHashTable(LookupInterface): self._shared_name = "table_%d" % (ops.uid(),) super(DenseHashTable, self).__init__(key_dtype, value_dtype) - self._resource_handle = self.create_resource() + self._resource_handle = self._create_resource() if checkpoint: saveable = DenseHashTable._Saveable(self, name) if not context.executing_eagerly(): ops.add_to_collection(ops.GraphKeys.SAVEABLE_OBJECTS, saveable) - def create_resource(self): + def _create_resource(self): # The table must be shared if checkpointing is requested for multi-worker # training to work correctly. Use the node name if no shared_name has been # explicitly specified. diff --git a/tensorflow/python/saved_model/load.py b/tensorflow/python/saved_model/load.py index f37678b093..41ef165ed5 100644 --- a/tensorflow/python/saved_model/load.py +++ b/tensorflow/python/saved_model/load.py @@ -65,7 +65,7 @@ class _Loader(object): for node in self._nodes: if isinstance(node, tracking.TrackableResource): - init_op = node.initialize() + init_op = node._initialize() # pylint: disable=protected-access ops.add_to_collection(ops.GraphKeys.TABLE_INITIALIZERS, init_op) def _setup_functions_structures(self): @@ -242,10 +242,10 @@ class _Loader(object): class _RestoredResource(tracking.TrackableResource): """Restored SavedResource.""" - def create_resource(self): + def _create_resource(self): raise RuntimeError() - def initialize(self): + def _initialize(self): raise RuntimeError() def _list_functions_for_serialization(self): @@ -253,8 +253,8 @@ class _RestoredResource(tracking.TrackableResource): # base class to re-wrap the polymorphic functions into # another layer of `tf.function`. return { - "create_resource": self.create_resource, - "initialize": self.initialize, + "_create_resource": self._create_resource, + "_initialize": self._initialize, } diff --git a/tensorflow/python/saved_model/load_v1_in_v2.py b/tensorflow/python/saved_model/load_v1_in_v2.py index bb8ed7cfcc..54971c0718 100644 --- a/tensorflow/python/saved_model/load_v1_in_v2.py +++ b/tensorflow/python/saved_model/load_v1_in_v2.py @@ -49,11 +49,11 @@ class _Initializer(tracking.TrackableResource): self._asset_paths = asset_paths self._init_fn = init_fn - def create_resource(self): + def _create_resource(self): return array_ops.placeholder( dtype=dtypes.resource, shape=[], name="unused_resource") - def initialize(self): + def _initialize(self): self._init_fn(*[path.asset_path for path in self._asset_paths]) @@ -140,7 +140,7 @@ class _EagerSavedModelLoader(loader_impl.SavedModelLoader): feeds=asset_feed_tensors, fetches=[wrapped.graph.as_graph_element(init_op)]) initializer = _Initializer(init_fn, asset_paths) - initializer.initialize() + initializer._initialize() # pylint: disable=protected-access root.initializer = initializer root.asset_paths = asset_paths else: diff --git a/tensorflow/python/saved_model/save.py b/tensorflow/python/saved_model/save.py index 6cdbee4187..c2c0795f4e 100644 --- a/tensorflow/python/saved_model/save.py +++ b/tensorflow/python/saved_model/save.py @@ -214,7 +214,7 @@ class _SaveableView(object): asset_index={}) for node_id, obj in enumerate(self.nodes): if isinstance(obj, tracking.TrackableResource): - new_resource = obj.create_resource() + new_resource = obj._create_resource() # pylint: disable=protected-access resource_map[obj.resource_handle] = new_resource self.captured_tensor_node_ids[obj.resource_handle] = node_id elif resource_variable_ops.is_resource_variable(obj): @@ -417,7 +417,7 @@ def _trace_resource_initializers(accessible_objects): resource_initializers = [] def _wrap_initializer(obj): - obj.initialize() + obj._initialize() # pylint: disable=protected-access return constant_op.constant(1.) # Dummy control output def _wrap_obj_initializer(obj): diff --git a/tensorflow/python/training/tracking/tracking.py b/tensorflow/python/training/tracking/tracking.py index c8024e14ab..45e37379f1 100644 --- a/tensorflow/python/training/tracking/tracking.py +++ b/tensorflow/python/training/tracking/tracking.py @@ -160,12 +160,12 @@ class TrackableResource(base.Trackable): self._resource_handle = None - def create_resource(self): + def _create_resource(self): """A function that creates a resource handle.""" raise NotImplementedError("TrackableResource.create_resource not " "implemented.") - def initialize(self): + def _initialize(self): """A function that initializes the resource. Optional.""" pass @@ -173,23 +173,23 @@ class TrackableResource(base.Trackable): def resource_handle(self): """Returns the resource handle associated with this Resource.""" if self._resource_handle is None: - self._resource_handle = self.create_resource() + self._resource_handle = self._create_resource() return self._resource_handle def _list_functions_for_serialization(self): @def_function.function(input_signature=[], autograph=False) def _creator(): - resource = self.create_resource() + resource = self._create_resource() return resource @def_function.function(input_signature=[], autograph=False) def _initializer(): - self.initialize() + self._initialize() return 1 # Dummy return return { - "create_resource": _creator, - "initialize": _initializer, + "_create_resource": _creator, + "_initialize": _initializer, } diff --git a/tensorflow/python/training/tracking/tracking_test.py b/tensorflow/python/training/tracking/tracking_test.py index 37e1417436..adef69f45b 100644 --- a/tensorflow/python/training/tracking/tracking_test.py +++ b/tensorflow/python/training/tracking/tracking_test.py @@ -215,7 +215,7 @@ class _DummyResource(tracking.TrackableResource): self._handle_name = handle_name super(_DummyResource, self).__init__() - def create_resource(self): + def _create_resource(self): return self._handle_name -- GitLab From db7cb475b875ad0a1f08275ccf68a17a6f3036e2 Mon Sep 17 00:00:00 2001 From: Bixia Zheng Date: Mon, 25 Feb 2019 15:06:32 -0800 Subject: [PATCH 484/766] [XLA:GPU] Workaround the LLVM PTX backend bug for llvm.round. The llvm.round intrinsic and the HLO RoundNearestAfz instruction have the same semantics. As such, we previously translate the RoundNearestAfz HLO instruction to the llvm.round intrinsic. However, the PTX LLVM backend currently translates llvm.round to PTX cvt.rni, which rounds to the even integer when the source is equidistant between two integers. This change translates the RoundNearestAfz HLO instruction to a call of the NVIDIA libdevice routine __nv_round_ instead, to workaround this LLVM PTX backend bug which we are going to fix later. This workaround may be more preferable than the LLVM PTX backend fix for the XLA use case as expanding the non-trivial llvm.round implementation early allows better optimizations. Add an exhaustive test for RoundNearestAfz. PiperOrigin-RevId: 235610143 --- .../compiler/xla/service/elemental_ir_emitter.cc | 10 +++++++--- tensorflow/compiler/xla/service/elemental_ir_emitter.h | 3 +++ .../compiler/xla/service/gpu/elemental_ir_emitter.cc | 10 ++++++++++ .../compiler/xla/service/gpu/elemental_ir_emitter.h | 3 +++ tensorflow/compiler/xla/tests/exhaustive_op_test.cc | 2 ++ 5 files changed, 25 insertions(+), 3 deletions(-) diff --git a/tensorflow/compiler/xla/service/elemental_ir_emitter.cc b/tensorflow/compiler/xla/service/elemental_ir_emitter.cc index dffa6cc199..abd8ead52a 100644 --- a/tensorflow/compiler/xla/service/elemental_ir_emitter.cc +++ b/tensorflow/compiler/xla/service/elemental_ir_emitter.cc @@ -440,9 +440,7 @@ StatusOr ElementalIrEmitter::EmitFloatUnaryOp( {operand_value}, {operand_value->getType()}, b_); case HloOpcode::kRoundNearestAfz: - return llvm_ir::EmitCallToIntrinsic(llvm::Intrinsic::round, - {operand_value}, - {operand_value->getType()}, b_); + return EmitRoundNearestAfz(op->shape().element_type(), operand_value); case HloOpcode::kSign: { auto type = operand_value->getType(); auto zero = llvm::ConstantFP::get(type, 0.0); @@ -1129,6 +1127,12 @@ StatusOr ElementalIrEmitter::EmitExpm1(PrimitiveType prim_type, return Select(x_is_small, for_small_x, for_large_x); } +StatusOr ElementalIrEmitter::EmitRoundNearestAfz( + PrimitiveType /*prim_type*/, llvm::Value* value) { + return llvm_ir::EmitCallToIntrinsic(llvm::Intrinsic::round, {value}, + {value->getType()}, b_); +} + StatusOr ElementalIrEmitter::EmitPow(PrimitiveType prim_type, llvm::Value* lhs, llvm::Value* rhs) { diff --git a/tensorflow/compiler/xla/service/elemental_ir_emitter.h b/tensorflow/compiler/xla/service/elemental_ir_emitter.h index 819465f1e5..7afecbbd31 100644 --- a/tensorflow/compiler/xla/service/elemental_ir_emitter.h +++ b/tensorflow/compiler/xla/service/elemental_ir_emitter.h @@ -146,6 +146,9 @@ class ElementalIrEmitter : public IrBuilderMixin { virtual StatusOr EmitTanh(PrimitiveType prim_type, llvm::Value* value); + virtual StatusOr EmitRoundNearestAfz(PrimitiveType prim_type, + llvm::Value* value); + virtual StatusOr EmitReducePrecision(const HloInstruction* hlo, llvm::Value* x); diff --git a/tensorflow/compiler/xla/service/gpu/elemental_ir_emitter.cc b/tensorflow/compiler/xla/service/gpu/elemental_ir_emitter.cc index b36ed5c6e0..b024bbe4b5 100644 --- a/tensorflow/compiler/xla/service/gpu/elemental_ir_emitter.cc +++ b/tensorflow/compiler/xla/service/gpu/elemental_ir_emitter.cc @@ -271,6 +271,16 @@ StatusOr GpuElementalIrEmitter::EmitTanh(PrimitiveType prim_type, return FPCast(fast_tanh, value->getType()); } +StatusOr GpuElementalIrEmitter::EmitRoundNearestAfz( + PrimitiveType prim_type, llvm::Value* value) { + // Use libdevice __nv_round instead of llvm.round. This is to workaround a + // bug in the PTX backend, which implements llvm.round with PTX cvt.rni. + // When the llvm.round is fixed, we may still want to use __nv_round here as + // expanding the non-trivial implementation early while inlining allows better + // optimizations. + return EmitLibdeviceMathCall("__nv_round", {value}, {prim_type}, prim_type); +} + llvm::Value* GpuElementalIrEmitter::EmitDeviceFunctionCall( const string& callee_name, absl::Span operands, absl::Span input_types, PrimitiveType output_type, diff --git a/tensorflow/compiler/xla/service/gpu/elemental_ir_emitter.h b/tensorflow/compiler/xla/service/gpu/elemental_ir_emitter.h index 2aedbf05ab..e9d08177ad 100644 --- a/tensorflow/compiler/xla/service/gpu/elemental_ir_emitter.h +++ b/tensorflow/compiler/xla/service/gpu/elemental_ir_emitter.h @@ -91,6 +91,9 @@ class GpuElementalIrEmitter : public ElementalIrEmitter { StatusOr EmitTanh(PrimitiveType prim_type, llvm::Value* value) override; + StatusOr EmitRoundNearestAfz(PrimitiveType prim_type, + llvm::Value* value) override; + llvm::Value* EmitThreadId() override; private: diff --git a/tensorflow/compiler/xla/tests/exhaustive_op_test.cc b/tensorflow/compiler/xla/tests/exhaustive_op_test.cc index 66451a5633..58bb9a217b 100644 --- a/tensorflow/compiler/xla/tests/exhaustive_op_test.cc +++ b/tensorflow/compiler/xla/tests/exhaustive_op_test.cc @@ -610,6 +610,8 @@ XLA_TEST_P(ExhaustiveOpTest, Lgamma) { Run(Lgamma, std::lgamma); } +XLA_TEST_P(ExhaustiveOpTest, Round) { Run(Round, std::round); } + std::vector> CreateExhaustiveF32Ranges() { // We break up the 2^32-element space into small'ish chunks to keep peak // memory usage low. -- GitLab From 517112e77fb68d164fb67a62ad57baadc6a20bc4 Mon Sep 17 00:00:00 2001 From: Justin Lebar Date: Mon, 25 Feb 2019 15:08:22 -0800 Subject: [PATCH 485/766] [XLA:GPU] Enhance for-loop analysis. XLA:GPU's "for-loop" analysis looks at `while` loops and tries to determine whether they run a constant number of times. If so, the loop is emitted as a "ForThunk", which can be run more efficiently on the GPU than the more general WhileThunk. A problem with our for-loop analysis is that it runs very late in the pass pipeline, after fusion, layout-assignment, copy-insertion, etc. At this point it's challenging to pattern-match the HLO to figure out whether or not a loop is a bona fide `for` loop. We can try to brute-force the loop count, but that only works for relatively small loops. This patch makes two changes: - Moves `for` loop matching to a separate pass, run before layout assignment or fusion. The loop count is stored in a backend-config on the while instruction. - Adds pattern-matching machinery for loop counts. PiperOrigin-RevId: 235610452 --- tensorflow/compiler/xla/service/BUILD | 32 +++ tensorflow/compiler/xla/service/gpu/BUILD | 1 + .../xla/service/gpu/ir_emitter_unnested.cc | 9 +- .../xla/service/gpu/nvptx_compiler.cc | 12 + .../xla/service/gpu/while_transformer_test.cc | 34 --- .../xla/service/while_loop_analysis.cc | 236 +++++++++++++++--- .../xla/service/while_loop_analysis.h | 13 +- .../while_loop_trip_count_annotator.cc | 40 +++ .../service/while_loop_trip_count_annotator.h | 48 ++++ .../while_loop_trip_count_annotator_test.cc | 207 +++++++++++++++ tensorflow/compiler/xla/xla_data.proto | 26 +- 11 files changed, 576 insertions(+), 82 deletions(-) create mode 100644 tensorflow/compiler/xla/service/while_loop_trip_count_annotator.cc create mode 100644 tensorflow/compiler/xla/service/while_loop_trip_count_annotator.h create mode 100644 tensorflow/compiler/xla/service/while_loop_trip_count_annotator_test.cc diff --git a/tensorflow/compiler/xla/service/BUILD b/tensorflow/compiler/xla/service/BUILD index 8d8394cb43..881d450947 100644 --- a/tensorflow/compiler/xla/service/BUILD +++ b/tensorflow/compiler/xla/service/BUILD @@ -1788,6 +1788,8 @@ cc_library( deps = [ ":hlo", ":hlo_evaluator", + ":pattern_matcher", + "@com_google_absl//absl/base", "@com_google_absl//absl/types:optional", ], ) @@ -1847,6 +1849,36 @@ tf_cc_test( ], ) +cc_library( + name = "while_loop_trip_count_annotator", + srcs = ["while_loop_trip_count_annotator.cc"], + hdrs = ["while_loop_trip_count_annotator.h"], + deps = [ + ":hlo", + ":hlo_pass", + ":while_loop_analysis", + "//tensorflow/compiler/xla:statusor", + "//tensorflow/compiler/xla:xla_data_proto", + ], +) + +tf_cc_test( + name = "while_loop_trip_count_annotator_test", + srcs = ["while_loop_trip_count_annotator_test.cc"], + deps = [ + ":pattern_matcher", + ":while_loop_simplifier", + ":while_loop_trip_count_annotator", + "//tensorflow/compiler/xla:literal", + "//tensorflow/compiler/xla:shape_util", + "//tensorflow/compiler/xla:status_macros", + "//tensorflow/compiler/xla:test", + "//tensorflow/compiler/xla/tests:hlo_test_base", + "//tensorflow/compiler/xla/tests:xla_internal_test_main", # fixdeps: keep + "//tensorflow/core:test", + ], +) + cc_library( name = "defuser", srcs = ["defuser.cc"], diff --git a/tensorflow/compiler/xla/service/gpu/BUILD b/tensorflow/compiler/xla/service/gpu/BUILD index 25c4f70d89..16492c085c 100644 --- a/tensorflow/compiler/xla/service/gpu/BUILD +++ b/tensorflow/compiler/xla/service/gpu/BUILD @@ -770,6 +770,7 @@ cc_library( "//tensorflow/compiler/xla/service:tuple_simplifier", "//tensorflow/compiler/xla/service:while_loop_constant_sinking", "//tensorflow/compiler/xla/service:while_loop_simplifier", + "//tensorflow/compiler/xla/service:while_loop_trip_count_annotator", "//tensorflow/compiler/xla/service:zero_sized_hlo_elimination", "//tensorflow/compiler/xla/service/gpu:cudnn_batchnorm_rewriter", "//tensorflow/compiler/xla/service/gpu/llvm_gpu_backend", diff --git a/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.cc b/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.cc index 0cc65ebb52..4e5b86adb1 100644 --- a/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.cc +++ b/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.cc @@ -926,13 +926,12 @@ Status IrEmitterUnnested::HandleWhile(HloInstruction* xla_while) { condition->root_instruction()->shape().element_type() == PRED) << "While condition computation must return bool"; // Build ForThunk for conformant while loops, otherwise build WhileThunk. - // TODO(b/112163966): Move trip count computation earlier in the pipeline. - if (auto loop_trip_count = ComputeWhileLoopTripCount(xla_while)) { - AddThunkToThunkSequence(BuildForThunk(xla_while, *loop_trip_count)); - VLOG(3) << "Built ForThunk for while: " << xla_while->name(); + auto config = xla_while->backend_config(); + if (config.ok() && config.ValueOrDie().has_known_trip_count()) { + AddThunkToThunkSequence( + BuildForThunk(xla_while, config.ValueOrDie().known_trip_count().n())); } else { AddThunkToThunkSequence(BuildWhileThunk(xla_while)); - VLOG(3) << "Built WhileThunk for while: " << xla_while->name(); } return Status::OK(); } diff --git a/tensorflow/compiler/xla/service/gpu/nvptx_compiler.cc b/tensorflow/compiler/xla/service/gpu/nvptx_compiler.cc index 6e00e4b4ff..27f2cb3bdf 100644 --- a/tensorflow/compiler/xla/service/gpu/nvptx_compiler.cc +++ b/tensorflow/compiler/xla/service/gpu/nvptx_compiler.cc @@ -87,6 +87,7 @@ limitations under the License. #include "tensorflow/compiler/xla/service/tuple_simplifier.h" #include "tensorflow/compiler/xla/service/while_loop_constant_sinking.h" #include "tensorflow/compiler/xla/service/while_loop_simplifier.h" +#include "tensorflow/compiler/xla/service/while_loop_trip_count_annotator.h" #include "tensorflow/compiler/xla/service/zero_sized_hlo_elimination.h" #include "tensorflow/compiler/xla/status_macros.h" #include "tensorflow/compiler/xla/types.h" @@ -247,6 +248,17 @@ Status OptimizeHloModule(HloModule* hlo_module, se::StreamExecutor* stream_exec, TransposeFolding::NeverFoldTranspose); pipeline.AddPass(/*is_layout_sensitive=*/false); pipeline.AddPass(); + + // Run WhileLoopTripCountAnnotator at the end of the simplification + // pipeline, before layout assignment and fusion. This pass does some + // pattern-matching on while bodies/conditions, and this is where the HLO is + // "nicest". + // + // It's important that we don't make semantic changes (e.g. unrolling) to + // any `while` loops after this point, because otherwise the trip-count + // annotations added by this pass may not be correct after the + // modifications. + pipeline.AddPass(); TF_RETURN_IF_ERROR(pipeline.Run(hlo_module).status()); } diff --git a/tensorflow/compiler/xla/service/gpu/while_transformer_test.cc b/tensorflow/compiler/xla/service/gpu/while_transformer_test.cc index 2dce7749bb..77e49f0e46 100644 --- a/tensorflow/compiler/xla/service/gpu/while_transformer_test.cc +++ b/tensorflow/compiler/xla/service/gpu/while_transformer_test.cc @@ -106,24 +106,6 @@ class WhileTransformerTest : public HloTestBase { return while_hlo; } - void RunFusionPasses() { - // Run standard fusion passes. - TF_ASSERT_OK(gpu::GpuInstructionFusion(/*may_duplicate=*/false) - .Run(module_.get()) - .status()); - TF_ASSERT_OK(gpu::GpuInstructionFusion(/*may_duplicate=*/true) - .Run(module_.get()) - .status()); - } - - void RunCopyInsertionPass() { - HloVerifier verifier(/*layout_sensitive=*/false, - /*allow_mixed_precision=*/false); - TF_ASSERT_OK(verifier.Run(module_.get()).status()); - CopyInsertion copy_insertion; - TF_ASSERT_OK(copy_insertion.Run(module_.get()).status()); - } - Shape GetLoopStateShape(const int64 ind_var_tuple_index) { if (ind_var_tuple_index == 0) { return ShapeUtil::MakeTupleShape( @@ -146,10 +128,6 @@ TEST_F(WhileTransformerTest, InductionVariableAtTupleElement0) { module_->AddEmbeddedComputation(BuildConditionComputation(0, 10)); auto body = module_->AddEmbeddedComputation(BuildBodyComputation(0, 1, 1)); auto while_hlo = BuildWhileInstruction(condition, body, 0, 0); - // Run HLO Optimization passes. - RunFusionPasses(); - RunCopyInsertionPass(); - auto result = ComputeWhileLoopTripCount(while_hlo); ASSERT_TRUE(result); EXPECT_EQ(10, *result); @@ -161,10 +139,6 @@ TEST_F(WhileTransformerTest, InductionVariableAtTupleElement1) { module_->AddEmbeddedComputation(BuildConditionComputation(1, 10)); auto body = module_->AddEmbeddedComputation(BuildBodyComputation(1, 0, 1)); auto while_hlo = BuildWhileInstruction(condition, body, 1, 0); - // Run HLO Optimization passes. - RunFusionPasses(); - RunCopyInsertionPass(); - auto result = ComputeWhileLoopTripCount(while_hlo); ASSERT_TRUE(result); EXPECT_EQ(10, *result); @@ -176,10 +150,6 @@ TEST_F(WhileTransformerTest, ImpossibleLoopLimit) { module_->AddEmbeddedComputation(BuildConditionComputation(0, 5)); auto body = module_->AddEmbeddedComputation(BuildBodyComputation(0, 1, 1)); auto while_hlo = BuildWhileInstruction(condition, body, 0, 10); - // Run HLO Optimization passes. - RunFusionPasses(); - RunCopyInsertionPass(); - auto result = ComputeWhileLoopTripCount(while_hlo); ASSERT_TRUE(result); EXPECT_EQ(0, *result); @@ -191,10 +161,6 @@ TEST_F(WhileTransformerTest, InvalidLoopIncrement) { module_->AddEmbeddedComputation(BuildConditionComputation(0, 10)); auto body = module_->AddEmbeddedComputation(BuildBodyComputation(0, 1, -1)); auto while_hlo = BuildWhileInstruction(condition, body, 0, 0); - // Run HLO Optimization passes. - RunFusionPasses(); - RunCopyInsertionPass(); - auto result = ComputeWhileLoopTripCount(while_hlo); ASSERT_FALSE(result); } diff --git a/tensorflow/compiler/xla/service/while_loop_analysis.cc b/tensorflow/compiler/xla/service/while_loop_analysis.cc index c93a9ba317..40f268f889 100644 --- a/tensorflow/compiler/xla/service/while_loop_analysis.cc +++ b/tensorflow/compiler/xla/service/while_loop_analysis.cc @@ -14,15 +14,18 @@ limitations under the License. ==============================================================================*/ #include "tensorflow/compiler/xla/service/while_loop_analysis.h" +#include "absl/base/casts.h" #include "tensorflow/compiler/xla/service/hlo_evaluator.h" #include "tensorflow/compiler/xla/service/hlo_instruction.h" #include "tensorflow/compiler/xla/service/hlo_module.h" #include "tensorflow/compiler/xla/service/hlo_opcode.h" +#include "tensorflow/compiler/xla/service/pattern_matcher.h" namespace xla { using absl::nullopt; using absl::optional; +namespace m = match; // Finds and returns the non-constant operand in instr. // @@ -48,34 +51,29 @@ static optional GetGTEOperandIndex(const HloInstruction* instr, const HloInstruction* gte_operand) { VLOG(2) << "GetGTEOperandIndex(" << instr->ToString() << ", " << gte_operand->ToString() << ")"; - optional tuple_idx; + + // Among the operands of `instr`, find one that is a get-tuple-element op. + auto gte_it = c_find_if(instr->operands(), [](const HloInstruction* instr) { + return instr->opcode() == HloOpcode::kGetTupleElement; + }); + if (gte_it == instr->operands().end()) { + VLOG(2) << "instr does not have a gte operand."; + return nullopt; + } + + // All operands of `instr` must be either constants or of the form + // get-tuple-element(gte_operand, tuple_idx) + // for the same value tuple_idx. + int64 tuple_idx = (*gte_it)->tuple_index(); for (const HloInstruction* operand : instr->operands()) { - if (operand->IsConstant()) { - continue; - } - // Look through copies. - // TODO(b/68830972): We wouldn't need this if for loop matching on the GPU - // would run before copy insertion. - if (operand->opcode() == HloOpcode::kCopy) { - operand = operand->operand(0); - } - if (operand->opcode() != HloOpcode::kGetTupleElement) { - VLOG(2) << "instr uses something other than gte(gte_operand): " - << operand->ToString(); - return nullopt; - } - if (operand->operand(0) != gte_operand) { - VLOG(2) << "instr has gte whose operand is not gte_operand: " - << operand->ToString(); + if (!Match(operand, m::Constant()) && + !Match(operand, + m::GetTupleElement(m::Op().Is(gte_operand), tuple_idx))) { + VLOG(2) + << "instr uses something other than a constant or gte(gte_operand, " + << tuple_idx << "): " << operand->ToString(); return nullopt; } - if (tuple_idx && tuple_idx != operand->tuple_index()) { - VLOG(2) << "instr has operands with conflicting gte indices, " - << *tuple_idx << " vs " << operand->tuple_index(); - return nullopt; - } - - tuple_idx = operand->tuple_index(); } return tuple_idx; } @@ -166,8 +164,171 @@ static optional GetLoopInductionVarTupleIdx( return indvar_tuple_idx; } +// Converts the given literal to a scalar int64, if possible. +// +// Fails if the literal is not an integral type or if the value it contains +// cannot be represented in an int64. +static optional LiteralAsScalarInt64(const Literal& l) { + if (!ShapeUtil::IsEffectiveScalar(l.shape())) { + VLOG(2) << "literal is not an effective scalar: " << l.ToString(); + return nullopt; + } + switch (l.shape().element_type()) { + case S8: + return l.GetFirstElement(); + case S16: + return l.GetFirstElement(); + case S32: + return l.GetFirstElement(); + case S64: + return l.GetFirstElement(); + case U8: + return l.GetFirstElement(); + case U16: + return l.GetFirstElement(); + case U32: + return l.GetFirstElement(); + case U64: { + uint64 v = l.GetFirstElement(); + if (v > static_cast(std::numeric_limits::max())) { + VLOG(2) << "uint64 literal is out of range for int64: " << v; + return nullopt; + } + return v; + } + default: + VLOG(2) << "literal is of non-integral type " << l.shape().ToString(); + return nullopt; + } +} + +// Computes a + b, returning nullopt if it overflows. +optional CheckedAdd(int64 a, int64 b) { + // Overflow occurred iff `a` and `b` have the same sign and `a + b` has a + // different sign, see Hacker's Delignt 2nd Ed. pp 28. + uint64 aa = absl::bit_cast(a); + uint64 bb = absl::bit_cast(b); + int64 result = absl::bit_cast(aa + bb); + if (a >= 0 == b >= 0 && result >= 0 != a >= 0) { + return nullopt; + } + return result; +} + +// Computes a - b, returning nullopt if it overflows. +optional CheckedSubtract(int64 a, int64 b) { + uint64 aa = absl::bit_cast(a); + uint64 bb = absl::bit_cast(b); + int64 result = absl::bit_cast(aa - bb); + // Overflow occurred iff `a` and `b` have different signs and the sign of + // `a - b` is the same as that of `b`, see Hacker's Delight 2nd Ed. pp 29. + if (a >= 0 != b >= 0 && result >= 0 == b >= 0) { + return nullopt; + } + return result; +} + +// Check if +// - `i` is initialized to a scalar constant K (namely, `indvar_init`), +// - the while condition does `i < N` or `i <= N`, and +// - the while body does `i++`. +// If so, it's trivial to compute the loop bound. +static optional PatternMatchLoopTripCount(HloInstruction* while_op, + int64 indvar_tuple_idx, + const Literal& indvar_init) { + // First, find the scalar constant K that `i` is initialized to. + optional indvar_init_val = LiteralAsScalarInt64(indvar_init); + if (!indvar_init_val) { + VLOG(2) << "Pattern-match failed: induction variable init is not a " + "constant scalar representable as an int64: " + << indvar_init.ToString(); + return nullopt; + } + + // Check that `i` goes as `i++` in the while body. + // + // TODO(jlebar): We could also handle i-- and other idioms. + auto* while_body = while_op->while_body(); + auto* while_body_indvar_update = + while_body->root_instruction()->operand(indvar_tuple_idx); + auto* while_body_indvar = NonConstantOperand(while_body_indvar_update); + if (!Match(while_body_indvar_update, + m::AddAnyOrder(m::Op().Is(while_body_indvar), + m::ConstantEffectiveScalar(1)))) { + VLOG(2) << "Pattern-match failed: induction variable does not go as i++: " + << while_body_indvar_update->ToString(); + return nullopt; + } + + // Check that we do op(i, N) or op(N, i) as the while condition. Capture the + // value N. + auto* while_cond = while_op->while_condition(); + auto* while_cond_root = while_cond->root_instruction(); + auto* while_cond_indvar = NonConstantOperand(while_cond_root); + HloInstruction* while_cond_bound = nullptr; + if (!Match(while_cond_root, + m::Op().WithBinaryOperandsAnyOrder( + m::Op().Is(while_cond_indvar), + m::ConstantEffectiveScalar(&while_cond_bound)))) { + VLOG(2) << "Pattern-match failed: while condition is not of the form " + "op(i, N) or op(N, i)."; + return nullopt; + } + // Note: If this succeeds, the constant `N` is representable as an int64 -- + // that is, if it's an XLA U64, it fits within an int64. + optional while_cond_bound_val = + LiteralAsScalarInt64(while_cond_bound->literal()); + if (!while_cond_bound_val) { + VLOG(2) << "Pattern-match failed: while condition induction variable is " + "not a constant scalar representable as an int64."; + return nullopt; + } + + // Handle `i = K; i < N; ++i`. + if (Match(while_cond_root, + m::Op() + .WithOpcode(HloOpcode::kLt) + .WithOperand(0, m::Op().Is(while_cond_indvar)))) { + VLOG(2) << "Pattern-match succeeded: loop condition is i < N: " + << while_cond_root->ToString(); + optional trips = + CheckedSubtract(*while_cond_bound_val, *indvar_init_val); + if (trips) { + return std::max(int64{0}, *trips); + } else { + VLOG(2) << "Pattern-match failed: Trip count exceeds INT64_MAX."; + return nullopt; + } + } + + // Handle `i = K; i <= N; ++i`. + if (Match(while_cond_root, + m::Op() + .WithOpcode(HloOpcode::kLe) + .WithOperand(0, m::Op().Is(while_cond_indvar)))) { + VLOG(2) << "Pattern-match succeeded: loop condition is i <= N: " + << while_cond_root->ToString(); + optional trips = + CheckedSubtract(*while_cond_bound_val, *indvar_init_val); + if (!trips) { + VLOG(2) << "Pattern-match failed: Trip count exceeds INT64_MAX"; + return nullopt; + } + trips = CheckedAdd(*trips, 1); + if (!trips) { + VLOG(2) << "Pattern-match failed: Trip count exceeds INT64_MAX"; + return nullopt; + } + return std::max(0, *trips); + } + + VLOG(2) << "Pattern-match failed: while condition follows unknown pattern: " + << while_cond_root->ToString(); + return nullopt; +} + optional ComputeWhileLoopTripCount(HloInstruction* while_op, - int64 max_value_returned) { + int64 max_brute_force_iters) { VLOG(2) << "Getting trip count for loop " << while_op->ToString(); // The loop's induction variable is found at @@ -188,23 +349,30 @@ optional ComputeWhileLoopTripCount(HloInstruction* while_op, auto* indvar_init = while_init->mutable_operand(*indvar_tuple_idx); StatusOr indvar_init_result = evaluator.Evaluate(indvar_init); if (!indvar_init_result.ok()) { - VLOG(2) << "Couldn't evaluate induction variable init: " - << indvar_init_result.status(); + VLOG(2) << "Couldn't evaluate induction variable init, " + << indvar_init_result.status() << ", " << indvar_init->ToString(); return nullopt; } + Literal indvar_iter_val = std::move(indvar_init_result).ValueOrDie(); + + // First, try to pattern-match. + if (auto trip_count = PatternMatchLoopTripCount(while_op, *indvar_tuple_idx, + indvar_iter_val)) { + return trip_count; + } + // If our pattern-match failed, try brute-forcing the loop trip count. auto* while_body = while_op->while_body(); auto* while_body_indvar_update = while_body->root_instruction()->operand(*indvar_tuple_idx); auto* while_body_indvar = NonConstantOperand(while_body_indvar_update); - // The initial value of the induction variable. - Literal indvar_iter_val = std::move(indvar_init_result).ValueOrDie(); - for (int64 trip_count = 0; trip_count != max_value_returned + 1; + auto* while_cond = while_op->while_condition(); + auto* while_cond_root = while_cond->root_instruction(); + auto* while_cond_indvar = NonConstantOperand(while_cond_root); + + for (int64 trip_count = 0; trip_count != max_brute_force_iters + 1; ++trip_count) { - auto* while_cond = while_op->while_condition(); - auto* while_cond_root = while_cond->root_instruction(); - auto* while_cond_indvar = NonConstantOperand(while_cond_root); StatusOr result = evaluator.EvaluateWithSubstitutions( while_cond_root, {{while_cond_indvar, &indvar_iter_val}}); if (!result.ok()) { diff --git a/tensorflow/compiler/xla/service/while_loop_analysis.h b/tensorflow/compiler/xla/service/while_loop_analysis.h index ac69a727bd..9bb784a544 100644 --- a/tensorflow/compiler/xla/service/while_loop_analysis.h +++ b/tensorflow/compiler/xla/service/while_loop_analysis.h @@ -22,11 +22,14 @@ limitations under the License. namespace xla { // Returns the precise trip count of the loop if it's statically known, -// nullopt otherwise. max_value_returned limits the number of steps that are -// evaluated while trying to brute force a loop trip count, trip counts larger -// than max_value_returned result in nullopt. -absl::optional ComputeWhileLoopTripCount(HloInstruction *while_op, - int64 max_value_returned = 128); +// nullopt otherwise. +// +// max_brute_force_iters limits the number of steps that are evaluated while +// trying to brute force a loop trip count. trip counts larger than +// max_brute_force_iters may be returned if we can pattern-match the loop +// condition. +absl::optional ComputeWhileLoopTripCount( + HloInstruction *while_op, int64 max_brute_force_iters = 128); // Returns an upper bound on the trip count of the loop if it's statically // known, nullopt otherwise. diff --git a/tensorflow/compiler/xla/service/while_loop_trip_count_annotator.cc b/tensorflow/compiler/xla/service/while_loop_trip_count_annotator.cc new file mode 100644 index 0000000000..03bb6792fe --- /dev/null +++ b/tensorflow/compiler/xla/service/while_loop_trip_count_annotator.cc @@ -0,0 +1,40 @@ +/* Copyright 2019 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/while_loop_trip_count_annotator.h" +#include "tensorflow/compiler/xla/service/while_loop_analysis.h" +#include "tensorflow/compiler/xla/xla_data.pb.h" + +namespace xla { + +StatusOr WhileLoopTripCountAnnotator::Run(HloModule* module) { + bool changed = false; + for (const HloComputation* comp : module->computations()) { + for (HloInstruction* instr : comp->instructions()) { + if (instr->opcode() != HloOpcode::kWhile) { + continue; + } + if (auto trip_count = ComputeWhileLoopTripCount(instr)) { + WhileLoopBackendConfig config; + config.mutable_known_trip_count()->set_n(*trip_count); + TF_RETURN_IF_ERROR(instr->set_backend_config(config)); + changed = true; + } + } + } + return changed; +} + +} // namespace xla diff --git a/tensorflow/compiler/xla/service/while_loop_trip_count_annotator.h b/tensorflow/compiler/xla/service/while_loop_trip_count_annotator.h new file mode 100644 index 0000000000..7cda2f10ce --- /dev/null +++ b/tensorflow/compiler/xla/service/while_loop_trip_count_annotator.h @@ -0,0 +1,48 @@ +/* Copyright 2019 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_SERVICE_WHILE_LOOP_TRIP_COUNT_ANNOTATOR_H_ +#define TENSORFLOW_COMPILER_XLA_SERVICE_WHILE_LOOP_TRIP_COUNT_ANNOTATOR_H_ + +#include "tensorflow/compiler/xla/service/hlo_module.h" +#include "tensorflow/compiler/xla/service/hlo_pass_interface.h" +#include "tensorflow/compiler/xla/statusor.h" + +namespace xla { + +// Pass that annotates `while` loops with known trip counts. +// +// The annotation is stored as a backend-config on the while loop node. +// +// This pass should run after all passes that might semantically modify a while +// loop, e.g. by unrolling it. Otherwise, a loop could end up with a +// backend-config that doesn't match its true trip-count. +// +// This pass does some pattern-matching on loop bodies and conditions, so it +// should run after most HLO simplifications and before fusion and layout +// assignment, which make pattern matching much more difficult by e.g. +// introducing `copy` nodes. +class WhileLoopTripCountAnnotator : public HloModulePass { + public: + ~WhileLoopTripCountAnnotator() override {} + absl::string_view name() const override { + return "while-loop-trip-count-annotator"; + } + StatusOr Run(HloModule* module) override; +}; + +} // namespace xla + +#endif // TENSORFLOW_COMPILER_XLA_SERVICE_WHILE_LOOP_TRIP_COUNT_ANNOTATOR_H_ diff --git a/tensorflow/compiler/xla/service/while_loop_trip_count_annotator_test.cc b/tensorflow/compiler/xla/service/while_loop_trip_count_annotator_test.cc new file mode 100644 index 0000000000..5c19cbc015 --- /dev/null +++ b/tensorflow/compiler/xla/service/while_loop_trip_count_annotator_test.cc @@ -0,0 +1,207 @@ +/* Copyright 2019 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/while_loop_trip_count_annotator.h" +#include "tensorflow/compiler/xla/service/pattern_matcher.h" +#include "tensorflow/compiler/xla/service/while_loop_simplifier.h" +#include "tensorflow/compiler/xla/status_macros.h" +#include "tensorflow/compiler/xla/test.h" +#include "tensorflow/compiler/xla/tests/hlo_test_base.h" +#include "tensorflow/core/lib/core/status_test_util.h" + +namespace xla { +namespace { + +class TripCountAnnotatorTest : public HloTestBase {}; + +TEST_F(TripCountAnnotatorTest, KnownSmallTripCount) { + const char* kModuleStr = R"( + HloModule test + Body { + param = (s32[]) parameter(0) + i = s32[] get-tuple-element(param), index=0 + one = s32[] constant(1) + i_plus_one = s32[] add(i, one) + ROOT tuple = (s32[]) tuple(i_plus_one) + } + + Cond { + param = (s32[]) parameter(0) + i = s32[] get-tuple-element(param), index=0 + trip_count = s32[] constant(10) + ROOT done = pred[] less-than(i, trip_count) + } + + ENTRY test { + i_start = s32[] constant(0) + initial_tuple = (s32[]) tuple(i_start) + ROOT while = (s32[]) while(initial_tuple), condition=Cond, body=Body + })"; + + TF_ASSERT_OK_AND_ASSIGN(auto m, ParseAndReturnVerifiedModule(kModuleStr)); + WhileLoopTripCountAnnotator pass; + TF_ASSERT_OK_AND_ASSIGN(bool changed, RunHloPass(&pass, m.get())); + ASSERT_TRUE(changed); + + TF_ASSERT_OK_AND_ASSIGN(auto config, + m->entry_computation() + ->root_instruction() + ->backend_config()); + EXPECT_EQ(10, config.known_trip_count().n()); +} + +TEST_F(TripCountAnnotatorTest, KnownLargeTripCount) { + const char* kModuleStr = R"( + HloModule test + Body { + param = (s32[]) parameter(0) + i = s32[] get-tuple-element(param), index=0 + one = s32[] constant(1) + i_plus_one = s32[] add(i, one) + ROOT tuple = (s32[]) tuple(i_plus_one) + } + + Cond { + param = (s32[]) parameter(0) + i = s32[] get-tuple-element(param), index=0 + trip_count = s32[] constant(1000000) + ROOT done = pred[] less-than(i, trip_count) + } + + ENTRY test { + i_start = s32[] constant(0) + initial_tuple = (s32[]) tuple(i_start) + ROOT while = (s32[]) while(initial_tuple), condition=Cond, body=Body + })"; + + TF_ASSERT_OK_AND_ASSIGN(auto m, ParseAndReturnVerifiedModule(kModuleStr)); + WhileLoopTripCountAnnotator pass; + TF_ASSERT_OK_AND_ASSIGN(bool changed, RunHloPass(&pass, m.get())); + ASSERT_TRUE(changed); + + TF_ASSERT_OK_AND_ASSIGN(auto config, + m->entry_computation() + ->root_instruction() + ->backend_config()); + EXPECT_EQ(1000000, config.known_trip_count().n()); +} + +TEST_F(TripCountAnnotatorTest, NonzeroStart) { + const char* kModuleStr = R"( + HloModule test + Body { + param = (s32[]) parameter(0) + i = s32[] get-tuple-element(param), index=0 + one = s32[] constant(1) + i_plus_one = s32[] add(i, one) + ROOT tuple = (s32[]) tuple(i_plus_one) + } + + Cond { + param = (s32[]) parameter(0) + i = s32[] get-tuple-element(param), index=0 + trip_count = s32[] constant(1000000) + ROOT done = pred[] less-than(i, trip_count) + } + + ENTRY test { + i_start = s32[] constant(10) + initial_tuple = (s32[]) tuple(i_start) + ROOT while = (s32[]) while(initial_tuple), condition=Cond, body=Body + })"; + + TF_ASSERT_OK_AND_ASSIGN(auto m, ParseAndReturnVerifiedModule(kModuleStr)); + WhileLoopTripCountAnnotator pass; + TF_ASSERT_OK_AND_ASSIGN(bool changed, RunHloPass(&pass, m.get())); + ASSERT_TRUE(changed); + + TF_ASSERT_OK_AND_ASSIGN(auto config, + m->entry_computation() + ->root_instruction() + ->backend_config()); + EXPECT_EQ(999990, config.known_trip_count().n()); +} + +TEST_F(TripCountAnnotatorTest, LessThanOrEqualTo) { + const char* kModuleStr = R"( + HloModule test + Body { + param = (s32[]) parameter(0) + i = s32[] get-tuple-element(param), index=0 + one = s32[] constant(1) + i_plus_one = s32[] add(i, one) + ROOT tuple = (s32[]) tuple(i_plus_one) + } + + Cond { + param = (s32[]) parameter(0) + i = s32[] get-tuple-element(param), index=0 + trip_count = s32[] constant(1000000) + ROOT done = pred[] less-than-or-equal-to(i, trip_count) + } + + ENTRY test { + i_start = s32[] constant(10) + initial_tuple = (s32[]) tuple(i_start) + ROOT while = (s32[]) while(initial_tuple), condition=Cond, body=Body + })"; + + TF_ASSERT_OK_AND_ASSIGN(auto m, ParseAndReturnVerifiedModule(kModuleStr)); + WhileLoopTripCountAnnotator pass; + TF_ASSERT_OK_AND_ASSIGN(bool changed, RunHloPass(&pass, m.get())); + ASSERT_TRUE(changed); + + TF_ASSERT_OK_AND_ASSIGN(auto config, + m->entry_computation() + ->root_instruction() + ->backend_config()); + EXPECT_EQ(999991, config.known_trip_count().n()); +} + +TEST_F(TripCountAnnotatorTest, Int64Overflow) { + // for(i = INT64_MIN; i < INT64_MAX; ++i) + // + // We store the trip count as an int64, so this loop is unanalyzable. + const char* kModuleStr = R"( + HloModule test + Body { + param = (s64[]) parameter(0) + i = s64[] get-tuple-element(param), index=0 + one = s64[] constant(1) + i_plus_one = s64[] add(i, one) + ROOT tuple = (s64[]) tuple(i_plus_one) + } + + Cond { + param = (s64[]) parameter(0) + i = s64[] get-tuple-element(param), index=0 + trip_count = s64[] constant(9223372036854775807) // 2^63-1 + ROOT done = pred[] less-than-or-equal-to(i, trip_count) + } + + ENTRY test { + i_start = s64[] constant(-9223372036854775808) // -2^63 + initial_tuple = (s64[]) tuple(i_start) + ROOT while = (s64[]) while(initial_tuple), condition=Cond, body=Body + })"; + + TF_ASSERT_OK_AND_ASSIGN(auto m, ParseAndReturnVerifiedModule(kModuleStr)); + WhileLoopTripCountAnnotator pass; + TF_ASSERT_OK_AND_ASSIGN(bool changed, RunHloPass(&pass, m.get())); + EXPECT_FALSE(changed); +} + +} // namespace +} // namespace xla diff --git a/tensorflow/compiler/xla/xla_data.proto b/tensorflow/compiler/xla/xla_data.proto index 226299a718..505c565729 100644 --- a/tensorflow/compiler/xla/xla_data.proto +++ b/tensorflow/compiler/xla/xla_data.proto @@ -16,6 +16,7 @@ limitations under the License. syntax = "proto3"; package xla; + option cc_enable_arenas = true; // Primitive types are the individual values that can be held in rectangular @@ -55,7 +56,7 @@ enum PrimitiveType { F64 = 12; // Complex values of fixed width. - C64 = 15; // Paired F32 (real, imag), as in std::complex. + C64 = 15; // Paired F32 (real, imag), as in std::complex. C128 = 18; // Paired F64 (real, imag), as in std::complex. // A tuple is a polymorphic sequence; e.g. a shape that holds different @@ -367,7 +368,7 @@ message LiteralProto { repeated uint64 u64s = 7; repeated float f32s = 8; repeated double f64s = 9; - repeated float c64s = 12; // Stored as interleaved real, imag floats. + repeated float c64s = 12; // Stored as interleaved real, imag floats. repeated double c128s = 18; // Stored as interleaved real, imag doubles. repeated LiteralProto tuple_literals = 10; // The F16s, BF16s, U16s and S16s are encoded in little endian byte order @@ -510,7 +511,7 @@ message ConvolutionDimensionNumbers { repeated int64 output_spatial_dimensions = 12; // Next = 13 -}; +} enum FftType { FFT = 0; // Forward FFT; complex in, complex out. @@ -529,7 +530,7 @@ message DotDimensionNumbers { repeated int64 lhs_batch_dimensions = 3; // The dimension numbers that represent the 'rhs' batch dimensions. repeated int64 rhs_batch_dimensions = 4; -}; +} enum RandomDistribution { RNG_INVALID = 0; @@ -636,3 +637,20 @@ message ParameterReplication { // the HLO instruction's shape. repeated bool replicated_at_leaf_buffers = 1; } + +// A backend-config for kWhile loops that stores the loop's trip count, if it is +// known. +// +// This is useful for backends that can implement a `for i in 0..N` loop more +// efficiently than a `while` loop. For example, on GPUs, we can implement a +// `for i in 0..N` loop by enqueueing the kernels for the loop body N times, +// whereas implementing a `while` loop requires a host-device sync on each +// iteration. +message WhileLoopBackendConfig { + message KnownTripCount { + int64 n = 1; + } + // This indirection lets us distinguish between known-trip-count == 0 and + // unknown-trip-count. + KnownTripCount known_trip_count = 1; +} -- GitLab From fb7ee7016f1c4b985220d9283b25386def06cb80 Mon Sep 17 00:00:00 2001 From: Shivani Agrawal Date: Mon, 25 Feb 2019 15:11:55 -0800 Subject: [PATCH 486/766] [tf.data] Exposes `bytes_produced_stats` in `tf.data.experimental`. PiperOrigin-RevId: 235611136 --- tensorflow/python/data/experimental/__init__.py | 2 ++ tensorflow/python/data/experimental/ops/stats_ops.py | 3 +-- .../tools/api/golden/v1/tensorflow.data.experimental.pbtxt | 4 ++++ .../tools/api/golden/v2/tensorflow.data.experimental.pbtxt | 4 ++++ 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/data/experimental/__init__.py b/tensorflow/python/data/experimental/__init__.py index 275bdf7ef4..f7103a621e 100644 --- a/tensorflow/python/data/experimental/__init__.py +++ b/tensorflow/python/data/experimental/__init__.py @@ -42,6 +42,7 @@ See [Importing Data](https://tensorflow.org/guide/datasets) for an overview. @@ThreadingOptions @@bucket_by_sequence_length +@@bytes_produced_stats @@cardinality @@choose_from_datasets @@copy_to_device @@ -116,6 +117,7 @@ from tensorflow.python.data.experimental.ops.resampling import rejection_resampl from tensorflow.python.data.experimental.ops.scan_ops import scan from tensorflow.python.data.experimental.ops.shuffle_ops import shuffle_and_repeat from tensorflow.python.data.experimental.ops.stats_aggregator import StatsAggregator +from tensorflow.python.data.experimental.ops.stats_ops import bytes_produced_stats from tensorflow.python.data.experimental.ops.stats_ops import latency_stats from tensorflow.python.data.experimental.ops.stats_options import StatsOptions from tensorflow.python.data.experimental.ops.take_while_ops import take_while diff --git a/tensorflow/python/data/experimental/ops/stats_ops.py b/tensorflow/python/data/experimental/ops/stats_ops.py index 13dcb92fa0..dff4286dab 100644 --- a/tensorflow/python/data/experimental/ops/stats_ops.py +++ b/tensorflow/python/data/experimental/ops/stats_ops.py @@ -48,8 +48,7 @@ def set_stats_aggregator(stats_aggregator, prefix="", counter_prefix=""): return _apply_fn -# TODO(b/38416882): Properly export in the `tf.data.experimental` API when -# stable or make private / remove. +@tf_export("data.experimental.bytes_produced_stats") def bytes_produced_stats(tag): """Records the number of bytes produced by each element of the input dataset. diff --git a/tensorflow/tools/api/golden/v1/tensorflow.data.experimental.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.data.experimental.pbtxt index 0e3999c0f4..853054aab0 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.data.experimental.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.data.experimental.pbtxt @@ -88,6 +88,10 @@ tf_module { name: "bucket_by_sequence_length" argspec: "args=[\'element_length_func\', \'bucket_boundaries\', \'bucket_batch_sizes\', \'padded_shapes\', \'padding_values\', \'pad_to_bucket_boundary\', \'no_padding\', \'drop_remainder\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'False\', \'False\', \'False\'], " } + member_method { + name: "bytes_produced_stats" + argspec: "args=[\'tag\'], varargs=None, keywords=None, defaults=None" + } member_method { name: "cardinality" argspec: "args=[\'dataset\'], varargs=None, keywords=None, defaults=None" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.pbtxt index 48d9002864..08b7657a5f 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.pbtxt @@ -88,6 +88,10 @@ tf_module { name: "bucket_by_sequence_length" argspec: "args=[\'element_length_func\', \'bucket_boundaries\', \'bucket_batch_sizes\', \'padded_shapes\', \'padding_values\', \'pad_to_bucket_boundary\', \'no_padding\', \'drop_remainder\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'False\', \'False\', \'False\'], " } + member_method { + name: "bytes_produced_stats" + argspec: "args=[\'tag\'], varargs=None, keywords=None, defaults=None" + } member_method { name: "cardinality" argspec: "args=[\'dataset\'], varargs=None, keywords=None, defaults=None" -- GitLab From 831addfea46847836fa47e76055ec4ed4cd880ff Mon Sep 17 00:00:00 2001 From: Andy Ly Date: Mon, 25 Feb 2019 15:30:50 -0800 Subject: [PATCH 487/766] Automated rollback of commit c9e141c4ed2898568e019b8de5463254a011a3c7 PiperOrigin-RevId: 235614315 --- tensorflow/core/grappler/optimizers/BUILD | 8 +- .../optimizers/dependency_optimizer.cc | 547 ++++++++++-------- .../optimizers/dependency_optimizer.h | 33 +- .../optimizers/dependency_optimizer_test.cc | 285 +++++---- 4 files changed, 463 insertions(+), 410 deletions(-) diff --git a/tensorflow/core/grappler/optimizers/BUILD b/tensorflow/core/grappler/optimizers/BUILD index 7b52e7c288..95063b65dc 100644 --- a/tensorflow/core/grappler/optimizers/BUILD +++ b/tensorflow/core/grappler/optimizers/BUILD @@ -1,7 +1,8 @@ licenses(["notice"]) # Apache 2.0 -load("//tensorflow:tensorflow.bzl", "tf_cc_test", "tf_kernel_library") +load("//tensorflow:tensorflow.bzl", "tf_cc_test") load("//tensorflow:tensorflow.bzl", "tf_cuda_cc_test") +load("//tensorflow:tensorflow.bzl", "tf_kernel_library") # Platform specific build config load( @@ -325,19 +326,14 @@ cc_library( ":constant_folding", ":graph_optimizer", "//tensorflow/core:framework", - "//tensorflow/core:graph", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", "//tensorflow/core:protos_all_cc", "//tensorflow/core/grappler:grappler_item", - "//tensorflow/core/grappler:mutable_graph_view", "//tensorflow/core/grappler:op_types", "//tensorflow/core/grappler:utils", "//tensorflow/core/grappler/costs:graph_properties", "//tensorflow/core/grappler/utils:topological_sort", - "@com_google_absl//absl/container:flat_hash_map", - "@com_google_absl//absl/container:flat_hash_set", - "@com_google_absl//absl/strings", ], ) diff --git a/tensorflow/core/grappler/optimizers/dependency_optimizer.cc b/tensorflow/core/grappler/optimizers/dependency_optimizer.cc index 2dfa5e99d6..8b81cb2430 100644 --- a/tensorflow/core/grappler/optimizers/dependency_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/dependency_optimizer.cc @@ -15,13 +15,13 @@ limitations under the License. #include "tensorflow/core/grappler/optimizers/dependency_optimizer.h" -#include "absl/container/flat_hash_map.h" +#include +#include + #include "tensorflow/core/framework/node_def.pb.h" #include "tensorflow/core/framework/op.h" -#include "tensorflow/core/graph/tensor_id.h" #include "tensorflow/core/grappler/costs/graph_properties.h" #include "tensorflow/core/grappler/grappler_item.h" -#include "tensorflow/core/grappler/mutable_graph_view.h" #include "tensorflow/core/grappler/op_types.h" #include "tensorflow/core/grappler/optimizers/constant_folding.h" #include "tensorflow/core/grappler/utils.h" @@ -38,15 +38,20 @@ namespace grappler { namespace { -// Builds a map from the &graph->node(i) to i. -absl::flat_hash_map BuildNodeToIdx(const GraphDef& graph) { - // Set up &node -> index map. - absl::flat_hash_map node_to_idx; - for (int i = 0; i < graph.node_size(); ++i) { - const NodeDef& node = graph.node(i); - node_to_idx[&node] = i; +bool RemoveInput(NodeDef* node, const string& input, NodeMap* node_map) { + bool removed_input = false; + int pos = 0; + while (pos < node->input_size()) { + if (node->input(pos) == input) { + node->mutable_input()->SwapElements(pos, node->input_size() - 1); + node->mutable_input()->RemoveLast(); + node_map->RemoveOutput(NodeName(input), node->name()); + removed_input = true; + } else { + ++pos; + } } - return node_to_idx; + return removed_input; } } // namespace @@ -63,9 +68,7 @@ bool DependencyOptimizer::SafeToRemoveIdentity(const NodeDef& node) const { // The output values of this node may be needed. return false; } - MutableGraphView::OutputPort port = graph_view_->GetRegularFanin( - MutableGraphView::InputPort(const_cast(&node), 0)); - NodeDef* input = port.node; + const NodeDef* input = node_map_->GetNode(NodeName(node.input(0))); CHECK(input != nullptr) << "node = " << node.name() << " input = " << node.input(0); // Don't remove Identity nodes corresponding to Variable reads or following @@ -74,28 +77,22 @@ bool DependencyOptimizer::SafeToRemoveIdentity(const NodeDef& node) const { return false; } else if (IsSwitch(*input)) { // Don't turn Identity nodes following Switch into NoOp or remove them - // if it requires anchoring a control dependencies to the Switch node, which + // if it requires anchoring a control dependencies the Switch node, which // is not valid. - MutableGraphView::OutputPort control_port(const_cast(&node), - Graph::kControlSlot); - auto control_fanouts = graph_view_->GetFanout(control_port); - if (!control_fanouts.empty()) { + if (str_util::StartsWith(node.name(), kConstantFoldingCtrl)) { + // TODO(rmlarsen): Try to remove this artificial contraint. return false; } } - bool node_has_multiple_inputs = - graph_view_->NumFanins(node, /*include_controlling_nodes=*/true) > 1; - - auto fanouts = - graph_view_->GetFanouts(node, /*include_controlled_nodes=*/true); - for (auto fanout : fanouts) { - if (node_has_multiple_inputs && IsMerge(*fanout.node)) { + for (auto consumer : node_map_->GetOutputs(node.name())) { + if (node.input_size() > 1 && IsMerge(*consumer)) { return false; } if (IsSwitch(*input)) { - if (graph_view_->HasFanin(*fanout.node, - {node.name(), Graph::kControlSlot})) { - return false; + for (const string& consumer_input : consumer->input()) { + if (consumer_input == AsControlDependency(node.name())) { + return false; + } } } } @@ -119,7 +116,7 @@ bool DependencyOptimizer::SafeToConvertToNoOp(const NodeDef& node) const { if (!status.ok() || op_def->output_arg_size() == 0) { return false; } - const absl::flat_hash_set do_not_rewrite_ops{ + const std::unordered_set do_not_rewrite_ops{ "Assert", "CheckNumerics", "_Retval", "_Arg", "_ParallelConcatUpdate", "TPUExecute", "TPUCompile", "ControlTrigger"}; @@ -129,7 +126,7 @@ bool DependencyOptimizer::SafeToConvertToNoOp(const NodeDef& node) const { if (!SafeToRemoveIdentity(node)) { return false; } - if (graph_view_->NumFanouts(node, /*include_controlled_nodes=*/false) > 0) { + if (NumNonControlOutputs(node, *node_map_) > 0) { // The output values of this node may be needed. return false; } @@ -137,61 +134,61 @@ bool DependencyOptimizer::SafeToConvertToNoOp(const NodeDef& node) const { } int DependencyOptimizer::NumEdgesIfBypassed( - const NodeDef& node, int num_controlling_fanins, - const absl::flat_hash_set& fanin_edges, - const absl::flat_hash_set& fanout_edges, - int num_unique_fanout_nodes) const { + const NodeDef& node, const std::vector& output_nodes) const { const bool is_multi_input_identity_n = IsIdentityN(node) && !IsIdentityNSingleInput(node); - const int num_fanins = fanin_edges.size(); + const int num_outputs = output_nodes.size(); + const int num_inputs = node.input_size(); if (is_multi_input_identity_n) { // multi-input identity_n with input/output control dependencies will likely // increase number of edges after optimization. - int num_edges_if_bypassed = 0; - int num_non_controlling_fanins = num_fanins - num_controlling_fanins; - num_edges_if_bypassed += num_non_controlling_fanins; - num_edges_if_bypassed += num_controlling_fanins * num_unique_fanout_nodes; - - for (const auto& fanout : fanout_edges) { - if (fanout.dst.port_id == Graph::kControlSlot) { - num_edges_if_bypassed += num_fanins; + int num_edges_if_bypassed(0); + for (string input_node_name : node.input()) { + if (IsControlInput(input_node_name)) { + num_edges_if_bypassed += num_outputs; } else { ++num_edges_if_bypassed; } } + + for (auto consumer : output_nodes) { + for (int j = 0; j < consumer->input_size(); ++j) { + const TensorId consumer_input = ParseTensorName(consumer->input(j)); + if (consumer_input.node() == node.name()) { + if (IsControlInput(consumer_input)) { + num_edges_if_bypassed += num_inputs; + } else { + ++num_edges_if_bypassed; + } + } + } + } return num_edges_if_bypassed; } else { - return num_fanins * num_unique_fanout_nodes; + return num_inputs * num_outputs; } } bool DependencyOptimizer::BypassingNodeIsBeneficial( - const NodeDef& node, int num_controlling_fanins, - const absl::flat_hash_set& fanin_edges, - const absl::flat_hash_set& fanout_edges) const { + const NodeDef& node, const std::vector& input_nodes, + const std::vector& output_nodes) const { const bool is_identity = IsIdentity(node) || IsIdentityNSingleInput(node); const bool is_multi_input_identity_n = IsIdentityN(node) && !IsIdentityNSingleInput(node); - const int num_fanins = fanin_edges.size(); - absl::flat_hash_set unique_fanout_nodes; - for (const auto& fanout_edge : fanout_edges) { - unique_fanout_nodes.insert(fanout_edge.dst.node); - } - const int num_unique_fanout_nodes = unique_fanout_nodes.size(); + const int num_outputs = output_nodes.size(); + const int num_inputs = node.input_size(); - if (NumEdgesIfBypassed(node, num_controlling_fanins, fanin_edges, - fanout_edges, num_unique_fanout_nodes) > - num_fanins + num_unique_fanout_nodes) { + if (NumEdgesIfBypassed(node, output_nodes) > num_inputs + num_outputs) { return false; } // Make sure that we don't increase the number of edges that cross // device boundaries. - if ((num_fanins == 1 && num_unique_fanout_nodes > 1 && - fanin_edges.begin()->src.node->device() != node.device()) || - (num_fanins > 1 && num_unique_fanout_nodes == 1 && - fanout_edges.begin()->dst.node->device() != node.device())) { + if ((num_inputs == 1 && num_outputs > 1 && + input_nodes[0]->device() != node.device()) || + (num_inputs > 1 && num_outputs == 1 && + output_nodes[0]->device() != node.device())) { return false; } @@ -200,21 +197,21 @@ bool DependencyOptimizer::BypassingNodeIsBeneficial( // cost before and after. const string& node_dev = node.device(); int num_cross_in = 0; - for (const auto& fanin : fanin_edges) { - num_cross_in += static_cast(fanin.src.node->device() != node_dev); + for (NodeDef* input_node : input_nodes) { + num_cross_in += static_cast(input_node->device() != node_dev); } int num_cross_out = 0; - for (const auto& fanout : unique_fanout_nodes) { - num_cross_out += static_cast(fanout->device() != node_dev); + for (NodeDef* output_node : output_nodes) { + num_cross_out += static_cast(output_node->device() != node_dev); } // Make sure we do not increase the number of device crossings. const int num_cross_before = num_cross_in + num_cross_out; int num_cross_after = 0; - for (const auto& fanin : fanin_edges) { - for (const auto& fanout : unique_fanout_nodes) { + for (NodeDef* input_node : input_nodes) { + for (NodeDef* output_node : output_nodes) { num_cross_after += - static_cast(fanin.src.node->device() != fanout->device()); + static_cast(input_node->device() != output_node->device()); } } if (num_cross_after > num_cross_before) { @@ -232,58 +229,83 @@ bool DependencyOptimizer::BypassingNodeIsBeneficial( return true; } -Status DependencyOptimizer::OptimizeNode( - const string& node_name, SetVector* nodes_to_simplify, - absl::flat_hash_set* nodes_to_delete) { - NodeDef* node = graph_view_->GetNode(node_name); +void DependencyOptimizer::OptimizeNode(int node_idx, + SetVector* nodes_to_simplify, + std::set* nodes_to_delete) { + NodeDef* node = optimized_graph_->mutable_node(node_idx); const bool is_noop = IsNoOp(*node); const bool is_identity = IsIdentity(*node) || IsIdentityNSingleInput(*node); const bool is_multi_input_identity = IsIdentityN(*node) && !IsIdentityNSingleInput(*node); - // WARNING: This is a strong assumption based on the executor behavior that - // constant nodes with no input control dependency are always executed early. - // In this case we then can prune all their output control dependencies. - if (IsConstant(*node) && - graph_view_->NumFanins(*node, /*include_controlling_nodes=*/true) == 0) { - MutableGraphView::OutputPort control_port(node, Graph::kControlSlot); - auto control_fanouts = graph_view_->GetFanout(control_port); - for (const auto& fanout : control_fanouts) { - TF_RETURN_IF_ERROR( - graph_view_->RemoveControllingFanin(fanout.node->name(), node_name)); - nodes_to_simplify->PushBack(fanout.node->name()); + const string node_name = node->name(); + // Constant nodes with no input control dependency are always executed early, + // so we can prune all their output control dependencies. + if (IsConstant(*node) && node->input_size() == 0) { + const std::set output_nodes = node_map_->GetOutputs(node_name); + for (NodeDef* fanout : output_nodes) { + bool optimize_fanout = false; + bool data_connection = false; + for (int i = fanout->input_size() - 1; i >= 0; --i) { + const TensorId input_tensor = ParseTensorName(fanout->input(i)); + if (input_tensor.node() == node_name) { + if (input_tensor.index() < 0) { + fanout->mutable_input()->SwapElements(i, fanout->input_size() - 1); + fanout->mutable_input()->RemoveLast(); + optimize_fanout = true; + } else { + data_connection = true; + } + } + } + if (optimize_fanout) { + nodes_to_simplify->PushBack(node_to_idx_[fanout]); + if (!data_connection) { + node_map_->RemoveOutput(node_name, fanout->name()); + } + } } - - if (graph_view_->NumFanouts(*node, /*include_controlled_nodes=*/true) == - 0 && - fetch_nodes_known_ && + if (node_map_->GetOutputs(node_name).empty() && fetch_nodes_known_ && nodes_to_preserve_.find(node_name) == nodes_to_preserve_.end()) { // Mark the node for deletion. - nodes_to_delete->insert(node_name); + nodes_to_delete->insert(node_to_idx_[node]); } - return Status::OK(); + return; } // Change ops that only have control dependencies as outputs to NoOps. if (!is_noop && SafeToConvertToNoOp(*node)) { - VLOG(1) << "***** Replacing " << node_name << " (" << node->op() + VLOG(1) << "***** Replacing " << node_name << " (" << node->op() << ") with NoOp."; // The outputs of this node are not consumed. Replace its inputs with // control dependencies and replace the op itself with the NoOp op. - const int num_regular_fanins = - graph_view_->NumFanins(*node, /*include_controlling_nodes=*/false); - absl::flat_hash_set regular_fanin_names; - for (int i = 0; i < num_regular_fanins; ++i) { - regular_fanin_names.emplace(ParseTensorName(node->input(i)).node()); - } - TF_RETURN_IF_ERROR( - graph_view_->UpdateAllRegularFaninsToControlling(node_name)); - TF_RETURN_IF_ERROR( - graph_view_->UpdateNode(node_name, "NoOp", node->device(), {})); - for (const string& regular_fanin_name : regular_fanin_names) { - nodes_to_simplify->PushBack(regular_fanin_name); + std::unordered_set ctrl_inputs; + int pos = 0; + while (pos < node->input_size()) { + const string old_input = node->input(pos); + if (IsControlInput(old_input)) { + if (!ctrl_inputs.insert(old_input).second) { + // We found a duplicate control input. Remove it. + node->mutable_input()->SwapElements(pos, node->input_size() - 1); + node->mutable_input()->RemoveLast(); + } else { + ++pos; + } + continue; + } + // Replace a normal input with a control input. + const string ctrl_input = ConstantFolding::AddControlDependency( + old_input, optimized_graph_, node_map_.get()); + ctrl_inputs.insert(ctrl_input); + node->set_input(pos, ctrl_input); + node_map_->UpdateInput(node_name, old_input, ctrl_input); + const NodeDef* old_input_node = node_map_->GetNode(old_input); + nodes_to_simplify->PushBack(node_to_idx_[old_input_node]); + ++pos; } - nodes_to_simplify->PushBack(node_name); - return Status::OK(); + node->set_op("NoOp"); + node->clear_attr(); + nodes_to_simplify->PushBack(node_to_idx_[node]); + return; } // Remove NoOp nodes if the product of their fan-in and fan-out is less than @@ -336,131 +358,154 @@ Status DependencyOptimizer::OptimizeNode( if (is_noop || ((is_identity || is_multi_input_identity) && SafeToRemoveIdentity(*node))) { - auto fanin_edges = - graph_view_->GetFaninEdges(*node, /*include_controlling_edges=*/true); - std::vector controlling_fanins; - controlling_fanins.reserve(fanin_edges.size()); - for (const auto& fanin_edge : fanin_edges) { - if (fanin_edge.src.port_id == Graph::kControlSlot) { - controlling_fanins.push_back(fanin_edge.src.node); + const auto& output_node_set = node_map_->GetOutputs(node_name); + const std::vector output_nodes(output_node_set.begin(), + output_node_set.end()); + const int num_inputs = node->input_size(); + std::vector input_nodes; + for (int i = 0; i < num_inputs; ++i) { + NodeDef* input_node = node_map_->GetNode(node->input(i)); + if (input_node == nullptr) { + LOG(ERROR) << "Invalid input " << node->input(i); + return; } + input_nodes.push_back(input_node); } - auto fanout_edges = - graph_view_->GetFanoutEdges(*node, /*include_controlled_edges=*/true); - if (!BypassingNodeIsBeneficial(*node, controlling_fanins.size(), - fanin_edges, fanout_edges)) { - return Status::OK(); + + if (!BypassingNodeIsBeneficial(*node, input_nodes, output_nodes)) { + return; } VLOG(1) << "***** Rerouting input around\n" << node->DebugString(); - - absl::flat_hash_set processed_nodes; - for (const auto& fanout_edge : fanout_edges) { - NodeDef* consumer = fanout_edge.dst.node; - const int src_port = fanout_edge.src.port_id; - if ((is_identity && src_port == 0) || - (is_multi_input_identity && src_port > Graph::kControlSlot)) { - // Identity regular fanins. - const string& input_to_forwards = node->input(src_port); - TF_RETURN_IF_ERROR(graph_view_->UpdateRegularFaninByPort( - consumer->name(), fanout_edge.dst.port_id, - ParseTensorName(input_to_forwards))); - } else if (is_identity || is_multi_input_identity) { - // Identity control dependency. - // TODO(lyandy): Handle IdentityN properly here by adding all regular - // fanins as controlling fanins. - const string& node_first_input = node->input(0); - TF_RETURN_IF_ERROR(graph_view_->UpdateFanin( - consumer->name(), {node_name, Graph::kControlSlot}, - {ParseTensorName(node_first_input).node(), Graph::kControlSlot})); - } else { - // NoOp. - TF_RETURN_IF_ERROR( - graph_view_->RemoveControllingFanin(consumer->name(), node_name)); + // Now remove the node and re-wire its inputs to its outputs. + for (auto consumer : output_nodes) { + bool updated_consumer = false; + VLOG(1) << "consumer before:\n" << consumer->DebugString(); + for (int i = 0; i < num_inputs; ++i) { + const NodeDef* input = input_nodes[i]; + // Forward dependency from input to consumer if it doesn't already + // depend on it. + if ((is_identity && i == 0) || + (is_multi_input_identity && !IsControlInput(node->input(i)))) { + // Replace regular input from Identity node. + string new_input; + const string& input_to_forward = node->input(i); + CHECK(!IsControlInput(input_to_forward)); + for (int j = 0; j < consumer->input_size(); ++j) { + const TensorId old_input = ParseTensorName(consumer->input(j)); + if (old_input.node() == node_name) { + if (old_input.index() == i) { + // Regular input + new_input = input_to_forward; + node_map_->UpdateInput(consumer->name(), old_input.ToString(), + new_input); + consumer->set_input(j, new_input); + } else if (old_input.index() == -1) { + // Control dependency + new_input = AsControlDependency(NodeName(input_to_forward)); + node_map_->UpdateInput(consumer->name(), old_input.ToString(), + new_input); + consumer->set_input(j, new_input); + } + } + } + updated_consumer = true; + } else { + // Forward dependency from input to consumer if it doesn't already + // depend on it. + if (node_map_->GetOutputs(input->name()).count(consumer) == 0) { + consumer->add_input(AsControlDependency(input->name())); + node_map_->AddOutput(input->name(), consumer->name()); + nodes_to_simplify->PushBack(node_to_idx_[input]); + updated_consumer = true; + } + } } - processed_nodes.insert(consumer); - nodes_to_simplify->PushBack(consumer->name()); - } - for (const auto& processed_node : processed_nodes) { - // Forward dependency from input to consumer if it doesn't already - // depend on it. - for (const auto& controlling_fanin : controlling_fanins) { - TF_RETURN_IF_ERROR(graph_view_->AddControllingFanin( - processed_node->name(), - {controlling_fanin->name(), Graph::kControlSlot})); - nodes_to_simplify->PushBack(controlling_fanin->name()); + // Remove dependency on node from consumer. + updated_consumer |= RemoveInput(consumer, AsControlDependency(node_name), + node_map_.get()); + if (updated_consumer) { + nodes_to_simplify->PushBack(node_to_idx_[consumer]); } + VLOG(1) << "consumer after:\n" << consumer->DebugString(); } - + node_map_->RemoveOutputs(node_name); if (fetch_nodes_known_ && nodes_to_preserve_.find(node_name) == nodes_to_preserve_.end()) { - // Disconnect the node from its inputs to enable further optimizations. - TF_RETURN_IF_ERROR(graph_view_->RemoveAllFanins( - node_name, /*keep_controlling_fanins=*/false)); // Mark the node for deletion. - nodes_to_delete->insert(node_name); + nodes_to_delete->insert(node_idx); + + // Disconnect the node from its inputs to enable further optimizations. + node_map_->RemoveInputs(node_name); + node->clear_input(); } } - return Status::OK(); +} + +void DependencyOptimizer::CleanControlInputs() { + for (int i = 0; i < optimized_graph_->node_size(); ++i) { + DedupControlInputs(optimized_graph_->mutable_node(i)); + } } Status DependencyOptimizer::OptimizeDependencies() { - SetVector nodes_to_simplify; - absl::flat_hash_set nodes_to_delete; - for (int i = 0; i < graph_view_->graph()->node_size(); ++i) { - const NodeDef& node = graph_view_->graph()->node(i); + SetVector nodes_to_simplify; + std::set nodes_to_delete; + for (int i = 0; i < optimized_graph_->node_size(); ++i) { + const NodeDef& node = optimized_graph_->node(i); if (IsNoOp(node) || IsIdentity(node) || IsIdentityN(node) || IsConstant(node) || SafeToConvertToNoOp(node)) { - nodes_to_simplify.PushBack(node.name()); + nodes_to_simplify.PushBack(i); } } while (!nodes_to_simplify.Empty()) { - string node_to_simplify = nodes_to_simplify.PopBack(); + int node_to_simplify = nodes_to_simplify.PopBack(); // Discard nodes that were marked for deletion already. while (nodes_to_delete.find(node_to_simplify) != nodes_to_delete.end()) { node_to_simplify = nodes_to_simplify.PopBack(); } - TF_RETURN_IF_ERROR( - OptimizeNode(node_to_simplify, &nodes_to_simplify, &nodes_to_delete)); + OptimizeNode(node_to_simplify, &nodes_to_simplify, &nodes_to_delete); } if (fetch_nodes_known_) { VLOG(1) << "Deleted " << nodes_to_delete.size() << " out of " - << graph_view_->graph()->node_size() << " nodes."; - TF_RETURN_IF_ERROR(graph_view_->DeleteNodes(nodes_to_delete)); + << optimized_graph_->node_size() << " nodes."; + EraseNodesFromGraph(nodes_to_delete, optimized_graph_); + node_map_.reset(new NodeMap(optimized_graph_)); + BuildNodeToIdx(); } return Status::OK(); } Status DependencyOptimizer::TransitiveReduction() { // PRECONDITION: optimized_graph_ must be sorted topologically. - GraphDef* graph = graph_view_->graph(); - auto node_to_idx = BuildNodeToIdx(*graph); - const int num_nodes = graph->node_size(); + const int num_nodes = optimized_graph_->node_size(); // Set up a compressed version of the graph to save a constant factor in the // expensive algorithm below. Also cache the set of control outputs and the // highest index of a target of any control output from each node. int num_controls = 0; std::vector> inputs(num_nodes); - std::vector> control_outputs(num_nodes); + std::vector, 2>> control_outputs( + num_nodes); for (int node_idx = 0; node_idx < num_nodes; ++node_idx) { - const NodeDef& node = graph->node(node_idx); + const NodeDef& node = optimized_graph_->node(node_idx); if (ModifiesFrameInfo(node) || !HasOpDef(node)) { // Ignore function nodes and nodes that modify frame info. continue; } - for (const string& input : node.input()) { - const NodeDef* input_node = graph_view_->GetNode(NodeName(input)); + for (int input_slot = 0; input_slot < node.input_size(); ++input_slot) { + const string& input = node.input(input_slot); + const NodeDef* input_node = node_map_->GetNode(input); if (ModifiesFrameInfo(*input_node) || IsMerge(*input_node)) { // Ignore edges from nodes that modify frame info and from Merge nodes, // because we cannot know which of it's input paths executes. continue; } - const int input_node_idx = node_to_idx[input_node]; + const int input_node_idx = node_to_idx_[input_node]; inputs[node_idx].push_back(input_node_idx); if (IsControlInput(input)) { ++num_controls; - control_outputs[input_node_idx].emplace_back(node_idx); + control_outputs[input_node_idx].emplace_back(node_idx, input_slot); } } } @@ -475,12 +520,14 @@ Status DependencyOptimizer::TransitiveReduction() { // such that when we swap them out so we don't clobber the // node(target).input() repeated field. typedef std::pair InputSlotAndSource; - absl::flat_hash_map> control_edges_to_remove; + std::unordered_map< + int, std::set>> + control_edges_to_remove; for (int source = 0; source < num_nodes; ++source) { int highest_control_target = -1; for (const auto& control_output : control_outputs[source]) { - if (control_output > highest_control_target) { - highest_control_target = control_output; + if (control_output.first > highest_control_target) { + highest_control_target = control_output.first; } } if (highest_control_target <= source) { @@ -510,21 +557,26 @@ Status DependencyOptimizer::TransitiveReduction() { // longer than 1, there exists an alternate path, and we can eliminate the // redundant direct control dependency. for (const auto& control_output : control_outputs[source]) { - const int target = control_output; + const int target = control_output.first; if (longest_distance[target] > 1) { - control_edges_to_remove[target].emplace(source); + const int input_slot = control_output.second; + control_edges_to_remove[target].emplace(input_slot, source); } } } for (const auto& it : control_edges_to_remove) { const int target = it.first; - const NodeDef& target_node = graph->node(target); - const string target_node_name = target_node.name(); - for (const int& source : it.second) { - const NodeDef& source_node = graph->node(source); - TF_RETURN_IF_ERROR(graph_view_->RemoveControllingFanin( - target_node_name, source_node.name())); + NodeDef* target_node = optimized_graph_->mutable_node(target); + for (const InputSlotAndSource& slot_and_source : it.second) { + const int input_slot = slot_and_source.first; + const int source = slot_and_source.second; + const NodeDef& source_node = optimized_graph_->node(source); + CHECK_LT(input_slot, target_node->input_size()); + target_node->mutable_input()->SwapElements(input_slot, + target_node->input_size() - 1); + node_map_->RemoveOutput(source_node.name(), target_node->name()); + target_node->mutable_input()->RemoveLast(); ++num_controls_removed; } } @@ -533,17 +585,26 @@ Status DependencyOptimizer::TransitiveReduction() { return Status::OK(); } +void DependencyOptimizer::BuildNodeToIdx() { + // Set up &node -> index map. + node_to_idx_.clear(); + for (int i = 0; i < optimized_graph_->node_size(); ++i) { + const NodeDef& node = optimized_graph_->node(i); + node_to_idx_[&node] = i; + } +} + // Suppose there are cross-device control inputs to node C from multiple nodes // that are located on another device, e.g., we have control edges: // A->C, B->C // where A and B are on device X and C is on device Y. // We can reduce cross-device communication by introducing an intermediate // NoOp node C' on device X and rewriting the control edges to: -// A->C', B->C', C'->C -Status DependencyOptimizer::GroupCrossDeviceControlEdges() { - const int num_nodes = graph_view_->graph()->node_size(); +// A->C', B->C', C' -> C +void DependencyOptimizer::GroupCrossDeviceControlEdges() { + const int num_nodes = optimized_graph_->node_size(); for (int i = 0; i < num_nodes; ++i) { - NodeDef* node = graph_view_->graph()->mutable_node(i); + NodeDef* node = optimized_graph_->mutable_node(i); if (node->device().empty()) continue; // Creates new noop nodes for devices on which multiple control inputs are @@ -554,71 +615,88 @@ Status DependencyOptimizer::GroupCrossDeviceControlEdges() { // that device. std::map noops; int num_noops = 0; - auto controlling_fanins = graph_view_->GetFanin( - MutableGraphView::InputPort(node, Graph::kControlSlot)); - for (const auto& controlling_fanin : controlling_fanins) { - const NodeDef* fanin_node = controlling_fanin.node; - if (!fanin_node->device().empty() && - fanin_node->device() != node->device()) { - auto emplace_result = noops.emplace(fanin_node->device(), nullptr); - if (!emplace_result.second && emplace_result.first->second == nullptr) { - // This is the second cross-device control input from the same - // device. Creates an intermediate noop node on that device. - string group_name; - NodeDef* noop; - // Creates a fresh node name; there may be conflicting names from - // a previous iteration of the optimizer. - do { - group_name = AddPrefixToNodeName( - node->name(), - strings::StrCat("GroupCrossDeviceControlEdges_", num_noops)); - noop = graph_view_->GetNode(group_name); - ++num_noops; - } while (noop != nullptr); - NodeDef new_node; - new_node.set_name(group_name); - new_node.set_device(fanin_node->device()); - new_node.set_op("NoOp"); - emplace_result.first->second = - graph_view_->AddNode(std::move(new_node)); + for (int j = 0; j < node->input_size(); ++j) { + if (IsControlInput(node->input(j))) { + const NodeDef* input = node_map_->GetNode(node->input(j)); + if (input != nullptr && !input->device().empty() && + input->device() != node->device()) { + auto emplace_result = noops.emplace(input->device(), nullptr); + if (!emplace_result.second && + emplace_result.first->second == nullptr) { + // This is the second cross-device control input from the same + // device. Creates an intermediate noop node on that device. + string group_name; + NodeDef* noop; + // Creates a fresh node name; there may be conflicting names from + // a previous iteration of the optimizer. + do { + group_name = AddPrefixToNodeName( + node->name(), + strings::StrCat("GroupCrossDeviceControlEdges_", num_noops)); + noop = node_map_->GetNode(group_name); + ++num_noops; + } while (noop != nullptr); + noop = optimized_graph_->add_node(); + noop->set_name(group_name); + noop->set_device(input->device()); + noop->set_op("NoOp"); + node_map_->AddNode(noop->name(), noop); + emplace_result.first->second = noop; + } } } } // Reroute existing control edges to go via the newly introduced NoOp nodes. - for (const auto& controlling_fanin : controlling_fanins) { - auto it = noops.find(controlling_fanin.node->device()); - if (it != noops.end() && it->second != nullptr) { - TF_RETURN_IF_ERROR(graph_view_->RemoveControllingFanin( - node->name(), controlling_fanin.node->name())); - TF_RETURN_IF_ERROR(graph_view_->AddControllingFanin( - it->second->name(), - {controlling_fanin.node->name(), Graph::kControlSlot})); + int pos = 0; + while (pos < node->input_size()) { + const string& input_name = node->input(pos); + if (IsControlInput(input_name)) { + NodeDef* input = node_map_->GetNode(input_name); + if (input == nullptr) { + ++pos; + } else { + auto it = noops.find(input->device()); + if (it == noops.end() || it->second == nullptr) { + ++pos; + } else { + node->mutable_input()->SwapElements(pos, node->input_size() - 1); + node->mutable_input()->RemoveLast(); + it->second->add_input(AsControlDependency(*input)); + node_map_->UpdateOutput(input_name, node->name(), + it->second->name()); + } + } + } else { + ++pos; } } for (const auto& entry : noops) { if (entry.second) { - TF_RETURN_IF_ERROR(graph_view_->AddControllingFanin( - node->name(), {entry.second->name(), Graph::kControlSlot})); + node->add_input(AsControlDependency(*entry.second)); + node_map_->AddOutput(entry.second->name(), node->name()); } } } - return Status::OK(); } Status DependencyOptimizer::Optimize(Cluster* cluster, const GrapplerItem& item, GraphDef* optimized_graph) { - *optimized_graph = item.graph; + optimized_graph_ = optimized_graph; + *optimized_graph_ = item.graph; nodes_to_preserve_ = item.NodesToPreserve(); fetch_nodes_known_ = !item.fetch.empty(); - graph_view_.reset(new MutableGraphView(optimized_graph)); + CleanControlInputs(); const int num_iterations = 2; for (int iteration = 0; iteration < num_iterations; ++iteration) { GRAPPLER_RETURN_IF_DEADLINE_EXCEEDED(); Status topo_sort_status; // Perform topological sort to prepare the graph for transitive reduction. - topo_sort_status = TopologicalSort(optimized_graph); + topo_sort_status = TopologicalSort(optimized_graph_); + // Set up index-based graph datastructures to speed up analysis steps below. + node_map_.reset(new NodeMap(optimized_graph_)); + BuildNodeToIdx(); if (topo_sort_status.ok()) { // Remove redundant control dependencies. @@ -632,7 +710,10 @@ Status DependencyOptimizer::Optimize(Cluster* cluster, const GrapplerItem& item, // nodes. TF_RETURN_IF_ERROR(OptimizeDependencies()); - TF_RETURN_IF_ERROR(GroupCrossDeviceControlEdges()); + // Dedup control inputs. + CleanControlInputs(); + + GroupCrossDeviceControlEdges(); } return Status::OK(); diff --git a/tensorflow/core/grappler/optimizers/dependency_optimizer.h b/tensorflow/core/grappler/optimizers/dependency_optimizer.h index a60e7a3522..99021b955f 100644 --- a/tensorflow/core/grappler/optimizers/dependency_optimizer.h +++ b/tensorflow/core/grappler/optimizers/dependency_optimizer.h @@ -17,10 +17,6 @@ limitations under the License. #define TENSORFLOW_CORE_GRAPPLER_OPTIMIZERS_DEPENDENCY_OPTIMIZER_H_ #include - -#include "absl/container/flat_hash_set.h" -#include "absl/strings/string_view.h" -#include "tensorflow/core/grappler/mutable_graph_view.h" #include "tensorflow/core/grappler/optimizers/graph_optimizer.h" #include "tensorflow/core/grappler/utils.h" #include "tensorflow/core/protobuf/rewriter_config.pb.h" @@ -49,25 +45,24 @@ class DependencyOptimizer : public GraphOptimizer { // Returns true if bypassing node does not increase the number of edges or // number of edges crossing a device boundary. bool BypassingNodeIsBeneficial( - const NodeDef& node, int num_controlling_fanins, - const absl::flat_hash_set& fanin_edges, - const absl::flat_hash_set& fanout_edges) const; - int NumEdgesIfBypassed( - const NodeDef& node, int num_controlling_fanins, - const absl::flat_hash_set& fanin_edges, - const absl::flat_hash_set& fanout_edges, - int num_unique_fanout_nodes) const; + const NodeDef& node, const std::vector& input_nodes, + const std::vector& output_nodes) const; + int NumEdgesIfBypassed(const NodeDef& node, + const std::vector& output_nodes) const; // Returns true if node is not an Identity node or if it is an Identity // that is safe to remove. bool SafeToRemoveIdentity(const NodeDef& node) const; // Returns true if it is safe to convert node to NoOp. bool SafeToConvertToNoOp(const NodeDef& node) const; - // Tries to optimize the node with the given node name, possibly additional + // Removes all duplicate control dependencies. + void CleanControlInputs(); + // Builds a map from the &optimized_graph_->node(i) to i. + void BuildNodeToIdx(); + // Tries to optimize the node with the given index, possibly additional // optimizations by inserting nodes in nodes_to_simplify, and pruning nodes by // inserting them in nodes_to_delete. - Status OptimizeNode(const string& node_name, - SetVector* nodes_to_simplify, - absl::flat_hash_set* nodes_to_delete); + void OptimizeNode(int node_idx, SetVector* nodes_to_simplify, + std::set* nodes_to_delete); // Eliminates redundant control dependencies by computing the transitive // reduction of the graph. Status TransitiveReduction(); @@ -75,11 +70,13 @@ class DependencyOptimizer : public GraphOptimizer { Status OptimizeDependencies(); // Replaces multiple cross-device control edges from the same device with a // single control edge. - Status GroupCrossDeviceControlEdges(); + void GroupCrossDeviceControlEdges(); bool fetch_nodes_known_; std::unordered_set nodes_to_preserve_; - std::unique_ptr graph_view_; + std::unique_ptr node_map_; + std::unordered_map node_to_idx_; + GraphDef* optimized_graph_; // Not owned. }; } // end namespace grappler diff --git a/tensorflow/core/grappler/optimizers/dependency_optimizer_test.cc b/tensorflow/core/grappler/optimizers/dependency_optimizer_test.cc index 80a0189b30..5883fcb926 100644 --- a/tensorflow/core/grappler/optimizers/dependency_optimizer_test.cc +++ b/tensorflow/core/grappler/optimizers/dependency_optimizer_test.cc @@ -41,32 +41,13 @@ void VerifyGraphsEqual(const GraphDef& original_graph, const NodeDef& optimized = optimized_graph.node(i); EXPECT_EQ(original.name(), optimized.name()) << func; EXPECT_EQ(original.op(), optimized.op()) << func; - ASSERT_EQ(original.input_size(), optimized.input_size()) << func; + EXPECT_EQ(original.input_size(), optimized.input_size()) << func; for (int j = 0; j < original.input_size(); ++j) { EXPECT_EQ(original.input(j), optimized.input(j)) << func; } } } -bool NodeHasControllingFanins(const NodeDef& node, - const absl::flat_hash_set& expected) { - absl::flat_hash_set actual; - for (const string& fanin : node.input()) { - if (IsControlInput(fanin)) { - actual.insert(fanin); - } - } - if (actual.size() != expected.size()) { - return false; - } - for (const auto& expected_fanin : expected) { - if (!actual.contains(expected_fanin)) { - return false; - } - } - return true; -} - TEST_F(DependencyOptimizerTest, NoOp) { // This trivial graph is so basic there's nothing to optimize. TrivialTestGraphInputYielder fake_input(4, 1, 10, false, {"CPU:0"}); @@ -108,12 +89,12 @@ TEST_F(DependencyOptimizerTest, DependenciesDrivenByConstants) { TF_EXPECT_OK(status); // The 'z' node should have been optimized away leaving only 5 nodes. - EXPECT_EQ(output.node_size(), 5); + EXPECT_EQ(5, output.node_size()); - for (const NodeDef& node : output.node()) { + for (const NodeDef& node : item.graph.node()) { if (node.name() == "id1" || node.name() == "id2") { - ASSERT_EQ(node.input_size(), 1); - EXPECT_EQ(node.input(0), "add"); + EXPECT_EQ(1, node.input_size()); + EXPECT_EQ("add", node.input(0)); } } } @@ -142,30 +123,30 @@ TEST_F(DependencyOptimizerTest, ChangeToNoop) { status = optimizer.Optimize(nullptr, item, &output); TF_EXPECT_OK(status); - EXPECT_EQ(output.node_size(), item.graph.node_size()); + EXPECT_EQ(item.graph.node_size(), output.node_size()); int found = 0; - for (int i = 0; i < output.node_size(); ++i) { - const NodeDef& node = output.node(i); + for (int i = 0; i < item.graph.node_size(); ++i) { + const NodeDef& node = item.graph.node(i); // "add" should get turned into a NoOp and removed. - EXPECT_NE(node.name(), "add"); + EXPECT_NE("add", node.name()); if (node.name() == "id1") { - EXPECT_EQ(node.op(), "Identity"); - ASSERT_EQ(node.input_size(), 2); - EXPECT_EQ(node.input(0), "x"); - EXPECT_EQ(node.input(1), "^y"); + EXPECT_EQ("Identity", node.op()); + EXPECT_EQ(2, node.input_size()); + EXPECT_EQ("x", node.input(0)); + EXPECT_EQ("^y", node.input(1)); ++found; } else if (node.name() == "id2") { - EXPECT_EQ(node.op(), "Identity"); - ASSERT_EQ(node.input_size(), 2); - EXPECT_EQ(node.input(0), "y"); - EXPECT_EQ(node.input(1), "^x"); + EXPECT_EQ("Identity", node.op()); + EXPECT_EQ(2, node.input_size()); + EXPECT_EQ("y", node.input(0)); + EXPECT_EQ("^x", node.input(1)); ++found; } } - EXPECT_EQ(found, 2); + EXPECT_EQ(2, found); } -TEST_F(DependencyOptimizerTest, ChangeToNoopRepeatedInput) { +TEST_F(DependencyOptimizerTest, ChangeToNoop_RepeatedInput) { tensorflow::Scope s = tensorflow::Scope::NewRootScope(); Output x = ops::RandomUniform(s.WithOpName("x"), {1, 2}, DT_FLOAT); Output add = ops::Add(s.WithOpName("add"), x, x); @@ -183,24 +164,25 @@ TEST_F(DependencyOptimizerTest, ChangeToNoopRepeatedInput) { item.graph.Swap(&output); status = optimizer.Optimize(nullptr, item, &output); TF_EXPECT_OK(status); + LOG(INFO) << output.DebugString(); - EXPECT_EQ(output.node_size(), item.graph.node_size()); + EXPECT_EQ(item.graph.node_size(), output.node_size()); int found = 0; for (int i = 0; i < item.graph.node_size(); ++i) { const NodeDef& node = item.graph.node(i); // "add" should get turned into a NoOp and removed. - EXPECT_NE(node.name(), "add"); + EXPECT_NE("add", node.name()); if (node.name() == "id1") { - EXPECT_EQ(node.op(), "Identity"); - EXPECT_EQ(node.input_size(), 1); - EXPECT_EQ(node.input(0), "x"); + EXPECT_EQ("Identity", node.op()); + EXPECT_EQ(1, node.input_size()); + EXPECT_EQ("x", node.input(0)); ++found; } } - EXPECT_EQ(found, 1); + EXPECT_EQ(1, found); } -TEST_F(DependencyOptimizerTest, ChangeToNoopSwitchIdentity) { +TEST_F(DependencyOptimizerTest, ChangeToNoop_SwitchIdentity) { // This tests that we don't try to repeatedly add Identity nodes // with names like "ConstantFoldingCtrl/foo/bar/switch_$port" when // multiple nodes reading the same output of a Switch node get @@ -238,23 +220,23 @@ TEST_F(DependencyOptimizerTest, ChangeToNoopSwitchIdentity) { Status status = optimizer.Optimize(nullptr, item, &output); TF_EXPECT_OK(status); - EXPECT_EQ(output.node_size(), item.graph.node_size() - 1); + EXPECT_EQ(item.graph.node_size() - 1, output.node_size()); for (int i = 0; i < output.node_size(); ++i) { const NodeDef& node = output.node(i); // "neg" should be eliminated. - EXPECT_NE(node.name(), "neg"); + EXPECT_NE("neg", node.name()); // A control dep from "^ConstantFoldingCtrl/switch_1" // should be attached to "c1". if (node.name() == "c1") { - EXPECT_EQ(node.op(), "Const"); - ASSERT_EQ(node.input_size(), 1); - EXPECT_EQ(node.input(0), "^ConstantFoldingCtrl/switch_1"); + EXPECT_EQ("Const", node.op()); + EXPECT_EQ(1, node.input_size()); + EXPECT_EQ("^ConstantFoldingCtrl/switch_1", node.input(0)); } } } // TODO(rmlarsen): Add test to make sure we skip Switch and Merge. -TEST_F(DependencyOptimizerTest, ChangeToNoopNoFetch) { +TEST_F(DependencyOptimizerTest, ChangeToNoop_NoFetch) { tensorflow::Scope s = tensorflow::Scope::NewRootScope(); Output x = ops::RandomUniform(s.WithOpName("x"), {1, 2}, DT_FLOAT); Output y = ops::RandomUniform(s.WithOpName("y"), {1, 2}, DT_FLOAT); @@ -276,7 +258,7 @@ TEST_F(DependencyOptimizerTest, ChangeToNoopNoFetch) { VerifyGraphsEqual(item.graph, output, __FUNCTION__); } -TEST_F(DependencyOptimizerTest, RemoveNoOpsEmptyInputOrOutput) { +TEST_F(DependencyOptimizerTest, RemoveNoOps_EmptyInputOrOutput) { tensorflow::Scope s = tensorflow::Scope::NewRootScope(); Output x = ops::RandomUniform(s, {1, 2}, DT_FLOAT); auto noop1 = ops::NoOp(s); @@ -296,18 +278,18 @@ TEST_F(DependencyOptimizerTest, RemoveNoOpsEmptyInputOrOutput) { status = optimizer.Optimize(nullptr, item, &output); TF_EXPECT_OK(status); - EXPECT_EQ(output.node_size(), item.graph.node_size()); + EXPECT_EQ(item.graph.node_size(), output.node_size()); for (const NodeDef& node : output.node()) { if (node.name() == "NoOp" || node.name() == "NoOp_1") { EXPECT_EQ(0, node.input_size()); } else if (node.name() == "Identity") { - ASSERT_EQ(node.input_size(), 1); - EXPECT_EQ(node.input(0), "RandomUniform"); + EXPECT_EQ(1, node.input_size()); + EXPECT_EQ("RandomUniform", node.input(0)); } } } -TEST_F(DependencyOptimizerTest, RemoveNoOpsDeviceBoundaries) { +TEST_F(DependencyOptimizerTest, RemoveNoOps_DeviceBoundaries) { tensorflow::Scope s = tensorflow::Scope::NewRootScope(); Output x = ops::RandomUniform(s.WithOpName("x").WithDevice("/CPU:0"), {1, 2}, DT_FLOAT); @@ -342,7 +324,7 @@ TEST_F(DependencyOptimizerTest, RemoveNoOpsDeviceBoundaries) { VerifyGraphsEqual(item.graph, output, __FUNCTION__); } -TEST_F(DependencyOptimizerTest, RemoveIdentityOpsDeviceBoundaries) { +TEST_F(DependencyOptimizerTest, RemoveIdentityOps_DeviceBoundaries) { tensorflow::Scope s = tensorflow::Scope::NewRootScope(); Output x = ops::RandomUniform(s.WithOpName("x").WithDevice("/CPU:0"), {1, 2}, DT_FLOAT); @@ -374,7 +356,7 @@ TEST_F(DependencyOptimizerTest, RemoveIdentityOpsDeviceBoundaries) { VerifyGraphsEqual(item.graph, output, __FUNCTION__); } -TEST_F(DependencyOptimizerTest, RemoveIdentityOpsIdenticalDevices) { +TEST_F(DependencyOptimizerTest, RemoveIdentityOps_IdenticalDevices) { tensorflow::Scope s = tensorflow::Scope::NewRootScope(); Output x = ops::RandomUniform(s.WithOpName("x").WithDevice("/CPU:0"), {1, 2}, DT_FLOAT); @@ -391,17 +373,16 @@ TEST_F(DependencyOptimizerTest, RemoveIdentityOpsIdenticalDevices) { Status status = optimizer.Optimize(nullptr, item, &output); TF_EXPECT_OK(status); - EXPECT_EQ(output.node_size(), item.graph.node_size() - 1); + EXPECT_EQ(item.graph.node_size() - 1, output.node_size()); for (const NodeDef& node : output.node()) { EXPECT_NE(node.name(), "id_a"); if (node.name() == "Identity") { - ASSERT_EQ(node.input_size(), 1); EXPECT_EQ(node.input(0), "x"); } } } -TEST_F(DependencyOptimizerTest, RemoveNoOpsSingleInputOrOutput) { +TEST_F(DependencyOptimizerTest, RemoveNoOps_SingleInputOrOutput) { tensorflow::Scope s = tensorflow::Scope::NewRootScope(); Output x = ops::RandomUniform(s.WithOpName("x"), {1, 2}, DT_FLOAT); Output y = ops::RandomUniform(s.WithOpName("y"), {1, 2}, DT_FLOAT); @@ -428,17 +409,15 @@ TEST_F(DependencyOptimizerTest, RemoveNoOpsSingleInputOrOutput) { status = optimizer.Optimize(nullptr, item, &output); TF_EXPECT_OK(status); - EXPECT_EQ(output.node_size(), item.graph.node_size()); + EXPECT_EQ(item.graph.node_size(), output.node_size()); for (const NodeDef& node : output.node()) { if (node.name() == "NoOp" || node.name() == "NoOp_1") { - EXPECT_EQ(node.input_size(), 0); + EXPECT_EQ(0, node.input_size()); } else if (node.name() == "Identity") { - ASSERT_EQ(node.input_size(), 1); - EXPECT_EQ(node.input(0), "x"); + EXPECT_EQ("x", node.input(0)); } else if (node.name() == "Identity_1") { - ASSERT_EQ(node.input_size(), 2); - EXPECT_EQ(node.input(0), "y"); - EXPECT_EQ(node.input(1), "^x"); + EXPECT_EQ("y", node.input(0)); + EXPECT_EQ("^x", node.input(1)); } } } @@ -483,46 +462,48 @@ TEST_F(DependencyOptimizerTest, RemoveIdentity) { Status status = optimizer.Optimize(nullptr, item, &output); TF_EXPECT_OK(status); - EXPECT_EQ(output.node_size(), item.graph.node_size() - 3); + EXPECT_EQ(item.graph.node_size() - 3, output.node_size()); int found = 0; for (const NodeDef& node : output.node()) { - EXPECT_NE(node.name(), "id_a"); - EXPECT_NE(node.name(), "id_b"); - EXPECT_NE(node.name(), "id_c"); + EXPECT_NE("id_a", node.name()); + EXPECT_NE("id_b", node.name()); + EXPECT_NE("id_c", node.name()); if (node.name() == "a_a" || node.name() == "a_b") { - ASSERT_EQ(node.input_size(), 1); - EXPECT_EQ(node.input(0), "x"); + EXPECT_EQ(1, node.input_size()); + EXPECT_EQ("x", node.input(0)); ++found; } if (node.name() == "a_c" || node.name() == "a_d") { - ASSERT_EQ(node.input_size(), 2); - EXPECT_EQ(node.input(0), "z"); - EXPECT_EQ(node.input(1), "^x"); + EXPECT_EQ(2, node.input_size()); + EXPECT_EQ("z", node.input(0)); + EXPECT_EQ("^x", node.input(1)); ++found; } if (node.name() == "b_a") { - ASSERT_EQ(node.input_size(), 3); - EXPECT_EQ(node.input(0), "x"); - EXPECT_TRUE(NodeHasControllingFanins(node, {"^y", "^z"})); + EXPECT_EQ(3, node.input_size()); + EXPECT_EQ("x", node.input(0)); + EXPECT_EQ("^y", node.input(1)); + EXPECT_EQ("^z", node.input(2)); ++found; } if (node.name() == "c_a") { - ASSERT_EQ(node.input_size(), 2); - EXPECT_EQ(node.input(0), "x"); - EXPECT_EQ(node.input(1), "^y"); + EXPECT_EQ(2, node.input_size()); + EXPECT_EQ("x", node.input(0)); + EXPECT_EQ("^y", node.input(1)); ++found; } if (node.name() == "c_b") { - ASSERT_EQ(node.input_size(), 3); - EXPECT_EQ(node.input(0), "z"); - EXPECT_TRUE(NodeHasControllingFanins(node, {"^x", "^y"})); + EXPECT_EQ(3, node.input_size()); + EXPECT_EQ("z", node.input(0)); + EXPECT_EQ("^x", node.input(1)); + EXPECT_EQ("^y", node.input(2)); ++found; } } EXPECT_EQ(found, 7); } -TEST_F(DependencyOptimizerTest, RemoveIdentityRepeatedInputs) { +TEST_F(DependencyOptimizerTest, RemoveIdentity_RepeatedInputs) { // Corner cases with repeated inputs. tensorflow::Scope scope = tensorflow::Scope::NewRootScope(); ops::Variable x(scope.WithOpName("x"), {}, DT_BOOL); @@ -548,35 +529,35 @@ TEST_F(DependencyOptimizerTest, RemoveIdentityRepeatedInputs) { Status status = optimizer.Optimize(nullptr, item, &output); TF_EXPECT_OK(status); - EXPECT_EQ(output.node_size(), item.graph.node_size() - 1); + EXPECT_EQ(item.graph.node_size() - 1, output.node_size()); int found = 0; for (const NodeDef& node : output.node()) { - EXPECT_NE(node.name(), "id0"); + EXPECT_NE("id0", node.name()); if (node.name() == "or0") { - ASSERT_EQ(node.input_size(), 2); - EXPECT_EQ(node.input(0), "switch:1"); - EXPECT_EQ(node.input(1), "switch:1"); + EXPECT_EQ(2, node.input_size()); + EXPECT_EQ("switch:1", node.input(0)); + EXPECT_EQ("switch:1", node.input(1)); ++found; } if (node.name() == "or1") { - ASSERT_EQ(node.input_size(), 2); - EXPECT_EQ(node.input(0), "switch:1"); - EXPECT_EQ(node.input(1), "y"); + EXPECT_EQ(2, node.input_size()); + EXPECT_EQ("switch:1", node.input(0)); + EXPECT_EQ("y", node.input(1)); ++found; } if (node.name() == "or2") { // or1 should be unchanged. - ASSERT_EQ(node.input_size(), 3); - EXPECT_EQ(node.input(0), "y"); - EXPECT_EQ(node.input(1), "y"); - EXPECT_EQ(node.input(2), "^id1"); + EXPECT_EQ(3, node.input_size()); + EXPECT_EQ("y", node.input(0)); + EXPECT_EQ("y", node.input(1)); + EXPECT_EQ("^id1", node.input(2)); ++found; } } EXPECT_EQ(found, 3); } -TEST_F(DependencyOptimizerTest, TransitiveReductionSimple) { +TEST_F(DependencyOptimizerTest, Transitive_Reduction_Simple) { tensorflow::Scope s = tensorflow::Scope::NewRootScope(); Output c = ops::Const(s.WithOpName("c"), {1.0f, 2.0f}, {1, 2}); Output x = ops::Square(s.WithOpName("x"), c); @@ -591,13 +572,13 @@ TEST_F(DependencyOptimizerTest, TransitiveReductionSimple) { GraphDef output; Status status = optimizer.Optimize(nullptr, item, &output); TF_EXPECT_OK(status); - ASSERT_EQ(output.node_size(), 4); - EXPECT_EQ(output.node(3).name(), "neg2"); - ASSERT_EQ(output.node(3).input_size(), 1); - EXPECT_EQ(output.node(3).input(0), "neg1"); + EXPECT_EQ(4, output.node_size()); + EXPECT_EQ("neg2", output.node(3).name()); + EXPECT_EQ(1, output.node(3).input_size()); + EXPECT_EQ("neg1", output.node(3).input(0)); } -TEST_F(DependencyOptimizerTest, ChangeToNoopIdentity) { +TEST_F(DependencyOptimizerTest, ChangeToNoop_Identity) { tensorflow::Scope scope = tensorflow::Scope::NewRootScope(); ops::Variable v_in(scope.WithOpName("v_in"), {3}, DT_FLOAT); Output id_after_var = ops::Identity(scope.WithOpName("id_after_var"), v_in); @@ -628,18 +609,18 @@ TEST_F(DependencyOptimizerTest, ChangeToNoopIdentity) { Status status = optimizer.Optimize(nullptr, item, &output); TF_EXPECT_OK(status); - EXPECT_EQ(output.node_size(), item.graph.node_size() - 2); + EXPECT_EQ(item.graph.node_size() - 2, output.node_size()); bool found = false; for (int i = 0; i < output.node_size(); ++i) { const NodeDef& node = output.node(i); // "id0" and "id1" but neither "ConstantFoldingCtrl/switch_1", // "id_after_var, nor "id2"" should be eliminated. - EXPECT_NE(node.name(), "id0"); - EXPECT_NE(node.name(), "id1"); + EXPECT_NE("id0", node.name()); + EXPECT_NE("id1", node.name()); if (node.name() == "c1") { - EXPECT_EQ(node.op(), "Const"); - ASSERT_EQ(node.input_size(), 1); - EXPECT_EQ(node.input(0), "^ConstantFoldingCtrl/switch_1"); + EXPECT_EQ("Const", node.op()); + EXPECT_EQ(1, node.input_size()); + EXPECT_EQ("^ConstantFoldingCtrl/switch_1", node.input(0)); found = true; } } @@ -669,17 +650,17 @@ TEST_F(DependencyOptimizerTest, IdentityInputs) { Status status = optimizer.Optimize(nullptr, item, &output); TF_EXPECT_OK(status); - ASSERT_EQ(output.node_size(), 6); - EXPECT_EQ(output.node(4).name(), "out2"); - ASSERT_EQ(output.node(4).input_size(), 1); - EXPECT_EQ(output.node(4).input(0), "s:1"); + EXPECT_EQ(6, output.node_size()); + EXPECT_EQ("out1", output.node(4).name()); + EXPECT_EQ(1, output.node(4).input_size()); + EXPECT_EQ("s", output.node(4).input(0)); - EXPECT_EQ(output.node(5).name(), "out1"); - ASSERT_EQ(output.node(5).input_size(), 1); - EXPECT_EQ(output.node(5).input(0), "s"); + EXPECT_EQ("out2", output.node(5).name()); + EXPECT_EQ(1, output.node(5).input_size()); + EXPECT_EQ("s:1", output.node(5).input(0)); } -TEST_F(DependencyOptimizerTest, RemoveIdentityNSwitchInput) { +TEST_F(DependencyOptimizerTest, RemoveIdentityN_SwitchInput) { tensorflow::Scope scope = tensorflow::Scope::NewRootScope(); Output b = ops::Placeholder(scope.WithOpName("b"), DT_BOOL); Output x = ops::RandomUniform(scope.WithOpName("x"), {1, 2}, DT_FLOAT); @@ -706,27 +687,27 @@ TEST_F(DependencyOptimizerTest, RemoveIdentityNSwitchInput) { Status status = optimizer.Optimize(nullptr, item, &output); TF_EXPECT_OK(status); - ASSERT_EQ(output.node_size(), 8); + EXPECT_EQ(8, output.node_size()); auto out1_node = output.node(7); - EXPECT_EQ(out1_node.name(), "out1"); - ASSERT_EQ(out1_node.input_size(), 1); - EXPECT_EQ(out1_node.input(0), "s"); + EXPECT_EQ("out1", out1_node.name()); + EXPECT_EQ(1, out1_node.input_size()); + EXPECT_EQ("s", out1_node.input(0)); - auto out2_node = output.node(6); - EXPECT_EQ(out2_node.name(), "out2"); - ASSERT_EQ(out2_node.input_size(), 1); - EXPECT_EQ(out2_node.input(0), "s:1"); + auto out2_node = output.node(4); + EXPECT_EQ("out2", out2_node.name()); + EXPECT_EQ(1, out2_node.input_size()); + EXPECT_EQ("s:1", out2_node.input(0)); auto out3_node = output.node(5); - EXPECT_EQ(out3_node.name(), "out3"); - ASSERT_EQ(out3_node.input_size(), 1); - EXPECT_EQ(out3_node.input(0), "s"); - - auto out4_node = output.node(4); - EXPECT_EQ(out4_node.name(), "out4"); - ASSERT_EQ(out4_node.input_size(), 1); - EXPECT_EQ(out4_node.input(0), "s:1"); + EXPECT_EQ("out3", out3_node.name()); + EXPECT_EQ(1, out3_node.input_size()); + EXPECT_EQ("s", out3_node.input(0)); + + auto out4_node = output.node(6); + EXPECT_EQ("out4", out4_node.name()); + EXPECT_EQ(1, out4_node.input_size()); + EXPECT_EQ("s:1", out4_node.input(0)); } TEST_F(DependencyOptimizerTest, DoNotRemoveIdentityNWithControlDependency) { @@ -749,11 +730,11 @@ TEST_F(DependencyOptimizerTest, DoNotRemoveIdentityNWithControlDependency) { Status status = optimizer.Optimize(nullptr, item, &optimized_graph_def); TF_EXPECT_OK(status); - EXPECT_EQ(optimized_graph_def.node_size(), 6); + EXPECT_EQ(6, optimized_graph_def.node_size()); } TEST_F(DependencyOptimizerTest, - IdentityDeviceCrossingConsumerOnDifferentDevice) { + Identity_DeviceCrossing_ConsumerOnDifferentDevice) { tensorflow::Scope s = tensorflow::Scope::NewRootScope(); Output x_on_1 = ops::Const(s.WithOpName("x_on_1").WithDevice("/gpu:1"), {1.0f}, {}); @@ -775,7 +756,7 @@ TEST_F(DependencyOptimizerTest, VerifyGraphsEqual(item.graph, output, __FUNCTION__); } -TEST_F(DependencyOptimizerTest, IdentityDeviceCrossingConsumerOnSameDevice) { +TEST_F(DependencyOptimizerTest, Identity_DeviceCrossing_ConsumerOnSameDevice) { tensorflow::Scope s = tensorflow::Scope::NewRootScope(); Output x_on_1 = ops::Const(s.WithOpName("x_on_1").WithDevice("/gpu:1"), {1.0f}, {}); @@ -793,14 +774,12 @@ TEST_F(DependencyOptimizerTest, IdentityDeviceCrossingConsumerOnSameDevice) { GraphDef output; Status status = optimizer.Optimize(nullptr, item, &output); TF_EXPECT_OK(status); - - EXPECT_EQ(output.node_size(), 3); + LOG(INFO) << output.DebugString(); + EXPECT_EQ(3, output.node_size()); for (const auto& node : output.node()) { - EXPECT_NE(node.name(), "x_on_2"); + EXPECT_NE("x_on_2", node.name()); if (node.name() == "result") { - ASSERT_EQ(node.input_size(), 2); - EXPECT_EQ(node.input(0), "x_on_1"); - EXPECT_EQ(node.input(1), "one_on_2"); + EXPECT_EQ("x_on_1", node.input(0)); } } } @@ -828,25 +807,25 @@ TEST_F(DependencyOptimizerTest, RemoveGreaterEqualWithNoOp) { for (const NodeDef& node : output.node()) { if (node.name() == "x") { count++; - EXPECT_EQ(node.op(), "Placeholder"); - EXPECT_EQ(node.input_size(), 0); + EXPECT_EQ("Placeholder", node.op()); + EXPECT_EQ(0, node.input_size()); } else if (node.name() == "y") { count++; - EXPECT_EQ(node.op(), "Placeholder"); - EXPECT_EQ(node.input_size(), 0); + EXPECT_EQ("Placeholder", node.op()); + EXPECT_EQ(0, node.input_size()); } else if (node.name() == "GreaterEqual") { count++; } else if (node.name() == "NoOp") { count++; } else if (node.name() == "z") { count++; - EXPECT_EQ(node.op(), "Add"); - ASSERT_EQ(node.input_size(), 2); - EXPECT_EQ(node.input(0), "x"); - EXPECT_EQ(node.input(1), "y"); + EXPECT_EQ("Add", node.op()); + EXPECT_EQ(2, node.input_size()); + EXPECT_EQ("x", node.input(0)); + EXPECT_EQ("y", node.input(1)); } } - EXPECT_EQ(count, 3); + EXPECT_EQ(3, count); } TEST_F(DependencyOptimizerTest, GroupCrossDeviceControlDeps) { -- GitLab From 88c3347e21bf669d4744b8d6201e1833c8dc5aef Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Mon, 25 Feb 2019 15:32:49 -0800 Subject: [PATCH 488/766] Make get_accelerator_devices work with eager mode. PiperOrigin-RevId: 235614683 --- .../cluster_resolver/cluster_resolver.py | 28 +++++++++++++++---- .../cluster_resolver/cluster_resolver_test.py | 13 +++++++-- .../tpu_cluster_resolver_test.py | 14 ++++++++-- 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/tensorflow/python/distribute/cluster_resolver/cluster_resolver.py b/tensorflow/python/distribute/cluster_resolver/cluster_resolver.py index e269040665..0043691473 100644 --- a/tensorflow/python/distribute/cluster_resolver/cluster_resolver.py +++ b/tensorflow/python/distribute/cluster_resolver/cluster_resolver.py @@ -21,14 +21,19 @@ from __future__ import print_function import abc import collections +import re import six from tensorflow.python.client import session +from tensorflow.python.eager import context from tensorflow.python.framework import ops from tensorflow.python.training.server_lib import ClusterSpec from tensorflow.python.util.tf_export import tf_export +DEVICE_TYPE_REGEX = re.compile('.*device:([^:]+).*') + + def format_master_url(master, rpc_layer=None): if rpc_layer: return '%s://%s' % (rpc_layer, master) @@ -37,11 +42,24 @@ def format_master_url(master, rpc_layer=None): def get_accelerator_devices(master, config_proto): - # TODO(frankchn): Add support for eager mode as well as graph mode. - with ops.Graph().as_default(): - with session.Session(master, config=config_proto) as s: - devices = s.list_devices() - return devices + """Returns accelerator devices given a master and a configuration.""" + if context.executing_eagerly(): + device_names = context.list_devices() # list_devices returns list(string) + devices = [] + for name in device_names: + device_type = 'GPU' # default device type is GPU + device_match = DEVICE_TYPE_REGEX.match(name) + if device_match: + device_type = device_match.group(1) + if device_type == 'CPU': # CPUs are not accelerators + continue + devices.append(session._DeviceAttributes(name, device_type, 0, 0)) # pylint: disable=protected-access + return devices + else: + with ops.Graph().as_default(): + with session.Session(master, config=config_proto) as s: + devices = s.list_devices() + return devices @tf_export('distribute.cluster_resolver.ClusterResolver') diff --git a/tensorflow/python/distribute/cluster_resolver/cluster_resolver_test.py b/tensorflow/python/distribute/cluster_resolver/cluster_resolver_test.py index 9978cb47c1..cc1dd0111f 100644 --- a/tensorflow/python/distribute/cluster_resolver/cluster_resolver_test.py +++ b/tensorflow/python/distribute/cluster_resolver/cluster_resolver_test.py @@ -18,10 +18,12 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +from tensorflow.python import eager from tensorflow.python.client import session from tensorflow.python.distribute.cluster_resolver import ClusterResolver from tensorflow.python.distribute.cluster_resolver import SimpleClusterResolver from tensorflow.python.distribute.cluster_resolver import UnionClusterResolver +from tensorflow.python.framework import test_util from tensorflow.python.platform import test from tensorflow.python.training import server_lib @@ -40,10 +42,13 @@ class MockBaseClusterResolver(ClusterResolver): return "" +@test_util.run_all_in_graph_and_eager_modes class BaseClusterResolverTest(test.TestCase): + @mock.patch.object(eager.context, "list_devices") @mock.patch.object(session.BaseSession, "list_devices") - def testNumAcceleratorsSuccess(self, mock_list_devices): + def testNumAcceleratorsSuccess(self, mock_list_devices, + mock_eager_list_devices): device_names = [ "/job:worker/task:0/device:GPU:0", "/job:worker/task:0/device:GPU:1", @@ -54,13 +59,16 @@ class BaseClusterResolverTest(test.TestCase): session._DeviceAttributes( name, "GPU", 1024, 0) for name in device_names ] + mock_eager_list_devices.return_value = device_names mock_list_devices.return_value = device_list resolver = MockBaseClusterResolver() self.assertEqual(resolver.num_accelerators(), {"GPU": 4}) + @mock.patch.object(eager.context, "list_devices") @mock.patch.object(session.BaseSession, "list_devices") - def testNumAcceleratorsMultiDeviceSuccess(self, mock_list_devices): + def testNumAcceleratorsMultiDeviceSuccess(self, mock_list_devices, + mock_eager_list_devices): device_names = [ "/job:worker/task:0/device:TPU:0", "/job:worker/task:0/device:TPU:1", @@ -75,6 +83,7 @@ class BaseClusterResolverTest(test.TestCase): session._DeviceAttributes( name, name[26:29], 1024, 0) for name in device_names ] + mock_eager_list_devices.return_value = device_names mock_list_devices.return_value = device_list resolver = MockBaseClusterResolver() diff --git a/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver_test.py b/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver_test.py index 816a2614f7..a0027b211c 100644 --- a/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver_test.py +++ b/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver_test.py @@ -23,9 +23,11 @@ import os import six from six.moves.urllib.error import URLError +from tensorflow.python import eager from tensorflow.python.client import session from tensorflow.python.distribute.cluster_resolver import TPUClusterResolver from tensorflow.python.framework import errors +from tensorflow.python.framework import test_util from tensorflow.python.platform import test from tensorflow.python.training import server_lib from tensorflow.python.util import compat @@ -88,6 +90,7 @@ def mock_not_running_in_gce_urlopen(cls, *args, **kwargs): raise URLError(reason='Host does not exist.') +@test_util.run_all_in_graph_and_eager_modes class TPUClusterResolverTest(test.TestCase): def _verifyClusterSpecEquality(self, cluster_spec, expected_proto): @@ -616,11 +619,13 @@ class TPUClusterResolverTest(test.TestCase): TPUClusterResolver._verify_and_return_same_core_count( {0: [0], 1: [1, 2]}) + @mock.patch.object(eager.context, 'list_devices') @mock.patch.object(session.BaseSession, 'list_devices') @mock.patch.object(TPUClusterResolver, '_isRunningInGCE', mock_is_not_running_in_gce) - def testNumAcceleratorsSuccess(self, mock_list_devices): + def testNumAcceleratorsSuccess(self, mock_list_devices, + mock_eager_list_devices): device_names = [ '/job:tpu_worker/task:0/device:TPU:0', '/job:tpu_worker/task:1/device:TPU:1', @@ -635,19 +640,24 @@ class TPUClusterResolverTest(test.TestCase): session._DeviceAttributes( name, 'TPU', 1024, 0) for name in device_names ] + mock_eager_list_devices.return_value = device_names mock_list_devices.return_value = device_list resolver = TPUClusterResolver(tpu='') self.assertEqual(resolver.num_accelerators(), {'TPU': 2}) + @mock.patch.object(eager.context, 'list_devices') @mock.patch.object(session.BaseSession, 'list_devices') @mock.patch.object(TPUClusterResolver, '_isRunningInGCE', mock_is_not_running_in_gce) - def testNumAcceleratorsRetryFailure(self, mock_list_devices): + def testNumAcceleratorsRetryFailure(self, mock_list_devices, + mock_eager_list_devices): resolver = TPUClusterResolver(tpu='') mock_list_devices.side_effect = errors.DeadlineExceededError( None, None, 'timeout') + mock_eager_list_devices.side_effect = errors.DeadlineExceededError( + None, None, 'timeout') with self.assertRaises(RuntimeError): resolver.num_accelerators() -- GitLab From 4b8740faf664cb795c89b8943a36dac640897539 Mon Sep 17 00:00:00 2001 From: Alexandre Passos Date: Mon, 25 Feb 2019 15:46:20 -0800 Subject: [PATCH 489/766] Supporting external control dependencies in func_graph, defun, and control flow v2. PiperOrigin-RevId: 235617033 --- tensorflow/python/eager/function.py | 31 +++++++------ tensorflow/python/eager/function_test.py | 15 ++++++ tensorflow/python/framework/func_graph.py | 46 +++++++++++++++++++ .../python/kernel_tests/cond_v2_test.py | 15 ++++++ .../kernel_tests/control_flow_ops_py_test.py | 5 +- .../python/kernel_tests/while_v2_test.py | 14 ++++++ tensorflow/python/ops/cond_v2.py | 18 ++++---- tensorflow/python/ops/while_v2.py | 16 ++++--- 8 files changed, 127 insertions(+), 33 deletions(-) diff --git a/tensorflow/python/eager/function.py b/tensorflow/python/eager/function.py index 487fd5a972..6922f79a02 100644 --- a/tensorflow/python/eager/function.py +++ b/tensorflow/python/eager/function.py @@ -318,6 +318,7 @@ class _EagerDefinedFunction(object): self._num_outputs = len(self.signature.output_arg) self._output_types = [o.type for o in self.signature.output_arg] self._output_shapes = [o.shape for o in outputs] + self._control_captures = graph.control_captures self._func_graph_outputs = outputs self.grad_func_name = None self.python_grad_func = None @@ -386,13 +387,14 @@ class _EagerDefinedFunction(object): g = ops.get_default_graph() self.add_to_graph(g) signature = self.signature - op = g.create_op( - signature.name, - [ops.internal_convert_to_tensor(x, ctx=ctx) for x in args], - tuple(dtypes_module.DType(x.type) for x in signature.output_arg), - op_def=signature, - name="FunctionCall", - compute_shapes=False) + with ops.control_dependencies(self._control_captures): + op = g.create_op( + signature.name, + [ops.internal_convert_to_tensor(x, ctx=ctx) for x in args], + tuple(dtypes_module.DType(x.type) for x in signature.output_arg), + op_def=signature, + name="FunctionCall", + compute_shapes=False) outputs = op.outputs if not outputs: return op @@ -405,13 +407,14 @@ class _EagerDefinedFunction(object): # creates `PartitionedCallOp` kernels by default, or remove the previous # branch if a TPU kernel is registered for `PartitionedCall`. with _InterpolateFunctionError(self): - outputs = functional_ops.partitioned_call( - args=args, - f=self, - tout=self._output_types, - executing_eagerly=executing_eagerly, - config=config, - executor_type=executor_type) + with ops.control_dependencies(self._control_captures): + outputs = functional_ops.partitioned_call( + args=args, + f=self, + tout=self._output_types, + executing_eagerly=executing_eagerly, + config=config, + executor_type=executor_type) if executing_eagerly: return outputs diff --git a/tensorflow/python/eager/function_test.py b/tensorflow/python/eager/function_test.py index 270e420fa8..dc5b03bdf7 100644 --- a/tensorflow/python/eager/function_test.py +++ b/tensorflow/python/eager/function_test.py @@ -114,6 +114,21 @@ class FunctionTest(test.TestCase, parameterized.TestCase): r = add(x, v2) self.assertEqual(3.0, self.evaluate(r)) + def testExternalControlDependency(self): + with ops.Graph().as_default(), self.test_session(): + v = variables.Variable(1.0) + v.initializer.run() + + op = v.assign_add(1.0) + + @function.defun + def f(): + with ops.control_dependencies([op]): + return 1.0 + + self.evaluate(f()) + self.assertAllEqual(self.evaluate(v), 2.0) + def testInputShapeFunctionRelaxation(self): unknown_dim = [False] diff --git a/tensorflow/python/framework/func_graph.py b/tensorflow/python/framework/func_graph.py index 9097a8dd1f..91d80a388a 100644 --- a/tensorflow/python/framework/func_graph.py +++ b/tensorflow/python/framework/func_graph.py @@ -125,6 +125,8 @@ class FuncGraph(ops.Graph): inputs coming first. outputs: Tensors that will be returned by this function. The tensors are in this FuncGraph. + control_outputs: Operations that must be executed before the function + represented by this graph can be said to have been executed. structured_input_signature: A tuple of (args, kwargs), which are both possibly-nested python objects that were received by this function. Note that these structures might contain Python `None`s. @@ -136,6 +138,8 @@ class FuncGraph(ops.Graph): or the global default Graph. captures: Maps external tensor -> internal tensor (i.e. input placeholder). The entries are in the order they were captured. + control_captures: Set of external ops on which this graph has a control + dependency. seed: The graph-level random seed. capture_by_value: If True, the func graph will capture Variables by value instead of reference. @@ -166,6 +170,7 @@ class FuncGraph(ops.Graph): self.inputs = [] self.outputs = [] self.control_outputs = [] + self.control_captures = set() self.structured_input_signature = None self.structured_outputs = None self._weak_variables = [] @@ -214,6 +219,47 @@ class FuncGraph(ops.Graph): def __str__(self): return "FuncGraph(name=%s, id=%s)" % (self.name, id(self)) + def control_dependencies(self, control_inputs): + """Handles control dependencies. + + FuncGraph wraps Graph's control_dependencies logic by first filtering out + any external tensors / operations and storing them in the graph's + control_captures member. Any consumers of this function graph must then + decide how to handle the control captures. + + Args: + control_inputs: A list of `Operation` or `Tensor` objects which + must be executed or computed before running the operations + defined in the context. Can also be `None` to clear the control + dependencies. + + Returns: + A context manager that specifies control dependencies for all + operations constructed within the context. + + Raises: + TypeError: If `control_inputs` is not a list of `Operation` or + `Tensor` objects. + """ + if control_inputs is None: + return super(FuncGraph, self).control_dependencies(control_inputs) + + filtered_control_inputs = [] + for c in control_inputs: + # Check for _UnreadVariable + if (isinstance(c, ops.IndexedSlices) or + (hasattr(c, "_handle") and hasattr(c, "op"))): + c = c.op + graph_element = ops._as_graph_element(c) # pylint: disable=protected-access + if graph_element is None: + graph_element = c + if graph_element is not None and getattr( + graph_element, "graph", None) is not self: + self.control_captures.add(graph_element) + else: + filtered_control_inputs.append(graph_element) + return super(FuncGraph, self).control_dependencies(filtered_control_inputs) + def as_default(self): outer_cm = super(FuncGraph, self).as_default() diff --git a/tensorflow/python/kernel_tests/cond_v2_test.py b/tensorflow/python/kernel_tests/cond_v2_test.py index 244b0bdd7f..05544a416f 100644 --- a/tensorflow/python/kernel_tests/cond_v2_test.py +++ b/tensorflow/python/kernel_tests/cond_v2_test.py @@ -85,6 +85,21 @@ class CondV2Test(test.TestCase): self._testCond(true_fn, false_fn, [x, y]) self._testCond(true_fn, false_fn, [y]) + def testExternalControlDependencies(self): + with ops.Graph().as_default(), self.test_session(): + v = variables.Variable(1.0) + v.initializer.run() + op = v.assign_add(1.0) + + def true_branch(): + with ops.control_dependencies([op]): + return 1.0 + + cond_v2.cond_v2(array_ops.placeholder_with_default(False, None), + true_branch, + lambda: 2.0).eval() + self.assertAllEqual(self.evaluate(v), 2.0) + @test_util.run_deprecated_v1 def testMultipleOutputs(self): x = constant_op.constant(1.0, name="x") diff --git a/tensorflow/python/kernel_tests/control_flow_ops_py_test.py b/tensorflow/python/kernel_tests/control_flow_ops_py_test.py index 20c03178ea..4cf71bf892 100644 --- a/tensorflow/python/kernel_tests/control_flow_ops_py_test.py +++ b/tensorflow/python/kernel_tests/control_flow_ops_py_test.py @@ -777,7 +777,7 @@ class ControlFlowTest(test.TestCase): r = control_flow_ops.cond(constant_op.constant(False), true_fn, false_fn) self.assertAllEqual([2.0], self.evaluate(r)) - @test_util.disable_control_flow_v2("b/79881896 (control deps)") + @test_util.disable_control_flow_v2("b/79881896 (placeholder)") @test_util.run_v1_only("b/120545219") def testCondWithControl(self): with self.cached_session(): @@ -1218,7 +1218,6 @@ class ControlFlowTest(test.TestCase): r = control_flow_ops.while_loop(c, b, [n], parallel_iterations=20) self.assertEqual(10000, self.evaluate(r)) - @test_util.disable_control_flow_v2("b/79881896 (control deps)") @test_util.run_v1_only("b/120545219") def testWhileExternalControlDependencies(self): with self.cached_session(): @@ -1235,7 +1234,6 @@ class ControlFlowTest(test.TestCase): self.assertAllEqual(result, 2) self.assertAllEqual(v.read_value(), 1.0) - @test_util.disable_control_flow_v2("b/79881896 (control deps)") @test_util.run_v1_only("b/120545219") def testWhileExternalControlDependenciesNoInput(self): with self.cached_session(): @@ -1981,7 +1979,6 @@ class ControlFlowTest(test.TestCase): lambda x: x < 10, lambda x: x + array_ops.identity(c), [x0]) self.assertEqual(10, sess.run(r, {b: True})) - @test_util.disable_control_flow_v2("b/79881896 (control_deps)") @test_util.run_v1_only("b/120545219") def testWhileWithControl_5(self): with self.cached_session() as sess: diff --git a/tensorflow/python/kernel_tests/while_v2_test.py b/tensorflow/python/kernel_tests/while_v2_test.py index 2c6d275900..ad5041e811 100644 --- a/tensorflow/python/kernel_tests/while_v2_test.py +++ b/tensorflow/python/kernel_tests/while_v2_test.py @@ -83,6 +83,20 @@ class WhileV2Test(test.TestCase, parameterized.TestCase): self.assertAllEqual(fnWithLoop(), 4.0) + def testExternalControlDependencies(self): + with ops.Graph().as_default(), self.test_session(): + v = variables.Variable(1.) + v.initializer.run() + op = v.assign_add(1.) + + def body_fn(i): # pylint: disable=invalid-name + with ops.control_dependencies([op]): + return i + 1 + + loop = while_loop_v2(lambda i: i < 1, body_fn, [0]) + loop[0].op.run() + self.assertAllEqual(self.evaluate(v), 2.0) + @test_util.run_deprecated_v1 def testMultipleLoopVarsBasic(self): x = constant_op.constant(5.) diff --git a/tensorflow/python/ops/cond_v2.py b/tensorflow/python/ops/cond_v2.py index 74f5b52f18..98f641e540 100644 --- a/tensorflow/python/ops/cond_v2.py +++ b/tensorflow/python/ops/cond_v2.py @@ -192,14 +192,16 @@ def _build_cond(pred, true_graph, false_graph, true_inputs, false_inputs, true_inputs, false_inputs) # Create the If op. - tensors = gen_functional_ops._if( # pylint: disable=protected-access - pred, - cond_inputs, [t.dtype for t in true_graph.outputs], - util.create_new_tf_function(true_graph), - util.create_new_tf_function(false_graph), - output_shapes=_get_output_shapes(true_graph.outputs, - false_graph.outputs), - name=name) + with ops.control_dependencies( + list(true_graph.control_captures) + list(false_graph.control_captures)): + tensors = gen_functional_ops._if( # pylint: disable=protected-access + pred, + cond_inputs, [t.dtype for t in true_graph.outputs], + util.create_new_tf_function(true_graph), + util.create_new_tf_function(false_graph), + output_shapes=_get_output_shapes(true_graph.outputs, + false_graph.outputs), + name=name) # TODO(b/110167197) this approach requires cond_v2 to have at least 1 output if_op = tensors[0].op diff --git a/tensorflow/python/ops/while_v2.py b/tensorflow/python/ops/while_v2.py index 68f1cbae0a..413186b4bb 100644 --- a/tensorflow/python/ops/while_v2.py +++ b/tensorflow/python/ops/while_v2.py @@ -203,13 +203,15 @@ def while_loop(cond, _check_num_inputs_outputs(cond_graph, body_graph, len(flattened_loop_vars)) - outputs = gen_functional_ops._while( - flattened_loop_vars, - util.create_new_tf_function(cond_graph), - util.create_new_tf_function(body_graph), - output_shapes=[t.shape for t in body_graph.outputs], - parallel_iterations=parallel_iterations, - name=scope) + with ops.control_dependencies( + list(cond_graph.control_captures) + list(body_graph.control_captures)): + outputs = gen_functional_ops._while( + flattened_loop_vars, + util.create_new_tf_function(cond_graph), + util.create_new_tf_function(body_graph), + output_shapes=[t.shape for t in body_graph.outputs], + parallel_iterations=parallel_iterations, + name=scope) _copy_handle_data(body_graph.outputs, outputs) util.maybe_set_lowering_attr(outputs[0].op) -- GitLab From 3eb0f77ddd7add67a008d468a5a7e0a22ab9c431 Mon Sep 17 00:00:00 2001 From: Albin Joy Date: Tue, 26 Feb 2019 06:29:33 +0530 Subject: [PATCH 490/766] Updated reshape_test.cc --- tensorflow/lite/kernels/reshape_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/kernels/reshape_test.cc b/tensorflow/lite/kernels/reshape_test.cc index e9d12a9def..eb05eb6d4e 100644 --- a/tensorflow/lite/kernels/reshape_test.cc +++ b/tensorflow/lite/kernels/reshape_test.cc @@ -33,7 +33,7 @@ enum ShapeSpecificationType { // Const node, which is guaranteed not to change once inference starts. The // shape is also hardcoded as in kAsReshapeOption. kAsConstantTensor, - // The output shape is specifed as an input tensor that can change based on + // The output shape is specified as an input tensor that can change based on // external input. That is, the shape is not know before the inference // starts. The shape is also hardcoded as in kAsReshapeOption. kAsTensor, -- GitLab From a9ef3efe24bdd35d954d287174a919a4a7febb39 Mon Sep 17 00:00:00 2001 From: Albin Joy Date: Tue, 26 Feb 2019 06:33:03 +0530 Subject: [PATCH 491/766] Updated unidirectional_sequence_lstm.cc --- tensorflow/lite/kernels/unidirectional_sequence_lstm.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/kernels/unidirectional_sequence_lstm.cc b/tensorflow/lite/kernels/unidirectional_sequence_lstm.cc index e2fc73ba29..8c2d0d57c7 100644 --- a/tensorflow/lite/kernels/unidirectional_sequence_lstm.cc +++ b/tensorflow/lite/kernels/unidirectional_sequence_lstm.cc @@ -246,7 +246,7 @@ TfLiteStatus CheckInputTensorDimensions(TfLiteContext* context, } // Resize the output and state tensors based on the sizes of the input tensors. -// Allocate a temprory scratch tensor. Also check that the sizes of the input +// Allocate a temporary scratch tensor. Also check that the sizes of the input // tensors match each other. TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { int* scratch_tensor_index = reinterpret_cast(node->user_data); -- GitLab From 36724b63f628ea62f5616fae70128bbe25988763 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 25 Feb 2019 15:52:40 -0800 Subject: [PATCH 492/766] Remove some unused C++ variables in `core/`. PiperOrigin-RevId: 235617985 --- tensorflow/core/common_runtime/device_mgr.cc | 1 - tensorflow/core/common_runtime/device_resolver_local_test.cc | 1 - tensorflow/core/common_runtime/function.cc | 1 - .../collective_param_resolver_distributed_test.cc | 1 - .../distributed_runtime/collective_rma_distributed_test.cc | 4 ---- .../distributed_runtime/device_resolver_distributed_test.cc | 1 - tensorflow/core/distributed_runtime/session_mgr_test.cc | 2 -- tensorflow/core/framework/op_gen_lib.cc | 1 - tensorflow/core/framework/rendezvous_test.cc | 3 --- tensorflow/core/framework/tensor_slice.cc | 1 - tensorflow/core/graph/algorithm_test.cc | 1 - tensorflow/core/graph/mkl_layout_pass.cc | 1 - tensorflow/core/grappler/clusters/single_machine.cc | 1 - tensorflow/core/kernels/deserialize_sparse_string_op.cc | 2 -- tensorflow/core/kernels/hexagon/graph_transferer.cc | 1 - tensorflow/core/kernels/map_stage_op.cc | 2 -- tensorflow/core/kernels/mfcc_dct_test.cc | 3 --- tensorflow/core/kernels/sparse_reshape_op.cc | 2 -- tensorflow/core/lib/gtl/map_util_test.cc | 1 - tensorflow/core/lib/io/buffered_inputstream_test.cc | 2 -- tensorflow/core/lib/io/table.cc | 1 - tensorflow/core/lib/strings/ordered_code_test.cc | 1 - tensorflow/core/ops/boosted_trees_ops.cc | 1 - tensorflow/core/ops/math_ops_test.cc | 1 - tensorflow/core/ops/sparse_ops.cc | 1 - tensorflow/core/profiler/internal/tfprof_tensor_test.cc | 1 - tensorflow/core/util/stat_summarizer_test.cc | 1 - 27 files changed, 39 deletions(-) diff --git a/tensorflow/core/common_runtime/device_mgr.cc b/tensorflow/core/common_runtime/device_mgr.cc index 1f7d7c4699..26602a45be 100644 --- a/tensorflow/core/common_runtime/device_mgr.cc +++ b/tensorflow/core/common_runtime/device_mgr.cc @@ -95,7 +95,6 @@ string DeviceMgr::DeviceMappingString() const { } Status DeviceMgr::LookupDevice(StringPiece name, Device** device) const { - Status s; auto iter = device_map_.find(name); if (iter == device_map_.end()) { std::vector device_names; diff --git a/tensorflow/core/common_runtime/device_resolver_local_test.cc b/tensorflow/core/common_runtime/device_resolver_local_test.cc index 62e82bcc5a..b8dac8e0dd 100644 --- a/tensorflow/core/common_runtime/device_resolver_local_test.cc +++ b/tensorflow/core/common_runtime/device_resolver_local_test.cc @@ -31,7 +31,6 @@ namespace { 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(); diff --git a/tensorflow/core/common_runtime/function.cc b/tensorflow/core/common_runtime/function.cc index 99841588dc..9fa6f717b9 100644 --- a/tensorflow/core/common_runtime/function.cc +++ b/tensorflow/core/common_runtime/function.cc @@ -684,7 +684,6 @@ Status FunctionLibraryRuntimeImpl::Instantiate( } } - Status s; const FunctionLibraryDefinition* lib_def = options.overlay_lib ? options.overlay_lib : base_lib_def_; FunctionBody* fbody = nullptr; diff --git a/tensorflow/core/distributed_runtime/collective_param_resolver_distributed_test.cc b/tensorflow/core/distributed_runtime/collective_param_resolver_distributed_test.cc index 823d7d5eb9..5cd75a3100 100644 --- a/tensorflow/core/distributed_runtime/collective_param_resolver_distributed_test.cc +++ b/tensorflow/core/distributed_runtime/collective_param_resolver_distributed_test.cc @@ -107,7 +107,6 @@ class FakeCache : public TestWorkerCache { WorkerInterface* wi = it->second; GetStatusRequest req; GetStatusResponse resp; - Notification note; Status status = wi->GetStatus(&req, &resp); if (!status.ok()) { done(status); diff --git a/tensorflow/core/distributed_runtime/collective_rma_distributed_test.cc b/tensorflow/core/distributed_runtime/collective_rma_distributed_test.cc index 26f722a6bd..cac17ab51a 100644 --- a/tensorflow/core/distributed_runtime/collective_rma_distributed_test.cc +++ b/tensorflow/core/distributed_runtime/collective_rma_distributed_test.cc @@ -147,7 +147,6 @@ class FakeCache : public TestWorkerCache { WorkerInterface* wi = it->second; GetStatusRequest req; GetStatusResponse resp; - Notification note; Status status = wi->GetStatus(&req, &resp); if (!status.ok()) { done(status); @@ -271,7 +270,6 @@ TEST_F(CollRMADistTest, ProdFirstOK) { producer_status.Update(s); producer_note.Notify(); }); - Status status; Device* dst_device = nullptr; string dev_name = "CPU:0"; TF_EXPECT_OK(device_mgrs_[0]->LookupDevice(dev_name, &dst_device)); @@ -300,7 +298,6 @@ TEST_F(CollRMADistTest, ConsFirstOK) { Status producer_status; FakeWorker* wi = workers_[1]; const string kBufKey = "fake_buf_key"; - Status status; Device* dst_device = nullptr; string dev_name = "CPU:0"; TF_EXPECT_OK(device_mgrs_[0]->LookupDevice(dev_name, &dst_device)); @@ -333,7 +330,6 @@ TEST_F(CollRMADistTest, ConsFirstAbort) { Notification consumer_note; Status consumer_status; const string kBufKey = "fake_buf_key"; - Status status; Device* dst_device = nullptr; string dev_name = "CPU:0"; TF_EXPECT_OK(device_mgrs_[0]->LookupDevice(dev_name, &dst_device)); diff --git a/tensorflow/core/distributed_runtime/device_resolver_distributed_test.cc b/tensorflow/core/distributed_runtime/device_resolver_distributed_test.cc index 842a2b3b05..0bc370c93b 100644 --- a/tensorflow/core/distributed_runtime/device_resolver_distributed_test.cc +++ b/tensorflow/core/distributed_runtime/device_resolver_distributed_test.cc @@ -109,7 +109,6 @@ class FakeCache : public TestWorkerCache { WorkerInterface* wi = it->second; GetStatusRequest req; GetStatusResponse resp; - Notification note; Status status = wi->GetStatus(&req, &resp); if (!status.ok()) { done(status); diff --git a/tensorflow/core/distributed_runtime/session_mgr_test.cc b/tensorflow/core/distributed_runtime/session_mgr_test.cc index 1ab0d20f0b..edbd7ddbcb 100644 --- a/tensorflow/core/distributed_runtime/session_mgr_test.cc +++ b/tensorflow/core/distributed_runtime/session_mgr_test.cc @@ -140,7 +140,6 @@ TEST_F(SessionMgrTest, CreateSessionIsolateSessionState) { } TEST_F(SessionMgrTest, LegacySession) { - ServerDef server_def; string session_handle = ""; std::shared_ptr session; TF_EXPECT_OK(mgr_.WorkerSessionForSession(session_handle, &session)); @@ -150,7 +149,6 @@ TEST_F(SessionMgrTest, LegacySession) { } TEST_F(SessionMgrTest, UnknownSessionHandle) { - ServerDef server_def; string session_handle = "unknown_session_handle"; std::shared_ptr session; Status s = mgr_.WorkerSessionForSession(session_handle, &session); diff --git a/tensorflow/core/framework/op_gen_lib.cc b/tensorflow/core/framework/op_gen_lib.cc index 92a7038a40..4a94f97c76 100644 --- a/tensorflow/core/framework/op_gen_lib.cc +++ b/tensorflow/core/framework/op_gen_lib.cc @@ -228,7 +228,6 @@ string PBTxtFromMultiline(StringPiece multiline_pbtxt) { // Add every line to unescaped until we see the "END" string. string unescaped; bool first = true; - string suffix; while (!multiline_pbtxt.empty()) { SplitAt('\n', &multiline_pbtxt, &line); if (str_util::ConsumePrefix(&line, end)) break; diff --git a/tensorflow/core/framework/rendezvous_test.cc b/tensorflow/core/framework/rendezvous_test.cc index 7a777f064c..8f16c6fd83 100644 --- a/tensorflow/core/framework/rendezvous_test.cc +++ b/tensorflow/core/framework/rendezvous_test.cc @@ -318,7 +318,6 @@ void BM_SendRecv(int iters) { Tensor val(DT_STRING, TensorShape({})); bool is_dead = false; Rendezvous::Args args; - Status s; if (iters > 0) { while (iters--) { TF_CHECK_OK(rendez->Send(KeyFoo(), args, orig, is_dead)); @@ -343,7 +342,6 @@ void BM_PingPong(int iters) { Tensor foo(DT_STRING, TensorShape({})); bool is_dead = false; Rendezvous::Args args; - Status s; for (int i = 0; i < iters; ++i) { TF_CHECK_OK(rendez->Recv(KeyFoo(), args, &foo, &is_dead)); TF_CHECK_OK(rendez->Send(KeyBar(), args, bar, is_dead)); @@ -354,7 +352,6 @@ void BM_PingPong(int iters) { Tensor bar(DT_STRING, TensorShape({})); bool is_dead = false; Rendezvous::Args args; - Status s; for (int i = 0; i < iters; ++i) { TF_CHECK_OK(rendez->Send(KeyFoo(), args, foo, is_dead)); TF_CHECK_OK(rendez->Recv(KeyBar(), args, &bar, &is_dead)); diff --git a/tensorflow/core/framework/tensor_slice.cc b/tensorflow/core/framework/tensor_slice.cc index eb3a7f52c2..975e1e2e24 100644 --- a/tensorflow/core/framework/tensor_slice.cc +++ b/tensorflow/core/framework/tensor_slice.cc @@ -128,7 +128,6 @@ string TensorSlice::DebugString() const { if (!first) { buffer.append(":"); } - string s; if (IsFullAt(d)) { buffer.append("-"); } else { diff --git a/tensorflow/core/graph/algorithm_test.cc b/tensorflow/core/graph/algorithm_test.cc index 60a3e66aa1..a62f8f02a0 100644 --- a/tensorflow/core/graph/algorithm_test.cc +++ b/tensorflow/core/graph/algorithm_test.cc @@ -156,7 +156,6 @@ TEST(AlgorithmTest, ReversePostOrderStable) { TEST(AlgorithmTest, PostOrderWithEdgeFilter) { GraphDefBuilder b(GraphDefBuilder::kFailImmediately); - string error; Node* n0 = ops::SourceOp("TestParams", b.opts().WithName("n0")); Node* n1 = ops::UnaryOp("TestUnary", n0, b.opts().WithName("n1")); Node* n2 = ops::UnaryOp("TestUnary", n1, b.opts().WithName("n2")); diff --git a/tensorflow/core/graph/mkl_layout_pass.cc b/tensorflow/core/graph/mkl_layout_pass.cc index d5dcd16be7..c66dfe10f5 100644 --- a/tensorflow/core/graph/mkl_layout_pass.cc +++ b/tensorflow/core/graph/mkl_layout_pass.cc @@ -2366,7 +2366,6 @@ void MklLayoutRewritePass::CopyAttrsQuantizedPooling(const Node* orig_node, NodeBuilder* nb, bool change_format) { DataType T; - string data_format; string padding; std::vector ksize, strides; diff --git a/tensorflow/core/grappler/clusters/single_machine.cc b/tensorflow/core/grappler/clusters/single_machine.cc index e7c72b8088..c7827c17dd 100644 --- a/tensorflow/core/grappler/clusters/single_machine.cc +++ b/tensorflow/core/grappler/clusters/single_machine.cc @@ -455,7 +455,6 @@ Status SingleMachine::ClearAllocatorStats() const { std::vector devices = device_mgr->ListDevices(); for (Device* device : devices) { - AllocatorStats stats; auto* allocator = device->GetAllocator(AllocatorAttributes()); if (!allocator->TracksAllocationSizes()) { return Status(error::INVALID_ARGUMENT, diff --git a/tensorflow/core/kernels/deserialize_sparse_string_op.cc b/tensorflow/core/kernels/deserialize_sparse_string_op.cc index 2c13f24ad6..d26d8188d5 100644 --- a/tensorflow/core/kernels/deserialize_sparse_string_op.cc +++ b/tensorflow/core/kernels/deserialize_sparse_string_op.cc @@ -204,8 +204,6 @@ class DeserializeSparseOp : public OpKernel { target_shape.vec()(i + ndims - 1) = output.shape().data()[i + 1]; } - Tensor output_indices; - Tensor output_shape; Reshape(context, output.indices(), input_shape, target_shape, 0 /* output indices index */, 2 /* output shape index */); context->set_output(1, output.values()); diff --git a/tensorflow/core/kernels/hexagon/graph_transferer.cc b/tensorflow/core/kernels/hexagon/graph_transferer.cc index 477e729dcb..df2796f24b 100644 --- a/tensorflow/core/kernels/hexagon/graph_transferer.cc +++ b/tensorflow/core/kernels/hexagon/graph_transferer.cc @@ -338,7 +338,6 @@ Status GraphTransferer::TransformGraphToAddAggregatedInputNode( shapes.emplace_back(input_node_info_list.at(i).second.shape()); } - NodeDef input_node_def; auto builder = NodeBuilder(AGGREGATED_INPUT_NODE_NAME, "RemoteFusedGraphExecute") .Input(std::vector{}) diff --git a/tensorflow/core/kernels/map_stage_op.cc b/tensorflow/core/kernels/map_stage_op.cc index 27a8696e54..82b1397d24 100644 --- a/tensorflow/core/kernels/map_stage_op.cc +++ b/tensorflow/core/kernels/map_stage_op.cc @@ -582,7 +582,6 @@ class MapUnstageOp : public OpKernel { const Tensor* key_tensor; const Tensor* indices_tensor; - OpInputList values_tensor; OP_REQUIRES_OK(ctx, ctx->input("key", &key_tensor)); OP_REQUIRES_OK(ctx, ctx->input("indices", &indices_tensor)); @@ -644,7 +643,6 @@ class MapPeekOp : public OpKernel { const Tensor* key_tensor; const Tensor* indices_tensor; - OpInputList values_tensor; OP_REQUIRES_OK(ctx, ctx->input("key", &key_tensor)); OP_REQUIRES_OK(ctx, ctx->input("indices", &indices_tensor)); diff --git a/tensorflow/core/kernels/mfcc_dct_test.cc b/tensorflow/core/kernels/mfcc_dct_test.cc index 7526278fe9..7015864897 100644 --- a/tensorflow/core/kernels/mfcc_dct_test.cc +++ b/tensorflow/core/kernels/mfcc_dct_test.cc @@ -44,11 +44,8 @@ TEST(MfccDctTest, AgreesWithMatlab) { TEST(MfccDctTest, InitializeFailsOnInvalidInput) { MfccDct dct1; EXPECT_FALSE(dct1.Initialize(-50, 1)); - MfccDct dct2; EXPECT_FALSE(dct1.Initialize(10, -4)); - MfccDct dct3; EXPECT_FALSE(dct1.Initialize(-1, -1)); - MfccDct dct4; EXPECT_FALSE(dct1.Initialize(20, 21)); } diff --git a/tensorflow/core/kernels/sparse_reshape_op.cc b/tensorflow/core/kernels/sparse_reshape_op.cc index 939d404aa4..059519a913 100644 --- a/tensorflow/core/kernels/sparse_reshape_op.cc +++ b/tensorflow/core/kernels/sparse_reshape_op.cc @@ -34,8 +34,6 @@ class SparseReshapeOp : public OpKernel { explicit SparseReshapeOp(OpKernelConstruction* context) : OpKernel(context) {} void Compute(OpKernelContext* context) override { - Tensor output_indices; - Tensor output_shape; Reshape(context, context->input(0), context->input(1), context->input(2), 0 /* output indices index */, 1 /* output shape index */); } diff --git a/tensorflow/core/lib/gtl/map_util_test.cc b/tensorflow/core/lib/gtl/map_util_test.cc index e19459c091..bcb1a4a6cf 100644 --- a/tensorflow/core/lib/gtl/map_util_test.cc +++ b/tensorflow/core/lib/gtl/map_util_test.cc @@ -34,7 +34,6 @@ TEST(MapUtil, Find) { m["foo"] = "bar"; EXPECT_EQ("bar", gtl::FindWithDefault(m, "foo", "")); EXPECT_EQ("bar", *gtl::FindOrNull(m, "foo")); - string str; EXPECT_TRUE(m.count("foo") > 0); EXPECT_EQ(m["foo"], "bar"); } diff --git a/tensorflow/core/lib/io/buffered_inputstream_test.cc b/tensorflow/core/lib/io/buffered_inputstream_test.cc index 49b2b1a861..ad4c8013bc 100644 --- a/tensorflow/core/lib/io/buffered_inputstream_test.cc +++ b/tensorflow/core/lib/io/buffered_inputstream_test.cc @@ -337,7 +337,6 @@ TEST(BufferedInputStream, ReadAll_Empty) { for (auto buf_size : BufferSizes()) { RandomAccessInputStream input_stream(file.get()); - string read; BufferedInputStream in(&input_stream, buf_size); string contents; TF_ASSERT_OK(in.ReadAll(&contents)); @@ -355,7 +354,6 @@ TEST(BufferedInputStream, ReadAll_Text) { for (auto buf_size : BufferSizes()) { RandomAccessInputStream input_stream(file.get()); - string read; BufferedInputStream in(&input_stream, buf_size); string contents; TF_ASSERT_OK(in.ReadAll(&contents)); diff --git a/tensorflow/core/lib/io/table.cc b/tensorflow/core/lib/io/table.cc index 1ef7bb6ccd..1e68493bfe 100644 --- a/tensorflow/core/lib/io/table.cc +++ b/tensorflow/core/lib/io/table.cc @@ -133,7 +133,6 @@ Status Table::InternalGet(const StringPiece& k, void* arg, Iterator* iiter = rep_->index_block->NewIterator(); iiter->Seek(k); if (iiter->Valid()) { - BlockHandle handle; Iterator* block_iter = BlockReader(this, iiter->value()); block_iter->Seek(k); if (block_iter->Valid()) { diff --git a/tensorflow/core/lib/strings/ordered_code_test.cc b/tensorflow/core/lib/strings/ordered_code_test.cc index ede9f4d390..5334394d35 100644 --- a/tensorflow/core/lib/strings/ordered_code_test.cc +++ b/tensorflow/core/lib/strings/ordered_code_test.cc @@ -396,7 +396,6 @@ void BM_WriteNum(int n, T multiplier) { template void BM_ReadNum(int n, T multiplier) { - string x; random::PhiloxRandom philox(301, 17); random::SimplePhilox rnd(&philox); // Use enough distinct values to confuse the branch predictor diff --git a/tensorflow/core/ops/boosted_trees_ops.cc b/tensorflow/core/ops/boosted_trees_ops.cc index 1c854f6619..852b8d326c 100644 --- a/tensorflow/core/ops/boosted_trees_ops.cc +++ b/tensorflow/core/ops/boosted_trees_ops.cc @@ -400,7 +400,6 @@ REGISTER_OP("BoostedTreesMakeQuantileSummaries") c->WithRank(c->input(num_features), 1, &example_weights_shape)); for (int i = 0; i < num_features; ++i) { ShapeHandle feature_shape; - DimensionHandle unused_dim; TF_RETURN_IF_ERROR(c->WithRank(c->input(i), 1, &feature_shape)); // the columns are value, weight, min_rank, max_rank. c->set_output(i, c->MakeShape({c->UnknownDim(), 4})); diff --git a/tensorflow/core/ops/math_ops_test.cc b/tensorflow/core/ops/math_ops_test.cc index 1e6dbbfb2f..a1abdb6aed 100644 --- a/tensorflow/core/ops/math_ops_test.cc +++ b/tensorflow/core/ops/math_ops_test.cc @@ -205,7 +205,6 @@ TEST(MathOpsTest, Select_ShapeFn) { typedef std::vector> ShapeDtypeV; std::vector> handle_data; std::unique_ptr c; - Status run_status; auto run_inference_for_handles = [&]() -> Status { CHECK(op_reg_data->shape_inference_fn != nullptr); c.reset(new shape_inference::InferenceContext( diff --git a/tensorflow/core/ops/sparse_ops.cc b/tensorflow/core/ops/sparse_ops.cc index de08a10784..85186c4a2d 100644 --- a/tensorflow/core/ops/sparse_ops.cc +++ b/tensorflow/core/ops/sparse_ops.cc @@ -97,7 +97,6 @@ REGISTER_OP("SparseTensorDenseMatMul") ShapeHandle unused; ShapeHandle b; ShapeHandle a_shape; - ShapeHandle a_shape_shape; TF_RETURN_IF_ERROR(c->WithRank(c->input(0), 2, &unused)); // a_indices TF_RETURN_IF_ERROR(c->WithRank(c->input(1), 1, &unused)); // a_values TF_RETURN_IF_ERROR(c->MakeShapeFromShapeTensor(2, &a_shape)); diff --git a/tensorflow/core/profiler/internal/tfprof_tensor_test.cc b/tensorflow/core/profiler/internal/tfprof_tensor_test.cc index 7fa79d23d8..b9eb1a4892 100644 --- a/tensorflow/core/profiler/internal/tfprof_tensor_test.cc +++ b/tensorflow/core/profiler/internal/tfprof_tensor_test.cc @@ -63,7 +63,6 @@ TEST_F(TFProfTensorTest, Basics) { "", {}); const GraphNodeProto& root = tf_stats_->ShowGraphNode("scope", opts); - GraphNodeProto expected; EXPECT_EQ(root.children(0).name(), "DW"); EXPECT_GT(root.children(0).tensor_value().value_double_size(), 10); EXPECT_EQ(root.children(1).name(), "DW2"); diff --git a/tensorflow/core/util/stat_summarizer_test.cc b/tensorflow/core/util/stat_summarizer_test.cc index 1feedf99cb..4553559e7a 100644 --- a/tensorflow/core/util/stat_summarizer_test.cc +++ b/tensorflow/core/util/stat_summarizer_test.cc @@ -70,7 +70,6 @@ versions { TF_ASSERT_OK(session->Run(run_options, {}, {"myconstant:0"}, {}, &outputs, &run_metadata)); - StatSummarizerOptions opts; StatSummarizer stats(graph_def); stats.ProcessStepStats(run_metadata.step_stats()); -- GitLab From 19069a720a9f0711db22f14fce58efb41030e017 Mon Sep 17 00:00:00 2001 From: Albin Joy Date: Tue, 26 Feb 2019 06:41:51 +0530 Subject: [PATCH 493/766] Typo fix while.cc --- tensorflow/lite/kernels/while.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/lite/kernels/while.cc b/tensorflow/lite/kernels/while.cc index 07a48e6750..b3f00d3fe1 100644 --- a/tensorflow/lite/kernels/while.cc +++ b/tensorflow/lite/kernels/while.cc @@ -229,8 +229,8 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { // subgraph. This is always true before step 2. // // This is the best we can do without sharing tensor buffer across subgraph - // boundry. Currently we copy the input / output between the subgraphs. This - // isn't optimized yet and a lot of redundent copies are made. + // boundary. Currently we copy the input / output between the subgraphs. This + // isn't optimized yet and a lot of redundant copies are made. // TODO(b/120234921): Optimize and avoid copying tensors between subgraphs. TF_LITE_ENSURE_OK( context, -- GitLab From 188aae620c1b5d08a1172b3c0aaf5c4773cb34ae Mon Sep 17 00:00:00 2001 From: Albin Joy Date: Tue, 26 Feb 2019 06:25:37 +0530 Subject: [PATCH 494/766] Updated maximum_minimum.cc --- tensorflow/lite/kernels/maximum_minimum.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/kernels/maximum_minimum.cc b/tensorflow/lite/kernels/maximum_minimum.cc index 0e15254f47..6ae836bc3f 100644 --- a/tensorflow/lite/kernels/maximum_minimum.cc +++ b/tensorflow/lite/kernels/maximum_minimum.cc @@ -26,7 +26,7 @@ namespace ops { namespace builtin { namespace maximum_minimum { -// This file has a reference implemenation of TFMaximum/TFMinimum. +// This file has a reference implementation of TFMaximum/TFMinimum. enum KernelType { kReference, }; -- GitLab From 28981cb08103c8930409ccc7bb367c009b3297cd Mon Sep 17 00:00:00 2001 From: Justin Lebar Date: Mon, 25 Feb 2019 16:02:41 -0800 Subject: [PATCH 495/766] [XLA] Fix typo in comment. PiperOrigin-RevId: 235619675 --- tensorflow/compiler/xla/service/algebraic_simplifier.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/compiler/xla/service/algebraic_simplifier.cc b/tensorflow/compiler/xla/service/algebraic_simplifier.cc index d566062e74..94ae6e685c 100644 --- a/tensorflow/compiler/xla/service/algebraic_simplifier.cc +++ b/tensorflow/compiler/xla/service/algebraic_simplifier.cc @@ -109,7 +109,7 @@ bool IsAllFpConstantPowerOf2(const HloInstruction* op) { int exp; double mantissa = std::frexp(*val, &exp); - // frexp returns a value in the range (-1; -0.5] U [0.5, 1). A return value + // frexp returns a value in the range (-1, -0.5] U [0.5, 1). A return value // of +/-0.5 therefore indicates that the floating point value is a power of // 2. return mantissa == 0.5 || mantissa == -0.5; -- GitLab From 268f66fb2f1c973192801f1da60a6cca37f18132 Mon Sep 17 00:00:00 2001 From: Derek Murray Date: Mon, 25 Feb 2019 16:11:00 -0800 Subject: [PATCH 496/766] [tf.data] Remove the `Dataset.output_{types,shapes,classes}` properties in V2. These reflection properties are lightly used in non-test code, and do not properly reflect some of the recent developments in custom and nested structures (e.g. nested datasets returned by `Dataset.window()`, datasets containing `tf.data.experimental.Optional`, etc.). For internal users, `dataset_ops.get_legacy_output_{types,shapes,classes}(dataset)` provide a short-term replacement, and `dataset_ops.get_structure(dataset)` exposes the same information as a `tf.data.experimental.Structure` object. The replacement public property/API is still TBD, and will be added post 2.0-alpha. PiperOrigin-RevId: 235621094 --- tensorflow/python/data/ops/dataset_ops.py | 64 +++++++++---------- .../golden/v2/tensorflow.data.-dataset.pbtxt | 12 ---- ...ow.data.-fixed-length-record-dataset.pbtxt | 12 ---- .../tensorflow.data.-t-f-record-dataset.pbtxt | 12 ---- .../tensorflow.data.-text-line-dataset.pbtxt | 12 ---- ...rflow.data.experimental.-csv-dataset.pbtxt | 12 ---- ...ow.data.experimental.-random-dataset.pbtxt | 12 ---- ...rflow.data.experimental.-sql-dataset.pbtxt | 12 ---- 8 files changed, 32 insertions(+), 116 deletions(-) diff --git a/tensorflow/python/data/ops/dataset_ops.py b/tensorflow/python/data/ops/dataset_ops.py index 91a07a5534..2355a0abea 100644 --- a/tensorflow/python/data/ops/dataset_ops.py +++ b/tensorflow/python/data/ops/dataset_ops.py @@ -229,38 +229,6 @@ class DatasetV2(object): """ raise NotImplementedError("Dataset._element_structure") - @property - def output_classes(self): - """Returns the class of each component of an element of this dataset. - - The expected values are `tf.Tensor` and `tf.SparseTensor`. - - Returns: - A nested structure of Python `type` objects corresponding to each - component of an element of this dataset. - """ - return self._element_structure._to_legacy_output_classes() # pylint: disable=protected-access - - @property - def output_shapes(self): - """Returns the shape of each component of an element of this dataset. - - Returns: - A nested structure of `tf.TensorShape` objects corresponding to each - component of an element of this dataset. - """ - return self._element_structure._to_legacy_output_shapes() # pylint: disable=protected-access - - @property - def output_types(self): - """Returns the type of each component of an element of this dataset. - - Returns: - A nested structure of `tf.DType` objects corresponding to each component - of an element of this dataset. - """ - return self._element_structure._to_legacy_output_types() # pylint: disable=protected-access - def __repr__(self): output_shapes = nest.map_structure(str, get_legacy_output_shapes(self)) output_shapes = str(output_shapes).replace("'", "") @@ -1547,6 +1515,38 @@ class DatasetV1(DatasetV2): iterator_resource, initializer, get_legacy_output_types(dataset), get_legacy_output_shapes(dataset), get_legacy_output_classes(dataset)) + @property + def output_classes(self): + """Returns the class of each component of an element of this dataset. + + The expected values are `tf.Tensor` and `tf.SparseTensor`. + + Returns: + A nested structure of Python `type` objects corresponding to each + component of an element of this dataset. + """ + return self._element_structure._to_legacy_output_classes() # pylint: disable=protected-access + + @property + def output_shapes(self): + """Returns the shape of each component of an element of this dataset. + + Returns: + A nested structure of `tf.TensorShape` objects corresponding to each + component of an element of this dataset. + """ + return self._element_structure._to_legacy_output_shapes() # pylint: disable=protected-access + + @property + def output_types(self): + """Returns the type of each component of an element of this dataset. + + Returns: + A nested structure of `tf.DType` objects corresponding to each component + of an element of this dataset. + """ + return self._element_structure._to_legacy_output_types() # pylint: disable=protected-access + @property def _element_structure(self): # TODO(b/110122868): Remove this override once all `Dataset` instances diff --git a/tensorflow/tools/api/golden/v2/tensorflow.data.-dataset.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.data.-dataset.pbtxt index 195c10445d..21859d7662 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.data.-dataset.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.data.-dataset.pbtxt @@ -2,18 +2,6 @@ path: "tensorflow.data.Dataset" tf_class { is_instance: "" is_instance: "" - member { - name: "output_classes" - mtype: "" - } - member { - name: "output_shapes" - mtype: "" - } - member { - name: "output_types" - mtype: "" - } member_method { name: "__init__" argspec: "args=[\'self\', \'variant_tensor\'], varargs=None, keywords=None, defaults=None" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.data.-fixed-length-record-dataset.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.data.-fixed-length-record-dataset.pbtxt index 043584c233..512446e93f 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.data.-fixed-length-record-dataset.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.data.-fixed-length-record-dataset.pbtxt @@ -4,18 +4,6 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - member { - name: "output_classes" - mtype: "" - } - member { - name: "output_shapes" - mtype: "" - } - member { - name: "output_types" - mtype: "" - } member_method { name: "__init__" argspec: "args=[\'self\', \'filenames\', \'record_bytes\', \'header_bytes\', \'footer_bytes\', \'buffer_size\', \'compression_type\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'None\', \'None\'], " diff --git a/tensorflow/tools/api/golden/v2/tensorflow.data.-t-f-record-dataset.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.data.-t-f-record-dataset.pbtxt index 76d15f4634..0ba3e488dd 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.data.-t-f-record-dataset.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.data.-t-f-record-dataset.pbtxt @@ -3,18 +3,6 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - member { - name: "output_classes" - mtype: "" - } - member { - name: "output_shapes" - mtype: "" - } - member { - name: "output_types" - mtype: "" - } member_method { name: "__init__" argspec: "args=[\'self\', \'filenames\', \'compression_type\', \'buffer_size\', \'num_parallel_reads\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'None\'], " diff --git a/tensorflow/tools/api/golden/v2/tensorflow.data.-text-line-dataset.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.data.-text-line-dataset.pbtxt index a6c7a2d7c5..72e12d0e7f 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.data.-text-line-dataset.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.data.-text-line-dataset.pbtxt @@ -4,18 +4,6 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - member { - name: "output_classes" - mtype: "" - } - member { - name: "output_shapes" - mtype: "" - } - member { - name: "output_types" - mtype: "" - } member_method { name: "__init__" argspec: "args=[\'self\', \'filenames\', \'compression_type\', \'buffer_size\'], varargs=None, keywords=None, defaults=[\'None\', \'None\'], " diff --git a/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.-csv-dataset.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.-csv-dataset.pbtxt index ae177ebc6b..5f5e60fae9 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.-csv-dataset.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.-csv-dataset.pbtxt @@ -4,18 +4,6 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - member { - name: "output_classes" - mtype: "" - } - member { - name: "output_shapes" - mtype: "" - } - member { - name: "output_types" - mtype: "" - } member_method { name: "__init__" argspec: "args=[\'self\', \'filenames\', \'record_defaults\', \'compression_type\', \'buffer_size\', \'header\', \'field_delim\', \'use_quote_delim\', \'na_value\', \'select_cols\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'False\', \',\', \'True\', \'\', \'None\'], " diff --git a/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.-random-dataset.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.-random-dataset.pbtxt index c15c73f3f7..88ce93511b 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.-random-dataset.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.-random-dataset.pbtxt @@ -4,18 +4,6 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - member { - name: "output_classes" - mtype: "" - } - member { - name: "output_shapes" - mtype: "" - } - member { - name: "output_types" - mtype: "" - } member_method { name: "__init__" argspec: "args=[\'self\', \'seed\'], varargs=None, keywords=None, defaults=[\'None\'], " diff --git a/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.-sql-dataset.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.-sql-dataset.pbtxt index 567f48d1aa..381eba742b 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.-sql-dataset.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.-sql-dataset.pbtxt @@ -4,18 +4,6 @@ tf_class { is_instance: "" is_instance: "" is_instance: "" - member { - name: "output_classes" - mtype: "" - } - member { - name: "output_shapes" - mtype: "" - } - member { - name: "output_types" - mtype: "" - } member_method { name: "__init__" argspec: "args=[\'self\', \'driver_name\', \'data_source_name\', \'query\', \'output_types\'], varargs=None, keywords=None, defaults=None" -- GitLab From 82cf60d9d71b1e8748d4c20586e9867a63164f1e Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Mon, 25 Feb 2019 16:20:53 -0800 Subject: [PATCH 497/766] Filter num_accelerators by task_name and task_id where applicable. PiperOrigin-RevId: 235622654 --- .../cluster_resolver/cluster_resolver.py | 5 +++ .../cluster_resolver/cluster_resolver_test.py | 37 +++++++++++++++++-- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/tensorflow/python/distribute/cluster_resolver/cluster_resolver.py b/tensorflow/python/distribute/cluster_resolver/cluster_resolver.py index 0043691473..ca6b60ea5b 100644 --- a/tensorflow/python/distribute/cluster_resolver/cluster_resolver.py +++ b/tensorflow/python/distribute/cluster_resolver/cluster_resolver.py @@ -151,6 +151,11 @@ class ClusterResolver(object): devices = get_accelerator_devices(master, config_proto) mapping = collections.defaultdict(int) for device in devices: + if task_type is not None and task_id is not None: + job_path = '/job:%s' % task_type + task_path = '/task:%s' % task_id + if job_path not in device.name or task_path not in device.name: + continue mapping[device.device_type] += 1 return mapping diff --git a/tensorflow/python/distribute/cluster_resolver/cluster_resolver_test.py b/tensorflow/python/distribute/cluster_resolver/cluster_resolver_test.py index cc1dd0111f..c9aebbb468 100644 --- a/tensorflow/python/distribute/cluster_resolver/cluster_resolver_test.py +++ b/tensorflow/python/distribute/cluster_resolver/cluster_resolver_test.py @@ -56,8 +56,8 @@ class BaseClusterResolverTest(test.TestCase): "/job:worker/task:0/device:GPU:3", ] device_list = [ - session._DeviceAttributes( - name, "GPU", 1024, 0) for name in device_names + session._DeviceAttributes(name, "GPU", 1024, 0) + for name in device_names ] mock_eager_list_devices.return_value = device_names mock_list_devices.return_value = device_list @@ -80,8 +80,8 @@ class BaseClusterResolverTest(test.TestCase): "/job:worker/task:0/device:GPU:3", ] device_list = [ - session._DeviceAttributes( - name, name[26:29], 1024, 0) for name in device_names + session._DeviceAttributes(name, name[26:29], 1024, 0) + for name in device_names ] mock_eager_list_devices.return_value = device_names mock_list_devices.return_value = device_list @@ -89,6 +89,35 @@ class BaseClusterResolverTest(test.TestCase): resolver = MockBaseClusterResolver() self.assertEqual(resolver.num_accelerators(), {"TPU": 4, "GPU": 4}) + @mock.patch.object(eager.context, "list_devices") + @mock.patch.object(session.BaseSession, "list_devices") + def testNumAcceleratorsFilterTasks(self, mock_list_devices, + mock_eager_list_devices): + device_names = [ + "/job:worker1/task:0/device:TPU:0", + "/job:worker1/task:0/device:TPU:1", + "/job:worker1/task:0/device:GPU:0", + "/job:worker1/task:0/device:GPU:1", + "/job:worker2/task:1/device:TPU:2", + "/job:worker2/task:2/device:TPU:3", + "/job:worker2/task:3/device:GPU:2", + "/job:worker2/task:4/device:GPU:3", + ] + device_list = [ + session._DeviceAttributes(name, name[27:30], 1024, 0) + for name in device_names + ] + mock_eager_list_devices.return_value = device_names + mock_list_devices.return_value = device_list + + resolver = MockBaseClusterResolver() + self.assertEqual(resolver.num_accelerators(task_type="worker1", task_id=0), + {"TPU": 2, "GPU": 2}) + self.assertEqual(resolver.num_accelerators(task_type="worker2", task_id=3), + {"GPU": 1}) + self.assertEqual(resolver.num_accelerators(task_type="worker2", task_id=4), + {"GPU": 1}) + class UnionClusterResolverTest(test.TestCase): # TODO(frankchn): Transform to parameterized test after it is included in the -- GitLab From 391bee73641f1c23ef90cc40a9e8893f343c30fc Mon Sep 17 00:00:00 2001 From: Scott Zhu Date: Mon, 25 Feb 2019 16:21:01 -0800 Subject: [PATCH 498/766] Fix conv_lstm and test for TF 2.0. PiperOrigin-RevId: 235622678 --- tensorflow/python/keras/BUILD | 4 +- .../keras/layers/convolutional_recurrent.py | 62 +++++-------- .../layers/convolutional_recurrent_test.py | 93 ++++++++++--------- 3 files changed, 74 insertions(+), 85 deletions(-) diff --git a/tensorflow/python/keras/BUILD b/tensorflow/python/keras/BUILD index b854088e40..a2f023b810 100755 --- a/tensorflow/python/keras/BUILD +++ b/tensorflow/python/keras/BUILD @@ -563,7 +563,7 @@ tf_py_test( tf_py_test( name = "convolutional_recurrent_test", - size = "large", + size = "medium", srcs = ["layers/convolutional_recurrent_test.py"], additional_deps = [ ":keras", @@ -571,7 +571,7 @@ tf_py_test( "//third_party/py/numpy", "//tensorflow/python:client_testlib", ], - shard_count = 2, + shard_count = 4, ) cuda_py_test( diff --git a/tensorflow/python/keras/layers/convolutional_recurrent.py b/tensorflow/python/keras/layers/convolutional_recurrent.py index c0479e71a2..1fe7eac45a 100644 --- a/tensorflow/python/keras/layers/convolutional_recurrent.py +++ b/tensorflow/python/keras/layers/convolutional_recurrent.py @@ -34,6 +34,7 @@ from tensorflow.python.keras.layers.recurrent import RNN from tensorflow.python.keras.utils import conv_utils from tensorflow.python.keras.utils import generic_utils from tensorflow.python.keras.utils import tf_utils +from tensorflow.python.ops import array_ops from tensorflow.python.util.tf_export import keras_export @@ -272,7 +273,7 @@ class ConvRNN2D(RNN): shape = list(self.cell.kernel_shape) shape[-1] = self.cell.filters initial_state = self.cell.input_conv(initial_state, - K.zeros(tuple(shape)), + array_ops.zeros(tuple(shape)), padding=self.cell.padding) if hasattr(self.cell.state_size, '__len__'): @@ -625,31 +626,8 @@ class ConvLSTM2DCell(Layer): initializer=bias_initializer, regularizer=self.bias_regularizer, constraint=self.bias_constraint) - else: self.bias = None - - self.kernel_i = self.kernel[:, :, :, :self.filters] - self.recurrent_kernel_i = self.recurrent_kernel[:, :, :, :self.filters] - self.kernel_f = self.kernel[:, :, :, self.filters: self.filters * 2] - self.recurrent_kernel_f = self.recurrent_kernel[:, :, :, self.filters: - self.filters * 2] - self.kernel_c = self.kernel[:, :, :, self.filters * 2: self.filters * 3] - self.recurrent_kernel_c = self.recurrent_kernel[:, :, :, self.filters * 2: - self.filters * 3] - self.kernel_o = self.kernel[:, :, :, self.filters * 3:] - self.recurrent_kernel_o = self.recurrent_kernel[:, :, :, self.filters * 3:] - - if self.use_bias: - self.bias_i = self.bias[:self.filters] - self.bias_f = self.bias[self.filters: self.filters * 2] - self.bias_c = self.bias[self.filters * 2: self.filters * 3] - self.bias_o = self.bias[self.filters * 3:] - else: - self.bias_i = None - self.bias_f = None - self.bias_c = None - self.bias_o = None self.built = True def call(self, inputs, states, training=None): @@ -697,22 +675,26 @@ class ConvLSTM2DCell(Layer): h_tm1_c = h_tm1 h_tm1_o = h_tm1 - x_i = self.input_conv(inputs_i, self.kernel_i, self.bias_i, - padding=self.padding) - x_f = self.input_conv(inputs_f, self.kernel_f, self.bias_f, - padding=self.padding) - x_c = self.input_conv(inputs_c, self.kernel_c, self.bias_c, - padding=self.padding) - x_o = self.input_conv(inputs_o, self.kernel_o, self.bias_o, - padding=self.padding) - h_i = self.recurrent_conv(h_tm1_i, - self.recurrent_kernel_i) - h_f = self.recurrent_conv(h_tm1_f, - self.recurrent_kernel_f) - h_c = self.recurrent_conv(h_tm1_c, - self.recurrent_kernel_c) - h_o = self.recurrent_conv(h_tm1_o, - self.recurrent_kernel_o) + (kernel_i, kernel_f, + kernel_c, kernel_o) = array_ops.split(self.kernel, 4, axis=3) + (recurrent_kernel_i, + recurrent_kernel_f, + recurrent_kernel_c, + recurrent_kernel_o) = array_ops.split(self.recurrent_kernel, 4, axis=3) + + if self.use_bias: + bias_i, bias_f, bias_c, bias_o = array_ops.split(self.bias, 4) + else: + bias_i, bias_f, bias_c, bias_o = None, None, None, None + + x_i = self.input_conv(inputs_i, kernel_i, bias_i, padding=self.padding) + x_f = self.input_conv(inputs_f, kernel_f, bias_f, padding=self.padding) + x_c = self.input_conv(inputs_c, kernel_c, bias_c, padding=self.padding) + x_o = self.input_conv(inputs_o, kernel_o, bias_o, padding=self.padding) + h_i = self.recurrent_conv(h_tm1_i, recurrent_kernel_i) + h_f = self.recurrent_conv(h_tm1_f, recurrent_kernel_f) + h_c = self.recurrent_conv(h_tm1_c, recurrent_kernel_c) + h_o = self.recurrent_conv(h_tm1_o, recurrent_kernel_o) i = self.recurrent_activation(x_i + h_i) f = self.recurrent_activation(x_f + h_f) diff --git a/tensorflow/python/keras/layers/convolutional_recurrent_test.py b/tensorflow/python/keras/layers/convolutional_recurrent_test.py index 4a75793884..916cd63303 100644 --- a/tensorflow/python/keras/layers/convolutional_recurrent_test.py +++ b/tensorflow/python/keras/layers/convolutional_recurrent_test.py @@ -18,16 +18,24 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +from absl.testing import parameterized import numpy as np from tensorflow.python import keras +from tensorflow.python.framework import test_util +from tensorflow.python.keras import keras_parameterized from tensorflow.python.keras import testing_utils from tensorflow.python.platform import test -class ConvLSTMTest(test.TestCase): +@keras_parameterized.run_all_keras_modes +class ConvLSTMTest(keras_parameterized.TestCase): - def test_conv_lstm(self): + @parameterized.named_parameters( + *test_util.generate_combinations_with_testcase_name( + data_format=['channels_first', 'channels_last'], + return_sequences=[True, False])) + def test_conv_lstm(self, data_format, return_sequences): num_row = 3 num_col = 3 filters = 2 @@ -36,47 +44,44 @@ class ConvLSTMTest(test.TestCase): input_num_row = 5 input_num_col = 5 sequence_len = 2 - for data_format in ['channels_first', 'channels_last']: - if data_format == 'channels_first': - inputs = np.random.rand(num_samples, sequence_len, - input_channel, - input_num_row, input_num_col) - else: - inputs = np.random.rand(num_samples, sequence_len, - input_num_row, input_num_col, - input_channel) - - for return_sequences in [True, False]: - with self.cached_session(): - # test for return state: - x = keras.Input(batch_shape=inputs.shape) - kwargs = {'data_format': data_format, - 'return_sequences': return_sequences, - 'return_state': True, - 'stateful': True, - 'filters': filters, - 'kernel_size': (num_row, num_col), - 'padding': 'valid'} - layer = keras.layers.ConvLSTM2D(**kwargs) - layer.build(inputs.shape) - outputs = layer(x) - _, states = outputs[0], outputs[1:] - self.assertEqual(len(states), 2) - model = keras.models.Model(x, states[0]) - state = model.predict(inputs) - - self.assertAllClose( - keras.backend.eval(layer.states[0]), state, atol=1e-4) - - # test for output shape: - testing_utils.layer_test( - keras.layers.ConvLSTM2D, - kwargs={'data_format': data_format, - 'return_sequences': return_sequences, - 'filters': filters, - 'kernel_size': (num_row, num_col), - 'padding': 'valid'}, - input_shape=inputs.shape) + if data_format == 'channels_first': + inputs = np.random.rand(num_samples, sequence_len, + input_channel, + input_num_row, input_num_col) + else: + inputs = np.random.rand(num_samples, sequence_len, + input_num_row, input_num_col, + input_channel) + + # test for return state: + x = keras.Input(batch_shape=inputs.shape) + kwargs = {'data_format': data_format, + 'return_sequences': return_sequences, + 'return_state': True, + 'stateful': True, + 'filters': filters, + 'kernel_size': (num_row, num_col), + 'padding': 'valid'} + layer = keras.layers.ConvLSTM2D(**kwargs) + layer.build(inputs.shape) + outputs = layer(x) + _, states = outputs[0], outputs[1:] + self.assertEqual(len(states), 2) + model = keras.models.Model(x, states[0]) + state = model.predict(inputs) + + self.assertAllClose( + keras.backend.eval(layer.states[0]), state, atol=1e-4) + + # test for output shape: + testing_utils.layer_test( + keras.layers.ConvLSTM2D, + kwargs={'data_format': data_format, + 'return_sequences': return_sequences, + 'filters': filters, + 'kernel_size': (num_row, num_col), + 'padding': 'valid'}, + input_shape=inputs.shape) def test_conv_lstm_statefulness(self): # Tests for statefulness @@ -167,6 +172,8 @@ class ConvLSTMTest(test.TestCase): self.assertEqual(len(layer.losses), 4) def test_conv_lstm_dropout(self): + if testing_utils.should_run_eagerly(): + self.skipTest('Skip test due to b/126246383.') # check dropout with self.cached_session(): testing_utils.layer_test( -- GitLab From 8d4cdf8444389286ec54b7105b07e9059dba4f66 Mon Sep 17 00:00:00 2001 From: Jian Li Date: Mon, 25 Feb 2019 16:24:00 -0800 Subject: [PATCH 499/766] Create int8 L2Norm. PiperOrigin-RevId: 235623180 --- tensorflow/lite/kernels/internal/BUILD | 1 + tensorflow/lite/kernels/internal/common.h | 49 ++++++++++++++ .../internal/optimized/optimized_ops.h | 53 +-------------- .../reference/integer_ops/l2normalization.h | 65 +++++++++++++++++++ .../internal/reference/reference_ops.h | 54 +-------------- tensorflow/lite/kernels/l2norm.cc | 26 ++++++-- tensorflow/lite/kernels/l2norm_test.cc | 47 ++++++++++++-- tensorflow/lite/kernels/register.cc | 4 +- tensorflow/lite/toco/tflite/operator.cc | 6 ++ tensorflow/lite/toco/tflite/operator_test.cc | 26 ++++++++ 10 files changed, 219 insertions(+), 112 deletions(-) create mode 100644 tensorflow/lite/kernels/internal/reference/integer_ops/l2normalization.h diff --git a/tensorflow/lite/kernels/internal/BUILD b/tensorflow/lite/kernels/internal/BUILD index 816b88d675..2355dab64e 100644 --- a/tensorflow/lite/kernels/internal/BUILD +++ b/tensorflow/lite/kernels/internal/BUILD @@ -311,6 +311,7 @@ cc_library( "reference/integer_ops/depthwise_conv.h", "reference/integer_ops/dequantize.h", "reference/integer_ops/fully_connected.h", + "reference/integer_ops/l2normalization.h", "reference/integer_ops/log_softmax.h", "reference/integer_ops/logistic.h", "reference/integer_ops/mul.h", diff --git a/tensorflow/lite/kernels/internal/common.h b/tensorflow/lite/kernels/internal/common.h index e00a3f405e..e40c282b9c 100644 --- a/tensorflow/lite/kernels/internal/common.h +++ b/tensorflow/lite/kernels/internal/common.h @@ -363,6 +363,55 @@ inline int32 GetReciprocal(int32 x, int x_integer_digits, return shifted_scale.raw(); } +inline void GetInvSqrtQuantizedMultiplierExp(int32 input, int reverse_shift, + int32* output_inv_sqrt, + int* output_shift) { + *output_shift = 11; + while (input >= (1 << 29)) { + input /= 4; + ++*output_shift; + } + TFLITE_DCHECK_GT(input, 0); + const unsigned max_left_shift_bits = + CountLeadingZeros(static_cast(input)) - 1; + const unsigned max_left_shift_bit_pairs = max_left_shift_bits / 2; + const unsigned left_shift_bit_pairs = max_left_shift_bit_pairs - 1; + *output_shift -= left_shift_bit_pairs; + input <<= 2 * left_shift_bit_pairs; + TFLITE_DCHECK_GE(input, (1 << 27)); + TFLITE_DCHECK_LT(input, (1 << 29)); + using gemmlowp::FixedPoint; + using gemmlowp::Rescale; + using gemmlowp::SaturatingRoundingMultiplyByPOT; + // Using 3 integer bits gives us enough room for the internal arithmetic in + // this Newton-Raphson iteration. + using F3 = FixedPoint; + using F0 = FixedPoint; + const F3 fixedpoint_input = F3::FromRaw(input >> 1); + const F3 fixedpoint_half_input = + SaturatingRoundingMultiplyByPOT<-1>(fixedpoint_input); + const F3 fixedpoint_half_three = + GEMMLOWP_CHECKED_FIXEDPOINT_CONSTANT(F3, (1 << 28) + (1 << 27), 1.5); + // Newton-Raphson iteration + // Naive unoptimized starting guess: x = 1 + F3 x = F3::One(); + // Naive unoptimized number of iterations: 5 + for (int i = 0; i < 5; i++) { + const F3 x3 = Rescale<3>(x * x * x); + x = Rescale<3>(fixedpoint_half_three * x - fixedpoint_half_input * x3); + } + const F0 fixedpoint_half_sqrt_2 = + GEMMLOWP_CHECKED_FIXEDPOINT_CONSTANT(F0, 1518500250, std::sqrt(2.) / 2.); + x = x * fixedpoint_half_sqrt_2; + *output_inv_sqrt = x.raw(); + if (*output_shift < 0) { + *output_inv_sqrt <<= -*output_shift; + *output_shift = 0; + } + // Convert right shift (right is positive) to left shift. + *output_shift *= reverse_shift; +} + // DO NOT USE THIS STRUCT FOR NEW FUNCTIONALITY BEYOND IMPLEMENTING // BROADCASTING. // diff --git a/tensorflow/lite/kernels/internal/optimized/optimized_ops.h b/tensorflow/lite/kernels/internal/optimized/optimized_ops.h index 6e8390ce55..3e2fce5605 100644 --- a/tensorflow/lite/kernels/internal/optimized/optimized_ops.h +++ b/tensorflow/lite/kernels/internal/optimized/optimized_ops.h @@ -2357,55 +2357,6 @@ inline void L2Normalization(const tflite::L2NormalizationParams& op_params, } } -inline void GetInvSqrtQuantizedMultiplierExp(int32 input, - int32* output_inv_sqrt, - int* output_shift) { - *output_shift = 11; - while (input >= (1 << 29)) { - input /= 4; - ++*output_shift; - } - TFLITE_DCHECK_GT(input, 0); - const unsigned max_left_shift_bits = - CountLeadingZeros(static_cast(input)) - 1; - const unsigned max_left_shift_bit_pairs = max_left_shift_bits / 2; - const unsigned left_shift_bit_pairs = max_left_shift_bit_pairs - 1; - *output_shift -= left_shift_bit_pairs; - input <<= 2 * left_shift_bit_pairs; - TFLITE_DCHECK_GE(input, (1 << 27)); - TFLITE_DCHECK_LT(input, (1 << 29)); - using gemmlowp::FixedPoint; - using gemmlowp::Rescale; - using gemmlowp::SaturatingRoundingMultiplyByPOT; - // Using 3 integer bits gives us enough room for the internal arithmetic in - // this Newton-Raphson iteration. - using F3 = FixedPoint; - using F0 = FixedPoint; - const F3 fixedpoint_input = F3::FromRaw(input >> 1); - const F3 fixedpoint_half_input = - SaturatingRoundingMultiplyByPOT<-1>(fixedpoint_input); - const F3 fixedpoint_half_three = - GEMMLOWP_CHECKED_FIXEDPOINT_CONSTANT(F3, (1 << 28) + (1 << 27), 1.5); - // Newton-Raphson iteration - // Naive unoptimized starting guess: x = 1 - F3 x = F3::One(); - // Naive unoptimized number of iterations: 5 - for (int i = 0; i < 5; i++) { - const F3 x3 = Rescale<3>(x * x * x); - x = Rescale<3>(fixedpoint_half_three * x - fixedpoint_half_input * x3); - } - const F0 fixedpoint_half_sqrt_2 = - GEMMLOWP_CHECKED_FIXEDPOINT_CONSTANT(F0, 1518500250, std::sqrt(2.) / 2.); - x = x * fixedpoint_half_sqrt_2; - *output_inv_sqrt = x.raw(); - if (*output_shift < 0) { - *output_inv_sqrt <<= -*output_shift; - *output_shift = 0; - } - // Convert right shift (right is positive) to left shift. - *output_shift *= kReverseShift; -} - inline void L2Normalization(const tflite::L2NormalizationParams& op_params, const RuntimeShape& input_shape, const uint8* input_data, @@ -2427,8 +2378,8 @@ inline void L2Normalization(const tflite::L2NormalizationParams& op_params, } int32 inv_l2norm_multiplier; int inv_l2norm_shift; - GetInvSqrtQuantizedMultiplierExp(square_l2_norm, &inv_l2norm_multiplier, - &inv_l2norm_shift); + GetInvSqrtQuantizedMultiplierExp(square_l2_norm, kReverseShift, + &inv_l2norm_multiplier, &inv_l2norm_shift); for (int c = 0; c < depth; c++) { int32 diff = *input_data - input_zero_point; diff --git a/tensorflow/lite/kernels/internal/reference/integer_ops/l2normalization.h b/tensorflow/lite/kernels/internal/reference/integer_ops/l2normalization.h new file mode 100644 index 0000000000..8e7c7f3176 --- /dev/null +++ b/tensorflow/lite/kernels/internal/reference/integer_ops/l2normalization.h @@ -0,0 +1,65 @@ +/* Copyright 2019 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_LITE_KERNELS_INTERNAL_REFERENCE_INTEGER_OPS_L2NORMALIZATION_H_ +#define TENSORFLOW_LITE_KERNELS_INTERNAL_REFERENCE_INTEGER_OPS_L2NORMALIZATION_H_ + +#include "tensorflow/lite/kernels/internal/common.h" + +namespace tflite { +namespace reference_integer_ops { + +inline void L2Normalization(int32_t input_zero_point, int32_t outer_size, + int32_t depth, const int8* input_data, + int8* output_data) { + static constexpr int8_t kMinInt8 = std::numeric_limits::min(); + static constexpr int8_t kMaxInt8 = std::numeric_limits::max(); + // The output scale must be in sync with Prepare(). + // Output is in 1/128 scale so the actual output range is nudged from [-1, 1] + // to [-1, 127/128]. + static constexpr int32_t kOutputScale = 7; + for (int outer_index = 0; outer_index < outer_size; ++outer_index) { + // int32 = (int8 - int8) ^ 2. + // ([-128, 127] - [-128, 127]) ^ 2 = [0, (2^8 - 1)^2] so the accumulator is + // safe from overflowing in at least 2^16 steps. + int32_t acc = 0; + for (int inner_index = 0; inner_index < depth; ++inner_index) { + int32_t input = + input_data[depth * outer_index + inner_index] - input_zero_point; + acc += input * input; + } + int32_t inv_l2norm_multiplier; + int inv_l2norm_shift; + GetInvSqrtQuantizedMultiplierExp(acc, /*reverse_shift*/ -1, + &inv_l2norm_multiplier, &inv_l2norm_shift); + + for (int inner_index = 0; inner_index < depth; ++inner_index) { + int32_t input = + input_data[depth * outer_index + inner_index] - input_zero_point; + + // Rescale and downcast. Rescale is folded into the division. + int32_t output_in_q24 = MultiplyByQuantizedMultiplier( + input, inv_l2norm_multiplier, inv_l2norm_shift + kOutputScale); + output_in_q24 = + std::min(static_cast(kMaxInt8), + std::max(static_cast(kMinInt8), output_in_q24)); + output_data[depth * outer_index + inner_index] = + static_cast(output_in_q24); + } + } +} +} // namespace reference_integer_ops +} // namespace tflite + +#endif // TENSORFLOW_LITE_KERNELS_INTERNAL_REFERENCE_INTEGER_OPS_L2NORMALIZATION_H_ diff --git a/tensorflow/lite/kernels/internal/reference/reference_ops.h b/tensorflow/lite/kernels/internal/reference/reference_ops.h index 60ed828bb5..c20a5f774e 100644 --- a/tensorflow/lite/kernels/internal/reference/reference_ops.h +++ b/tensorflow/lite/kernels/internal/reference/reference_ops.h @@ -489,55 +489,6 @@ inline void L2Normalization(const tflite::L2NormalizationParams& op_params, } } -inline void GetInvSqrtQuantizedMultiplierExp(int32 input, - int32* output_inv_sqrt, - int* output_shift) { - *output_shift = 11; - while (input >= (1 << 29)) { - input /= 4; - ++*output_shift; - } - TFLITE_DCHECK_GT(input, 0); - const unsigned max_left_shift_bits = - CountLeadingZeros(static_cast(input)) - 1; - const unsigned max_left_shift_bit_pairs = max_left_shift_bits / 2; - const unsigned left_shift_bit_pairs = max_left_shift_bit_pairs - 1; - *output_shift -= left_shift_bit_pairs; - input <<= 2 * left_shift_bit_pairs; - TFLITE_DCHECK_GE(input, (1 << 27)); - TFLITE_DCHECK_LT(input, (1 << 29)); - using gemmlowp::FixedPoint; - using gemmlowp::Rescale; - using gemmlowp::SaturatingRoundingMultiplyByPOT; - // Using 3 integer bits gives us enough room for the internal arithmetic in - // this Newton-Raphson iteration. - using F3 = FixedPoint; - using F0 = FixedPoint; - const F3 fixedpoint_input = F3::FromRaw(input >> 1); - const F3 fixedpoint_half_input = - SaturatingRoundingMultiplyByPOT<-1>(fixedpoint_input); - const F3 fixedpoint_half_three = - GEMMLOWP_CHECKED_FIXEDPOINT_CONSTANT(F3, (1 << 28) + (1 << 27), 1.5); - // Newton-Raphson iteration - // Naive unoptimized starting guess: x = 1 - F3 x = F3::One(); - // Naive unoptimized number of iterations: 5 - for (int i = 0; i < 5; i++) { - const F3 x3 = Rescale<3>(x * x * x); - x = Rescale<3>(fixedpoint_half_three * x - fixedpoint_half_input * x3); - } - const F0 fixedpoint_half_sqrt_2 = - GEMMLOWP_CHECKED_FIXEDPOINT_CONSTANT(F0, 1518500250, std::sqrt(2.) / 2.); - x = x * fixedpoint_half_sqrt_2; - *output_inv_sqrt = x.raw(); - if (*output_shift < 0) { - *output_inv_sqrt <<= -*output_shift; - *output_shift = 0; - } - // Convert right shift (right is positive) to left shift. - *output_shift *= kReverseShift; -} - inline void L2Normalization(const tflite::L2NormalizationParams& op_params, const RuntimeShape& input_shape, const uint8* input_data, @@ -557,9 +508,8 @@ inline void L2Normalization(const tflite::L2NormalizationParams& op_params, } int32 inv_l2norm_multiplier; int inv_l2norm_shift; - GetInvSqrtQuantizedMultiplierExp(square_l2_norm, &inv_l2norm_multiplier, - &inv_l2norm_shift); - + GetInvSqrtQuantizedMultiplierExp(square_l2_norm, kReverseShift, + &inv_l2norm_multiplier, &inv_l2norm_shift); for (int c = 0; c < depth; c++) { int32 diff = input_data[depth * i + c] - input_zero_point; int32 rescaled_diff = MultiplyByQuantizedMultiplierSmallerThanOneExp( diff --git a/tensorflow/lite/kernels/l2norm.cc b/tensorflow/lite/kernels/l2norm.cc index 19a4824e93..5eeda0858f 100644 --- a/tensorflow/lite/kernels/l2norm.cc +++ b/tensorflow/lite/kernels/l2norm.cc @@ -15,6 +15,7 @@ limitations under the License. #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/c_api_internal.h" #include "tensorflow/lite/kernels/internal/optimized/optimized_ops.h" +#include "tensorflow/lite/kernels/internal/reference/integer_ops/l2normalization.h" #include "tensorflow/lite/kernels/internal/reference/reference_ops.h" #include "tensorflow/lite/kernels/internal/tensor.h" #include "tensorflow/lite/kernels/kernel_util.h" @@ -45,13 +46,19 @@ TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { TF_LITE_ENSURE(context, NumDimensions(input) <= 4); - TF_LITE_ENSURE( - context, output->type == kTfLiteFloat32 || output->type == kTfLiteUInt8); + TF_LITE_ENSURE(context, output->type == kTfLiteFloat32 || + output->type == kTfLiteUInt8 || + output->type == kTfLiteInt8); TF_LITE_ENSURE_EQ(context, input->type, output->type); - if (output->type == kTfLiteUInt8) { + if (output->type == kTfLiteUInt8 || output->type == kTfLiteInt8) { TF_LITE_ENSURE_EQ(context, output->params.scale, (1. / 128.)); - TF_LITE_ENSURE_EQ(context, output->params.zero_point, 128); + if (output->type == kTfLiteUInt8) { + TF_LITE_ENSURE_EQ(context, output->params.zero_point, 128); + } + if (output->type == kTfLiteInt8) { + TF_LITE_ENSURE_EQ(context, output->params.zero_point, 0); + } } // TODO(ahentz): For some reason our implementations don't support @@ -97,6 +104,17 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { TF_LITE_L2NORM(optimized_ops); } #undef TF_LITE_L2NORM + } else if (output->type == kTfLiteInt8) { + const auto input_shape = GetTensorShape(input); + const auto output_shape = GetTensorShape(output); + const int trailing_dim = input_shape.DimensionsCount() - 1; + const int depth = + MatchingDim(input_shape, trailing_dim, output_shape, trailing_dim); + const int outer_size = + MatchingFlatSizeSkipDim(input_shape, trailing_dim, output_shape); + reference_integer_ops::L2Normalization(input->params.zero_point, outer_size, + depth, GetTensorData(input), + GetTensorData(output)); } else { context->ReportError(context, "Output type is %d, requires float.", output->type); diff --git a/tensorflow/lite/kernels/l2norm_test.cc b/tensorflow/lite/kernels/l2norm_test.cc index 50108a5a26..4cd63155b9 100644 --- a/tensorflow/lite/kernels/l2norm_test.cc +++ b/tensorflow/lite/kernels/l2norm_test.cc @@ -55,9 +55,10 @@ class L2NormOpModel : public SingleOpModel { return ExtractVector(output_); } + template std::vector GetDequantizedOutput() { - return Dequantize(ExtractVector(output_), - GetScale(output_), GetZeroPoint(output_)); + return Dequantize(ExtractVector(output_), GetScale(output_), + GetZeroPoint(output_)); } int input() const { return input_; } @@ -100,7 +101,20 @@ TEST(L2NormOpTest, SimpleUint8Test) { m.Invoke(); EXPECT_THAT(m.GetOutput(), ElementsAreArray({58, 166, 173, 205, 83, 134})); - EXPECT_THAT(m.GetDequantizedOutput(), + EXPECT_THAT(m.GetDequantizedOutput(), + ElementsAreArray( + ArrayFloatNear({-0.55, 0.3, 0.35, 0.6, -0.35, 0.05}, 0.1))); +} + +TEST(L2NormOpTest, SimpleInt8Test) { + L2NormOpModel m({1, 1, 1, 6}, TensorType_INT8, ActivationFunctionType_NONE); + + m.QuantizeAndPopulate(m.input(), {-1.1, 0.6, 0.7, 1.2, -0.7, 0.1}); + m.Invoke(); + EXPECT_THAT(m.GetOutput(), + ElementsAreArray({-70, 38, 45, 77, -45, 6})); + + EXPECT_THAT(m.GetDequantizedOutput(), ElementsAreArray( ArrayFloatNear({-0.55, 0.3, 0.35, 0.6, -0.35, 0.05}, 0.1))); } @@ -121,7 +135,32 @@ TEST(L2NormOpTest, MultipleBatchUint8Test) { 58, 166, 173, 205, 83, 134, // batch 2 58, 166, 173, 205, 83, 134, // batch 3 })); - EXPECT_THAT(m.GetDequantizedOutput(), + EXPECT_THAT(m.GetDequantizedOutput(), + ElementsAreArray(ArrayFloatNear( + { + -0.55, 0.3, 0.35, 0.6, -0.35, 0.05, // batch 1 + -0.55, 0.3, 0.35, 0.6, -0.35, 0.05, // batch 2 + -0.55, 0.3, 0.35, 0.6, -0.35, 0.05, // batch 3 + }, + 0.1))); +} + +TEST(L2NormOpTest, MultipleBatchInt8Test) { + L2NormOpModel m({3, 1, 1, 6}, TensorType_INT8, ActivationFunctionType_NONE); + + m.QuantizeAndPopulate(m.input(), + { + -1.1, 0.6, 0.7, 1.2, -0.7, 0.1, // batch 1 + -1.1, 0.6, 0.7, 1.2, -0.7, 0.1, // batch 2 + -1.1, 0.6, 0.7, 1.2, -0.7, 0.1, // batch 3 + }); + m.Invoke(); + EXPECT_THAT(m.GetOutput(), ElementsAreArray({ + -70, 38, 45, 77, -45, 6, // batch 1 + -70, 38, 45, 77, -45, 6, // batch 2 + -70, 38, 45, 77, -45, 6, // batch 3 + })); + EXPECT_THAT(m.GetDequantizedOutput(), ElementsAreArray(ArrayFloatNear( { -0.55, 0.3, 0.35, 0.6, -0.35, 0.05, // batch 1 diff --git a/tensorflow/lite/kernels/register.cc b/tensorflow/lite/kernels/register.cc index d66bbf1bfb..1f717521b7 100644 --- a/tensorflow/lite/kernels/register.cc +++ b/tensorflow/lite/kernels/register.cc @@ -229,7 +229,9 @@ BuiltinOpResolver::BuiltinOpResolver() { /* min_version */ 1, /* max_version */ 2); AddBuiltin(BuiltinOperator_MUL, Register_MUL()); - AddBuiltin(BuiltinOperator_L2_NORMALIZATION, Register_L2_NORMALIZATION()); + AddBuiltin(BuiltinOperator_L2_NORMALIZATION, Register_L2_NORMALIZATION(), + /* min_version */ 1, + /* max_version */ 2); AddBuiltin(BuiltinOperator_LOCAL_RESPONSE_NORMALIZATION, Register_LOCAL_RESPONSE_NORMALIZATION()); AddBuiltin(BuiltinOperator_LSTM, Register_LSTM(), /* min_version */ 1, diff --git a/tensorflow/lite/toco/tflite/operator.cc b/tensorflow/lite/toco/tflite/operator.cc index a441fef861..03e634400c 100644 --- a/tensorflow/lite/toco/tflite/operator.cc +++ b/tensorflow/lite/toco/tflite/operator.cc @@ -618,6 +618,12 @@ class L2Normalization } int GetVersion(const OperatorSignature& op_signature) const override { + const string& output_name = op_signature.op->outputs[0]; + const Array& output_array = op_signature.model->GetArray(output_name); + // Version 2 supports signed int8 input types. + if (output_array.data_type == ArrayDataType::kInt8) { + return 2; + } return 1; } }; diff --git a/tensorflow/lite/toco/tflite/operator_test.cc b/tensorflow/lite/toco/tflite/operator_test.cc index 12b33e9b35..3c469e350c 100644 --- a/tensorflow/lite/toco/tflite/operator_test.cc +++ b/tensorflow/lite/toco/tflite/operator_test.cc @@ -765,6 +765,28 @@ void SimpleVersioningTest() { EXPECT_EQ(base_op->GetVersion(int8_signature), 2); } +// Test version for a simple Op with 2 versions and the output type controls the +// version. +template +void SimpleOutputVersioningTest() { + Op op; + op.outputs = {"output1"}; + auto operator_by_type_map = BuildOperatorByTypeMap(false /*enable_flex_ops*/); + const BaseOperator* base_op = operator_by_type_map.at(op.type).get(); + + Model uint8_model; + Array& uint8_array = uint8_model.GetOrCreateArray(op.outputs[0]); + uint8_array.data_type = ArrayDataType::kUint8; + OperatorSignature uint8_signature = {.model = &uint8_model, .op = &op}; + EXPECT_EQ(base_op->GetVersion(uint8_signature), 1); + + Model int8_model; + Array& int8_array = int8_model.GetOrCreateArray(op.outputs[0]); + int8_array.data_type = ArrayDataType::kInt8; + OperatorSignature int8_signature = {.model = &int8_model, .op = &op}; + EXPECT_EQ(base_op->GetVersion(int8_signature), 2); +} + TEST_F(OperatorTest, VersioningEqualTest) { SimpleVersioningTest(); } @@ -825,6 +847,10 @@ TEST_F(OperatorTest, VersioningLogisticTest) { SimpleVersioningTest(); } +TEST_F(OperatorTest, VersioningL2NormTest) { + SimpleOutputVersioningTest(); +} + TEST_F(OperatorTest, VersioningAddTest) { SimpleVersioningTest(); } TEST_F(OperatorTest, VersioningSubTest) { SimpleVersioningTest(); } -- GitLab From 4b9339ffa25e3e4834fb90192bceb2af6afca62f Mon Sep 17 00:00:00 2001 From: Gunhan Gulsoy Date: Mon, 25 Feb 2019 16:28:01 -0800 Subject: [PATCH 500/766] Remove dependency on kernels:bound_check. The library was already moved to core/frameworks. PiperOrigin-RevId: 235623784 --- tensorflow/compiler/tf2xla/kernels/BUILD | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tensorflow/compiler/tf2xla/kernels/BUILD b/tensorflow/compiler/tf2xla/kernels/BUILD index 343568b239..a802743403 100644 --- a/tensorflow/compiler/tf2xla/kernels/BUILD +++ b/tensorflow/compiler/tf2xla/kernels/BUILD @@ -151,6 +151,7 @@ tf_kernel_library( "//tensorflow/core:control_flow_ops_op_lib", "//tensorflow/core:data_flow_ops_op_lib", "//tensorflow/core:framework", + "//tensorflow/core:framework_bounds_check", "//tensorflow/core:functional_ops_op_lib", "//tensorflow/core:image_ops_op_lib", "//tensorflow/core:lib", @@ -168,7 +169,6 @@ tf_kernel_library( "//tensorflow/core:state_ops_op_lib", "//tensorflow/core:stateless_random_ops_op_lib", "//tensorflow/core:training_ops_op_lib", - "//tensorflow/core/kernels:bounds_check", "//tensorflow/core/kernels:concat_lib", "//tensorflow/core/kernels:constant_op", "//tensorflow/core/kernels:control_flow_ops", @@ -229,7 +229,7 @@ cc_library( "//tensorflow/compiler/xla/client/lib:arithmetic", "//tensorflow/compiler/xla/client/lib:constants", "//tensorflow/core:framework", - "//tensorflow/core/kernels:bounds_check", + "//tensorflow/core:framework_bounds_check", "//tensorflow/core/kernels:conv_ops", "//tensorflow/core/kernels:ops_util", "@com_google_absl//absl/types:span", @@ -304,9 +304,9 @@ tf_kernel_library( "//tensorflow/compiler/xla/client:xla_builder", "//tensorflow/compiler/xla/client/lib:arithmetic", "//tensorflow/core:framework", + "//tensorflow/core:framework_bounds_check", "//tensorflow/core:lib", "//tensorflow/core/kernels:argmax_op", - "//tensorflow/core/kernels:bounds_check", ], ) -- GitLab From 09d1dd9377a9daae3c5f14ed437d5af32b292deb Mon Sep 17 00:00:00 2001 From: Alexandre Passos Date: Mon, 25 Feb 2019 16:41:57 -0800 Subject: [PATCH 501/766] Fixes #25666 , issue with segmentmax gradient in eager PiperOrigin-RevId: 235625956 --- tensorflow/python/eager/pywrap_tfe_src.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/tensorflow/python/eager/pywrap_tfe_src.cc b/tensorflow/python/eager/pywrap_tfe_src.cc index 3286e1add8..89cc4d165d 100644 --- a/tensorflow/python/eager/pywrap_tfe_src.cc +++ b/tensorflow/python/eager/pywrap_tfe_src.cc @@ -1953,8 +1953,6 @@ bool OpGradientDoesntRequireOutputIndices( {"SparseSegmentSum", {true, {}}}, {"SparseSegmentMean", {true, {}}}, {"SparseSegmentSqrtN", {true, {}}}, - {"SegmentMin", {true, {}}}, - {"SegmentMax", {true, {}}}, {"UnsortedSegmentSum", {true, {}}}, {"UnsortedSegmentMax", {true, {}}}, {"Abs", {true, {}}}, -- GitLab From 25db44a049e65e15283fc5243a767d0845f0997a Mon Sep 17 00:00:00 2001 From: Rohan Jain Date: Mon, 25 Feb 2019 16:52:26 -0800 Subject: [PATCH 502/766] Making sure the tensorflow_estimator transition works out fine with the renaming. We alias create_resource with _create_resource so that old / new estimator code can work with old / new TF code. PiperOrigin-RevId: 235627518 --- tensorflow/python/training/tracking/tracking.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tensorflow/python/training/tracking/tracking.py b/tensorflow/python/training/tracking/tracking.py index 45e37379f1..50e85c8548 100644 --- a/tensorflow/python/training/tracking/tracking.py +++ b/tensorflow/python/training/tracking/tracking.py @@ -162,13 +162,21 @@ class TrackableResource(base.Trackable): def _create_resource(self): """A function that creates a resource handle.""" - raise NotImplementedError("TrackableResource.create_resource not " + raise NotImplementedError("TrackableResource._create_resource not " "implemented.") + # This method will be removed soon. + def create_resource(self): + return self._create_resource() + def _initialize(self): """A function that initializes the resource. Optional.""" pass + # This method will be removed soon. + def initialize(self): + return self._initialize() + @property def resource_handle(self): """Returns the resource handle associated with this Resource.""" -- GitLab From 16be3a49567277ce839a2c6d606f002f26c49826 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 25 Feb 2019 16:56:15 -0800 Subject: [PATCH 503/766] Minor description change in exception. PiperOrigin-RevId: 235628140 --- tensorflow/contrib/labeled_tensor/python/ops/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/contrib/labeled_tensor/python/ops/core.py b/tensorflow/contrib/labeled_tensor/python/ops/core.py index 0c6bba758b..8ee554ffa7 100644 --- a/tensorflow/contrib/labeled_tensor/python/ops/core.py +++ b/tensorflow/contrib/labeled_tensor/python/ops/core.py @@ -321,8 +321,8 @@ class LabeledTensor(object): for (d, axis) in zip(shape, unvalidated_axes.values()): if d != axis.size: raise ValueError( - 'Provided axis size %d does not match tensor dimension size %d' % - (axis.size, d)) + 'Provided axis size %d does not match tensor dimension size %d' + 'in tensor %r' % (axis.size, d, tensor)) self._axes = unvalidated_axes -- GitLab From 3ef4e3c24e1642f877d6f8ce778334b9fd6f1a87 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 25 Feb 2019 16:57:02 -0800 Subject: [PATCH 504/766] Internal Change PiperOrigin-RevId: 235628283 --- tensorflow/python/training/tracking/BUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/training/tracking/BUILD b/tensorflow/python/training/tracking/BUILD index be53cdd22b..7ee728c971 100644 --- a/tensorflow/python/training/tracking/BUILD +++ b/tensorflow/python/training/tracking/BUILD @@ -94,8 +94,8 @@ tf_py_test( "//tensorflow/python/keras:layers", ], tags = [ - "nomac" + "no_windows", + "nomac", ], ) -- GitLab From a494a4ef1f4307a6e4d6c096673956070b7e2bc5 Mon Sep 17 00:00:00 2001 From: Drew Szurko <15271992+drewszurko@users.noreply.github.com> Date: Mon, 25 Feb 2019 20:23:48 -0600 Subject: [PATCH 505/766] Fix MemoryWriterIterator LOG(WARNING) spacing --- tensorflow/core/kernels/data/cache_dataset_ops.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/core/kernels/data/cache_dataset_ops.cc b/tensorflow/core/kernels/data/cache_dataset_ops.cc index 343157de6f..3714d28212 100644 --- a/tensorflow/core/kernels/data/cache_dataset_ops.cc +++ b/tensorflow/core/kernels/data/cache_dataset_ops.cc @@ -815,7 +815,7 @@ class CacheDatasetOp : public UnaryDatasetOpKernel { LOG(WARNING) << "The calling iterator did not fully read the dataset being " "cached. In order to avoid unexpected truncation of the " - "dataset, the partially cached contents of the dataset" + "dataset, the partially cached contents of the dataset " "will be discarded. This can happen if you have an input " "pipeline similar to `dataset.cache().take(k).repeat()`. " "You should use `dataset.take(k).cache().repeat()` instead."; -- GitLab From 6a492ea39ed584f0ae941c4d480109a1eacf0468 Mon Sep 17 00:00:00 2001 From: Amit <30853054+amitsrivastava78@users.noreply.github.com> Date: Tue, 26 Feb 2019 07:55:45 +0530 Subject: [PATCH 506/766] Updated group_bidirectional_sequence_ops.cc Fixed warning issues in the file --- .../group_bidirectional_sequence_ops.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tensorflow/lite/toco/graph_transformations/group_bidirectional_sequence_ops.cc b/tensorflow/lite/toco/graph_transformations/group_bidirectional_sequence_ops.cc index 05c85b33d2..c72af2ce61 100644 --- a/tensorflow/lite/toco/graph_transformations/group_bidirectional_sequence_ops.cc +++ b/tensorflow/lite/toco/graph_transformations/group_bidirectional_sequence_ops.cc @@ -135,7 +135,7 @@ bool CheckTwoUnidirectionalSequenceOpsAreValid( } // fw_lstm & bw_lstm should point to the same input, but reversed sequence. - for (int i = 0; i < first_fw_sequence_op_input->inputs.size(); ++i) { + for (size_t i = 0; i < first_fw_sequence_op_input->inputs.size(); ++i) { if (first_fw_sequence_op_input->inputs[i] != first_bw_sequence_op_input ->inputs[first_fw_sequence_op_input->inputs.size() - i - 1]) { @@ -296,7 +296,7 @@ void RewireBidirectionalSequenceSequenceOpsConnections( } string cur_fw_input = input_array_name; string cur_bw_input = input_array_name; - for (int i = 0; i < bidirectional_sequence_ops.size(); ++i) { + for (size_t i = 0; i < bidirectional_sequence_ops.size(); ++i) { DeleteArrayIfUsedOnce(bidirectional_sequence_ops[i]->inputs[0], model); bidirectional_sequence_ops[i]->inputs[0] = cur_fw_input; if (i != 0) { @@ -329,7 +329,7 @@ void RewireFinalUnpackOutputs(const UnpackOperator& original_unpack_operator, (*final_unpack_operator)->axis = original_unpack_operator.axis; (*final_unpack_operator)->num = original_unpack_operator.num; - for (int i = 0; i < original_unpack_operator.outputs.size(); ++i) { + for (size_t i = 0; i < original_unpack_operator.outputs.size(); ++i) { const string& output_array_name = original_unpack_operator.outputs[i]; const string& final_unpack_output_array_name = AvailableArrayName( *model, "bidirectional_sequence_unpack_" + std::to_string(i)); -- GitLab From 84fdb93a936df628362bbfa5a8eee72ea9bc2e3d Mon Sep 17 00:00:00 2001 From: Allen Lavoie Date: Mon, 25 Feb 2019 17:01:30 -0800 Subject: [PATCH 507/766] Don't let Layers and Variables build up in Layer's __setattr__ tracking PiperOrigin-RevId: 235628910 --- tensorflow/python/keras/engine/base_layer.py | 57 +++++++++++++++++++ .../python/keras/engine/base_layer_test.py | 21 +++++++ 2 files changed, 78 insertions(+) diff --git a/tensorflow/python/keras/engine/base_layer.py b/tensorflow/python/keras/engine/base_layer.py index 2208b4a973..de33d2e177 100644 --- a/tensorflow/python/keras/engine/base_layer.py +++ b/tensorflow/python/keras/engine/base_layer.py @@ -49,6 +49,7 @@ from tensorflow.python.ops import variables as tf_variables from tensorflow.python.training.tracking import base as trackable from tensorflow.python.training.tracking import data_structures from tensorflow.python.training.tracking import layer_utils as trackable_layer_utils +from tensorflow.python.training.tracking import object_identity from tensorflow.python.util import function_utils from tensorflow.python.util import nest from tensorflow.python.util import tf_decorator @@ -1685,6 +1686,52 @@ class Layer(trackable.Trackable): return nest.map_structure(_make_placeholder_like, output_shapes) + @property + def _obj_reference_counts(self): + """A dictionary counting the number of attributes referencing an object.""" + if not hasattr(self, '_obj_reference_counts_dict'): + super(Layer, self).__setattr__( + '_obj_reference_counts_dict', + object_identity.ObjectIdentityDictionary()) + return self._obj_reference_counts_dict + + def __delattr__(self, name): + existing_value = getattr(self, name, None) + + # If this value is replacing an existing object assigned to an attribute, we + # should clean it out to avoid leaking memory. First we check if there are + # other attributes referencing it. + reference_counts = self._obj_reference_counts + if existing_value not in reference_counts: + super(Layer, self).__delattr__(name) + return + + reference_count = reference_counts[existing_value] + if reference_count > 1: + # There are other remaining references. We can't remove this object from + # _layers etc. + reference_counts[existing_value] = reference_count - 1 + super(Layer, self).__delattr__(name) + return + else: + # This is the last remaining reference. + del reference_counts[existing_value] + + super(Layer, self).__delattr__(name) + + if (isinstance(existing_value, Layer) + or trackable_layer_utils.has_weights(existing_value)): + super(Layer, self).__setattr__( + '_layers', + [l for l in self._layers if l is not existing_value]) + if isinstance(existing_value, tf_variables.Variable): + super(Layer, self).__setattr__( + '_trainable_weights', + [w for w in self._trainable_weights if w is not existing_value]) + super(Layer, self).__setattr__( + '_non_trainable_weights', + [w for w in self._non_trainable_weights if w is not existing_value]) + def __setattr__(self, name, value): if (not getattr(self, '_setattr_tracking', True) or getattr(self, '_is_graph_network', False)): @@ -1695,6 +1742,16 @@ class Layer(trackable.Trackable): value = data_structures.sticky_attribute_assignment( trackable=self, value=value, name=name) + reference_counts = self._obj_reference_counts + reference_counts[value] = reference_counts.get(value, 0) + 1 + + # Clean out the old attribute, which clears _layers and _trainable_weights + # if necessary. + try: + self.__delattr__(name) + except AttributeError: + pass + # Append value to self._layers if relevant if (isinstance(value, Layer) or trackable_layer_utils.has_weights(value)): diff --git a/tensorflow/python/keras/engine/base_layer_test.py b/tensorflow/python/keras/engine/base_layer_test.py index aa3c53306b..fda2b58f27 100644 --- a/tensorflow/python/keras/engine/base_layer_test.py +++ b/tensorflow/python/keras/engine/base_layer_test.py @@ -547,6 +547,27 @@ class NestedTrackingTest(test.TestCase): self.assertEqual(len(layer.losses), 3) self.assertEqual(len(layer.updates), 3) + def test_attribute_reassignment(self): + l = keras.layers.Layer() + l.a = keras.layers.Layer() + l.a = [] + l.a = variables.Variable(1.) + l.a = keras.layers.Layer() + last_assignment = keras.layers.Layer() + l.a = last_assignment + l.b = variables.Variable(1.) + del l.b + l.c = keras.layers.Layer() + del l.c + l.d = last_assignment + del l.d + self.assertEqual([last_assignment], l._layers) + self.assertEqual([], l.trainable_weights) + self.assertEqual([], l.non_trainable_weights) + self.assertEqual([], l.weights) + del l.a + self.assertEqual([], l._layers) + @test_util.run_all_in_graph_and_eager_modes class NameScopingTest(keras_parameterized.TestCase): -- GitLab From 12f73759762872de3b8c6e263afe2dd739210b2e Mon Sep 17 00:00:00 2001 From: Amit <30853054+amitsrivastava78@users.noreply.github.com> Date: Tue, 26 Feb 2019 08:00:11 +0530 Subject: [PATCH 508/766] Updated resolve_constant_strided_slice.cc Fixed the warning in the file --- .../graph_transformations/resolve_constant_strided_slice.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/toco/graph_transformations/resolve_constant_strided_slice.cc b/tensorflow/lite/toco/graph_transformations/resolve_constant_strided_slice.cc index 1c8f4619bc..a46bb803eb 100644 --- a/tensorflow/lite/toco/graph_transformations/resolve_constant_strided_slice.cc +++ b/tensorflow/lite/toco/graph_transformations/resolve_constant_strided_slice.cc @@ -71,7 +71,7 @@ void StridedSlice(StridedSliceOperator const& op, Array const& input_array, // Each "digit" is incremented individually (by the stride). When it overflows // (becomes greater than the stop), that digit is reset and a carry flag is // used to increment the next digit. - for (int dst_offset = 0; dst_offset < output_data.size(); ++dst_offset) { + for (size_t dst_offset = 0; dst_offset < output_data.size(); ++dst_offset) { // Copy element. output_data[dst_offset] = input_buffer.data[Offset(input_shape, src_coord)]; -- GitLab From 0956cbe9dedebc168ac3a52b6fdaee826a604a15 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 25 Feb 2019 17:02:36 -0800 Subject: [PATCH 509/766] Compress constants in graph at the end of meta-optimizer. PiperOrigin-RevId: 235629151 --- .../core/grappler/optimizers/meta_optimizer.cc | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tensorflow/core/grappler/optimizers/meta_optimizer.cc b/tensorflow/core/grappler/optimizers/meta_optimizer.cc index 36d68a7b0f..fd420e9f7f 100644 --- a/tensorflow/core/grappler/optimizers/meta_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/meta_optimizer.cc @@ -17,6 +17,7 @@ limitations under the License. #include "absl/strings/substitute.h" #include "tensorflow/core/common_runtime/function.h" #include "tensorflow/core/framework/function.pb.h" +#include "tensorflow/core/framework/tensor_util.h" #include "tensorflow/core/framework/versions.pb.h" #include "tensorflow/core/graph/graph_constructor.h" #include "tensorflow/core/grappler/clusters/virtual_cluster.h" @@ -102,6 +103,18 @@ uint64 DeadlineMicroSeconds(const RewriterConfig& cfg) { } } +Status CompressConstants(GraphDef* graph) { + for (int i = 0; i < graph->node_size(); ++i) { + NodeDef* node = graph->mutable_node(i); + if ((IsConstant(*node) || IsHostConstant(*node)) && + HasNodeAttr(*node, "value")) { + AttrValue& attr_val = (*node->mutable_attr())["value"]; + tensor::CompressTensorProtoInPlace(attr_val.mutable_tensor()); + } + } + return Status::OK(); +} + } // namespace #define MK_OPT(NAME, VALUE) \ @@ -432,6 +445,9 @@ Status MetaOptimizer::OptimizeGraph(Cluster* cluster, const GrapplerItem& item, RUN_OPTIMIZER_OR_RETURN_IF_ERROR(sa_optimizer); } + // Compress the constants in the final graph. + TF_RETURN_IF_ERROR(CompressConstants(optimized_graph)); + // Record graph optimization result. optimization_results_.push_back(optimization_result); -- GitLab From 5bce34fe7de3b39092d54b315aa31592e3cbaff0 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 25 Feb 2019 17:02:45 -0800 Subject: [PATCH 510/766] internal cleanups PiperOrigin-RevId: 235629187 --- tensorflow/lite/python/op_hint.py | 2 +- .../tools/api/golden/v1/tensorflow.lite.experimental.pbtxt | 4 ++++ .../tools/api/golden/v2/tensorflow.lite.experimental.pbtxt | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/tensorflow/lite/python/op_hint.py b/tensorflow/lite/python/op_hint.py index 90c87503f4..ae1f5f094b 100644 --- a/tensorflow/lite/python/op_hint.py +++ b/tensorflow/lite/python/op_hint.py @@ -1140,7 +1140,6 @@ def _get_correct_mapping(original_index, nodes): return original_index -@_tf_export("lite.convert_op_hints_to_stubs") def _convert_op_hints_to_stubs_helper( graph_def, write_callback=lambda sess, graph_def: None): """Converts a graph_def to a new graph_def where all op hints are stubbed. @@ -1248,6 +1247,7 @@ def find_all_hinted_output_nodes(session=None, graph_def=None): return hinted_outputs_nodes +@_tf_export("lite.experimental.convert_op_hints_to_stubs") def convert_op_hints_to_stubs(session=None, graph_def=None, write_callback=lambda graph_def, comments: None): diff --git a/tensorflow/tools/api/golden/v1/tensorflow.lite.experimental.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.lite.experimental.pbtxt index 8d585ea664..354a7086d6 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.lite.experimental.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.lite.experimental.pbtxt @@ -4,4 +4,8 @@ tf_module { name: "nn" mtype: "" } + member_method { + name: "convert_op_hints_to_stubs" + argspec: "args=[\'session\', \'graph_def\', \'write_callback\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \' instance>\'], " + } } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.lite.experimental.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.lite.experimental.pbtxt index 8d585ea664..354a7086d6 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.lite.experimental.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.lite.experimental.pbtxt @@ -4,4 +4,8 @@ tf_module { name: "nn" mtype: "" } + member_method { + name: "convert_op_hints_to_stubs" + argspec: "args=[\'session\', \'graph_def\', \'write_callback\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \' instance>\'], " + } } -- GitLab From 9c95751e87704292917dafcc00c41fdd06d30f74 Mon Sep 17 00:00:00 2001 From: Chris Leary Date: Mon, 25 Feb 2019 17:23:06 -0800 Subject: [PATCH 511/766] [XLA:GPU] Add NCCL-based AllReduce replica support to XLA. This requires a CUDA-config build to enable, as the NCCL library can only be built in a CUDA-enabled build. In non-CUDA-config builds the NCCL thunk returns an error. Used a super-conservative-and-quite-likely-overkill concurrency approach, in a followup CL it'd be better to optimize for the common case where we're enqueueing a lot of operations with the same replica count onto a stream in a non-synchronizing fashion, and only force thread synchronization if the number of replicas changes. In the future this should likely be unified with NcclManager in tensorflow/core/nccl -- for now it is separate since the EventMgr-style memory allocation strategy from TensorFlow is not used in XLA, so some parameterization of the memory strategy being used in that library is likely necessary, at which point it should be reasonable to scoop out this ~200 line implementation in the cc file and replace it with the NcclManager abstraction to unify the two implementations. PiperOrigin-RevId: 235632126 --- tensorflow/compiler/xla/service/BUILD | 7 +- tensorflow/compiler/xla/service/gpu/BUILD | 44 ++- .../xla/service/gpu/ir_emitter_unnested.cc | 54 ++- .../xla/service/gpu/nccl_all_reduce_thunk.cc | 356 ++++++++++++++++++ .../xla/service/gpu/nccl_all_reduce_thunk.h | 62 +++ tensorflow/compiler/xla/service/gpu/thunk.cc | 2 + tensorflow/compiler/xla/service/gpu/thunk.h | 1 + tensorflow/compiler/xla/service/hlo_runner.cc | 41 +- tensorflow/compiler/xla/service/hlo_runner.h | 6 +- tensorflow/compiler/xla/tests/BUILD | 33 ++ .../compiler/xla/tests/hlo_test_base.cc | 5 +- tensorflow/compiler/xla/tests/hlo_test_base.h | 6 +- .../xla/tests/multi_device_all_reduce_test.cc | 56 +++ 13 files changed, 654 insertions(+), 19 deletions(-) create mode 100644 tensorflow/compiler/xla/service/gpu/nccl_all_reduce_thunk.cc create mode 100644 tensorflow/compiler/xla/service/gpu/nccl_all_reduce_thunk.h create mode 100644 tensorflow/compiler/xla/tests/multi_device_all_reduce_test.cc diff --git a/tensorflow/compiler/xla/service/BUILD b/tensorflow/compiler/xla/service/BUILD index 881d450947..f2e85a5abf 100644 --- a/tensorflow/compiler/xla/service/BUILD +++ b/tensorflow/compiler/xla/service/BUILD @@ -3470,10 +3470,13 @@ cc_library( srcs = ["hlo_runner.cc"], hdrs = ["hlo_runner.h"], deps = [ + ":backend", + ":compiler", ":computation_placer", ":executable", ":hlo", ":hlo_module_group", + ":hlo_parser", ":transfer_manager", "//tensorflow/compiler/xla:shape_util", "//tensorflow/compiler/xla:status_macros", @@ -3481,11 +3484,9 @@ cc_library( "//tensorflow/compiler/xla:types", "//tensorflow/compiler/xla:util", "//tensorflow/compiler/xla:xla_data_proto", - "//tensorflow/compiler/xla/service:backend", - "//tensorflow/compiler/xla/service:compiler", - "//tensorflow/compiler/xla/service:hlo_parser", "//tensorflow/core:core_cpu_internal", "//tensorflow/core:lib", + "//tensorflow/core:lib_internal", "//tensorflow/core:stream_executor_no_cuda", "//third_party/eigen3", "@com_google_absl//absl/memory", diff --git a/tensorflow/compiler/xla/service/gpu/BUILD b/tensorflow/compiler/xla/service/gpu/BUILD index 16492c085c..5b5ad63ec9 100644 --- a/tensorflow/compiler/xla/service/gpu/BUILD +++ b/tensorflow/compiler/xla/service/gpu/BUILD @@ -7,7 +7,7 @@ load( "//tensorflow/core:platform/default/build_config_root.bzl", "tf_cuda_tests_tags", ) -load("//tensorflow:tensorflow.bzl", "tf_cc_test") +load("//tensorflow:tensorflow.bzl", "tf_cc_test", "tf_cuda_library", "if_cuda") licenses(["notice"]) # Apache 2.0 @@ -156,7 +156,6 @@ cc_library( "ir_emitter_unnested.h", ], deps = [ - ":backend_configs", ":buffer_allocations", ":cudnn_conv_runner", ":elemental_ir_emitter", @@ -164,8 +163,10 @@ cc_library( ":gpu_executable", ":hlo_to_ir_bindings", ":ir_emission_utils", + ":nccl_all_reduce_thunk", ":parallel_loop_emitter", ":partition_assignment", + ":thunk", "//tensorflow/compiler/xla:literal", "//tensorflow/compiler/xla:shape_util", "//tensorflow/compiler/xla:status_macros", @@ -179,6 +180,7 @@ cc_library( "//tensorflow/compiler/xla/service:hlo", "//tensorflow/compiler/xla/service:hlo_casting_utils", "//tensorflow/compiler/xla/service:name_uniquer", + "//tensorflow/compiler/xla/service:pattern_matcher", "//tensorflow/compiler/xla/service:while_loop_analysis", "//tensorflow/compiler/xla/service/llvm_ir:buffer_assignment_util", "//tensorflow/compiler/xla/service/llvm_ir:dynamic_update_slice_util", @@ -287,6 +289,40 @@ cc_library( ], ) +cc_library( + name = "thunk", + srcs = ["thunk.cc"], + hdrs = ["thunk.h"], + deps = [ + ":buffer_allocations", + ":hlo_execution_profiler", + "//tensorflow/compiler/xla/service:hlo", + "//tensorflow/core:lib", + "//tensorflow/core:stream_executor_no_cuda", + ], +) + +tf_cuda_library( + name = "nccl_all_reduce_thunk", + srcs = ["nccl_all_reduce_thunk.cc"], + hdrs = ["nccl_all_reduce_thunk.h"], + deps = [ + ":buffer_allocations", + ":hlo_execution_profiler", + ":thunk", + "@com_google_absl//absl/synchronization", + "//tensorflow/compiler/xla:util", + "//tensorflow/compiler/xla/service:buffer_assignment", + "//tensorflow/compiler/xla/service:hlo", + "//tensorflow/core:lib", + "//tensorflow/core:stream_executor_no_cuda", + "//tensorflow/stream_executor/cuda:cuda_activation", + "//tensorflow/stream_executor/cuda:cuda_gpu_executor", + ] + if_cuda([ + "@local_config_nccl//:nccl", + ]), +) + cc_library( name = "gpu_executable", srcs = [ @@ -303,7 +339,6 @@ cc_library( "memset_thunk.cc", "outfeed_thunk.cc", "sequential_thunk.cc", - "thunk.cc", "thunk_schedule.cc", "triangular_solve_thunk.cc", "tuple_thunk.cc", @@ -323,7 +358,6 @@ cc_library( "memset_thunk.h", "outfeed_thunk.h", "sequential_thunk.h", - "thunk.h", "thunk_schedule.h", "triangular_solve_thunk.h", "tuple_thunk.h", @@ -335,9 +369,11 @@ cc_library( ":hlo_execution_profiler", ":infeed_manager", ":ir_emission_utils", + ":nccl_all_reduce_thunk", ":outfeed_manager", ":partition_assignment", ":stream_assignment", + ":thunk", "//tensorflow/compiler/xla:array2d", "//tensorflow/compiler/xla:literal", "//tensorflow/compiler/xla:shape_tree", diff --git a/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.cc b/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.cc index 4e5b86adb1..cacf6b9731 100644 --- a/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.cc +++ b/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.cc @@ -54,6 +54,7 @@ limitations under the License. #include "tensorflow/compiler/xla/service/gpu/ir_emitter_context.h" #include "tensorflow/compiler/xla/service/gpu/kernel_thunk.h" #include "tensorflow/compiler/xla/service/gpu/memset_thunk.h" +#include "tensorflow/compiler/xla/service/gpu/nccl_all_reduce_thunk.h" #include "tensorflow/compiler/xla/service/gpu/outfeed_thunk.h" #include "tensorflow/compiler/xla/service/gpu/parallel_loop_emitter.h" #include "tensorflow/compiler/xla/service/gpu/partition_assignment.h" @@ -74,6 +75,7 @@ limitations under the License. #include "tensorflow/compiler/xla/service/llvm_ir/sort_util.h" #include "tensorflow/compiler/xla/service/llvm_ir/tuple_ops.h" #include "tensorflow/compiler/xla/service/name_uniquer.h" +#include "tensorflow/compiler/xla/service/pattern_matcher.h" #include "tensorflow/compiler/xla/service/while_loop_analysis.h" #include "tensorflow/compiler/xla/shape_util.h" #include "tensorflow/compiler/xla/status_macros.h" @@ -102,6 +104,8 @@ using absl::StrCat; using llvm_ir::IrArray; using llvm_ir::IrName; +namespace m = match; + // If a dimensions is smaller than this, untiled transposition may be more // efficient. const int64 kMinDimensionToTransposeTiled = 16; @@ -1318,11 +1322,55 @@ Status IrEmitterUnnested::HandleTupleSelect(HloInstruction* tuple_select) { return IrEmitter::HandleTupleSelect(tuple_select); } +namespace { + +bool IsScalarAddComputation(HloComputation* computation) { + return Match(computation->root_instruction(), + m::AddAnyOrder(m::Parameter(0), m::Parameter(1)) + .WithShape(m::Shape().IsEffectiveScalar())); +} + +} // namespace + Status IrEmitterUnnested::HandleAllReduce(HloInstruction* crs) { + VLOG(2) << "AllReduce; replica count: " << hlo_module_config_.replica_count() + << "; operand count: " << crs->operand_count() + << "; NCCL is enabled: " << NcclAllReduceThunk::NcclIsEnabled(); + + // Note the replica_count == 1 case is handled via device-to-device copy + // below. + bool should_use_nccl_thunk = + hlo_module_config_.replica_count() > 1 && + crs->IsCrossReplicaAllReduce() && + crs->operand_count() == 1 && // One array to reduce. + crs->operand(0)->shape().element_type() == F32 && + // Check the computation is a summation. + IsScalarAddComputation(crs->to_apply()); + + if (should_use_nccl_thunk) { + CHECK(crs->operand(0)->shape().IsArray()) + << "Operands to all-reduce must be arrays: " << crs->ToString(); + AddThunkToThunkSequence(absl::make_unique( + /*replica_count=*/hlo_module_config_.replica_count(), + /*elements=*/ShapeUtil::ElementsIn(crs->operand(0)->shape()), + /*source_address=*/GetAllocationSlice(*crs->operand(0)), + /*destination_buffer=*/GetAllocationSlice(*crs), crs)); + return Status::OK(); + } + if (hlo_module_config_.replica_count() != 1) { - // TODO(b/33011107): Support nontrivial cross replica sum on GPU. - return Unimplemented( - "AllReduce with >1 replica is not implemented on GPU."); + // TODO(b/33011107): Support more AllReduce configurations on GPU. + string message = absl::StrFormat( + "Requested AllReduce not implemented on GPU; replica_count: %d; " + "operand_count: %d; IsCrossReplicaAllReduce: %d; NCCL support: %d", + hlo_module_config_.replica_count(), crs->operand_count(), + crs->IsCrossReplicaAllReduce(), NcclAllReduceThunk::NcclIsEnabled()); + if (crs->operand_count() > 0) { + absl::StrAppendFormat( + &message, "; first operand array element-type: %s", + PrimitiveType_Name(crs->operand(0)->shape().element_type())); + } + return Unimplemented("%s", message); } // CRS with one operand and one replica is simply the identity function. diff --git a/tensorflow/compiler/xla/service/gpu/nccl_all_reduce_thunk.cc b/tensorflow/compiler/xla/service/gpu/nccl_all_reduce_thunk.cc new file mode 100644 index 0000000000..3051db3af4 --- /dev/null +++ b/tensorflow/compiler/xla/service/gpu/nccl_all_reduce_thunk.cc @@ -0,0 +1,356 @@ +/* Copyright 2019 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/gpu/nccl_all_reduce_thunk.h" + +#include "tensorflow/compiler/xla/util.h" + +#if GOOGLE_CUDA +#include "absl/synchronization/blocking_counter.h" +#include "third_party/nccl/nccl.h" +#include "tensorflow/core/lib/core/blocking_counter.h" +#include "tensorflow/core/platform/mutex.h" +#include "tensorflow/stream_executor/cuda/cuda_activation.h" +#endif + +namespace xla { +namespace gpu { + +/* static */ bool NcclAllReduceThunk::NcclIsEnabled() { +#if GOOGLE_CUDA + return true; +#else + return false; +#endif +} + +#if GOOGLE_CUDA +namespace { + +// GPU-replica-driving host threads (i.e. the threads that call +// GpuExecutable::Execute) build up this structure to describe their +// participating replica, and then call to +// GlobalRendezvousManager::SubmitParticipant. +struct ParticipantData { + // Number of replicas particiating in the AllReduce. + int64 replica_count; + + int64 element_count; + int64 device_ordinal; + int64 generation_counter; + + // TODO(b/125951860): We should vet that we're buffer allocating such that + // source_buffer == destination_buffer if that avoids a NCCL copy (will depend + // on how well the NCCL in-place implementation performs vs the out-of-place + // implementation). + se::DeviceMemoryBase source_data; + se::DeviceMemoryBase destination_data; + se::Stream* stream; + + NcclAllReduceThunk* originator; + + string ToString() const { + return absl::StrFormat( + "ParticipantData{replica_count=%d, element_count=%d, " + "device_ordinal=%d, generation_counter=%d, stream=%p, originator=%p}", + replica_count, element_count, device_ordinal, generation_counter, + stream, originator); + } +}; + +// Class that gets instantiated as a singleton in GetGlobalRendezvous() to +// coordinate participating threads in performing an AllReduce operation. +// +// This manager is responsible for establishing communication channels and +// ultimately enqueueing the NCCL library operation onto the participating +// streams. +class GlobalRendezvousManager { + public: + // The GpuExecutable-executing threads call this in order to a) establish the + // all-reduce rendezvous and b) enqueue the AllReduce operation on the caller + // thread's associated stream (given in "participant"). + // + // Implementation note: since the rendezvous we're creating here is global, we + // try to be paranoid about the fact that the *correct* one is happening. In + // an ideal world we'd have some StreamExecutor se::Platform level construct + // that we could use for cross-device networking primitives (e.g. via a + // NetworkSupport interface) that could be shared between TensorFlow and XLA, + // but this is a reasonable stopgap measure to get multi-GPU-replica up and + // running properly for single-host, single-concurrent-XLA-module usage. + Status SubmitParticipant(ParticipantData participant); + + // Returns the current generation number of AllReduce operations. + // (Currently one AllReduce operation occurs per generation.) + int64 GetCurrentGeneration() { + tensorflow::mutex_lock lock(mutex_); + return current_generation_; + } + + private: + // Called by the primary thread to set up the communication links. + // + // TODO(b/125951860): This performs lots of (presumably) unnecessary host-side + // synchronization so that we can be paranoid about semantics in the earliest + // implementation. In the limit we should only need to synchronize host + // replica threads when the "number of replicas" or "participating device + // ordinals" change, to set up a new NCCL "communication" context, at which + // point we can enqueue onto device streams without host synchronization in + // our code -- this will likely be helpful for "lots of little AllReduce" + // cases. + Status InitializeCommunicationChannels() EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Called when all necessary participants are present, the functionality + // that's implemented by all executing threads lives in here. + Status DoAllReduce(ParticipantData data, ncclComm_t comm); + + // Puts all state back into a "reset" state for the next generation of + // AllReduce requests. + void DeinitializeGeneration() EXCLUSIVE_LOCKS_REQUIRED(mutex_) { + for (ncclComm_t& comm : comms_) { + ncclCommDestroy(comm); + } + comms_.clear(); + participants_.clear(); + current_generation_++; + initialized_ = false; + done_ = absl::nullopt; + } + + tensorflow::mutex mutex_; + tensorflow::condition_variable all_participants_present_; + tensorflow::condition_variable deinitialized_; + + // Communication handles that correspond to the participants below. + std::vector comms_ GUARDED_BY(mutex_); + + Status initialize_status_ GUARDED_BY(mutex_); + std::vector participants_ GUARDED_BY(mutex_); + int64 current_generation_ GUARDED_BY(mutex_) = 0; + bool initialized_ GUARDED_BY(mutex_) = false; + + // The participating threads wait for this to count down in order to know we + // can begin the teardown process. + absl::optional done_; +}; + +Status GlobalRendezvousManager::SubmitParticipant(ParticipantData participant) { + auto all_participants_present = [this, &participant]() + EXCLUSIVE_LOCKS_REQUIRED(mutex_) -> bool { + return participants_.size() >= participant.replica_count; + }; + + // We remember the participant index at which we are inserted and use that + // same index for referring to auxiliary metadata (e.g. the ncclComm_t handle + // index) below. + int64 index; + + { + tensorflow::mutex_lock lock(mutex_); + + // Spot check for consistent replica counts among submitting threads. + if (!participants_.empty() && + (participants_.back().replica_count != participant.replica_count || + participants_.back().originator != participant.originator)) { + return InvalidArgument( + "Running two XLA modules with AllReduces in parallel is not " + "supported. It is possible this is due to a bug where were try to " + "run two different AllReduces from the same module at once. " + "(Attempted a rendezvous with a different replica count from other " + "participants; existing: %s; submitted: %s)", + participants_.back().ToString(), participant.ToString()); + } + index = participants_.size(); + participants_.push_back(participant); + + if (all_participants_present()) { + all_participants_present_.notify_all(); + } + } + + // We pull into our thread a) the communication handle and b) whether we're + // the "primary" thread for this rendezvous -- the "primary" thread has some + // additional responsibilities for setup/teardown. + ncclComm_t comm; + bool primary; + + { + tensorflow::mutex_lock lock(mutex_); + while (!all_participants_present()) { + // Once all the participants have arrived, all participating threads will + // cross this barrier, though only (the first) one will be the "primary". + all_participants_present_.wait(lock); + } + + // Somebody will be the first -- that thread has some additional + // responsibilities. + primary = !initialized_; + + CHECK_EQ(participant.generation_counter, current_generation_); + + // Bump the generation counter so the other threads know we've completed the + // global rendezvous and have set up the AllReduce. + if (primary) { + VLOG(3) << "Primary initializing accounting data."; + initialized_ = true; + done_.emplace(participant.replica_count); + initialize_status_ = InitializeCommunicationChannels(); + VLOG(3) << "Done initializing communication channels; status: " + << initialize_status_; + if (!initialize_status_.ok()) { + DeinitializeGeneration(); + } + } + + if (!initialize_status_.ok()) { + // TODO(b/125951860): If this fails once, it will fail forever. + return initialize_status_; + } + + comm = comms_[index]; + + // Drop the lock at the end of scope so other participants may enter. + } + + VLOG(3) << "Performing all reduce from device ordinal: " + << participant.device_ordinal; + + Status all_reduce_status = DoAllReduce(participant, comm); + + VLOG(3) << "Waiting for all participants to complete enqueue."; + + done_->DecrementCount(); + + if (primary) { + // Primary thread clears out the AllReduce state when everybody is done to + // make it clean-slate for any subsequent AllReduce request (e.g. number of + // replicas may change in the next request). + // + // Note surrounding TODOs for only reinitializing this when the replica + // count / participants actually change -- lots of "playing it safe" + // happening in this first cut. + done_->Wait(); + VLOG(3) << "All participants completed enqueue."; + VLOG(3) << "Primary thread clearing."; + tensorflow::mutex_lock lock(mutex_); + DeinitializeGeneration(); + VLOG(3) << "Generation is now: " << current_generation_; + deinitialized_.notify_all(); + } else { + VLOG(3) << "Waiting to deinitialize."; + tensorflow::mutex_lock lock(mutex_); + while (initialized_) { + deinitialized_.wait(lock); + } + } + + VLOG(3) << "Returning status: " << all_reduce_status; + return all_reduce_status; +} + +Status GlobalRendezvousManager::InitializeCommunicationChannels() { + std::vector ordinals; + for (ParticipantData& data : participants_) { + ordinals.push_back(data.device_ordinal); + } + comms_.resize(ordinals.size()); + VLOG(3) << "Participants: " << participants_.size() + << "; initializing comms."; + ncclResult_t result = ncclCommInitAll(comms_.data(), comms_.size(), + /*devlist=*/ordinals.data()); + if (result != ncclSuccess) { + comms_.clear(); + return InternalError( + "Failed to initialize NCCL communication channels for %d participants: " + "%s", + participants_.size(), ncclGetErrorString(result)); + } + return Status::OK(); +} + +Status GlobalRendezvousManager::DoAllReduce(ParticipantData participant, + ncclComm_t comm) { + se::StreamExecutor* executor = participant.stream->parent(); + se::cuda::ScopedActivateExecutorContext scoped_context(executor); + cudaStream_t* cu_stream = reinterpret_cast( + participant.stream->implementation()->GpuStreamMemberHack()); + VLOG(3) << "Using stream pointer: " << cu_stream + << " on device: " << participant.device_ordinal; + void* send_buffer = participant.source_data.opaque(); + void* recv_buffer = participant.destination_data.opaque(); + ncclResult_t result = ncclAllReduce(send_buffer, recv_buffer, + /*count=*/participant.element_count, + /*datatype=*/ncclFloat, + /*op=*/ncclSum, + /*comm=*/comm, + /*stream=*/*cu_stream); + TF_RET_CHECK(ncclSuccess == result) + << "Failed to perform all-reduce: " << ncclGetErrorString(result); + + VLOG(3) << "Done performing all reduce for ordinal: " + << participant.device_ordinal; + + return Status::OK(); +} + +static GlobalRendezvousManager* GetGlobalRendezvous() { + static auto* manager = new GlobalRendezvousManager; + return manager; +} + +} // namespace + +Status NcclAllReduceThunk::ExecuteOnStream( + const BufferAllocations& buffer_allocations, se::Stream* stream, + HloExecutionProfiler* profiler) { + auto* global_rendezvous = GetGlobalRendezvous(); + + ParticipantData participant; + participant.replica_count = replica_count_; + participant.element_count = element_count_; + participant.device_ordinal = stream->parent()->device_ordinal(); + participant.generation_counter = global_rendezvous->GetCurrentGeneration(); + participant.source_data = buffer_allocations.GetDeviceAddress(source_buffer_); + participant.destination_data = + buffer_allocations.GetDeviceAddress(destination_buffer_); + participant.stream = stream; + participant.originator = this; + + return GetGlobalRendezvous()->SubmitParticipant(std::move(participant)); +} +#else + +Status NcclAllReduceThunk::ExecuteOnStream( + const BufferAllocations& buffer_allocations, se::Stream* stream, + HloExecutionProfiler* profiler) { + return Unimplemented( + "NCCL support is not available: this binary was not built with a CUDA " + "compiler, which is necessary to build the NCCL source library."); +} + +#endif // GOOGLE_CUDA + +NcclAllReduceThunk::NcclAllReduceThunk( + int64 replica_count, int64 element_count, + const BufferAllocation::Slice& source_buffer, + const BufferAllocation::Slice& destination_buffer, + const HloInstruction* all_reduce) + : Thunk(Thunk::kNcclAllReduce, all_reduce), + replica_count_(replica_count), + element_count_(element_count), + source_buffer_(source_buffer), + destination_buffer_(destination_buffer) {} + +} // namespace gpu +} // namespace xla diff --git a/tensorflow/compiler/xla/service/gpu/nccl_all_reduce_thunk.h b/tensorflow/compiler/xla/service/gpu/nccl_all_reduce_thunk.h new file mode 100644 index 0000000000..1a8d1356c0 --- /dev/null +++ b/tensorflow/compiler/xla/service/gpu/nccl_all_reduce_thunk.h @@ -0,0 +1,62 @@ +/* Copyright 2019 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_SERVICE_GPU_NCCL_ALL_REDUCE_THUNK_H_ +#define TENSORFLOW_COMPILER_XLA_SERVICE_GPU_NCCL_ALL_REDUCE_THUNK_H_ + +#include "tensorflow/compiler/xla/service/buffer_assignment.h" +#include "tensorflow/compiler/xla/service/gpu/buffer_allocations.h" +#include "tensorflow/compiler/xla/service/gpu/hlo_execution_profiler.h" +#include "tensorflow/compiler/xla/service/gpu/thunk.h" +#include "tensorflow/compiler/xla/service/hlo_instruction.h" +#include "tensorflow/core/platform/stream_executor_no_cuda.h" +#include "tensorflow/core/platform/types.h" + +namespace xla { +namespace gpu { + +// Thunk that performs a NCCL-based All-Reduce among CUDA GPU-based replicas. +class NcclAllReduceThunk : public Thunk { + public: + // Returns whether NCCL operations appear possible to perform; e.g. if we + // haven't done a build with the CUDA compiler enabled, we can't compile the + // NCCL header, and thus this will be false. + // + // When this is false, the ExecuteOnStream() call will simply return a status + // error. + static bool NcclIsEnabled(); + + // TODO(b/125951860): Plumb more datatypes / reduction operators. Initial + // implementation is simply F32 summation. + NcclAllReduceThunk(int64 replica_count, int64 element_count, + const BufferAllocation::Slice& source_buffer, + const BufferAllocation::Slice& destination_buffer, + const HloInstruction* all_reduce); + + Status ExecuteOnStream(const BufferAllocations& buffer_allocations, + se::Stream* stream, + HloExecutionProfiler* profiler) override; + + private: + const int64 replica_count_; + const int64 element_count_; + const BufferAllocation::Slice source_buffer_; + const BufferAllocation::Slice destination_buffer_; +}; + +} // namespace gpu +} // namespace xla + +#endif // TENSORFLOW_COMPILER_XLA_SERVICE_GPU_NCCL_ALL_REDUCE_THUNK_H_ diff --git a/tensorflow/compiler/xla/service/gpu/thunk.cc b/tensorflow/compiler/xla/service/gpu/thunk.cc index a677617727..2968c9ee39 100644 --- a/tensorflow/compiler/xla/service/gpu/thunk.cc +++ b/tensorflow/compiler/xla/service/gpu/thunk.cc @@ -32,6 +32,8 @@ std::ostream& operator<<(std::ostream& os, Thunk::Kind kind) { return os << "kCudnnBatchNormForwardInference"; case Thunk::kCudnnBatchNormForwardTraining: return os << "kCudnnBatchNormForwardTraining"; + case Thunk::kNcclAllReduce: + return os << "kNcclAllReduce"; case Thunk::kFft: return os << "kFft"; case Thunk::kGemm: diff --git a/tensorflow/compiler/xla/service/gpu/thunk.h b/tensorflow/compiler/xla/service/gpu/thunk.h index bc69af897a..728aef82a7 100644 --- a/tensorflow/compiler/xla/service/gpu/thunk.h +++ b/tensorflow/compiler/xla/service/gpu/thunk.h @@ -48,6 +48,7 @@ class Thunk { kCudnnBatchNormBackward, kCudnnBatchNormForwardInference, kCudnnBatchNormForwardTraining, + kNcclAllReduce, kFft, kGemm, kInfeed, diff --git a/tensorflow/compiler/xla/service/hlo_runner.cc b/tensorflow/compiler/xla/service/hlo_runner.cc index 5a5401e351..8f44e1b37e 100644 --- a/tensorflow/compiler/xla/service/hlo_runner.cc +++ b/tensorflow/compiler/xla/service/hlo_runner.cc @@ -27,6 +27,7 @@ limitations under the License. #include "tensorflow/compiler/xla/service/transfer_manager.h" #include "tensorflow/compiler/xla/shape_util.h" #include "tensorflow/core/common_runtime/eigen_thread_pool.h" +#include "tensorflow/core/lib/core/blocking_counter.h" #include "tensorflow/core/platform/logging.h" #include "tensorflow/core/platform/types.h" @@ -269,8 +270,8 @@ StatusOr HloRunner::ExecuteWithDeviceBuffers( } StatusOr> HloRunner::ExecuteReplicated( - std::unique_ptr module, - const ReplicatedExecuteOptions& options) { + std::unique_ptr module, const ReplicatedExecuteOptions& options, + bool use_threads) { TF_ASSIGN_OR_RETURN( std::unique_ptr executable, CreateExecutable(std::move(module), options.run_hlo_passes)); @@ -369,9 +370,39 @@ StatusOr> HloRunner::ExecuteReplicated( } LOG(INFO) << "Replicated execution started"; - TF_ASSIGN_OR_RETURN(std::vector results, - executable->ExecuteOnStreams(service_run_options, - argument_buffer_slices)); + std::vector results; + if (!use_threads) { + TF_ASSIGN_OR_RETURN(results, + executable->ExecuteOnStreams(service_run_options, + argument_buffer_slices)); + } else { + tensorflow::mutex mutex; + std::vector> thread_results( + options.num_replicas); + { + LOG(INFO) << "Creating thread pool for " << options.num_replicas + << " replicas"; + tensorflow::thread::ThreadPool pool(tensorflow::Env::Default(), + "replicas", options.num_replicas); + for (int64 i = 0; i < options.num_replicas; ++i) { + pool.Schedule([&, i] { + auto result = executable->ExecuteOnStream( + &service_run_options[i], argument_buffer_slices[i], nullptr); + tensorflow::mutex_lock lock(mutex); + thread_results[i] = std::move(result); + }); + } + + // Note: the thread pool destructor guarantees it completes all work + // before we leave this scope. + } + for (auto& thread_result : thread_results) { + if (!thread_result.ok()) { + return thread_result.status(); + } + results.push_back(std::move(thread_result).ValueOrDie()); + } + } LOG(INFO) << "Replicated execution terminated"; std::vector exec_results; diff --git a/tensorflow/compiler/xla/service/hlo_runner.h b/tensorflow/compiler/xla/service/hlo_runner.h index 098989cd4c..88a137e645 100644 --- a/tensorflow/compiler/xla/service/hlo_runner.h +++ b/tensorflow/compiler/xla/service/hlo_runner.h @@ -165,9 +165,13 @@ class HloRunner { // Executes a given HLO module into a set of replicas, and returns a map // with the replica number as key, and the corresponding returned literal as // value. + // + // use_threads indicates whether this replicated computation will be executed + // with a thread-per-replica, vs using an implicitly async call such as + // Executable::ExecuteOnStreams. StatusOr> ExecuteReplicated( std::unique_ptr module, - const ReplicatedExecuteOptions& options); + const ReplicatedExecuteOptions& options, bool use_threads = false); // If backend is not created in the constructor, creates and returns the // default backend. If creation fails, crashes the program. diff --git a/tensorflow/compiler/xla/tests/BUILD b/tensorflow/compiler/xla/tests/BUILD index 7158708e9c..79a5b7539d 100644 --- a/tensorflow/compiler/xla/tests/BUILD +++ b/tensorflow/compiler/xla/tests/BUILD @@ -1601,6 +1601,39 @@ xla_test( ], ) +xla_test( + name = "multi_device_all_reduce_test", + srcs = ["multi_device_all_reduce_test.cc"], + backends = ["gpu"], + tags = [ + "manual", + "multi_gpu", + "no_oss", + "notap", + ], + deps = [ + "//tensorflow/compiler/xla:literal", + "//tensorflow/compiler/xla:shape_util", + "//tensorflow/compiler/xla:test", + "//tensorflow/compiler/xla:test_helpers", + "//tensorflow/compiler/xla:xla_data_proto", + "//tensorflow/compiler/xla/client:local_client", + "//tensorflow/compiler/xla/service:hlo", + "//tensorflow/compiler/xla/service:hlo_parser", + "//tensorflow/compiler/xla/service:hlo_runner", + "//tensorflow/compiler/xla/tests:client_library_test_base", + "//tensorflow/compiler/xla/tests:hlo_test_base", + "//tensorflow/compiler/xla/tests:literal_test_util", + "//tensorflow/compiler/xla/tests:test_utils", + "//tensorflow/compiler/xla/tests:xla_internal_test_main", + "//tensorflow/core:lib", + "//tensorflow/core:test", + "@com_google_absl//absl/memory", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", + ], +) + xla_test( name = "bitcast_convert_test", srcs = ["bitcast_convert_test.cc"], diff --git a/tensorflow/compiler/xla/tests/hlo_test_base.cc b/tensorflow/compiler/xla/tests/hlo_test_base.cc index 0151981ef1..62e2b465cf 100644 --- a/tensorflow/compiler/xla/tests/hlo_test_base.cc +++ b/tensorflow/compiler/xla/tests/hlo_test_base.cc @@ -207,13 +207,14 @@ Literal HloTestBase::ExecuteAndTransfer(std::unique_ptr module, StatusOr> HloTestBase::ExecuteReplicated( std::unique_ptr module, absl::Span arguments, - int64 num_replicas) { + int64 num_replicas, bool use_threads) { HloRunner::ReplicatedExecuteOptions options; options.num_replicas = num_replicas; for (auto argument : arguments) { options.arguments.push_back(argument); } - return test_runner_.ExecuteReplicated(std::move(module), options); + return test_runner_.ExecuteReplicated(std::move(module), options, + use_threads); } StatusOr> HloTestBase::MakeReferenceModule( diff --git a/tensorflow/compiler/xla/tests/hlo_test_base.h b/tensorflow/compiler/xla/tests/hlo_test_base.h index 3c2bcbb5df..df9c29a186 100644 --- a/tensorflow/compiler/xla/tests/hlo_test_base.h +++ b/tensorflow/compiler/xla/tests/hlo_test_base.h @@ -174,9 +174,13 @@ class HloTestBase : public ::testing::Test { absl::Span arguments); // Executes the given module on multiple replicas. + // + // use_threads indicates whether this replicated computation will be executed + // with a thread-per-replica, vs using an implicitly async call such as + // Executable::ExecuteOnStreams. StatusOr> ExecuteReplicated( std::unique_ptr module, absl::Span arguments, - int64 num_replicas); + int64 num_replicas, bool use_threads); // Executes the given hlo module on two backends and compares results. // diff --git a/tensorflow/compiler/xla/tests/multi_device_all_reduce_test.cc b/tensorflow/compiler/xla/tests/multi_device_all_reduce_test.cc new file mode 100644 index 0000000000..1513d89ba9 --- /dev/null +++ b/tensorflow/compiler/xla/tests/multi_device_all_reduce_test.cc @@ -0,0 +1,56 @@ +/* Copyright 2019 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/literal.h" +#include "tensorflow/compiler/xla/service/hlo_parser.h" +#include "tensorflow/compiler/xla/shape_util.h" +#include "tensorflow/compiler/xla/test.h" +#include "tensorflow/compiler/xla/test_helpers.h" +#include "tensorflow/compiler/xla/tests/hlo_test_base.h" +#include "tensorflow/compiler/xla/tests/test_macros.h" + +namespace xla { +namespace { + +class MultiDeviceAllReduceTest : public HloTestBase {}; + +XLA_TEST_F(MultiDeviceAllReduceTest, TwoReplicasOneOperand) { + const char* module_str = R"( + HloModule test + + add { + x = f32[] parameter(0) + y = f32[] parameter(1) + add = f32[] add(x, y) + } + + ENTRY test_computation { + p = f32[3] parameter(0) + ROOT crs = f32[3] all-reduce(p), to_apply=add + })"; + auto config = GetModuleConfigForTest(); + config.set_replica_count(2); + auto module = ParseHloString(module_str, config).ValueOrDie(); + auto literal = LiteralUtil::CreateR1({1, 2, 3}); + auto expected = LiteralUtil::CreateR1({2, 4, 6}); + TF_ASSERT_OK_AND_ASSIGN(std::vector results, + ExecuteReplicated(std::move(module), {&literal}, 2, + /*use_threads=*/true)); + EXPECT_EQ(expected, results[0]); + EXPECT_EQ(expected, results[1]); +} + +} // namespace +} // namespace xla -- GitLab From 70650633f270cf940b74843e490d5aea89293506 Mon Sep 17 00:00:00 2001 From: Justin Lebar Date: Mon, 25 Feb 2019 17:26:05 -0800 Subject: [PATCH 512/766] [XLA] Remove redundant type info on constants in HLO graph dump. Instead of printing constants as e.g. "f32[] 1 (f32[])", print them simply as "f32[] 1". PiperOrigin-RevId: 235632525 --- tensorflow/compiler/xla/service/hlo_graph_dumper.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tensorflow/compiler/xla/service/hlo_graph_dumper.cc b/tensorflow/compiler/xla/service/hlo_graph_dumper.cc index 254f66021d..1bb4195b7e 100644 --- a/tensorflow/compiler/xla/service/hlo_graph_dumper.cc +++ b/tensorflow/compiler/xla/service/hlo_graph_dumper.cc @@ -829,8 +829,7 @@ string HloDotDumper::GetInstructionNodeInlinedOperands( // collected from profiling tools. Those constants may not have a valid // literal. if (elem_count.has_value() && *elem_count <= 8 && constant->HasLiteral()) { - return StrFormat("%s (%s)", constant->literal().ToString(), - ShapeUtil::HumanString(constant->shape())); + return constant->literal().ToString(); } // Otherwise, print e.g. "%constant.42 (s32[100])". -- GitLab From c2fe4fd25e489bac70661c5356987cef8967c484 Mon Sep 17 00:00:00 2001 From: Alexandre Passos Date: Mon, 25 Feb 2019 17:28:26 -0800 Subject: [PATCH 513/766] Worry about boolean conversions in get_argspec_for_partial This does not solve all tf.function-and-partial bugs but makes debugging simpler. PiperOrigin-RevId: 235632825 --- tensorflow/python/util/tf_inspect.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tensorflow/python/util/tf_inspect.py b/tensorflow/python/util/tf_inspect.py index 3a1c4a6e12..a53f03a03f 100644 --- a/tensorflow/python/util/tf_inspect.py +++ b/tensorflow/python/util/tf_inspect.py @@ -216,7 +216,8 @@ def _get_argspec_for_partial(obj): # Checks if all arguments have default value set after first one. invalid_default_values = [ - args[i] for i, j in enumerate(all_defaults) if not j and i > first_default + args[i] for i, j in enumerate(all_defaults) + if j is None and i > first_default ] if invalid_default_values: -- GitLab From 310d35fb50edfdd5314eabdf55483f81fcc8693d Mon Sep 17 00:00:00 2001 From: Amit <30853054+amitsrivastava78@users.noreply.github.com> Date: Tue, 26 Feb 2019 08:31:20 +0530 Subject: [PATCH 514/766] Updated resolve_svdf.cc Fixed warning in the file --- .../lite/toco/tensorflow_graph_matching/resolve_svdf.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/lite/toco/tensorflow_graph_matching/resolve_svdf.cc b/tensorflow/lite/toco/tensorflow_graph_matching/resolve_svdf.cc index fcd9ee45d9..2f9f9a8c9b 100644 --- a/tensorflow/lite/toco/tensorflow_graph_matching/resolve_svdf.cc +++ b/tensorflow/lite/toco/tensorflow_graph_matching/resolve_svdf.cc @@ -186,7 +186,7 @@ void SvdfCluster::MaybeMergeConstNodes( allocated_tensor->mutable_tensor_shape(); auto tensor_shape_dim0 = allocated_tensor_shape->add_dim(); int allocated_content_flat_size = 0; - for (int i = 0; i < const_node_parts.size(); i++) { + for (size_t i = 0; i < const_node_parts.size(); i++) { const auto& value_attr = const_node_parts[i]->attr().at("value"); const tensorflow::TensorProto& tensor = value_attr.tensor(); if (i == 0) { @@ -214,7 +214,7 @@ void SvdfCluster::MaybeMergeConstNodes( std::unique_ptr allocated_content( new char[allocated_content_flat_size]); char* content_ptr = allocated_content.get(); - for (int i = 0; i < const_node_parts.size(); i++) { + for (size_t i = 0; i < const_node_parts.size(); i++) { const auto& value_attr = const_node_parts[i]->attr().at("value"); const tensorflow::TensorProto& tensor = value_attr.tensor(); port::CopyToBuffer(tensor.tensor_content(), content_ptr); -- GitLab From fdd01eb06b2f01ca9db3fdde528aba6fffd8079b Mon Sep 17 00:00:00 2001 From: Akshay Modi Date: Mon, 25 Feb 2019 17:38:55 -0800 Subject: [PATCH 515/766] Correctly set execution mode on the context, not the module PiperOrigin-RevId: 235634263 --- tensorflow/python/eager/core_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/eager/core_test.py b/tensorflow/python/eager/core_test.py index 753c43314e..758817e676 100644 --- a/tensorflow/python/eager/core_test.py +++ b/tensorflow/python/eager/core_test.py @@ -311,7 +311,7 @@ class TFETest(test_util.TensorFlowTestCase): three.dtype.as_datatype_enum)) context.async_wait() context.async_clear_error() - context.execution_mode = context.SYNC + context.context().execution_mode = context.SYNC def testExecuteTooManyNumOutputs(self): # num_outputs provided is 50, but only one output is produced. -- GitLab From cc624f49d27e3a66ad6243b8657756ee9bacb8d1 Mon Sep 17 00:00:00 2001 From: James Ring Date: Mon, 25 Feb 2019 17:58:27 -0800 Subject: [PATCH 516/766] Use forward declaration for ServerInterface in c_api. PiperOrigin-RevId: 235636708 --- tensorflow/c/c_api.cc | 1 + tensorflow/c/c_api_internal.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tensorflow/c/c_api.cc b/tensorflow/c/c_api.cc index 245d7ba2b1..bc565f255e 100644 --- a/tensorflow/c/c_api.cc +++ b/tensorflow/c/c_api.cc @@ -26,6 +26,7 @@ limitations under the License. #include "tensorflow/cc/framework/scope_internal.h" #include "tensorflow/cc/ops/while_loop.h" #include "tensorflow/cc/saved_model/loader.h" +#include "tensorflow/core/distributed_runtime/server_lib.h" #include "tensorflow/core/framework/op_gen_lib.h" #include "tensorflow/core/kernels/logging_ops.h" #endif diff --git a/tensorflow/c/c_api_internal.h b/tensorflow/c/c_api_internal.h index d520b6b768..f96b9de14a 100644 --- a/tensorflow/c/c_api_internal.h +++ b/tensorflow/c/c_api_internal.h @@ -25,7 +25,6 @@ limitations under the License. #include #ifndef __ANDROID__ -#include "tensorflow/core/distributed_runtime/server_lib.h" #include "tensorflow/core/framework/op_gen_lib.h" #endif #include "tensorflow/core/common_runtime/shape_refiner.h" @@ -42,6 +41,7 @@ limitations under the License. namespace tensorflow { class Device; class DeviceMgr; +class ServerInterface; } // namespace tensorflow // Internal structures used by the C API. These are likely to change and should -- GitLab From f970a6d86f58da5fb2b275da1f5cc502453ea360 Mon Sep 17 00:00:00 2001 From: Amit <30853054+amitsrivastava78@users.noreply.github.com> Date: Tue, 26 Feb 2019 08:51:11 +0530 Subject: [PATCH 517/766] Updated model_cmdline_flags.cc Fixed warning in the file --- tensorflow/lite/toco/model_cmdline_flags.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tensorflow/lite/toco/model_cmdline_flags.cc b/tensorflow/lite/toco/model_cmdline_flags.cc index 717a28bc61..7e48bd9542 100644 --- a/tensorflow/lite/toco/model_cmdline_flags.cc +++ b/tensorflow/lite/toco/model_cmdline_flags.cc @@ -261,7 +261,7 @@ void ReadModelFlagsFromCommandLineFlags( std::vector mean_values = absl::StrSplit(parsed_model_flags.mean_values.value(), ','); QCHECK(mean_values.size() == model_flags->input_arrays_size()); - for (int i = 0; i < mean_values.size(); ++i) { + for (size_t i = 0; i < mean_values.size(); ++i) { char* last = nullptr; model_flags->mutable_input_arrays(i)->set_mean_value( strtod(mean_values[i].data(), &last)); @@ -278,7 +278,7 @@ void ReadModelFlagsFromCommandLineFlags( std::vector std_values = absl::StrSplit(parsed_model_flags.std_values.value(), ','); QCHECK(std_values.size() == model_flags->input_arrays_size()); - for (int i = 0; i < std_values.size(); ++i) { + for (size_t i = 0; i < std_values.size(); ++i) { char* last = nullptr; model_flags->mutable_input_arrays(i)->set_std_value( strtod(std_values[i].data(), &last)); @@ -296,7 +296,7 @@ void ReadModelFlagsFromCommandLineFlags( std::vector input_data_types = absl::StrSplit(parsed_model_flags.input_data_types.value(), ','); QCHECK(input_data_types.size() == model_flags->input_arrays_size()); - for (int i = 0; i < input_data_types.size(); ++i) { + for (size_t i = 0; i < input_data_types.size(); ++i) { IODataType type; QCHECK(IODataType_Parse(input_data_types[i], &type)); model_flags->mutable_input_arrays(i)->set_data_type(type); @@ -319,7 +319,7 @@ void ReadModelFlagsFromCommandLineFlags( std::vector input_shapes = absl::StrSplit(parsed_model_flags.input_shapes.value(), ':'); QCHECK(input_shapes.size() == model_flags->input_arrays_size()); - for (int i = 0; i < input_shapes.size(); ++i) { + for (size_t i = 0; i < input_shapes.size(); ++i) { auto* shape = model_flags->mutable_input_arrays(i)->mutable_shape(); shape->clear_dims(); // Treat an empty input shape as a scalar. -- GitLab From 7780ead0f822903ced9da6574ff9940e45e40746 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 25 Feb 2019 18:35:05 -0800 Subject: [PATCH 518/766] Fix error "TypeError: in method 'TFE_ProfilerClientStartTracing', argument 1 of type 'char *'". PiperOrigin-RevId: 235641068 --- tensorflow/c/eager/c_api_experimental.cc | 7 ++++--- tensorflow/c/eager/c_api_experimental.h | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tensorflow/c/eager/c_api_experimental.cc b/tensorflow/c/eager/c_api_experimental.cc index ff798593b5..c6a12247ef 100644 --- a/tensorflow/c/eager/c_api_experimental.cc +++ b/tensorflow/c/eager/c_api_experimental.cc @@ -78,9 +78,10 @@ void TFE_ContextDisableGraphCollection(TFE_Context* ctx) { ctx->context.SetShouldStoreGraphs(false); } -bool TFE_ProfilerClientStartTracing(char* service_addr, char* logdir, - char* worker_list, bool include_dataset_ops, - int duration_ms, int num_tracing_attempts) { +bool TFE_ProfilerClientStartTracing(const char* service_addr, + const char* logdir, const char* worker_list, + bool include_dataset_ops, int duration_ms, + int num_tracing_attempts) { tensorflow::Status s = tensorflow::profiler::client::ValidateHostPortPair(service_addr); if (!s.ok()) { diff --git a/tensorflow/c/eager/c_api_experimental.h b/tensorflow/c/eager/c_api_experimental.h index 89523793d3..219b9f4072 100644 --- a/tensorflow/c/eager/c_api_experimental.h +++ b/tensorflow/c/eager/c_api_experimental.h @@ -84,7 +84,7 @@ TF_CAPI_EXPORT extern void TFE_ContextDisableGraphCollection(TFE_Context* ctx); // tensorflow/contrib/tpu/profiler/capture_tpu_profile instead following // https://cloud.google.com/tpu/docs/cloud-tpu-tools#capture_trace. TF_CAPI_EXPORT extern bool TFE_ProfilerClientStartTracing( - char* service_addr, char* logdir, char* worker_list, + const char* service_addr, const char* logdir, const char* worker_list, bool include_dataset_ops, int duration_ms, int num_tracing_attempts); #ifdef __cplusplus -- GitLab From 5bb3184ba559e273ae03fa634602a64e0c1d432a Mon Sep 17 00:00:00 2001 From: Amit <30853054+amitsrivastava78@users.noreply.github.com> Date: Tue, 26 Feb 2019 09:37:56 +0530 Subject: [PATCH 519/766] Updated verifier_test.cc Fixed warning in the file --- tensorflow/lite/tools/verifier_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/tools/verifier_test.cc b/tensorflow/lite/tools/verifier_test.cc index 7d67d9430a..1c1f764ee2 100644 --- a/tensorflow/lite/tools/verifier_test.cc +++ b/tensorflow/lite/tools/verifier_test.cc @@ -206,7 +206,7 @@ TEST(VerifyModel, TestRandomModificationIsNotAllowed) { std::string model_content(reinterpret_cast(builder.GetBufferPointer()), builder.GetSize()); - for (int i = 0; i < model_content.size(); i++) { + for (size_t i = 0; i < model_content.size(); i++) { model_content[i] = (model_content[i] + 137) % 255; EXPECT_FALSE(Verify(model_content.data(), model_content.size(), MutableOpResolver{}, DefaultErrorReporter())) -- GitLab From f37a66cf1f7568c40b858878b34d2efca5f4566a Mon Sep 17 00:00:00 2001 From: Dan Moldovan Date: Mon, 25 Feb 2019 19:00:48 -0800 Subject: [PATCH 520/766] Multiple consistency fixes. Submitting in one change to make sure they work well together. Ensure consistency of handling bound methods between autograph and tf.function. Fix the detection of wrapt used on methods. Fix rewrap to consistently replace the target function even if it was not a decorator. PiperOrigin-RevId: 235643608 --- tensorflow/python/autograph/impl/api.py | 28 +++++++++---------- tensorflow/python/autograph/impl/api_test.py | 15 ---------- .../python/autograph/pyct/inspect_utils.py | 13 +++++---- .../autograph/pyct/inspect_utils_test.py | 11 ++++---- tensorflow/python/data/ops/dataset_ops.py | 3 ++ .../data/ops/multi_device_iterator_ops.py | 25 +++++++++++++---- tensorflow/python/eager/def_function.py | 2 +- tensorflow/python/eager/function.py | 27 +++++++++++++----- tensorflow/python/framework/func_graph.py | 3 +- tensorflow/python/util/tf_decorator.py | 14 +++++++++- 10 files changed, 85 insertions(+), 56 deletions(-) diff --git a/tensorflow/python/autograph/impl/api.py b/tensorflow/python/autograph/impl/api.py index d0f52f52eb..d28138b2ec 100644 --- a/tensorflow/python/autograph/impl/api.py +++ b/tensorflow/python/autograph/impl/api.py @@ -28,6 +28,7 @@ from enum import Enum # pylint:disable=g-bad-import-order import numpy as np +import six # pylint:enable=g-bad-import-order @@ -181,20 +182,20 @@ def _call_unconverted(f, args, kwargs): Returns: The return value of f(*args, **kwargs). """ - # TODO(mdan): This may be inconsistent in certain situations. - # If the function had already been annotated with @tf.function, it - # may be bound to the incorrect object. It's unclear if those situations - # are possible, but if they happen, we need to check if f is bound - # to a shim like WeakrefSelf and unpack it. - - if tf_inspect.ismethod(f) and args: - f_self = inspect_utils.getmethodself(f) - if args[0] is f_self: - args = args[1:] + if inspect_utils.istfmethodtarget(f): + return f.__self__.call(args, kwargs) return f(*args, **kwargs) +def _is_known_loaded_type(f, module_name, entity_name): + if tf_inspect.ismethod(f): + f = six.get_unbound_function(f) + return (module_name in sys.modules and + hasattr(sys.modules[module_name], entity_name) and + isinstance(f, getattr(sys.modules[module_name], entity_name))) + + def converted_call(f, owner, options, args, kwargs): """Compiles a function call inline. For internal use only.""" logging.log(1, @@ -219,13 +220,12 @@ def converted_call(f, owner, options, args, kwargs): return py_builtins.overload_of(f)(*args, **kwargs) # TODO(b/122265385): Remove this bypass. - if ('wrapt' in sys.modules and - hasattr(sys.modules['wrapt'], 'FunctionWrapper') and - isinstance(f, sys.modules['wrapt'].FunctionWrapper)): + if (_is_known_loaded_type(f, 'wrapt', 'FunctionWrapper') or + _is_known_loaded_type(f, 'wrapt', 'BoundFunctionWrapper')): logging.warn( 'Entity {} appears to be decorated by wrapt, which is not yet supported' ' by AutoGraph. The function will be called without transformation.' - ' You may however apply AutoGraph before the decorator.'.format(f), 1) + ' You may however apply AutoGraph before the decorator.'.format(f)) logging.log(2, 'Permanently whitelisted: %s: wrapt decorated', f) return _call_unconverted(f, args, kwargs) diff --git a/tensorflow/python/autograph/impl/api_test.py b/tensorflow/python/autograph/impl/api_test.py index ab24281924..00d5c4c2fd 100644 --- a/tensorflow/python/autograph/impl/api_test.py +++ b/tensorflow/python/autograph/impl/api_test.py @@ -402,21 +402,6 @@ class ApiTest(test.TestCase): self.evaluate(variables.global_variables_initializer()) self.assertAllEqual([[0.0, 0.0]], self.evaluate(x)) - def test_converted_call_whitelisted_method_extra_self(self): - - opts = converter.ConversionOptions() - - model = sequential.Sequential([ - core.Dense(2) - ]) - - x = api.converted_call(model.call, None, opts, - (model, constant_op.constant([[0.0]])), - {'training': True}) - - self.evaluate(variables.global_variables_initializer()) - self.assertAllEqual([[0.0, 0.0]], self.evaluate(x)) - def test_converted_call_whitelisted_method_via_owner(self): opts = converter.ConversionOptions() diff --git a/tensorflow/python/autograph/pyct/inspect_utils.py b/tensorflow/python/autograph/pyct/inspect_utils.py index eab01ee9cd..007c963d8c 100644 --- a/tensorflow/python/autograph/pyct/inspect_utils.py +++ b/tensorflow/python/autograph/pyct/inspect_utils.py @@ -191,9 +191,12 @@ def getdefiningclass(m, owner_class): return owner_class -def isweakrefself(m): - """Tests whether an object is a "weakref self" wrapper, see getmethodself.""" - return hasattr(m, '__self__') and hasattr(m.__self__, 'ag_self_weakref__') +def istfmethodtarget(m): + """Tests whether an object is a `function.TfMethodTarget`.""" + # See eager.function.TfMethodTarget for more details. + return (hasattr(m, '__self__') and + hasattr(m.__self__, 'weakrefself_target__') and + hasattr(m.__self__, 'weakrefself_func__')) def getmethodself(m): @@ -206,8 +209,8 @@ def getmethodself(m): # A fallback allowing methods to be actually bound to a type different # than __self__. This is useful when a strong reference from the method # to the object is not desired, for example when caching is involved. - if isweakrefself(m): - return m.__self__.ag_self_weakref__() + if istfmethodtarget(m): + return m.__self__.target return m.__self__ diff --git a/tensorflow/python/autograph/pyct/inspect_utils_test.py b/tensorflow/python/autograph/pyct/inspect_utils_test.py index 4c4c0977b0..0d0754ba15 100644 --- a/tensorflow/python/autograph/pyct/inspect_utils_test.py +++ b/tensorflow/python/autograph/pyct/inspect_utils_test.py @@ -28,6 +28,7 @@ import six from tensorflow.python import lib from tensorflow.python.autograph.pyct import inspect_utils +from tensorflow.python.eager import function from tensorflow.python.framework import constant_op from tensorflow.python.platform import test @@ -358,15 +359,13 @@ class InspectUtilsTest(test.TestCase): def test_getmethodclass_weakref_mechanism(self): test_obj = TestClass() - class WeakrefWrapper(object): - - def __init__(self): - self.ag_self_weakref__ = weakref.ref(test_obj) - def test_fn(self): return self - bound_method = types.MethodType(test_fn, WeakrefWrapper()) + bound_method = types.MethodType( + test_fn, + function.TfMethodTarget( + weakref.ref(test_obj), test_obj.member_function)) self.assertEqual(inspect_utils.getmethodclass(bound_method), TestClass) def test_getmethodclass_no_bool_conversion(self): diff --git a/tensorflow/python/data/ops/dataset_ops.py b/tensorflow/python/data/ops/dataset_ops.py index 2355a0abea..c0b120bf3c 100644 --- a/tensorflow/python/data/ops/dataset_ops.py +++ b/tensorflow/python/data/ops/dataset_ops.py @@ -2366,6 +2366,8 @@ class StructuredFunctionWrapper(object): else: defun_kwargs.update({"func_name": func_name}) + # TODO(b/124254153): Enable autograph once the overhead is low enough. + # TODO(mdan): Make sure autograph recurses into _wrapper_helper when on. @eager_function.defun_with_attributes( input_signature=[ tensor_spec.TensorSpec(input_shape, input_type) # pylint: disable=g-complex-comprehension @@ -2373,6 +2375,7 @@ class StructuredFunctionWrapper(object): self._input_structure._flat_shapes, self._input_structure._flat_types) ], + autograph=False, attributes=defun_kwargs) def wrapper_fn(*args): # pylint: disable=missing-docstring ret = _wrapper_helper(*args) diff --git a/tensorflow/python/data/ops/multi_device_iterator_ops.py b/tensorflow/python/data/ops/multi_device_iterator_ops.py index 36e1e311d3..f253f8bf42 100644 --- a/tensorflow/python/data/ops/multi_device_iterator_ops.py +++ b/tensorflow/python/data/ops/multi_device_iterator_ops.py @@ -42,13 +42,15 @@ class _PerDeviceGenerator(dataset_ops.DatasetV2): gen_dataset_ops.multi_device_iterator_to_string_handle( multi_device_iterator_resource)) - @function.defun() + # TODO(b/124254153): Enable autograph once the overhead is low enough. + @function.defun(autograph=False) # Pure graph code. def _init_func(): return multi_device_iterator_string_handle init_func_concrete = _init_func._get_concrete_function_internal() # pylint: disable=protected-access - @function.defun() + # TODO(b/124254153): Enable autograph once the overhead is low enough. + @function.defun(autograph=False) # Pure graph code. def _remote_init_func(): return functional_ops.remote_call( target=source_device, @@ -59,7 +61,10 @@ class _PerDeviceGenerator(dataset_ops.DatasetV2): self._init_func = _remote_init_func._get_concrete_function_internal() # pylint: disable=protected-access self._init_captured_args = self._init_func.captured_inputs - @function.defun(input_signature=[tensor_spec.TensorSpec([], dtypes.string)]) + # TODO(b/124254153): Enable autograph once the overhead is low enough. + @function.defun( + input_signature=[tensor_spec.TensorSpec([], dtypes.string)], + autograph=False) # Pure graph code. def _next_func(string_handle): # pylint: disable=protected-access multi_device_iterator = ( @@ -76,9 +81,11 @@ class _PerDeviceGenerator(dataset_ops.DatasetV2): next_func_concrete = _next_func._get_concrete_function_internal() # pylint: disable=protected-access + # TODO(b/124254153): Enable autograph once the overhead is low enough. @function.defun_with_attributes( input_signature=[tensor_spec.TensorSpec([], dtypes.string)], - attributes={"experimental_ints_on_device": True}) + attributes={"experimental_ints_on_device": True}, + autograph=False) # Pure graph code. def _remote_next_func(string_handle): return functional_ops.remote_call( target=source_device, @@ -94,13 +101,19 @@ class _PerDeviceGenerator(dataset_ops.DatasetV2): if arg == incarnation_id: self._incarnation_id_index = i - @function.defun(input_signature=[tensor_spec.TensorSpec([], dtypes.string)]) + # TODO(b/124254153): Enable autograph once the overhead is low enough. + @function.defun( + input_signature=[tensor_spec.TensorSpec([], dtypes.string)], + autograph=False) # Pure graph code. def _finalize_func(unused_string_handle): return array_ops.constant(0, dtypes.int64) finalize_func_concrete = _finalize_func._get_concrete_function_internal() # pylint: disable=protected-access - @function.defun(input_signature=[tensor_spec.TensorSpec([], dtypes.string)]) + # TODO(b/124254153): Enable autograph once the overhead is low enough. + @function.defun( + input_signature=[tensor_spec.TensorSpec([], dtypes.string)], + autograph=False) # Pure graph code. def _remote_finalize_func(string_handle): return functional_ops.remote_call( target=source_device, diff --git a/tensorflow/python/eager/def_function.py b/tensorflow/python/eager/def_function.py index 1d54973487..330b2e0a76 100644 --- a/tensorflow/python/eager/def_function.py +++ b/tensorflow/python/eager/def_function.py @@ -514,7 +514,7 @@ class Function(object): """Make and call a `ConcreteFunction` which initializes variables.""" # Note: using defun here avoids an infinite recursion. - @function_lib.defun + @function_lib.defun(autograph=False) # Pure graph code. def initialize_variables(): for v, init in initializer_map.items(): with ops.init_scope(): diff --git a/tensorflow/python/eager/function.py b/tensorflow/python/eager/function.py index 6922f79a02..25a1080c04 100644 --- a/tensorflow/python/eager/function.py +++ b/tensorflow/python/eager/function.py @@ -61,6 +61,7 @@ from tensorflow.python.util import tf_inspect FORWARD_FUNCTION_ATTRIBUTE_NAME = "forward_function_name" BACKWARD_FUNCTION_ATTRIBUTE_NAME = "backward_function_name" + class CacheKey( collections.namedtuple("CacheKey", [ "input_signature", "parent_graph", "device_functions", @@ -2019,13 +2020,23 @@ def defun_with_attributes(func=None, # When a method is bound to objects of this type, it allows AutoGraph to -# recover a weak reference the original method's self pointer. This uses the -# mechanism from pyct.inspect_utils.getmethodclass. +# recover a weak reference the original method's self pointer, so that it can +# execute it consistent with class_method_to_instance_method's +# bound_method_wrapper. # TODO(b/119246461): This is not pretty. Use a descriptor instead? -class _WeakrefSelf(object): +class TfMethodTarget(object): + """Binding target for methods replaced by function and defun.""" + + def __init__(self, target, original_python_function): + self.weakrefself_target__ = target + self.weakrefself_func__ = weakref.ref(original_python_function) + + @property + def target(self): + return self.weakrefself_target__() - def __init__(self, target): - self.ag_self_weakref__ = target + def call(self, args, kwargs): + return self.weakrefself_func__()(*args, **kwargs) def class_method_to_instance_method(original_function, instance): @@ -2034,8 +2045,9 @@ def class_method_to_instance_method(original_function, instance): # Note: while we could bind to a weakref proxy instead, that causes the # bound method to be unhashable. - bound_method = types_lib.MethodType(original_function.python_function, - _WeakrefSelf(weak_instance)) + bound_method = types_lib.MethodType( + original_function.python_function, + TfMethodTarget(weak_instance, original_function.python_function)) # original_function is expected to be of one of the two `Function` types # (defined either in function.py or def_function.py). @@ -2053,6 +2065,7 @@ def class_method_to_instance_method(original_function, instance): if wrapped_fn is strong_bound_method_wrapper.__original_wrapped__: # If __wrapped__ was not replaced, then call original_function. + # TODO(mdan): For better consistency, use the wrapper's call(). wrapped_fn = original_function.python_function if tf_inspect.ismethod(wrapped_fn): wrapped_fn = six.get_unbound_function(wrapped_fn) diff --git a/tensorflow/python/framework/func_graph.py b/tensorflow/python/framework/func_graph.py index 91d80a388a..fc02835399 100644 --- a/tensorflow/python/framework/func_graph.py +++ b/tensorflow/python/framework/func_graph.py @@ -679,7 +679,8 @@ def func_graph_from_py_func(name, # Wrapping around a decorator allows checks like tf_inspect.getargspec # to be accurate. converted_func = tf_decorator.make_decorator(original_func, wrapper) - tf_decorator.rewrap(python_func, original_func, converted_func) + python_func = tf_decorator.rewrap(python_func, original_func, + converted_func) func_outputs = python_func(*func_args, **func_kwargs) diff --git a/tensorflow/python/util/tf_decorator.py b/tensorflow/python/util/tf_decorator.py index 6c71f70c4e..cf380509e9 100644 --- a/tensorflow/python/util/tf_decorator.py +++ b/tensorflow/python/util/tf_decorator.py @@ -138,6 +138,10 @@ def rewrap(decorator_func, previous_target, new_target): decorator_func: Callable returned by `wrap`. previous_target: Callable that needs to be replaced. new_target: Callable to replace previous_target with. + + Returns: + The updated decorator. If decorator_func is not a tf_decorator, new_target + is returned. """ # Because the process mutates the decorator, we only need to alter the # innermost function that wraps previous_target. @@ -150,9 +154,15 @@ def rewrap(decorator_func, previous_target, new_target): if target.decorated_target is previous_target: break cur = target.decorated_target + assert cur is not None + # If decorator_func is not a decorator, new_target replaces it directly. if innermost_decorator is None: - return + # Consistency check. The caller should always pass the result of + # tf_decorator.unwrap as previous_target. If decorator_func is not a + # decorator, that will have returned decorator_func itself. + assert decorator_func is previous_target + return new_target target.decorated_target = new_target @@ -168,6 +178,8 @@ def rewrap(decorator_func, previous_target, new_target): else: innermost_decorator.__wrapped__ = new_target + return decorator_func + def unwrap(maybe_tf_decorator): """Unwraps an object into a list of TFDecorators and a final target. -- GitLab From 85d3d8c6cceebede4fda1defaa0c4dbc4a44a9a6 Mon Sep 17 00:00:00 2001 From: Thomas O'Malley Date: Mon, 25 Feb 2019 19:45:44 -0800 Subject: [PATCH 521/766] Automatically track updates in Keras and add them to `add_update`. PiperOrigin-RevId: 235647992 --- tensorflow/python/keras/engine/base_layer.py | 11 +- .../python/keras/engine/base_layer_utils.py | 119 ++++++++++++++++++ tensorflow/python/keras/engine/network.py | 8 +- .../python/keras/engine/training_test.py | 100 +++++++++++++++ tensorflow/python/keras/utils/tf_utils.py | 10 +- 5 files changed, 240 insertions(+), 8 deletions(-) diff --git a/tensorflow/python/keras/engine/base_layer.py b/tensorflow/python/keras/engine/base_layer.py index de33d2e177..d3218208fd 100644 --- a/tensorflow/python/keras/engine/base_layer.py +++ b/tensorflow/python/keras/engine/base_layer.py @@ -588,7 +588,11 @@ class Layer(trackable.Trackable): kwargs['training'] = backend.learning_phase() if not self.dynamic: try: - outputs = self.call(inputs, *args, **kwargs) + with base_layer_utils.AutoAddUpdates(self, + inputs) as auto_updater: + outputs = self.call(inputs, *args, **kwargs) + auto_updater.set_outputs(outputs) + except TypeError as e: messages = ('`tf.Tensor` as a Python `bool` is not allowed', 'Tensor objects are only iterable when eager') @@ -1806,6 +1810,11 @@ class Layer(trackable.Trackable): def _is_layer(self): return True + @property + def _unfiltered_updates(self): + # Overridden in `Network`. + return self.updates + class Node(object): """A `Node` describes the connectivity between two layers. diff --git a/tensorflow/python/keras/engine/base_layer_utils.py b/tensorflow/python/keras/engine/base_layer_utils.py index 89aa506efd..34b007ea95 100644 --- a/tensorflow/python/keras/engine/base_layer_utils.py +++ b/tensorflow/python/keras/engine/base_layer_utils.py @@ -21,10 +21,14 @@ import collections as collections_lib import threading import enum +from tensorflow.python.eager import context +from tensorflow.python.framework import auto_control_deps from tensorflow.python.framework import dtypes from tensorflow.python.framework import ops from tensorflow.python.keras import backend +from tensorflow.python.keras.utils import tf_utils from tensorflow.python.ops import array_ops +from tensorflow.python.ops import control_flow_util from tensorflow.python.ops import init_ops from tensorflow.python.ops import init_ops_v2 from tensorflow.python.ops import variables as tf_variables @@ -395,3 +399,118 @@ def training_arg_passed_to_call(argspec, args, kwargs): full_args = dict(zip(argspec.args[2:], args)) full_args.update(kwargs) return 'training' in full_args + + +class AutoAddUpdates(object): + """Automatically track stateful ops with `add_update`. + + This context manager is used to automatically add stateful ops to a Layer + or Model's `.updates`. This ensures that stateful ops are run in the Keras + training loop. It also allows for these stateful ops to be disabled by + setting `trainable=False`. + + Example: + + ``` + with AutoAddUpdates(layer, inputs) as auto_updates: + outputs = layer.call(inputs) + auto_updates.set_outputs(outputs) + ``` + + Attributes: + layer: Layer or Model instance to add the updates to. + inputs: The inputs to this Layer or Model, to be used for input-conditional + updates. + outputs: The outputs of this Layer or Model. + """ + + def __init__(self, layer, inputs): + self.layer = layer + self.inputs = inputs + self.outputs = [] + + def set_outputs(self, outputs): + if self.outputs: + raise RuntimeError('`set_outputs` should only be called once on an' + '`AutoAddUpdates` instance.') + self.outputs = outputs + + def __enter__(self): + # Only run in V2 Function mode. + if (context.executing_eagerly() or + not ops.executing_eagerly_outside_functions()): + return self + + self._graph = ops.get_default_graph() + self._num_operations = len(self._graph.get_operations()) + return self + + def __exit__(self, error_type, unused_value, unused_traceback): + if error_type: + # Allow errors that occurred inside this context manager to pass through + # normally. + return + + # Only run in V2 Function mode. + if (context.executing_eagerly() or + not ops.executing_eagerly_outside_functions()): + return + + if (self._graph is not ops.get_default_graph() or + self._graph.name != 'keras_graph'): + # Only auto-track updates when the Keras Graph is the only one used. + return + + new_operations = self._graph.get_operations()[self._num_operations:] + new_stateful_ops = set() + + # pylint: disable=protected-access + for op in new_operations: + # While loop is not supported in general for automatic control + # dependencies. + if control_flow_util.IsInWhileLoop(op): + continue + + # Track stateful ops via `add_update`. + is_stateful_op = ( + op.type not in self._graph._registered_ops or + auto_control_deps.op_is_stateful( + self._graph._registered_ops[op.type])) + + # Ignore ReadVariableOps as they are not needed to be run separately. + # This ensures existing Layers don't get extra updates. + if is_stateful_op and op.type != 'ReadVariableOp': + new_stateful_ops.add(op) + + explicit_updates = set( + [u for u in self.layer._unfiltered_updates if not isinstance(u, tuple)]) + # pylint: enable=protected-access + + # Don't add updates that will already be run by virtue of being consumed by + # other stateful ops or by the Layer's outputs. This ensures that existing + # Layers like `BatchNormalization` continue to return the same values for + # `.update` calls. + minimum_ops = set() + targets = new_stateful_ops.union( + set(nest.flatten(self.outputs)), explicit_updates) + for op in new_stateful_ops: + # Scrub any ops that are consumed by the outputs or other stateful ops. + reachable = tf_utils.get_reachable_from_inputs(op) + if not (targets - {op}).intersection(reachable): + minimum_ops.add(op) + new_stateful_ops = minimum_ops + + # Don't double-track updates added via explicitly calling `add_update`. + # Also don't double-track updates already tracked in sublayers. + new_stateful_ops = new_stateful_ops - explicit_updates + + # Decide whether to track as input-conditional or unconditional. + input_reachable_ops = tf_utils.get_reachable_from_inputs( + self.inputs, targets=new_stateful_ops) + unconditional_updates = new_stateful_ops - input_reachable_ops + conditional_updates = new_stateful_ops - unconditional_updates + + if unconditional_updates: + self.layer.add_update(list(unconditional_updates)) + if conditional_updates: + self.layer.add_update(list(conditional_updates), inputs=self.inputs) diff --git a/tensorflow/python/keras/engine/network.py b/tensorflow/python/keras/engine/network.py index b10aa98c7f..8863e1aafc 100644 --- a/tensorflow/python/keras/engine/network.py +++ b/tensorflow/python/keras/engine/network.py @@ -518,11 +518,8 @@ class Network(base_layer.Layer): def _unfiltered_updates(self): updates = [] for layer in self.layers: - if isinstance(layer, Network): - updates += layer._unfiltered_updates - else: - updates += layer.updates - updates += self._updates + updates += layer._unfiltered_updates + updates += list(self._updates) return updates @property @@ -1834,4 +1831,3 @@ def _map_graph_network(inputs, outputs): str(all_names.count(name)) + ' times in the model. ' 'All layer names should be unique.') return network_nodes, nodes_by_depth, layers, layers_by_depth - diff --git a/tensorflow/python/keras/engine/training_test.py b/tensorflow/python/keras/engine/training_test.py index 686cc2db36..cad89db227 100644 --- a/tensorflow/python/keras/engine/training_test.py +++ b/tensorflow/python/keras/engine/training_test.py @@ -44,6 +44,7 @@ from tensorflow.python.keras.engine.training_utils import weighted_masked_object from tensorflow.python.ops import array_ops from tensorflow.python.ops import math_ops from tensorflow.python.ops import sparse_ops +from tensorflow.python.ops import state_ops from tensorflow.python.ops import variables as variables_lib from tensorflow.python.platform import test from tensorflow.python.platform import tf_logging as logging @@ -2866,5 +2867,104 @@ class TestTrainingWithMetrics(keras_parameterized.TestCase): self.assertTrue(test_model.run_eagerly) +class BareUpdateLayer(keras.layers.Layer): + + def build(self, input_shape): + self.counter = self.add_weight( + 'counter', + dtype='int32', + shape=(), + initializer='zeros', + trainable=False) + + def call(self, inputs): + state_ops.assign_add(self.counter, 1) + return math_ops.cast(self.counter, inputs.dtype) * inputs + + +class AddUpdateLayer(keras.layers.Layer): + + def build(self, input_shape): + self.counter = self.add_weight( + 'counter', + dtype='int32', + shape=(), + initializer='zeros', + trainable=False) + + def call(self, inputs): + # Make sure update isn't run twice. + self.add_update(state_ops.assign_add(self.counter, 1)) + return math_ops.cast(self.counter, inputs.dtype) * inputs + + +class NestedUpdateLayer(keras.layers.Layer): + + def build(self, input_shape): + self.layer = BareUpdateLayer() + self.layer.build(input_shape) + + @property + def counter(self): + return self.layer.counter + + def call(self, inputs): + return self.layer(inputs) + + +@keras_parameterized.run_all_keras_modes(always_skip_v1=True) +class TestAutoUpdates(keras_parameterized.TestCase): + + @keras_parameterized.run_with_all_model_types + @parameterized.named_parameters(('bare_update', BareUpdateLayer()), + ('add_update', AddUpdateLayer()), + ('nested_update', NestedUpdateLayer())) + def test_updates_in_model(self, layer): + x, y = np.ones((10, 10)), np.ones((10, 1)) + model = testing_utils.get_model_from_layers( + [layer, keras.layers.Dense(1)], input_shape=(10,)) + model.compile('sgd', 'mse', run_eagerly=testing_utils.should_run_eagerly()) + model.fit(x, y, batch_size=2, epochs=1) + if not testing_utils.should_run_eagerly(): + # Check that `trainable=False` disables updates. + layer.trainable = False + model.compile( + 'sgd', 'mse', run_eagerly=testing_utils.should_run_eagerly()) + model.fit(x, y, batch_size=2, epochs=1) + self.assertEqual(self.evaluate(layer.counter), 5) + + @parameterized.named_parameters(('bare_update', BareUpdateLayer()), + ('add_update', AddUpdateLayer()), + ('nested_update', NestedUpdateLayer())) + def test_updates_standalone_layer(self, layer): + y = layer(np.ones((10, 10))) + self.evaluate(layer.counter.initializer) + self.evaluate(y) + self.assertEqual(self.evaluate(layer.counter), 1) + + def test_trainable_false(self): + x = keras.backend.placeholder(shape=(10, 10), dtype='float32') + layer = NestedUpdateLayer() + layer.trainable = False + y = layer(x) + func = keras.backend.function([x], [y]) + x_val = np.ones((10, 10)) + func(x_val) + counter = keras.backend.get_value(layer.counter) + self.assertEqual(counter, 0) + + @keras_parameterized.run_with_all_model_types + def test_batchnorm_trainable_false(self): + bn = keras.layers.BatchNormalization() + bn.trainable = False + model = testing_utils.get_model_from_layers([bn, keras.layers.Dense(1)], + input_shape=(10,)) + model.compile('sgd', 'mse', run_eagerly=testing_utils.should_run_eagerly()) + x, y = np.ones((10, 10)), np.ones((10, 1)) + model.fit(x, y, batch_size=2, epochs=1) + self.assertAllEqual(self.evaluate(bn.moving_mean), np.zeros((10,))) + self.assertAllEqual(self.evaluate(bn.moving_variance), np.ones((10,))) + + if __name__ == '__main__': test.main() diff --git a/tensorflow/python/keras/utils/tf_utils.py b/tensorflow/python/keras/utils/tf_utils.py index 1c1d30ba4b..57f0d5c76a 100644 --- a/tensorflow/python/keras/utils/tf_utils.py +++ b/tensorflow/python/keras/utils/tf_utils.py @@ -112,13 +112,21 @@ def get_reachable_from_inputs(inputs, targets=None): while queue: x = queue.pop() + if isinstance(x, tuple(_user_convertible_tensor_types)): + # Can't find consumers of user-specific types. + continue + if isinstance(x, ops.Operation): outputs = x.outputs[:] or [] outputs += x._control_outputs # pylint: disable=protected-access elif isinstance(x, variables.Variable): outputs = [x.op] elif tensor_util.is_tensor(x): - outputs = x.consumers() + try: + outputs = x.consumers() + except AttributeError: + # `RaggedTensors` have no `.consumers()` method. + continue else: raise TypeError('Expected Operation, Variable, or Tensor, got ' + str(x)) -- GitLab From 60089e430e34c870e074c6d5746ed88ea9b39909 Mon Sep 17 00:00:00 2001 From: Dan Moldovan Date: Mon, 25 Feb 2019 20:02:04 -0800 Subject: [PATCH 522/766] Automated rollback of commit f37a66cf1f7568c40b858878b34d2efca5f4566a PiperOrigin-RevId: 235649273 --- tensorflow/python/autograph/impl/api.py | 28 +++++++++---------- tensorflow/python/autograph/impl/api_test.py | 15 ++++++++++ .../python/autograph/pyct/inspect_utils.py | 13 ++++----- .../autograph/pyct/inspect_utils_test.py | 11 ++++---- tensorflow/python/data/ops/dataset_ops.py | 3 -- .../data/ops/multi_device_iterator_ops.py | 25 ++++------------- tensorflow/python/eager/def_function.py | 2 +- tensorflow/python/eager/function.py | 27 +++++------------- tensorflow/python/framework/func_graph.py | 3 +- tensorflow/python/util/tf_decorator.py | 14 +--------- 10 files changed, 56 insertions(+), 85 deletions(-) diff --git a/tensorflow/python/autograph/impl/api.py b/tensorflow/python/autograph/impl/api.py index d28138b2ec..d0f52f52eb 100644 --- a/tensorflow/python/autograph/impl/api.py +++ b/tensorflow/python/autograph/impl/api.py @@ -28,7 +28,6 @@ from enum import Enum # pylint:disable=g-bad-import-order import numpy as np -import six # pylint:enable=g-bad-import-order @@ -182,18 +181,18 @@ def _call_unconverted(f, args, kwargs): Returns: The return value of f(*args, **kwargs). """ - if inspect_utils.istfmethodtarget(f): - return f.__self__.call(args, kwargs) - - return f(*args, **kwargs) + # TODO(mdan): This may be inconsistent in certain situations. + # If the function had already been annotated with @tf.function, it + # may be bound to the incorrect object. It's unclear if those situations + # are possible, but if they happen, we need to check if f is bound + # to a shim like WeakrefSelf and unpack it. + if tf_inspect.ismethod(f) and args: + f_self = inspect_utils.getmethodself(f) + if args[0] is f_self: + args = args[1:] -def _is_known_loaded_type(f, module_name, entity_name): - if tf_inspect.ismethod(f): - f = six.get_unbound_function(f) - return (module_name in sys.modules and - hasattr(sys.modules[module_name], entity_name) and - isinstance(f, getattr(sys.modules[module_name], entity_name))) + return f(*args, **kwargs) def converted_call(f, owner, options, args, kwargs): @@ -220,12 +219,13 @@ def converted_call(f, owner, options, args, kwargs): return py_builtins.overload_of(f)(*args, **kwargs) # TODO(b/122265385): Remove this bypass. - if (_is_known_loaded_type(f, 'wrapt', 'FunctionWrapper') or - _is_known_loaded_type(f, 'wrapt', 'BoundFunctionWrapper')): + if ('wrapt' in sys.modules and + hasattr(sys.modules['wrapt'], 'FunctionWrapper') and + isinstance(f, sys.modules['wrapt'].FunctionWrapper)): logging.warn( 'Entity {} appears to be decorated by wrapt, which is not yet supported' ' by AutoGraph. The function will be called without transformation.' - ' You may however apply AutoGraph before the decorator.'.format(f)) + ' You may however apply AutoGraph before the decorator.'.format(f), 1) logging.log(2, 'Permanently whitelisted: %s: wrapt decorated', f) return _call_unconverted(f, args, kwargs) diff --git a/tensorflow/python/autograph/impl/api_test.py b/tensorflow/python/autograph/impl/api_test.py index 00d5c4c2fd..ab24281924 100644 --- a/tensorflow/python/autograph/impl/api_test.py +++ b/tensorflow/python/autograph/impl/api_test.py @@ -402,6 +402,21 @@ class ApiTest(test.TestCase): self.evaluate(variables.global_variables_initializer()) self.assertAllEqual([[0.0, 0.0]], self.evaluate(x)) + def test_converted_call_whitelisted_method_extra_self(self): + + opts = converter.ConversionOptions() + + model = sequential.Sequential([ + core.Dense(2) + ]) + + x = api.converted_call(model.call, None, opts, + (model, constant_op.constant([[0.0]])), + {'training': True}) + + self.evaluate(variables.global_variables_initializer()) + self.assertAllEqual([[0.0, 0.0]], self.evaluate(x)) + def test_converted_call_whitelisted_method_via_owner(self): opts = converter.ConversionOptions() diff --git a/tensorflow/python/autograph/pyct/inspect_utils.py b/tensorflow/python/autograph/pyct/inspect_utils.py index 007c963d8c..eab01ee9cd 100644 --- a/tensorflow/python/autograph/pyct/inspect_utils.py +++ b/tensorflow/python/autograph/pyct/inspect_utils.py @@ -191,12 +191,9 @@ def getdefiningclass(m, owner_class): return owner_class -def istfmethodtarget(m): - """Tests whether an object is a `function.TfMethodTarget`.""" - # See eager.function.TfMethodTarget for more details. - return (hasattr(m, '__self__') and - hasattr(m.__self__, 'weakrefself_target__') and - hasattr(m.__self__, 'weakrefself_func__')) +def isweakrefself(m): + """Tests whether an object is a "weakref self" wrapper, see getmethodself.""" + return hasattr(m, '__self__') and hasattr(m.__self__, 'ag_self_weakref__') def getmethodself(m): @@ -209,8 +206,8 @@ def getmethodself(m): # A fallback allowing methods to be actually bound to a type different # than __self__. This is useful when a strong reference from the method # to the object is not desired, for example when caching is involved. - if istfmethodtarget(m): - return m.__self__.target + if isweakrefself(m): + return m.__self__.ag_self_weakref__() return m.__self__ diff --git a/tensorflow/python/autograph/pyct/inspect_utils_test.py b/tensorflow/python/autograph/pyct/inspect_utils_test.py index 0d0754ba15..4c4c0977b0 100644 --- a/tensorflow/python/autograph/pyct/inspect_utils_test.py +++ b/tensorflow/python/autograph/pyct/inspect_utils_test.py @@ -28,7 +28,6 @@ import six from tensorflow.python import lib from tensorflow.python.autograph.pyct import inspect_utils -from tensorflow.python.eager import function from tensorflow.python.framework import constant_op from tensorflow.python.platform import test @@ -359,13 +358,15 @@ class InspectUtilsTest(test.TestCase): def test_getmethodclass_weakref_mechanism(self): test_obj = TestClass() + class WeakrefWrapper(object): + + def __init__(self): + self.ag_self_weakref__ = weakref.ref(test_obj) + def test_fn(self): return self - bound_method = types.MethodType( - test_fn, - function.TfMethodTarget( - weakref.ref(test_obj), test_obj.member_function)) + bound_method = types.MethodType(test_fn, WeakrefWrapper()) self.assertEqual(inspect_utils.getmethodclass(bound_method), TestClass) def test_getmethodclass_no_bool_conversion(self): diff --git a/tensorflow/python/data/ops/dataset_ops.py b/tensorflow/python/data/ops/dataset_ops.py index c0b120bf3c..2355a0abea 100644 --- a/tensorflow/python/data/ops/dataset_ops.py +++ b/tensorflow/python/data/ops/dataset_ops.py @@ -2366,8 +2366,6 @@ class StructuredFunctionWrapper(object): else: defun_kwargs.update({"func_name": func_name}) - # TODO(b/124254153): Enable autograph once the overhead is low enough. - # TODO(mdan): Make sure autograph recurses into _wrapper_helper when on. @eager_function.defun_with_attributes( input_signature=[ tensor_spec.TensorSpec(input_shape, input_type) # pylint: disable=g-complex-comprehension @@ -2375,7 +2373,6 @@ class StructuredFunctionWrapper(object): self._input_structure._flat_shapes, self._input_structure._flat_types) ], - autograph=False, attributes=defun_kwargs) def wrapper_fn(*args): # pylint: disable=missing-docstring ret = _wrapper_helper(*args) diff --git a/tensorflow/python/data/ops/multi_device_iterator_ops.py b/tensorflow/python/data/ops/multi_device_iterator_ops.py index f253f8bf42..36e1e311d3 100644 --- a/tensorflow/python/data/ops/multi_device_iterator_ops.py +++ b/tensorflow/python/data/ops/multi_device_iterator_ops.py @@ -42,15 +42,13 @@ class _PerDeviceGenerator(dataset_ops.DatasetV2): gen_dataset_ops.multi_device_iterator_to_string_handle( multi_device_iterator_resource)) - # TODO(b/124254153): Enable autograph once the overhead is low enough. - @function.defun(autograph=False) # Pure graph code. + @function.defun() def _init_func(): return multi_device_iterator_string_handle init_func_concrete = _init_func._get_concrete_function_internal() # pylint: disable=protected-access - # TODO(b/124254153): Enable autograph once the overhead is low enough. - @function.defun(autograph=False) # Pure graph code. + @function.defun() def _remote_init_func(): return functional_ops.remote_call( target=source_device, @@ -61,10 +59,7 @@ class _PerDeviceGenerator(dataset_ops.DatasetV2): self._init_func = _remote_init_func._get_concrete_function_internal() # pylint: disable=protected-access self._init_captured_args = self._init_func.captured_inputs - # TODO(b/124254153): Enable autograph once the overhead is low enough. - @function.defun( - input_signature=[tensor_spec.TensorSpec([], dtypes.string)], - autograph=False) # Pure graph code. + @function.defun(input_signature=[tensor_spec.TensorSpec([], dtypes.string)]) def _next_func(string_handle): # pylint: disable=protected-access multi_device_iterator = ( @@ -81,11 +76,9 @@ class _PerDeviceGenerator(dataset_ops.DatasetV2): next_func_concrete = _next_func._get_concrete_function_internal() # pylint: disable=protected-access - # TODO(b/124254153): Enable autograph once the overhead is low enough. @function.defun_with_attributes( input_signature=[tensor_spec.TensorSpec([], dtypes.string)], - attributes={"experimental_ints_on_device": True}, - autograph=False) # Pure graph code. + attributes={"experimental_ints_on_device": True}) def _remote_next_func(string_handle): return functional_ops.remote_call( target=source_device, @@ -101,19 +94,13 @@ class _PerDeviceGenerator(dataset_ops.DatasetV2): if arg == incarnation_id: self._incarnation_id_index = i - # TODO(b/124254153): Enable autograph once the overhead is low enough. - @function.defun( - input_signature=[tensor_spec.TensorSpec([], dtypes.string)], - autograph=False) # Pure graph code. + @function.defun(input_signature=[tensor_spec.TensorSpec([], dtypes.string)]) def _finalize_func(unused_string_handle): return array_ops.constant(0, dtypes.int64) finalize_func_concrete = _finalize_func._get_concrete_function_internal() # pylint: disable=protected-access - # TODO(b/124254153): Enable autograph once the overhead is low enough. - @function.defun( - input_signature=[tensor_spec.TensorSpec([], dtypes.string)], - autograph=False) # Pure graph code. + @function.defun(input_signature=[tensor_spec.TensorSpec([], dtypes.string)]) def _remote_finalize_func(string_handle): return functional_ops.remote_call( target=source_device, diff --git a/tensorflow/python/eager/def_function.py b/tensorflow/python/eager/def_function.py index 330b2e0a76..1d54973487 100644 --- a/tensorflow/python/eager/def_function.py +++ b/tensorflow/python/eager/def_function.py @@ -514,7 +514,7 @@ class Function(object): """Make and call a `ConcreteFunction` which initializes variables.""" # Note: using defun here avoids an infinite recursion. - @function_lib.defun(autograph=False) # Pure graph code. + @function_lib.defun def initialize_variables(): for v, init in initializer_map.items(): with ops.init_scope(): diff --git a/tensorflow/python/eager/function.py b/tensorflow/python/eager/function.py index 25a1080c04..6922f79a02 100644 --- a/tensorflow/python/eager/function.py +++ b/tensorflow/python/eager/function.py @@ -61,7 +61,6 @@ from tensorflow.python.util import tf_inspect FORWARD_FUNCTION_ATTRIBUTE_NAME = "forward_function_name" BACKWARD_FUNCTION_ATTRIBUTE_NAME = "backward_function_name" - class CacheKey( collections.namedtuple("CacheKey", [ "input_signature", "parent_graph", "device_functions", @@ -2020,23 +2019,13 @@ def defun_with_attributes(func=None, # When a method is bound to objects of this type, it allows AutoGraph to -# recover a weak reference the original method's self pointer, so that it can -# execute it consistent with class_method_to_instance_method's -# bound_method_wrapper. +# recover a weak reference the original method's self pointer. This uses the +# mechanism from pyct.inspect_utils.getmethodclass. # TODO(b/119246461): This is not pretty. Use a descriptor instead? -class TfMethodTarget(object): - """Binding target for methods replaced by function and defun.""" - - def __init__(self, target, original_python_function): - self.weakrefself_target__ = target - self.weakrefself_func__ = weakref.ref(original_python_function) - - @property - def target(self): - return self.weakrefself_target__() +class _WeakrefSelf(object): - def call(self, args, kwargs): - return self.weakrefself_func__()(*args, **kwargs) + def __init__(self, target): + self.ag_self_weakref__ = target def class_method_to_instance_method(original_function, instance): @@ -2045,9 +2034,8 @@ def class_method_to_instance_method(original_function, instance): # Note: while we could bind to a weakref proxy instead, that causes the # bound method to be unhashable. - bound_method = types_lib.MethodType( - original_function.python_function, - TfMethodTarget(weak_instance, original_function.python_function)) + bound_method = types_lib.MethodType(original_function.python_function, + _WeakrefSelf(weak_instance)) # original_function is expected to be of one of the two `Function` types # (defined either in function.py or def_function.py). @@ -2065,7 +2053,6 @@ def class_method_to_instance_method(original_function, instance): if wrapped_fn is strong_bound_method_wrapper.__original_wrapped__: # If __wrapped__ was not replaced, then call original_function. - # TODO(mdan): For better consistency, use the wrapper's call(). wrapped_fn = original_function.python_function if tf_inspect.ismethod(wrapped_fn): wrapped_fn = six.get_unbound_function(wrapped_fn) diff --git a/tensorflow/python/framework/func_graph.py b/tensorflow/python/framework/func_graph.py index fc02835399..91d80a388a 100644 --- a/tensorflow/python/framework/func_graph.py +++ b/tensorflow/python/framework/func_graph.py @@ -679,8 +679,7 @@ def func_graph_from_py_func(name, # Wrapping around a decorator allows checks like tf_inspect.getargspec # to be accurate. converted_func = tf_decorator.make_decorator(original_func, wrapper) - python_func = tf_decorator.rewrap(python_func, original_func, - converted_func) + tf_decorator.rewrap(python_func, original_func, converted_func) func_outputs = python_func(*func_args, **func_kwargs) diff --git a/tensorflow/python/util/tf_decorator.py b/tensorflow/python/util/tf_decorator.py index cf380509e9..6c71f70c4e 100644 --- a/tensorflow/python/util/tf_decorator.py +++ b/tensorflow/python/util/tf_decorator.py @@ -138,10 +138,6 @@ def rewrap(decorator_func, previous_target, new_target): decorator_func: Callable returned by `wrap`. previous_target: Callable that needs to be replaced. new_target: Callable to replace previous_target with. - - Returns: - The updated decorator. If decorator_func is not a tf_decorator, new_target - is returned. """ # Because the process mutates the decorator, we only need to alter the # innermost function that wraps previous_target. @@ -154,15 +150,9 @@ def rewrap(decorator_func, previous_target, new_target): if target.decorated_target is previous_target: break cur = target.decorated_target - assert cur is not None - # If decorator_func is not a decorator, new_target replaces it directly. if innermost_decorator is None: - # Consistency check. The caller should always pass the result of - # tf_decorator.unwrap as previous_target. If decorator_func is not a - # decorator, that will have returned decorator_func itself. - assert decorator_func is previous_target - return new_target + return target.decorated_target = new_target @@ -178,8 +168,6 @@ def rewrap(decorator_func, previous_target, new_target): else: innermost_decorator.__wrapped__ = new_target - return decorator_func - def unwrap(maybe_tf_decorator): """Unwraps an object into a list of TFDecorators and a final target. -- GitLab From ca823afe58bb5364f610ff61ae856c85cc89a180 Mon Sep 17 00:00:00 2001 From: Quan Wang Date: Mon, 25 Feb 2019 21:02:57 -0800 Subject: [PATCH 523/766] Allow TFLiteLSTMCell to accept dynamic input_shape. PiperOrigin-RevId: 235654762 --- tensorflow/lite/experimental/examples/lstm/rnn_cell.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tensorflow/lite/experimental/examples/lstm/rnn_cell.py b/tensorflow/lite/experimental/examples/lstm/rnn_cell.py index eeec76444d..44446fb509 100644 --- a/tensorflow/lite/experimental/examples/lstm/rnn_cell.py +++ b/tensorflow/lite/experimental/examples/lstm/rnn_cell.py @@ -291,10 +291,15 @@ class TFLiteLSTMCell(rnn_cell_impl.LayerRNNCell): Raises: ValueError: if the inputs_shape is invalid. """ - if len(inputs_shape) != 2 or inputs_shape[1].value is None: + if len(inputs_shape) != 2: + raise ValueError( + "inputs_shape must be 2-dimensional, saw shape: %s" % inputs_shape) + input_depth = ( + inputs_shape[1] + if isinstance(inputs_shape[1], int) else inputs_shape[1].value) + if input_depth is None: raise ValueError("Invalid inputs_shape, saw shape: %s" % inputs_shape) - input_depth = inputs_shape[1].value maybe_partitioner = ( partitioned_variables.fixed_size_partitioner(self._num_unit_shards) if self._num_unit_shards is not None else None) -- GitLab From 82741b867f1e92d2996f665503c7d4ba3d763129 Mon Sep 17 00:00:00 2001 From: Dan Moldovan Date: Mon, 25 Feb 2019 21:05:23 -0800 Subject: [PATCH 524/766] Dispatch to graph for queue types. PiperOrigin-RevId: 235655072 --- tensorflow/python/autograph/operators/control_flow.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tensorflow/python/autograph/operators/control_flow.py b/tensorflow/python/autograph/operators/control_flow.py index 0354c7286c..b58f971d54 100644 --- a/tensorflow/python/autograph/operators/control_flow.py +++ b/tensorflow/python/autograph/operators/control_flow.py @@ -20,6 +20,7 @@ from __future__ import print_function from tensorflow.python.autograph.operators import py_builtins from tensorflow.python.autograph.operators import special_values +from tensorflow.python.ops import data_flow_ops from tensorflow.python.data.ops import dataset_ops from tensorflow.python.framework import constant_op from tensorflow.python.framework import tensor_util @@ -177,7 +178,9 @@ def while_stmt(test, body, init_state, extra_deps, opts=None): # TODO(mdan): Consider adding a generic mechanism for dynamic dispatch. # That could be something as simple as a collection of dispatch rules, with # some prioritization. - if any(tensor_util.is_tensor(v) for v in nest.flatten(extra_deps)): + if any( + tensor_util.is_tensor(v) or isinstance(v, data_flow_ops.QueueBase) + for v in nest.flatten(extra_deps)): # Check for undefined symbols and report an error. This prevents the error # from propagating into the TF runtime. We have more information here and # can provide a clearer error message. -- GitLab From 173cf27476e311e11b1f3bea6d4ffd3f720b2c84 Mon Sep 17 00:00:00 2001 From: Smit Hinsu Date: Mon, 25 Feb 2019 22:03:13 -0800 Subject: [PATCH 525/766] Remove redundant namespace specifications from unqualified names in convert_nodes.cc No functional changes. PiperOrigin-RevId: 235660094 --- .../tf2tensorrt/convert/convert_nodes.cc | 522 +++++++++--------- 1 file changed, 254 insertions(+), 268 deletions(-) diff --git a/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.cc b/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.cc index 03064cbe4c..908aa5f4a4 100644 --- a/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.cc +++ b/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.cc @@ -57,10 +57,10 @@ limitations under the License. // would work! #define TFTRT_CHECK_EQ_TYPE(val1, val2) CHECK_EQ((int)val1, (int)val2) -#define TFTRT_INTERNAL_ERROR_AT_NODE(node) \ - do { \ - return tensorflow::errors::Internal( \ - "TFTRT::", __FUNCTION__, " failed to add TRT layer, at: ", node); \ +#define TFTRT_INTERNAL_ERROR_AT_NODE(node) \ + do { \ + return errors::Internal("TFTRT::", __FUNCTION__, \ + " failed to add TRT layer, at: ", node); \ } while (0) #define TFTRT_RETURN_ERROR_IF_FALSE(status, node) \ @@ -94,27 +94,26 @@ namespace convert { using absl::StrAppend; using absl::StrCat; -inline tensorflow::Status ConvertDType(tensorflow::DataType tf_dtype, - nvinfer1::DataType* trt_dtype) { +inline Status ConvertDType(DataType tf_dtype, nvinfer1::DataType* trt_dtype) { switch (tf_dtype) { - case tensorflow::DataType::DT_FLOAT: + case DataType::DT_FLOAT: *trt_dtype = nvinfer1::DataType::kFLOAT; break; // TODO(aaroey): this should be DT_QINT8 which is not a well supported type. - case tensorflow::DataType::DT_INT8: + case DataType::DT_INT8: *trt_dtype = nvinfer1::DataType::kINT8; break; - case tensorflow::DataType::DT_HALF: + case DataType::DT_HALF: *trt_dtype = nvinfer1::DataType::kHALF; break; - case tensorflow::DataType::DT_INT32: + case DataType::DT_INT32: *trt_dtype = nvinfer1::DataType::kINT32; break; default: - return tensorflow::errors::InvalidArgument( - "Unsupported data type ", tensorflow::DataTypeString(tf_dtype)); + return errors::InvalidArgument("Unsupported data type ", + DataTypeString(tf_dtype)); } - return tensorflow::Status::OK(); + return Status::OK(); } template @@ -135,13 +134,12 @@ Status TensorShapeArrayToTrtDims(const std::vector& shape, PartialTensorShape tensor_shape; TF_RETURN_IF_ERROR(TensorShapeUtils::MakeShape(shape, &tensor_shape)); *out = TensorShapeToTrtDims(tensor_shape, ignore_first_dim); - return tensorflow::Status::OK(); + return Status::OK(); } void GetOutputProperties(const grappler::GraphProperties& graph_properties, const Node* node, const int out_port, - PartialTensorShape* shape, - tensorflow::DataType* dtype) { + PartialTensorShape* shape, DataType* dtype) { if (graph_properties.HasOutputProperties(node->name())) { auto output_params = graph_properties.GetOutputProperties(node->name()); auto out_shape = output_params.at(out_port); @@ -155,8 +153,7 @@ void GetOutputProperties(const grappler::GraphProperties& graph_properties, void GetInputProperties(const grappler::GraphProperties& graph_properties, const Node* node, const int in_port, - PartialTensorShape* shape, - tensorflow::DataType* dtype) { + PartialTensorShape* shape, DataType* dtype) { if (graph_properties.HasInputProperties(node->name())) { auto input_params = graph_properties.GetInputProperties(node->name()); auto in_shape = input_params.at(in_port); @@ -168,7 +165,7 @@ void GetInputProperties(const grappler::GraphProperties& graph_properties, } Status ValidateTensorProperties(const string& producer_node_type, - const tensorflow::DataType dtype, + const DataType dtype, const PartialTensorShape& shape, bool validation_only, nvinfer1::DataType* trt_dtype, @@ -367,16 +364,16 @@ nvinfer1::ITensor* Converter::CreateConstantLayer( return trt_tensor; } -tensorflow::Status CreateBroadcastableScalarConstant( - OpConverterParams* params, float value, const nvinfer1::Dims& dims, - const nvinfer1::ITensor** tensor) { +Status CreateBroadcastableScalarConstant(OpConverterParams* params, float value, + const nvinfer1::Dims& dims, + const nvinfer1::ITensor** tensor) { // In order to be broadcastable, the number of dims has to match. nvinfer1::Dims broadcastable_dims(dims); for (int i = 0; i < broadcastable_dims.nbDims; i++) { broadcastable_dims.d[i] = 1; } TRT_ShapedWeights weights = params->weight_store->GetTempWeights( - tensorflow::DataType::DT_FLOAT, broadcastable_dims); + DataType::DT_FLOAT, broadcastable_dims); auto weights_ptr = static_cast(const_cast(weights.GetValues())); weights_ptr[0] = value; @@ -391,12 +388,12 @@ tensorflow::Status CreateBroadcastableScalarConstant( // includes the batch dimension, while TRT does not. TF can also use negative // indices. // TODO(tmorris): Use this method in more ops. -tensorflow::Status ConvertAxis(int tf_axis, int trt_nb_dims, - absl::string_view node_name, int* trt_axis) { +Status ConvertAxis(int tf_axis, int trt_nb_dims, absl::string_view node_name, + int* trt_axis) { const int tf_nb_dims = trt_nb_dims + 1; // Check bounds. if (tf_axis < -tf_nb_dims || tf_axis >= tf_nb_dims) { - return tensorflow::errors::InvalidArgument( + return errors::InvalidArgument( "Axis value of ", tf_axis, " is out of bounds, must be in range [", -tf_nb_dims, ", ", tf_nb_dims, "), at ", node_name); } @@ -404,7 +401,7 @@ tensorflow::Status ConvertAxis(int tf_axis, int trt_nb_dims, if (tf_axis < 0) tf_axis += tf_nb_dims; // Don't allow axis to be the batch dimension. if (tf_axis == 0) { - return tensorflow::errors::Unimplemented( + return errors::Unimplemented( "TensorRT does not allow manipulation of the batch dimension, at ", node_name); } @@ -435,7 +432,7 @@ bool AllLengthsEqual(const std::vector>& inputs) { return true; } -inline nvinfer1::Dims GetTrtDimsForTensor(const tensorflow::Tensor& tensor) { +inline nvinfer1::Dims GetTrtDimsForTensor(const Tensor& tensor) { nvinfer1::Dims dims; dims.nbDims = tensor.dims(); for (int i = 0; i < dims.nbDims; i++) { @@ -518,7 +515,7 @@ nvinfer1::Weights TRT_ShapedWeights::GetTrtWeights() const { } size_t TRT_ShapedWeights::size_bytes() const { - return this->count() * tensorflow::DataTypeSize(this->type_); + return this->count() * DataTypeSize(this->type_); } string TRT_ShapedWeights::DebugString() const { @@ -654,7 +651,7 @@ string TRT_TensorOrWeights::DebugString() const { class TFAttrs { public: - explicit TFAttrs(const tensorflow::NodeDef& tf_node) { + explicit TFAttrs(const NodeDef& tf_node) { for (const auto& attr : tf_node.attr()) { attrs_.insert({attr.first, &attr.second}); } @@ -662,7 +659,7 @@ class TFAttrs { bool count(const string& key) const { return attrs_.count(key); } - tensorflow::AttrValue const* at(const string& key) const { + AttrValue const* at(const string& key) const { if (!attrs_.count(key)) { LOG(FATAL) << "Attribute not found: " << key; } @@ -686,7 +683,7 @@ class TFAttrs { } private: - typedef std::map AttrMap; + typedef std::map AttrMap; AttrMap attrs_; }; @@ -715,8 +712,7 @@ nvinfer1::DataType TFAttrs::get(const string& key) const { } template <> -tensorflow::DataType TFAttrs::get( - const string& key) const { +DataType TFAttrs::get(const string& key) const { return this->at(key)->type(); } @@ -776,7 +772,7 @@ void ReorderCKtoKC(const TRT_ShapedWeights& iweights, const nvinfer1::DimsHW istrides = {1, k}; const nvinfer1::DimsHW ostrides = {c, 1}; switch (iweights.type_) { - case tensorflow::DataType::DT_FLOAT: { + case DataType::DT_FLOAT: { Reorder2({k, c}, static_cast(iweights.GetValues()), istrides, // TODO(aaroey): get rid of all the const_cast like this. @@ -784,7 +780,7 @@ void ReorderCKtoKC(const TRT_ShapedWeights& iweights, ostrides); break; } - case tensorflow::DataType::DT_HALF: { + case DataType::DT_HALF: { Reorder2( {k, c}, static_cast(iweights.GetValues()), istrides, @@ -820,14 +816,14 @@ void ReorderRSCKToKCRS(const TRT_ShapedWeights& iweights, const nvinfer1::DimsNCHW istrides = {1, k, s * k * c, c * k}; const nvinfer1::DimsNCHW ostrides = {c * r * s, r * s, s, 1}; switch (iweights.type_) { - case tensorflow::DataType::DT_FLOAT: { + case DataType::DT_FLOAT: { Reorder4({k, c, r, s}, static_cast(iweights.GetValues()), istrides, static_cast(const_cast(oweights->GetValues())), ostrides); break; } - case tensorflow::DataType::DT_HALF: { + case DataType::DT_HALF: { Reorder4( {k, c, r, s}, static_cast(iweights.GetValues()), istrides, @@ -842,7 +838,7 @@ void ReorderRSCKToKCRS(const TRT_ShapedWeights& iweights, } } -TRT_ShapedWeights TrtWeightStore::GetTempWeights(tensorflow::DataType type, +TRT_ShapedWeights TrtWeightStore::GetTempWeights(DataType type, const nvinfer1::Dims& dims) { TensorShape shape; // TODO(laigd): make it return a status. @@ -1130,11 +1126,11 @@ Status Converter::TransposeTensor(nvinfer1::ITensor* input_tensor, const auto dims = input_tensor->getDimensions(); if (order_with_batch_dim.size() - 1 != size_t(dims.nbDims)) { - return tensorflow::errors::InvalidArgument( + return errors::InvalidArgument( "Rank of perm for transpose does not match with that of the input."); } if (order_with_batch_dim[0] != 0) { - return tensorflow::errors::Unimplemented( + return errors::Unimplemented( "Transpose at batch dimension is not supported."); } @@ -1160,7 +1156,7 @@ Status Converter::TransposeTensor(nvinfer1::ITensor* input_tensor, layer->setReshapeDimensions(reshape_dims); *output_tensor = layer->getOutput(0); - return tensorflow::Status::OK(); + return Status::OK(); } Status Converter::GetWeightRange(const TRT_ShapedWeights& weights, @@ -1248,7 +1244,7 @@ Status Converter::PrepareTensorForShape(const TRT_TensorOrWeights& input, min_range, max_range); } } - return tensorflow::Status::OK(); + return Status::OK(); } void Converter::MarkQuantizationRangesAsInferrable(nvinfer1::ITensor* input, @@ -1346,7 +1342,7 @@ void Converter::PropagateQuantizationRanges() { } } -Status Converter::GetInputs(const tensorflow::NodeDef& node_def, +Status Converter::GetInputs(const NodeDef& node_def, std::vector* inputs) const { for (auto const& input_name : node_def.input()) { /************************************************************************* @@ -1381,48 +1377,48 @@ Status Converter::GetInputs(const tensorflow::NodeDef& node_def, StrAppend(&msg, node_def.name(), " should have an input named '", name, "' but it is not available"); LOG(ERROR) << msg; - return tensorflow::errors::InvalidArgument(msg); + return errors::InvalidArgument(msg); } } - return tensorflow::Status::OK(); + return Status::OK(); } // Checks that the number of inputs match, and enforces that the inputs marked // as true are constant weights. true means that the input must be a weight, // while false means the input must be a tensor. In the future, false will mean // the input can be a tensor or weight. -tensorflow::Status CheckInputsWeights( +Status CheckInputsWeights( const OpConverterParams& params, const std::vector>& inputs_is_weight) { const auto& inputs = params.inputs; const auto& node_def = params.node_def; if (inputs.size() != inputs_is_weight.size()) { - return tensorflow::errors::InvalidArgument( + return errors::InvalidArgument( node_def.op(), " got ", inputs.size(), " inputs but expected ", inputs_is_weight.size(), ", at ", node_def.name()); } for (int i = 0; i < inputs.size(); i++) { if (inputs_is_weight[i].second && inputs.at(i).is_tensor()) { - return tensorflow::errors::Unimplemented( - "The input \"", inputs_is_weight[i].first, "\" for ", node_def.op(), - " must be a constant, at ", node_def.name()); + return errors::Unimplemented("The input \"", inputs_is_weight[i].first, + "\" for ", node_def.op(), + " must be a constant, at ", node_def.name()); } // TODO(tmorris): Remove this check and provide a method to automatically // retrive an input as a tensor, converting via CreateConstantLayer if it // was originally a weight. We will want a caching mechanism to prevent many // duplicate constants from being created. if (!inputs_is_weight[i].second && inputs.at(i).is_weights()) { - return tensorflow::errors::Unimplemented( - "The input \"", inputs_is_weight[i].first, "\" for ", node_def.op(), - " must be a tensor, at ", node_def.name()); + return errors::Unimplemented("The input \"", inputs_is_weight[i].first, + "\" for ", node_def.op(), + " must be a tensor, at ", node_def.name()); } } - return tensorflow::Status::OK(); + return Status::OK(); } TRT_ShapedWeights ConvertFP32ToFP16(TrtWeightStore* store, const TRT_ShapedWeights& weights_src) { - auto dtype_new = tensorflow::DataType::DT_HALF; + auto dtype_new = DataType::DT_HALF; TRT_ShapedWeights weights = store->GetTempWeights(dtype_new, weights_src.shape_); const float* src = static_cast(weights_src.GetValues()); @@ -1481,18 +1477,17 @@ std::function LambdaFactory::unary() { } } -tensorflow::Status UnaryCompute(const TRT_ShapedWeights& iweights, - TRT_ShapedWeights* oweights, - LambdaFactory unary_op) { +Status UnaryCompute(const TRT_ShapedWeights& iweights, + TRT_ShapedWeights* oweights, LambdaFactory unary_op) { CHECK_EQ(iweights.type_, oweights->type_); switch (iweights.type_) { - case tensorflow::DataType::DT_FLOAT: { + case DataType::DT_FLOAT: { auto inp = static_cast(iweights.GetValues()); auto oup = static_cast(const_cast(oweights->GetValues())); std::transform(inp, inp + iweights.count(), oup, unary_op.unary()); break; } - case tensorflow::DataType::DT_HALF: { + case DataType::DT_HALF: { auto inp = static_cast(iweights.GetValues()); auto oup = static_cast(const_cast(oweights->GetValues())); @@ -1501,11 +1496,10 @@ tensorflow::Status UnaryCompute(const TRT_ShapedWeights& iweights, break; } default: - return tensorflow::errors::Unimplemented( - "Data type not supported: " + - tensorflow::DataTypeString(iweights.type_)); + return errors::Unimplemented("Data type not supported: " + + DataTypeString(iweights.type_)); } - return tensorflow::Status::OK(); + return Status::OK(); } // If swapped_inputs is false, 'tensor' is the left operand and 'weights' is the @@ -1702,11 +1696,11 @@ Status BinaryTensorOpWeight(OpConverterParams* params, // Pass the output params->outputs->push_back( TRT_TensorOrWeights(const_cast(output_tensor))); - return tensorflow::Status::OK(); + return Status::OK(); } -tensorflow::Status ConvertConv2DHelper(OpConverterParams* params, int group, - bool is_conv2d_backprop_input) { +Status ConvertConv2DHelper(OpConverterParams* params, int group, + bool is_conv2d_backprop_input) { const auto& inputs = params->inputs; const auto& node_def = params->node_def; TRT_TensorOrWeights backprop_output_size; @@ -1726,8 +1720,8 @@ tensorflow::Status ConvertConv2DHelper(OpConverterParams* params, int group, } TRT_ShapedWeights weights_rsck = inputs.at(1).weights(); if (weights_rsck.shape_.nbDims != 4) { - return tensorflow::errors::InvalidArgument( - "Conv2D expects kernel of dimension 4, at " + node_def.name()); + return errors::InvalidArgument("Conv2D expects kernel of dimension 4, at " + + node_def.name()); } TFAttrs attrs(node_def); auto data_format = attrs.get("data_format"); @@ -1736,35 +1730,35 @@ tensorflow::Status ConvertConv2DHelper(OpConverterParams* params, int group, int w_index = (data_format == "NHWC") ? 2 : 3; auto tf_dilations = attrs.get>("dilations"); if (tf_dilations.size() != 4) { - return tensorflow::errors::InvalidArgument( + return errors::InvalidArgument( "Convolution dilations field must specify 4 dimensions, at ", node_def.name()); } if (tf_dilations[0] != 1 || tf_dilations[c_index] != 1) { - return tensorflow::errors::Unimplemented( + return errors::Unimplemented( "Dilation rate must be 1 for batch and channel dimensions, at ", node_def.name()); } const nvinfer1::DimsHW dilation(tf_dilations[h_index], tf_dilations[w_index]); if (is_conv2d_backprop_input && (dilation.d[0] != 1 || dilation.d[1] != 1)) { - return tensorflow::errors::Unimplemented( + return errors::Unimplemented( "Dilation with Conv2DBackpropInput (conv2d_transpose) is not supported", ", at ", node_def.name()); } const auto tf_stride = attrs.get>("strides"); if (tf_stride.size() != 4) { - return tensorflow::errors::InvalidArgument( + return errors::InvalidArgument( "Convolution strides field must specify 4 dimensions, at ", node_def.name()); } if (tf_stride[0] != 1 || tf_stride[c_index] != 1) { - return tensorflow::errors::Unimplemented( + return errors::Unimplemented( "Stride must be 1 for batch and channel dimensions, at ", node_def.name()); } const nvinfer1::DimsHW stride(tf_stride[h_index], tf_stride[w_index]); - if (params->validation_only) return tensorflow::Status::OK(); + if (params->validation_only) return Status::OK(); // Transpose to NCHW (NCHW is required for IConvLayer). const bool need_transpose = (data_format == "NHWC"); @@ -1872,7 +1866,7 @@ tensorflow::Status ConvertConv2DHelper(OpConverterParams* params, int group, } params->outputs->push_back( TRT_TensorOrWeights(const_cast(output_tensor))); - return tensorflow::Status::OK(); + return Status::OK(); } Status BinaryTensorOpTensor(OpConverterParams* params, @@ -1940,10 +1934,10 @@ Status BinaryTensorOpTensor(OpConverterParams* params, // Pass the output params->outputs->push_back(TRT_TensorOrWeights(output_tensor)); - return tensorflow::Status::OK(); + return Status::OK(); } -tensorflow::Status ConvertPlugin(OpConverterParams* params) { +Status ConvertPlugin(OpConverterParams* params) { const auto& inputs = params->inputs; const auto& node_def = params->node_def; // prepare input @@ -1968,7 +1962,7 @@ tensorflow::Status ConvertPlugin(OpConverterParams* params) { size_t size_data = data.size() * sizeof(float); if (!plugin->SetAttribute(attr_key, static_cast(data.data()), size_data)) { - return tensorflow::errors::InvalidArgument("plugin SetAttribute failed"); + return errors::InvalidArgument("plugin SetAttribute failed"); } } @@ -1979,10 +1973,10 @@ tensorflow::Status ConvertPlugin(OpConverterParams* params) { nvinfer1::ITensor* output_tensor = layer->getOutput(i); params->outputs->push_back(TRT_TensorOrWeights(output_tensor)); } - return tensorflow::Status::OK(); + return Status::OK(); } -tensorflow::Status ConvertTranspose(OpConverterParams* params) { +Status ConvertTranspose(OpConverterParams* params) { const auto& inputs = params->inputs; TF_RETURN_IF_ERROR( CheckInputsWeights(*params, {{"x", false}, {"perm", true}})); @@ -2012,10 +2006,10 @@ tensorflow::Status ConvertTranspose(OpConverterParams* params) { params->converter->TransposeTensor(input_tensor, perm, &output_tensor)); params->outputs->push_back( TRT_TensorOrWeights(const_cast(output_tensor))); - return tensorflow::Status::OK(); + return Status::OK(); } -tensorflow::Status ConvertReshape(OpConverterParams* params) { +Status ConvertReshape(OpConverterParams* params) { const auto& inputs = params->inputs; const auto& node_def = params->node_def; TF_RETURN_IF_ERROR( @@ -2023,8 +2017,8 @@ tensorflow::Status ConvertReshape(OpConverterParams* params) { TRT_TensorOrWeights input_tensor = inputs.at(0); TRT_ShapedWeights weights = inputs.at(1).weights(); if (weights.count() == 0) { - return tensorflow::errors::Unimplemented( - "Reshape to shape=[] is not supported, at ", node_def.name()); + return errors::Unimplemented("Reshape to shape=[] is not supported, at ", + node_def.name()); } const int* weights_ptr = @@ -2109,10 +2103,10 @@ tensorflow::Status ConvertReshape(OpConverterParams* params) { input_tensor, reshape_dims, &output_tensor)); params->outputs->push_back( TRT_TensorOrWeights(const_cast(output_tensor))); - return tensorflow::Status::OK(); + return Status::OK(); } -tensorflow::Status ConvertExpandDims(OpConverterParams* params) { +Status ConvertExpandDims(OpConverterParams* params) { const auto& inputs = params->inputs; const auto& node_def = params->node_def; TF_RETURN_IF_ERROR( @@ -2127,15 +2121,15 @@ tensorflow::Status ConvertExpandDims(OpConverterParams* params) { // Get axis to expand on. TRT_ShapedWeights weights = inputs.at(1).weights(); if (weights.count() != 1) { - return tensorflow::errors::InvalidArgument( - "ExpandDims axis must be a scalar, at ", node_def.name()); + return errors::InvalidArgument("ExpandDims axis must be a scalar, at ", + node_def.name()); } const int* weights_ptr = static_cast(const_cast(weights.GetValues())); int axis = weights_ptr[0]; // Make sure axis is valid. if ((axis < (-input_rank - 1)) || (axis > input_rank)) { - return tensorflow::errors::InvalidArgument( + return errors::InvalidArgument( "Axis for ExpandDims is invalid, must be in the range " "[-rank(input) - 1, rank(input)], at ", node_def.name()); @@ -2143,7 +2137,7 @@ tensorflow::Status ConvertExpandDims(OpConverterParams* params) { // Convert negative axis to corresponding positive axis. if (axis < 0) axis += input_rank + 1; if (axis == 0) { - return tensorflow::errors::Unimplemented( + return errors::Unimplemented( "Modifying batch dimension is not supported for ExpandDims, at ", node_def.name()); } @@ -2160,10 +2154,10 @@ tensorflow::Status ConvertExpandDims(OpConverterParams* params) { input_tensor, new_dims, &output_tensor)); params->outputs->push_back( TRT_TensorOrWeights(const_cast(output_tensor))); - return tensorflow::Status::OK(); + return Status::OK(); } -tensorflow::Status ConvertSqueeze(OpConverterParams* params) { +Status ConvertSqueeze(OpConverterParams* params) { const auto& inputs = params->inputs; const auto& node_def = params->node_def; TF_RETURN_IF_ERROR(CheckInputsWeights(*params, {{"input", false}})); @@ -2178,13 +2172,13 @@ tensorflow::Status ConvertSqueeze(OpConverterParams* params) { TFAttrs attrs(node_def); auto squeeze_dims = attrs.get>("squeeze_dims"); if (squeeze_dims.empty()) { - return tensorflow::errors::Unimplemented( + return errors::Unimplemented( "Squeeze is only implemented for explicit dims, at ", node_def.name()); } for (int axis : squeeze_dims) { // Make sure axis is valid. if ((axis < -input_rank) || (axis >= input_rank)) { - return tensorflow::errors::InvalidArgument( + return errors::InvalidArgument( "Axis for Squeeze is invalid, must be in the range " "[-rank(input), rank(input)), at ", node_def.name()); @@ -2193,12 +2187,12 @@ tensorflow::Status ConvertSqueeze(OpConverterParams* params) { if (axis < 0) axis += input_rank; // Don't squeeze batch dim. if (axis == 0) { - return tensorflow::errors::Unimplemented( - "Cannot squeeze batch dimension, at ", node_def.name()); + return errors::Unimplemented("Cannot squeeze batch dimension, at ", + node_def.name()); } // Make sure target dimension is size 1. if (input_dims[axis] != 1) { - return tensorflow::errors::InvalidArgument( + return errors::InvalidArgument( "Cannot squeeze a dimension which isn't size 1, at ", node_def.name()); } @@ -2219,14 +2213,13 @@ tensorflow::Status ConvertSqueeze(OpConverterParams* params) { input_tensor, new_dims, &output_tensor)); params->outputs->push_back( TRT_TensorOrWeights(const_cast(output_tensor))); - return tensorflow::Status::OK(); + return Status::OK(); } -tensorflow::Status ConvertStridedSliceHelper(OpConverterParams* params, - const TRT_TensorOrWeights& input, - std::vector begin, - std::vector size, - const std::vector& stride) { +Status ConvertStridedSliceHelper(OpConverterParams* params, + const TRT_TensorOrWeights& input, + std::vector begin, std::vector size, + const std::vector& stride) { const auto& node_def = params->node_def; // Get input dims. nvinfer1::Dims dims = input.GetTrtDims(); @@ -2236,20 +2229,19 @@ tensorflow::Status ConvertStridedSliceHelper(OpConverterParams* params, // Check bounds. for (int i = 1; i < input_dims.size(); i++) { if (begin[i] < 0 || begin[i] > input_dims[i]) { - return tensorflow::errors::InvalidArgument( - "\"begin\" for dimension ", std::to_string(i), " in ", node_def.op(), - " is out of range, at ", node_def.name()); + return errors::InvalidArgument("\"begin\" for dimension ", + std::to_string(i), " in ", node_def.op(), + " is out of range, at ", node_def.name()); } const int end = begin[i] + size[i]; if (end < 0 || end > input_dims[i]) { - return tensorflow::errors::InvalidArgument( - "\"begin\" + \"size\" for dimension ", std::to_string(i), " in ", - node_def.op(), " is out of range, at ", node_def.name()); + return errors::InvalidArgument("\"begin\" + \"size\" for dimension ", + std::to_string(i), " in ", node_def.op(), + " is out of range, at ", node_def.name()); } if (size[i] <= 0) { - return tensorflow::errors::InvalidArgument( - "\"size\" cannot be negative or zero for ", node_def.op(), ", at ", - node_def.name()); + return errors::InvalidArgument("\"size\" cannot be negative or zero for ", + node_def.op(), ", at ", node_def.name()); } } // TRT 5.1 adds a slice layer. For older versions, we attempt to use the @@ -2269,13 +2261,13 @@ tensorflow::Status ConvertStridedSliceHelper(OpConverterParams* params, *const_cast(input.tensor()), begin_dims, size_dims, stride_dims); params->outputs->push_back(TRT_TensorOrWeights(layer->getOutput(0))); - return tensorflow::Status::OK(); + return Status::OK(); #else // Use IPaddingLayer. // Strides must be 1 in this case. for (int x : stride) { if (x != 1) { - return tensorflow::errors::Unimplemented( + return errors::Unimplemented( "Strides other than 1 are not supported with this version of TRT, " "at ", node_def.name()); @@ -2283,11 +2275,11 @@ tensorflow::Status ConvertStridedSliceHelper(OpConverterParams* params, } // Rank must be 2, 3 or 4. if (input_dims.size() > 4) { - return tensorflow::errors::Unimplemented(node_def.op(), - " for tensors with rank > 4 is " - "not supported in this version of " - "TRT, at ", - node_def.name()); + return errors::Unimplemented(node_def.op(), + " for tensors with rank > 4 is " + "not supported in this version of " + "TRT, at ", + node_def.name()); } // Reshape if necessary to 4-D, since IPaddingLayer requires a 4-D input. const bool need_reshape = (input_dims.size() != 4); @@ -2322,7 +2314,7 @@ tensorflow::Status ConvertStridedSliceHelper(OpConverterParams* params, nvinfer1::IShuffleLayer* layer = params->converter->network()->addShuffle( *const_cast(input.tensor())); params->outputs->push_back(TRT_TensorOrWeights(layer->getOutput(0))); - return tensorflow::Status::OK(); + return Status::OK(); } else if (pad_dims.size() == 1) { // Only one dim is modified but we have to have 2, mark a second dim which // will have padding of 0. The dim we add is chosen to avoid an unecessary @@ -2333,7 +2325,7 @@ tensorflow::Status ConvertStridedSliceHelper(OpConverterParams* params, pad_dims.push_back(3); } } else if (pad_dims.size() > 2) { - return tensorflow::errors::Unimplemented( + return errors::Unimplemented( node_def.op(), " can only modify up to 2 dimensions in this version of TRT, at ", node_def.name()); @@ -2404,8 +2396,8 @@ tensorflow::Status ConvertStridedSliceHelper(OpConverterParams* params, for (int i = 0; i < reshape_dims_added; i++) { int value = input_dims[1]; if (value != 1) { - return tensorflow::errors::Internal( - "StridedSlice error when reshaping, at ", node_def.name()); + return errors::Internal("StridedSlice error when reshaping, at ", + node_def.name()); } input_dims.erase(input_dims.begin() + 1); } @@ -2421,11 +2413,11 @@ tensorflow::Status ConvertStridedSliceHelper(OpConverterParams* params, params->outputs->push_back( TRT_TensorOrWeights(const_cast(tensor))); - return tensorflow::Status::OK(); + return Status::OK(); #endif } -tensorflow::Status ConvertSlice(OpConverterParams* params) { +Status ConvertSlice(OpConverterParams* params) { const auto& inputs = params->inputs; const auto& node_def = params->node_def; TF_RETURN_IF_ERROR(CheckInputsWeights( @@ -2438,7 +2430,7 @@ tensorflow::Status ConvertSlice(OpConverterParams* params) { // Add batch dimension so that indexes line up properly. input_dims.insert(input_dims.begin(), inputs.at(0).batch_size()); if (!AllLengthsEqual({input_dims, begin, size})) { - return tensorflow::errors::InvalidArgument( + return errors::InvalidArgument( "Length of begin and size arguments must equal rank of input for " "Slice, at ", node_def.name()); @@ -2453,7 +2445,7 @@ tensorflow::Status ConvertSlice(OpConverterParams* params) { size[0] != -1 && (!batch_size_is_defined || (batch_size_is_defined && size[0] != input_dims[0])); if (begin_is_modified || size_is_modified) { - return tensorflow::errors::Unimplemented( + return errors::Unimplemented( "TensorRT does not allow modifications to the batch dimension, at ", node_def.name()); } @@ -2468,7 +2460,7 @@ tensorflow::Status ConvertSlice(OpConverterParams* params) { return ConvertStridedSliceHelper(params, inputs.at(0), begin, size, stride); } -tensorflow::Status ConvertStridedSlice(OpConverterParams* params) { +Status ConvertStridedSlice(OpConverterParams* params) { const auto& inputs = params->inputs; const auto& node_def = params->node_def; TF_RETURN_IF_ERROR(CheckInputsWeights( @@ -2484,7 +2476,7 @@ tensorflow::Status ConvertStridedSlice(OpConverterParams* params) { std::vector end = inputs.at(2).weights().ToVector(); std::vector stride = inputs.at(3).weights().ToVector(); if (!AllLengthsEqual({input_dims, begin, end, stride})) { - return tensorflow::errors::InvalidArgument( + return errors::InvalidArgument( "Length of begin, end, and stride arguments must equal rank of input " "for StridedSlice, at ", node_def.name()); @@ -2495,7 +2487,7 @@ tensorflow::Status ConvertStridedSlice(OpConverterParams* params) { {"ellipsis_mask", "new_axis_mask", "shrink_axis_mask"}) { int attr_val = attrs.get(attr); if (attr_val != 0) { - return tensorflow::errors::Unimplemented( + return errors::Unimplemented( attr, " is not supported for StridedSlice, at ", node_def.name()); } } @@ -2512,7 +2504,7 @@ tensorflow::Status ConvertStridedSlice(OpConverterParams* params) { !(end_mask & 1) && (!batch_size_is_defined || (batch_size_is_defined && end[0] != input_dims[0])); if (begin_is_modified || stride_is_modified || end_is_modified) { - return tensorflow::errors::Unimplemented( + return errors::Unimplemented( "TensorRT does not allow modifications to the batch dimension, at ", node_def.name()); } @@ -2538,7 +2530,7 @@ tensorflow::Status ConvertStridedSlice(OpConverterParams* params) { // Negative or zero strides currently not supported. for (int i = 0; i < input_dims.size(); i++) { if (stride[i] <= 0) { - return tensorflow::errors::Unimplemented( + return errors::Unimplemented( "Negative or zero stride values are not supported for StridedSlice, " "at ", node_def.name()); @@ -2553,19 +2545,19 @@ tensorflow::Status ConvertStridedSlice(OpConverterParams* params) { return ConvertStridedSliceHelper(params, inputs.at(0), begin, size, stride); } -tensorflow::Status ConvertConv2D(OpConverterParams* params) { +Status ConvertConv2D(OpConverterParams* params) { return ConvertConv2DHelper(params, 1, /*is_conv2d_backprop_input=*/false); } -tensorflow::Status ConvertConv2DDepthwise(OpConverterParams* params) { +Status ConvertConv2DDepthwise(OpConverterParams* params) { return ConvertConv2DHelper(params, 0, /*is_conv2d_backprop_input=*/false); } -tensorflow::Status ConvertConv2DBackpropInput(OpConverterParams* params) { +Status ConvertConv2DBackpropInput(OpConverterParams* params) { return ConvertConv2DHelper(params, 1, /*is_conv2d_backprop_input=*/true); } -tensorflow::Status ConvertPool(OpConverterParams* params) { +Status ConvertPool(OpConverterParams* params) { const auto& inputs = params->inputs; const auto& node_def = params->node_def; TF_RETURN_IF_ERROR(CheckInputsWeights(*params, {{"input", false}})); @@ -2575,14 +2567,14 @@ tensorflow::Status ConvertPool(OpConverterParams* params) { } else if (node_def.op() == "AvgPool") { type = nvinfer1::PoolingType::kAVERAGE; } else { - return tensorflow::errors::Unimplemented( - "Unsupported pooling type: ", node_def.op(), ", at ", node_def.name()); + return errors::Unimplemented("Unsupported pooling type: ", node_def.op(), + ", at ", node_def.name()); } TFAttrs attrs(node_def); const string padding_type = attrs.get("padding"); if ((padding_type != "SAME") && (padding_type != "VALID")) { - return tensorflow::errors::Unimplemented( - "Unsupported padding type: ", padding_type, ", at ", node_def.name()); + return errors::Unimplemented("Unsupported padding type: ", padding_type, + ", at ", node_def.name()); } if (params->validation_only) return Status::OK(); @@ -2652,31 +2644,31 @@ tensorflow::Status ConvertPool(OpConverterParams* params) { } params->outputs->push_back( TRT_TensorOrWeights(const_cast(output_tensor))); - return tensorflow::Status::OK(); + return Status::OK(); } // TODO(tmorris): Use ActivationType::kLEAKY_RELU in TRT 5.1+ once perf // improves. -tensorflow::Status ConvertLeakyRelu(OpConverterParams* params) { +Status ConvertLeakyRelu(OpConverterParams* params) { const auto& inputs = params->inputs; const auto& node_def = params->node_def; if (inputs.size() != 1) { - return tensorflow::errors::InvalidArgument( - node_def.op(), " expects one input, at ", node_def.name()); + return errors::InvalidArgument(node_def.op(), " expects one input, at ", + node_def.name()); } if (!inputs.at(0).is_tensor()) { - return tensorflow::errors::Unimplemented( - node_def.op(), " is only implemented for tensors, at ", - node_def.name()); + return errors::Unimplemented(node_def.op(), + " is only implemented for tensors, at ", + node_def.name()); } TFAttrs attrs(node_def); const float alpha = attrs.get("alpha"); if (alpha < 0.0f || alpha > 1.0f) { - return tensorflow::errors::Unimplemented( + return errors::Unimplemented( "Alpha value for LeakyRelu must be between 0 and 1, at ", node_def.name()); } - if (params->validation_only) return tensorflow::Status::OK(); + if (params->validation_only) return Status::OK(); // Input Tensor const nvinfer1::ITensor* tensor = inputs.at(0).tensor(); @@ -2706,7 +2698,7 @@ tensorflow::Status ConvertLeakyRelu(OpConverterParams* params) { return Status::OK(); } -tensorflow::Status ConvertActivation(OpConverterParams* params) { +Status ConvertActivation(OpConverterParams* params) { const auto& inputs = params->inputs; const auto& node_def = params->node_def; TF_RETURN_IF_ERROR(CheckInputsWeights(*params, {{"input", false}})); @@ -2717,11 +2709,10 @@ tensorflow::Status ConvertActivation(OpConverterParams* params) { }; auto op_pair = ops.find(node_def.op()); if (op_pair == ops.end()) { - return tensorflow::errors::Unimplemented( - "Activation op: ", node_def.op(), - " not supported at: ", node_def.name()); + return errors::Unimplemented("Activation op: ", node_def.op(), + " not supported at: ", node_def.name()); } - if (params->validation_only) return tensorflow::Status::OK(); + if (params->validation_only) return Status::OK(); // Start conversion. const nvinfer1::ITensor* tensor = inputs.at(0).tensor(); @@ -2737,7 +2728,7 @@ tensorflow::Status ConvertActivation(OpConverterParams* params) { params->converter->ProvideQuantizationRange(output_tensor, -1.0f, 1.0f); } params->outputs->push_back(TRT_TensorOrWeights(output_tensor)); - return tensorflow::Status::OK(); + return Status::OK(); } Status ConvertQuantize(OpConverterParams* params) { @@ -2803,7 +2794,7 @@ Status ConvertQuantize(OpConverterParams* params) { } // TODO(tmorris): Use ActivationType::kCLIP in TRT 5.1+ once perf improves. -tensorflow::Status ConvertRelu6(OpConverterParams* params) { +Status ConvertRelu6(OpConverterParams* params) { const auto& inputs = params->inputs; const auto& node_def = params->node_def; TF_RETURN_IF_ERROR(CheckInputsWeights(*params, {{"input", false}})); @@ -2851,13 +2842,13 @@ tensorflow::Status ConvertRelu6(OpConverterParams* params) { return Status::OK(); } -tensorflow::Status ConvertBiasAdd(OpConverterParams* params) { +Status ConvertBiasAdd(OpConverterParams* params) { const auto& inputs = params->inputs; const auto& node_def = params->node_def; TF_RETURN_IF_ERROR( CheckInputsWeights(*params, {{"value", false}, {"bias", true}})); TFAttrs attrs(node_def); - tensorflow::DataType tf_dtype = attrs.get("T"); + DataType tf_dtype = attrs.get("T"); if (tf_dtype != DataType::DT_FLOAT && tf_dtype != DataType::DT_HALF) { return errors::Unimplemented("Data type is not supported, for node ", node_def.name(), " got ", @@ -3029,7 +3020,7 @@ Status TfTensorToTrtWeights(const Tensor& tensor, TrtWeightStore* weight_store, // weights to params->outputs. We did this since TrtNodeValidator needs the // weights as input to other nodes, and use it to determine whether those nodes // are supported by TRT. -tensorflow::Status ConvertConst(OpConverterParams* params) { +Status ConvertConst(OpConverterParams* params) { const auto& inputs = params->inputs; const auto& node_def = params->node_def; if (!inputs.empty()) { @@ -3040,14 +3031,14 @@ tensorflow::Status ConvertConst(OpConverterParams* params) { // Create shaped weights as output const auto& tensor_proto = node_def.attr().at("value").tensor(); - tensorflow::Tensor tensor; + Tensor tensor; if (!tensor.FromProto(tensor_proto)) { - return tensorflow::errors::Internal("Cannot parse weight tensor proto: ", - node_def.name()); + return errors::Internal("Cannot parse weight tensor proto: ", + node_def.name()); } TFAttrs attrs(node_def); - const DataType dtype = attrs.get("dtype"); + const DataType dtype = attrs.get("dtype"); if (dtype != tensor.dtype()) { return errors::InvalidArgument("DataType mismatch between attr (", DataTypeString(dtype), ") and tensor (", @@ -3064,7 +3055,7 @@ tensorflow::Status ConvertConst(OpConverterParams* params) { return Status::OK(); } -tensorflow::Status ConvertIdentity(OpConverterParams* params) { +Status ConvertIdentity(OpConverterParams* params) { // TODO(tmorris): TRT's Identity layer does not get optimized away as of TRT // 5.0, however once we know that it does it would be nice to use that // instead. @@ -3080,9 +3071,9 @@ Status ConvertBinary(OpConverterParams* params) { // TF_RETURN_IF_ERROR(CheckInputsWeights(*params, {{"x", false}, {"y", // false}})); if (inputs.size() != 2) { - return tensorflow::errors::InvalidArgument( - node_def.op(), " got ", inputs.size(), " inputs but expected 2, at ", - node_def.name()); + return errors::InvalidArgument(node_def.op(), " got ", inputs.size(), + " inputs but expected 2, at ", + node_def.name()); } // Constant folding should have been done by TensorFlow @@ -3121,11 +3112,11 @@ Status ConvertBinary(OpConverterParams* params) { return status; } -tensorflow::Status ConvertRsqrt(OpConverterParams* params) { +Status ConvertRsqrt(OpConverterParams* params) { const auto& inputs = params->inputs; const auto& node_def = params->node_def; TF_RETURN_IF_ERROR(CheckInputsWeights(*params, {{"x", false}})); - if (params->validation_only) return tensorflow::Status::OK(); + if (params->validation_only) return Status::OK(); // TODO(tmorris): params->converter is null during validation. Allow // precision_mode and use_calibration to be accessed during validation and @@ -3155,7 +3146,7 @@ tensorflow::Status ConvertRsqrt(OpConverterParams* params) { *sqrt_layer->getOutput(0), nvinfer1::UnaryOperation::kRECIP); TFTRT_RETURN_ERROR_IF_NULLPTR(recip_layer, node_def.name()); params->outputs->push_back(TRT_TensorOrWeights(recip_layer->getOutput(0))); - return tensorflow::Status::OK(); + return Status::OK(); } const std::unordered_map* @@ -3187,16 +3178,16 @@ UnaryOperationMap() { return m; } -tensorflow::Status ConvertUnary(OpConverterParams* params) { +Status ConvertUnary(OpConverterParams* params) { const auto& inputs = params->inputs; const auto& node_def = params->node_def; TF_RETURN_IF_ERROR(CheckInputsWeights(*params, {{"x", false}})); auto op_pair = UnaryOperationMap()->find(node_def.op()); if (op_pair == UnaryOperationMap()->end()) { - return tensorflow::errors::Unimplemented( - "Unary op: ", node_def.op(), " not supported at: ", node_def.name()); + return errors::Unimplemented("Unary op: ", node_def.op(), + " not supported at: ", node_def.name()); } - if (params->validation_only) return tensorflow::Status::OK(); + if (params->validation_only) return Status::OK(); // Start conversion. const nvinfer1::ITensor* tensor = inputs.at(0).tensor(); @@ -3221,10 +3212,10 @@ tensorflow::Status ConvertUnary(OpConverterParams* params) { } params->outputs->push_back( TRT_TensorOrWeights(const_cast(output_tensor))); - return tensorflow::Status::OK(); + return Status::OK(); } -tensorflow::Status ConvertSquare(OpConverterParams* params) { +Status ConvertSquare(OpConverterParams* params) { const auto& inputs = params->inputs; const auto& node_def = params->node_def; TF_RETURN_IF_ERROR(CheckInputsWeights(*params, {{"x", false}})); @@ -3235,8 +3226,8 @@ tensorflow::Status ConvertSquare(OpConverterParams* params) { for (int i = 0; i < dims.nbDims; i++) { dims.d[i] = 1; } - TRT_ShapedWeights weights = params->weight_store->GetTempWeights( - tensorflow::DataType::DT_FLOAT, dims); + TRT_ShapedWeights weights = + params->weight_store->GetTempWeights(DataType::DT_FLOAT, dims); auto weights_ptr = static_cast(const_cast(weights.GetValues())); weights_ptr[0] = 2.f; @@ -3253,7 +3244,7 @@ tensorflow::Status ConvertSquare(OpConverterParams* params) { nvinfer1::ITensor* output_tensor = layer->getOutput(0); params->outputs->push_back(TRT_TensorOrWeights(output_tensor)); - return tensorflow::Status::OK(); + return Status::OK(); } Status ConvertReduce(OpConverterParams* params) { @@ -3317,7 +3308,7 @@ Status ConvertReduce(OpConverterParams* params) { return Status::OK(); } -tensorflow::Status ConvertPad(OpConverterParams* params) { +Status ConvertPad(OpConverterParams* params) { const auto& inputs = params->inputs; const auto& node_def = params->node_def; TF_RETURN_IF_ERROR( @@ -3334,19 +3325,18 @@ tensorflow::Status ConvertPad(OpConverterParams* params) { TFAttrs attrs(node_def); // Padding type here is done through TF type // so I can leverage their EnumToDataType for my cast - auto padding_type = attrs.get("Tpaddings"); + auto padding_type = attrs.get("Tpaddings"); // TODO(jie): handle data type conversion for TRT? if (pads.shape_.d[0] != nb_dims || pads.shape_.d[1] != 2) { - return tensorflow::errors::InvalidArgument( + return errors::InvalidArgument( "Pad only supports explicit padding on 4 dimensional tensor, at ", node_def.name()); } // Only expect to handle INT32 as attributes for now - if (padding_type != tensorflow::DataType::DT_INT32) { - return tensorflow::errors::Unimplemented( - "Tpaddings supports only DT_INT32"); + if (padding_type != DataType::DT_INT32) { + return errors::Unimplemented("Tpaddings supports only DT_INT32"); } auto pad_data = static_cast(const_cast(pads.GetValues())); @@ -3360,25 +3350,25 @@ tensorflow::Status ConvertPad(OpConverterParams* params) { // No padding at all, we should exit if (pad_index.empty()) { params->outputs->push_back(inputs.at(0)); - return tensorflow::Status::OK(); + return Status::OK(); } // Only supports padding on less than 2 axis GIE-2579 if (pad_index.size() > 2) { - return tensorflow::errors::InvalidArgument( + return errors::InvalidArgument( "Padding layer does not support padding on > 2"); } // Padding on batch dimension is not supported if (pad_index[0] == 0) { - return tensorflow::errors::InvalidArgument( + return errors::InvalidArgument( "Padding layer does not support padding on batch dimension"); } // Not doing the legit thing here. ignoring padding on dim 1 and 3; // TODO(jie): implement pad as uff parser if (pad_index.size() == 2 && pad_index[0] == 0 && pad_index[1] == 3) { - return tensorflow::errors::Unimplemented( + return errors::Unimplemented( "Padding layer does not support padding on dimension 1 and 3 yet"); } if (params->validation_only) return Status::OK(); @@ -3419,17 +3409,17 @@ tensorflow::Status ConvertPad(OpConverterParams* params) { params->outputs->push_back( TRT_TensorOrWeights(const_cast(output_tensor))); - return tensorflow::Status::OK(); + return Status::OK(); } -tensorflow::Status ConvertConcat(OpConverterParams* params) { +Status ConvertConcat(OpConverterParams* params) { const auto& inputs = params->inputs; const auto& node_def = params->node_def; // not including the last input (axis) here int input_size = static_cast(inputs.size()) - 1; if (!inputs.at(0).is_tensor()) { - return tensorflow::errors::InvalidArgument( + return errors::InvalidArgument( "Concat in TRT support only Tensor input, at ", node_def.name()); } @@ -3437,13 +3427,13 @@ tensorflow::Status ConvertConcat(OpConverterParams* params) { TRT_ShapedWeights axis = inputs.at(input_size).weights(); TFAttrs attrs(node_def); - auto index_type = attrs.get("Tidx"); + auto index_type = attrs.get("Tidx"); // TODO(jie): handle data type // Only expect to handle INT32 as index attributes for now - if (index_type != tensorflow::DataType::DT_INT32) - return tensorflow::errors::Unimplemented("Tidx supports only DT_INT32, at ", - node_def.name()); + if (index_type != DataType::DT_INT32) + return errors::Unimplemented("Tidx supports only DT_INT32, at ", + node_def.name()); int index = *(static_cast(const_cast(axis.GetValues()))); @@ -3452,11 +3442,11 @@ tensorflow::Status ConvertConcat(OpConverterParams* params) { auto dim = inputs.at(0).tensor()->getDimensions(); // dimension check if (index > dim.nbDims + 1) { - return tensorflow::errors::InvalidArgument( + return errors::InvalidArgument( "Concatenate on axis out of dimension range, at ", node_def.name()); } if (index == 0) { - return tensorflow::errors::InvalidArgument( + return errors::InvalidArgument( "Concatenate on batch dimension not supported, at ", node_def.name()); } if (index < 0) { @@ -3470,14 +3460,14 @@ tensorflow::Status ConvertConcat(OpConverterParams* params) { auto tensor_i = inputs.at(i).tensor(); auto dim_i = tensor_i->getDimensions(); if (dim_i.nbDims != dim.nbDims) { - return tensorflow::errors::InvalidArgument( + return errors::InvalidArgument( "Concatenate receives inputs with inconsistent dimensions, at ", node_def.name()); } for (int j = 0; j < dim.nbDims; j++) { // check dimension consistency on non-concatenate axis if (j != index - 1 && dim_i.d[j] != dim.d[j]) { - return tensorflow::errors::InvalidArgument( + return errors::InvalidArgument( "Concatenate receives inputs with inconsistent shape, at", node_def.name()); } @@ -3485,7 +3475,7 @@ tensorflow::Status ConvertConcat(OpConverterParams* params) { inputs_vec.push_back(tensor_i); } - if (params->validation_only) return tensorflow::Status::OK(); + if (params->validation_only) return Status::OK(); // nvinfer1::ITensor const* tensor = inputs.at(0).tensor(); nvinfer1::IConcatenationLayer* layer = @@ -3496,10 +3486,10 @@ tensorflow::Status ConvertConcat(OpConverterParams* params) { layer->setAxis(index - 1); nvinfer1::ITensor* output_tensor = layer->getOutput(0); params->outputs->push_back(TRT_TensorOrWeights(output_tensor)); - return tensorflow::Status::OK(); + return Status::OK(); } -tensorflow::Status ConvertFusedBatchNorm(OpConverterParams* params) { +Status ConvertFusedBatchNorm(OpConverterParams* params) { const auto& inputs = params->inputs; const auto& node_def = params->node_def; TF_RETURN_IF_ERROR(CheckInputsWeights(*params, {{"x", false}, @@ -3511,7 +3501,7 @@ tensorflow::Status ConvertFusedBatchNorm(OpConverterParams* params) { float epsilon = attrs.get("epsilon"); auto data_format = attrs.get("data_format"); if (data_format != "NCHW") { - return tensorflow::errors::Unimplemented( + return errors::Unimplemented( node_def.op(), " only supports data_format=NCHW, at ", node_def.name()); } bool is_training = attrs.get("is_training"); @@ -3523,23 +3513,23 @@ tensorflow::Status ConvertFusedBatchNorm(OpConverterParams* params) { << "are using Keras, please call " << "keras.backend.set_learning_phase(0) before constructing " << "your model. At " << node_def.name(); - return tensorflow::errors::Unimplemented( - node_def.op(), " only supports is_training=false, at ", - node_def.name()); + return errors::Unimplemented(node_def.op(), + " only supports is_training=false, 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( + if ((parameter_type != DataType::DT_FLOAT) && + (parameter_type != DataType::DT_HALF)) { + return errors::Unimplemented( "only float32 or float16 weight data type is supported, for node " + - node_def.name() + " got " + tensorflow::DataTypeString(parameter_type)); + node_def.name() + " got " + DataTypeString(parameter_type)); } for (int i = 1; i < 5; i++) { if (inputs.at(i).weights().type_ != parameter_type) { - return tensorflow::errors::Unimplemented( + return errors::Unimplemented( "Inconsistent parameter type for batchnorm is not supported, at: " + node_def.name()); } @@ -3556,7 +3546,7 @@ tensorflow::Status ConvertFusedBatchNorm(OpConverterParams* params) { ptr_shape_weights = const_cast(&(inputs.at(i).weights())); } else if (inputs.at(i).weights().count() != 1) { - return tensorflow::errors::InvalidArgument( + return errors::InvalidArgument( "Inconsistent batchnorm parameter count, at: " + node_def.name()); } } @@ -3590,16 +3580,16 @@ tensorflow::Status ConvertFusedBatchNorm(OpConverterParams* params) { 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) { + if (parameter_type == DT_FLOAT) { batchnorm_data[j] = vals_array[j][i]; - } else if (parameter_type == tensorflow::DT_HALF) { + } else if (parameter_type == DT_HALF) { batchnorm_data[j] = Eigen::half_impl::half_to_float(cast_vals_array[j][i]); } } else { - if (parameter_type == tensorflow::DT_FLOAT) { + if (parameter_type == DT_FLOAT) { batchnorm_data[j] = vals_array[j][0]; - } else if (parameter_type == tensorflow::DT_HALF) { + } else if (parameter_type == DT_HALF) { batchnorm_data[j] = Eigen::half_impl::half_to_float(cast_vals_array[j][0]); } @@ -3611,10 +3601,10 @@ tensorflow::Status ConvertFusedBatchNorm(OpConverterParams* params) { 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) { + if (parameter_type == DT_FLOAT) { combined_scale_vals[i] = combined_scale_val; combined_offset_vals[i] = combined_offset_val; - } else if (parameter_type == tensorflow::DT_HALF) { + } else if (parameter_type == DT_HALF) { cast_combined_scale_vals[i] = Eigen::half(combined_scale_val); cast_combined_offset_vals[i] = Eigen::half(combined_offset_val); } @@ -3630,18 +3620,18 @@ tensorflow::Status ConvertFusedBatchNorm(OpConverterParams* params) { TFTRT_RETURN_ERROR_IF_NULLPTR(layer, node_def.name()); nvinfer1::ITensor* output_tensor = layer->getOutput(0); params->outputs->push_back(TRT_TensorOrWeights(output_tensor)); - return tensorflow::Status::OK(); + return Status::OK(); } -tensorflow::Status ConvertGather(OpConverterParams* params) { +Status ConvertGather(OpConverterParams* params) { const auto& inputs = params->inputs; const auto& node_def = params->node_def; TF_RETURN_IF_ERROR(CheckInputsWeights( *params, {{"params", false}, {"indices", false}, {"axis", true}})); absl::Span axis = inputs.at(2).weights().GetSpan(); if (axis.size() != 1) { - return tensorflow::errors::InvalidArgument( - "Axis for GatherV2 must be a scalar, at ", node_def.name()); + return errors::InvalidArgument("Axis for GatherV2 must be a scalar, at ", + node_def.name()); } int trt_axis = 0; TF_RETURN_IF_ERROR(ConvertAxis(axis[0], inputs.at(0).GetTrtDims().nbDims, @@ -3656,14 +3646,13 @@ tensorflow::Status ConvertGather(OpConverterParams* params) { return Status::OK(); } -tensorflow::Status ConvertMatMulHelper(OpConverterParams* params, - TRT_TensorOrWeights tensor_input, - TRT_ShapedWeights weights_raw, - bool transpose_weight, - string node_name) { +Status ConvertMatMulHelper(OpConverterParams* params, + TRT_TensorOrWeights tensor_input, + TRT_ShapedWeights weights_raw, bool transpose_weight, + string node_name) { nvinfer1::ITensor* output_tensor; if (!tensor_input.is_tensor()) { - return tensorflow::errors::InvalidArgument("Input 0 expects tensor"); + return errors::InvalidArgument("Input 0 expects tensor"); } const nvinfer1::ITensor* tensor = tensor_input.tensor(); @@ -3699,17 +3688,17 @@ tensorflow::Status ConvertMatMulHelper(OpConverterParams* params, TRT_TensorOrWeights(output_tensor), output_dim, &temp_tensor)); output_tensor = const_cast(temp_tensor); params->outputs->push_back(TRT_TensorOrWeights(output_tensor)); - return tensorflow::Status::OK(); + return Status::OK(); } -// inputs are both two dimensional (tensorflow::ops::MatMul) -tensorflow::Status ConvertMatMul(OpConverterParams* params) { +// inputs are both two dimensional (ops::MatMul) +Status ConvertMatMul(OpConverterParams* params) { const auto& inputs = params->inputs; const auto& node_def = params->node_def; TF_RETURN_IF_ERROR(CheckInputsWeights(*params, {{"a", false}, {"b", true}})); TFAttrs attrs(node_def); - tensorflow::DataType tf_dtype = attrs.get("T"); + DataType tf_dtype = attrs.get("T"); if (tf_dtype != DataType::DT_FLOAT && tf_dtype != DataType::DT_HALF) { return errors::Unimplemented("Data type is not supported, for node ", node_def.name(), " got ", @@ -3837,7 +3826,7 @@ Status ConvertSoftmax(OpConverterParams* params) { return Status::OK(); } -tensorflow::Status ConvertTopK(OpConverterParams* params) { +Status ConvertTopK(OpConverterParams* params) { const auto& inputs = params->inputs; if (inputs.size() != 2 || !inputs.at(0).is_tensor() || !inputs.at(1).is_weights()) { @@ -3875,7 +3864,7 @@ tensorflow::Status ConvertTopK(OpConverterParams* params) { nvinfer1::ITensor* output_indices_tensor = layer->getOutput(1); params->outputs->push_back(TRT_TensorOrWeights(output_value_tensor)); params->outputs->push_back(TRT_TensorOrWeights(output_indices_tensor)); - return tensorflow::Status::OK(); + return Status::OK(); } static void RegisterValidatableOpConverters( @@ -3945,12 +3934,11 @@ void Converter::RegisterOpConverters() { plugin_converter_ = ConvertPlugin; } -tensorflow::Status ConvertGraphDefToEngine( - const tensorflow::GraphDef& gdef, TrtPrecisionMode precision_mode, - int max_batch_size, size_t max_workspace_size_bytes, - const std::vector& input_shapes, - Logger* logger, nvinfer1::IGpuAllocator* allocator, - TRTInt8Calibrator* calibrator, +Status ConvertGraphDefToEngine( + const GraphDef& gdef, TrtPrecisionMode precision_mode, int max_batch_size, + size_t max_workspace_size_bytes, + const std::vector& input_shapes, Logger* logger, + nvinfer1::IGpuAllocator* allocator, TRTInt8Calibrator* calibrator, TrtUniquePtrType* engine, bool use_calibration, bool* convert_successfully) { engine->reset(); @@ -3981,8 +3969,7 @@ tensorflow::Status ConvertGraphDefToEngine( auto trt_network = TrtUniquePtrType(builder->createNetwork()); if (!trt_network) { - return tensorflow::errors::Internal( - "Failed to create TensorRT network object"); + return errors::Internal("Failed to create TensorRT network object"); } // Build the network @@ -3995,10 +3982,10 @@ tensorflow::Status ConvertGraphDefToEngine( VLOG(2) << "Converting op name=" << node_name << ", op=" << node_def.op(); if (IsEngineInput(node_name) && (node_def.op() == "Placeholder")) { int32 slot_number = -1; - if (!tensorflow::strings::safe_strto32( // non-absl ok + if (!strings::safe_strto32( // non-absl ok node_name.c_str() + strlen(kInputPHName), &slot_number)) { - return tensorflow::errors::InvalidArgument( - "Failed to parse slot number from ", node_name); + return errors::InvalidArgument("Failed to parse slot number from ", + node_name); } nvinfer1::DataType trt_dtype; nvinfer1::Dims trt_dims; @@ -4023,14 +4010,14 @@ tensorflow::Status ConvertGraphDefToEngine( converter.AddInputTensor(node_name, trt_dtype, trt_dims, batch_size)); } else if (IsEngineOutput(node_name) && (node_def.op() == "Identity")) { int32 slot_number = -1; - if (!tensorflow::strings::safe_strto32( // non-absl ok + if (!strings::safe_strto32( // non-absl ok node_name.c_str() + strlen(kOutputPHName), &slot_number)) { - return tensorflow::errors::InvalidArgument( - "Failed to parse slot number from ", node_name); + return errors::InvalidArgument("Failed to parse slot number from ", + node_name); } // Get output type that TensorFlow expects TFAttrs attrs(node_def); - tensorflow::DataType tf_dtype = attrs.get("T"); + DataType tf_dtype = attrs.get("T"); nvinfer1::DataType trt_dtype; TF_RETURN_IF_ERROR(ConvertDType(tf_dtype, &trt_dtype)); if (output_tensors.size() <= slot_number) { @@ -4054,18 +4041,17 @@ tensorflow::Status ConvertGraphDefToEngine( VLOG(1) << "Starting engine creation"; engine->reset(builder->buildCudaEngine(*converter.network())); if (engine->get() == nullptr) { - return tensorflow::errors::Internal("Failed to build TensorRT engine"); + return errors::Internal("Failed to build TensorRT engine"); } VLOG(1) << "Finished conversion"; - return tensorflow::Status::OK(); + return Status::OK(); } -tensorflow::Status ConvertSegmentToGraphDef( - const tensorflow::Graph* graph, - const tensorflow::grappler::GraphProperties& graph_properties, +Status ConvertSegmentToGraphDef( + const Graph* graph, const grappler::GraphProperties& graph_properties, const std::vector& subgraph_nodes, // In topological order - std::vector* connections, - tensorflow::GraphDef* segment_def, string* common_scope) { + std::vector* connections, GraphDef* segment_def, + string* common_scope) { std::set marker_nodes; // Update connection shapes/data types and add corresponding input/output // nodes in the segment graphdef. @@ -4075,12 +4061,12 @@ tensorflow::Status ConvertSegmentToGraphDef( auto outside_node = graph->FindNodeId(connection.outside_id); if (!outside_node) { // This should never happen, unless the original graph is problematic. - return tensorflow::errors::NotFound( - "Cannot find node with id ", connection.outside_id, " in the graph."); + return errors::NotFound("Cannot find node with id ", + connection.outside_id, " in the graph."); } // Updates the shape and data types of input/output connections. - tensorflow::DataType dtype; - tensorflow::PartialTensorShape partial_shape; + DataType dtype; + PartialTensorShape partial_shape; if (connection.is_input_edge) { GetOutputProperties(graph_properties, graph->FindNodeId(connection.outside_id), @@ -4106,7 +4092,7 @@ tensorflow::Status ConvertSegmentToGraphDef( } marker_nodes.insert(node_name); auto seg_node = segment_def->add_node(); - tensorflow::NodeDefBuilder builder(node_name, "Placeholder"); + NodeDefBuilder builder(node_name, "Placeholder"); auto status = builder.Attr("shape", partial_shape) .Attr("dtype", dtype) .Finalize(seg_node); @@ -4125,7 +4111,7 @@ tensorflow::Status ConvertSegmentToGraphDef( } marker_nodes.insert(node_name); auto seg_node = segment_def->add_node(); - tensorflow::NodeDefBuilder builder(node_name, "Identity"); + NodeDefBuilder builder(node_name, "Identity"); auto status = builder .Input(connection.inside_node_name, connection.inside_port, dtype) @@ -4182,7 +4168,7 @@ tensorflow::Status ConvertSegmentToGraphDef( ++input_idx; continue; } else { - return tensorflow::errors::InvalidArgument( + return errors::InvalidArgument( "Found non control input outside the segment that is not an " "engine connection to ", snode->name(), ": ", input.first); @@ -4201,10 +4187,10 @@ tensorflow::Status ConvertSegmentToGraphDef( *common_scope = local_scope; VLOG(1) << "Converted TensorRT candidate segment @scope '" << local_scope << "' to a GraphDef"; - return tensorflow::Status::OK(); + return Status::OK(); } -bool OutputEdgeValidator::operator()(const tensorflow::Edge* out_edge) const { +bool OutputEdgeValidator::operator()(const Edge* out_edge) const { if (out_edge->IsControlEdge()) return true; if (out_edge->src()->type_string() == "Const") { VLOG(1) << "--> Need to remove output node " << out_edge->src()->name() -- GitLab From f4d29bb3635d2a7a9bb1f557d486de7fdaa3b101 Mon Sep 17 00:00:00 2001 From: Gunhan Gulsoy Date: Mon, 25 Feb 2019 22:16:04 -0800 Subject: [PATCH 526/766] Remove dependency on concat_lib in tf2xla PiperOrigin-RevId: 235661213 --- tensorflow/compiler/tf2xla/kernels/BUILD | 1 - tensorflow/compiler/tf2xla/kernels/concat_op.cc | 1 - tensorflow/compiler/tf2xla/kernels/pack_op.cc | 1 - tensorflow/compiler/tf2xla/kernels/scan_ops.cc | 1 - tensorflow/compiler/tf2xla/kernels/stack_ops.cc | 1 - tensorflow/compiler/tf2xla/kernels/tensor_array_ops.cc | 1 - tensorflow/compiler/tf2xla/kernels/tensor_list_ops.cc | 1 - tensorflow/compiler/tf2xla/kernels/unpack_op.cc | 1 - 8 files changed, 8 deletions(-) diff --git a/tensorflow/compiler/tf2xla/kernels/BUILD b/tensorflow/compiler/tf2xla/kernels/BUILD index a802743403..ffdc5a2b40 100644 --- a/tensorflow/compiler/tf2xla/kernels/BUILD +++ b/tensorflow/compiler/tf2xla/kernels/BUILD @@ -169,7 +169,6 @@ tf_kernel_library( "//tensorflow/core:state_ops_op_lib", "//tensorflow/core:stateless_random_ops_op_lib", "//tensorflow/core:training_ops_op_lib", - "//tensorflow/core/kernels:concat_lib", "//tensorflow/core/kernels:constant_op", "//tensorflow/core/kernels:control_flow_ops", "//tensorflow/core/kernels:conv_ops", diff --git a/tensorflow/compiler/tf2xla/kernels/concat_op.cc b/tensorflow/compiler/tf2xla/kernels/concat_op.cc index 91e4d9cea7..09c97de13e 100644 --- a/tensorflow/compiler/tf2xla/kernels/concat_op.cc +++ b/tensorflow/compiler/tf2xla/kernels/concat_op.cc @@ -31,7 +31,6 @@ limitations under the License. #include "tensorflow/core/framework/tensor_shape.h" #include "tensorflow/core/framework/tensor_types.h" #include "tensorflow/core/framework/types.h" -#include "tensorflow/core/kernels/concat_lib.h" #include "tensorflow/core/lib/core/status.h" #include "tensorflow/core/platform/types.h" diff --git a/tensorflow/compiler/tf2xla/kernels/pack_op.cc b/tensorflow/compiler/tf2xla/kernels/pack_op.cc index 426a0941df..6ca100a2f2 100644 --- a/tensorflow/compiler/tf2xla/kernels/pack_op.cc +++ b/tensorflow/compiler/tf2xla/kernels/pack_op.cc @@ -30,7 +30,6 @@ limitations under the License. #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/framework/tensor_types.h" #include "tensorflow/core/framework/types.h" -#include "tensorflow/core/kernels/concat_lib.h" #include "tensorflow/core/lib/core/status.h" #include "tensorflow/core/platform/types.h" diff --git a/tensorflow/compiler/tf2xla/kernels/scan_ops.cc b/tensorflow/compiler/tf2xla/kernels/scan_ops.cc index daefdfc58a..8431724f43 100644 --- a/tensorflow/compiler/tf2xla/kernels/scan_ops.cc +++ b/tensorflow/compiler/tf2xla/kernels/scan_ops.cc @@ -30,7 +30,6 @@ limitations under the License. #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/framework/tensor_types.h" #include "tensorflow/core/framework/types.h" -#include "tensorflow/core/kernels/concat_lib.h" #include "tensorflow/core/lib/core/status.h" #include "tensorflow/core/platform/types.h" diff --git a/tensorflow/compiler/tf2xla/kernels/stack_ops.cc b/tensorflow/compiler/tf2xla/kernels/stack_ops.cc index b6c96b1f58..a93d137e96 100644 --- a/tensorflow/compiler/tf2xla/kernels/stack_ops.cc +++ b/tensorflow/compiler/tf2xla/kernels/stack_ops.cc @@ -31,7 +31,6 @@ limitations under the License. #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/framework/tensor_types.h" #include "tensorflow/core/framework/types.h" -#include "tensorflow/core/kernels/concat_lib.h" #include "tensorflow/core/lib/core/status.h" #include "tensorflow/core/platform/types.h" diff --git a/tensorflow/compiler/tf2xla/kernels/tensor_array_ops.cc b/tensorflow/compiler/tf2xla/kernels/tensor_array_ops.cc index 77a3e5c001..b98b98ce50 100644 --- a/tensorflow/compiler/tf2xla/kernels/tensor_array_ops.cc +++ b/tensorflow/compiler/tf2xla/kernels/tensor_array_ops.cc @@ -34,7 +34,6 @@ limitations under the License. #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/framework/tensor_types.h" #include "tensorflow/core/framework/types.h" -#include "tensorflow/core/kernels/concat_lib.h" #include "tensorflow/core/lib/core/status.h" #include "tensorflow/core/platform/types.h" diff --git a/tensorflow/compiler/tf2xla/kernels/tensor_list_ops.cc b/tensorflow/compiler/tf2xla/kernels/tensor_list_ops.cc index 8958a48bc7..4feb17d2c8 100644 --- a/tensorflow/compiler/tf2xla/kernels/tensor_list_ops.cc +++ b/tensorflow/compiler/tf2xla/kernels/tensor_list_ops.cc @@ -35,7 +35,6 @@ limitations under the License. #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/framework/tensor_types.h" #include "tensorflow/core/framework/types.h" -#include "tensorflow/core/kernels/concat_lib.h" #include "tensorflow/core/lib/core/errors.h" #include "tensorflow/core/lib/core/status.h" #include "tensorflow/core/platform/types.h" diff --git a/tensorflow/compiler/tf2xla/kernels/unpack_op.cc b/tensorflow/compiler/tf2xla/kernels/unpack_op.cc index 2fc5619de7..2d95f2f30a 100644 --- a/tensorflow/compiler/tf2xla/kernels/unpack_op.cc +++ b/tensorflow/compiler/tf2xla/kernels/unpack_op.cc @@ -30,7 +30,6 @@ limitations under the License. #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/framework/tensor_types.h" #include "tensorflow/core/framework/types.h" -#include "tensorflow/core/kernels/concat_lib.h" #include "tensorflow/core/lib/core/status.h" #include "tensorflow/core/platform/types.h" -- GitLab From 7de4f3447380562a8bb9028199834f692abf3534 Mon Sep 17 00:00:00 2001 From: Gunhan Gulsoy Date: Mon, 25 Feb 2019 22:20:08 -0800 Subject: [PATCH 527/766] Remove Erroneous no_op.h dependencies. PiperOrigin-RevId: 235661472 --- tensorflow/compiler/tf2xla/kernels/elu_op.cc | 1 - tensorflow/compiler/tf2xla/kernels/l2loss_op.cc | 1 - tensorflow/compiler/tf2xla/kernels/topk_op.cc | 1 - tensorflow/compiler/tf2xla/kernels/training_ops.cc | 1 - 4 files changed, 4 deletions(-) diff --git a/tensorflow/compiler/tf2xla/kernels/elu_op.cc b/tensorflow/compiler/tf2xla/kernels/elu_op.cc index 5fdb1d972c..87bb9d49c0 100644 --- a/tensorflow/compiler/tf2xla/kernels/elu_op.cc +++ b/tensorflow/compiler/tf2xla/kernels/elu_op.cc @@ -22,7 +22,6 @@ limitations under the License. #include "tensorflow/compiler/xla/literal.h" #include "tensorflow/core/framework/kernel_def_builder.h" #include "tensorflow/core/framework/types.h" -#include "tensorflow/core/kernels/no_op.h" namespace tensorflow { namespace { diff --git a/tensorflow/compiler/tf2xla/kernels/l2loss_op.cc b/tensorflow/compiler/tf2xla/kernels/l2loss_op.cc index 93f029731c..7f25d34c3e 100644 --- a/tensorflow/compiler/tf2xla/kernels/l2loss_op.cc +++ b/tensorflow/compiler/tf2xla/kernels/l2loss_op.cc @@ -19,7 +19,6 @@ limitations under the License. #include "tensorflow/compiler/xla/client/xla_builder.h" #include "tensorflow/core/framework/op_kernel.h" #include "tensorflow/core/framework/types.h" -#include "tensorflow/core/kernels/no_op.h" namespace tensorflow { namespace { diff --git a/tensorflow/compiler/tf2xla/kernels/topk_op.cc b/tensorflow/compiler/tf2xla/kernels/topk_op.cc index ee3bdf3394..22cfd16008 100644 --- a/tensorflow/compiler/tf2xla/kernels/topk_op.cc +++ b/tensorflow/compiler/tf2xla/kernels/topk_op.cc @@ -20,7 +20,6 @@ limitations under the License. #include "tensorflow/compiler/xla/literal.h" #include "tensorflow/core/framework/kernel_def_builder.h" #include "tensorflow/core/framework/types.h" -#include "tensorflow/core/kernels/no_op.h" namespace tensorflow { namespace { diff --git a/tensorflow/compiler/tf2xla/kernels/training_ops.cc b/tensorflow/compiler/tf2xla/kernels/training_ops.cc index ceb7620380..247db8d5d1 100644 --- a/tensorflow/compiler/tf2xla/kernels/training_ops.cc +++ b/tensorflow/compiler/tf2xla/kernels/training_ops.cc @@ -22,7 +22,6 @@ limitations under the License. #include "tensorflow/compiler/xla/literal.h" #include "tensorflow/core/framework/kernel_def_builder.h" #include "tensorflow/core/framework/types.h" -#include "tensorflow/core/kernels/no_op.h" namespace tensorflow { namespace { -- GitLab From 0ae3728b74462fc2548cc9ebc330708c775fb387 Mon Sep 17 00:00:00 2001 From: Guangda Lai Date: Mon, 25 Feb 2019 22:27:47 -0800 Subject: [PATCH 528/766] Really cleanup the serialized GraphDef/engine when it's content is consumed. A simple test show that this saves 477MB memory for a TRT-converted RetinaNet model. PiperOrigin-RevId: 235661968 --- .../compiler/tf2tensorrt/kernels/trt_engine_op.cc | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tensorflow/compiler/tf2tensorrt/kernels/trt_engine_op.cc b/tensorflow/compiler/tf2tensorrt/kernels/trt_engine_op.cc index 07d6e5db9b..d949406344 100644 --- a/tensorflow/compiler/tf2tensorrt/kernels/trt_engine_op.cc +++ b/tensorflow/compiler/tf2tensorrt/kernels/trt_engine_op.cc @@ -197,7 +197,11 @@ TRTEngineOp::TRTEngineOp(OpKernelConstruction* context) errors::InvalidArgument("Failed to parse segment graphdef!")); return; } - serialized_segment_.resize(0); + VLOG(1) << "Size of serialized GraphDef: " + << serialized_segment_.capacity(); + string tmp; + // Swap with temporary empty string to deallocate the CPU memory. + serialized_segment_.swap(tmp); } VLOG(1) << "Constructing " << name(); string precision_string; @@ -564,7 +568,11 @@ EngineContext* TRTEngineOp::GetEngine( TrtUniquePtrType( raw_static_engine->createExecutionContext()))); // Runtime is safe to delete after engine creation - serialized_segment_.clear(); + VLOG(1) << "Size of serialized TRT engine: " + << serialized_segment_.capacity(); + string tmp; + // Swap with temporary empty string to deallocate the CPU memory. + serialized_segment_.swap(tmp); if (max_batch_size < batch_size) { return &empty_context; } -- GitLab From f745da9463ce3a16d8e462dc340448c6f72dfc4e Mon Sep 17 00:00:00 2001 From: Siju Date: Tue, 26 Feb 2019 12:19:09 +0530 Subject: [PATCH 529/766] Update fusion_merger.cc --- tensorflow/compiler/xla/service/gpu/fusion_merger.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/compiler/xla/service/gpu/fusion_merger.cc b/tensorflow/compiler/xla/service/gpu/fusion_merger.cc index 91930eccdf..853a09213b 100644 --- a/tensorflow/compiler/xla/service/gpu/fusion_merger.cc +++ b/tensorflow/compiler/xla/service/gpu/fusion_merger.cc @@ -62,7 +62,7 @@ double CalculateBytesReadByFusionParameter(HloInstruction* param) { // Iterate through 'instructions' accumulating byte sizes of each instruction // shape. For each 'instruction' in 'instructions', if all users of - // 'instruction' are Slice instructions, accumuates the byte sizes of each + // 'instruction' are Slice instructions, accumulates the byte sizes of each // Slice for a more accurate estimate of bytes read. double bytes = 0.0; for (auto& instruction : instructions) { -- GitLab From ffc4f5a2b9e3eb45e2d89d14bca152fe6af2ef26 Mon Sep 17 00:00:00 2001 From: Siju Date: Tue, 26 Feb 2019 12:26:48 +0530 Subject: [PATCH 530/766] Update mutex_ops.cc --- tensorflow/core/kernels/mutex_ops.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/core/kernels/mutex_ops.cc b/tensorflow/core/kernels/mutex_ops.cc index b06845f13a..2f4a5e9aa0 100644 --- a/tensorflow/core/kernels/mutex_ops.cc +++ b/tensorflow/core/kernels/mutex_ops.cc @@ -148,7 +148,7 @@ class Mutex : public ResourceBase { fn_(Status::OK(), SharedLockReleaser{std::make_shared(this)}); } else { - fn_(errors::Cancelled("Lock acqusition cancelled."), + fn_(errors::Cancelled("Lock acquisition cancelled."), SharedLockReleaser{nullptr}); } }, -- GitLab From a90b0a45b927911f0663298c6b5821b4368f3da1 Mon Sep 17 00:00:00 2001 From: Siju Date: Tue, 26 Feb 2019 12:28:49 +0530 Subject: [PATCH 531/766] Update operator.cc --- tensorflow/lite/toco/tflite/operator.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/toco/tflite/operator.cc b/tensorflow/lite/toco/tflite/operator.cc index 03e634400c..436beb01fe 100644 --- a/tensorflow/lite/toco/tflite/operator.cc +++ b/tensorflow/lite/toco/tflite/operator.cc @@ -1921,7 +1921,7 @@ class UnidirectionalSequenceRnn } void ReadOptions(const TfLiteOptions& options, TocoOperator* op) const override { - // Only support tanh actication, so check that tflite type is tanh. + // Only support tanh activation, so check that tflite type is tanh. DCHECK(options.fused_activation_function() == ::tflite::ActivationFunctionType_TANH); } -- GitLab From 676452d7fc6535ac2292c4a2f2513771fb341256 Mon Sep 17 00:00:00 2001 From: Siju Date: Tue, 26 Feb 2019 12:30:29 +0530 Subject: [PATCH 532/766] Update generate_examples.py --- tensorflow/lite/testing/generate_examples.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/testing/generate_examples.py b/tensorflow/lite/testing/generate_examples.py index d6f7b79f4f..0ec6c96062 100644 --- a/tensorflow/lite/testing/generate_examples.py +++ b/tensorflow/lite/testing/generate_examples.py @@ -2986,7 +2986,7 @@ def make_lstm_tests(zip_path): shape=[num_batchs, input_vec_size]) inputs_after_split.append(one_timestamp_input) # Currently lstm identifier has a few limitations: only supports - # forget_bias == 0, inner state activiation == tanh. + # forget_bias == 0, inner state activation == tanh. # TODO(zhixianyan): Add another test with forget_bias == 1. # TODO(zhixianyan): Add another test with relu as activation. lstm_cell = tf.contrib.rnn.BasicLSTMCell( -- GitLab From 9657662c48ae3f2ac27f86684d2021372db2e70f Mon Sep 17 00:00:00 2001 From: Siju Date: Tue, 26 Feb 2019 12:32:55 +0530 Subject: [PATCH 533/766] Update batchnorm_expander.cc --- tensorflow/compiler/xla/service/batchnorm_expander.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tensorflow/compiler/xla/service/batchnorm_expander.cc b/tensorflow/compiler/xla/service/batchnorm_expander.cc index 620876c264..d14e803be6 100644 --- a/tensorflow/compiler/xla/service/batchnorm_expander.cc +++ b/tensorflow/compiler/xla/service/batchnorm_expander.cc @@ -517,7 +517,7 @@ Status BatchNormExpanderVisitor::HandleBatchNormGrad( activation_shape, HloOpcode::kSubtract, activation, mean_broadcasted); // Grad[Y] * (X - E[X]). - auto grad_output_times_activiation_minus_mean = + auto grad_output_times_activation_minus_mean = add_binary(activation_shape, HloOpcode::kMultiply, grad_output, activation_minus_mean); @@ -525,9 +525,9 @@ Status BatchNormExpanderVisitor::HandleBatchNormGrad( GetOrCreateScalarAddComputation(ptype); // sum(Grad[Y] * (X - E[X])). - auto sum_grad_output_times_activiation_minus_mean = + auto sum_grad_output_times_activation_minus_mean = add(HloInstruction::CreateReduce( - feature_shape, grad_output_times_activiation_minus_mean, zero, + feature_shape, grad_output_times_activation_minus_mean, zero, dimensions_without_feature, add_reduce_computation)); // Grad[beta] = Sum(Grad[Y]). @@ -537,7 +537,7 @@ Status BatchNormExpanderVisitor::HandleBatchNormGrad( // Grad[scale] = Sum(Grad[Y] * (X - E[X]) * rsqrt[Var[X] + epsilon]). auto grad_scale = add_binary(feature_shape, HloOpcode::kMultiply, - sum_grad_output_times_activiation_minus_mean, + sum_grad_output_times_activation_minus_mean, rsqrt_var_add_epsilon); // I2 = Sum(Grad[Y]) @@ -546,7 +546,7 @@ Status BatchNormExpanderVisitor::HandleBatchNormGrad( // I3 = Sum(Grad[Y] * (X - E[X])) auto i3 = add(HloInstruction::CreateBroadcast( - activation_shape, sum_grad_output_times_activiation_minus_mean, + activation_shape, sum_grad_output_times_activation_minus_mean, {feature_index})); // I4 = (X - E[X]) * I3 -- GitLab From 8b2210a0f48b30a76b4c4e8225601dab755468a1 Mon Sep 17 00:00:00 2001 From: Smit Hinsu Date: Mon, 25 Feb 2019 23:29:27 -0800 Subject: [PATCH 534/766] Use int64 type to extract `int` attributes in TF-TensorRT TensorFlow always uses int64 to store `int` attributes. https://github.com/tensorflow/tensorflow/blob/60089e430e34c870e074c6d5746ed88ea9b39909/tensorflow/core/framework/attr_value.proto#L32 PiperOrigin-RevId: 235666747 --- .../tf2tensorrt/convert/convert_nodes.cc | 22 +++++++++---------- .../tf2tensorrt/convert/convert_nodes_test.cc | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.cc b/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.cc index 908aa5f4a4..e26f650aba 100644 --- a/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.cc +++ b/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.cc @@ -693,9 +693,9 @@ string TFAttrs::get(const string& key) const { } template <> -std::vector TFAttrs::get>(const string& key) const { +std::vector TFAttrs::get>(const string& key) const { auto attr = this->at(key)->list().i(); - return std::vector(attr.begin(), attr.end()); + return std::vector(attr.begin(), attr.end()); } template <> @@ -727,7 +727,7 @@ bool TFAttrs::get(const string& key) const { } template <> -int TFAttrs::get(const string& key) const { +int64 TFAttrs::get(const string& key) const { return this->at(key)->i(); } @@ -1728,7 +1728,7 @@ Status ConvertConv2DHelper(OpConverterParams* params, int group, int c_index = (data_format == "NHWC") ? 3 : 1; int h_index = (data_format == "NHWC") ? 1 : 2; int w_index = (data_format == "NHWC") ? 2 : 3; - auto tf_dilations = attrs.get>("dilations"); + auto tf_dilations = attrs.get>("dilations"); if (tf_dilations.size() != 4) { return errors::InvalidArgument( "Convolution dilations field must specify 4 dimensions, at ", @@ -1746,7 +1746,7 @@ Status ConvertConv2DHelper(OpConverterParams* params, int group, ", at ", node_def.name()); } - const auto tf_stride = attrs.get>("strides"); + const auto tf_stride = attrs.get>("strides"); if (tf_stride.size() != 4) { return errors::InvalidArgument( "Convolution strides field must specify 4 dimensions, at ", @@ -2170,7 +2170,7 @@ Status ConvertSqueeze(OpConverterParams* params) { const int input_rank = input_dims.size(); // Mark axes to remove by setting them to 0. TFAttrs attrs(node_def); - auto squeeze_dims = attrs.get>("squeeze_dims"); + auto squeeze_dims = attrs.get>("squeeze_dims"); if (squeeze_dims.empty()) { return errors::Unimplemented( "Squeeze is only implemented for explicit dims, at ", node_def.name()); @@ -2485,14 +2485,14 @@ Status ConvertStridedSlice(OpConverterParams* params) { TFAttrs attrs(node_def); for (const string& attr : {"ellipsis_mask", "new_axis_mask", "shrink_axis_mask"}) { - int attr_val = attrs.get(attr); + int attr_val = attrs.get(attr); if (attr_val != 0) { return errors::Unimplemented( attr, " is not supported for StridedSlice, at ", node_def.name()); } } - const int begin_mask = attrs.get("begin_mask"); - const int end_mask = attrs.get("end_mask"); + const int begin_mask = attrs.get("begin_mask"); + const int end_mask = attrs.get("end_mask"); // Check that batch dimension is unmodified. const bool begin_is_modified = !(begin_mask & 1) && begin[0] != 0; const bool stride_is_modified = stride[0] != 1; @@ -2589,10 +2589,10 @@ Status ConvertPool(OpConverterParams* params) { const_cast(tensor), {0, 3, 1, 2}, &tensor)); } - const auto tf_stride = attrs.get>("strides"); + const auto tf_stride = attrs.get>("strides"); const nvinfer1::DimsHW stride(tf_stride[h_index], tf_stride[w_index]); - const auto tf_kernel = attrs.get>("ksize"); + const auto tf_kernel = attrs.get>("ksize"); const nvinfer1::DimsHW ksize(tf_kernel[h_index], tf_kernel[w_index]); auto tensor_dim = tensor->getDimensions(); diff --git a/tensorflow/compiler/tf2tensorrt/convert/convert_nodes_test.cc b/tensorflow/compiler/tf2tensorrt/convert/convert_nodes_test.cc index 7899d6b199..2f02d366a4 100644 --- a/tensorflow/compiler/tf2tensorrt/convert/convert_nodes_test.cc +++ b/tensorflow/compiler/tf2tensorrt/convert/convert_nodes_test.cc @@ -2553,8 +2553,8 @@ TEST_F(OpConverterTest, ConvertStridedSlice) { // Get nodedef for StridedSlice layer. auto get_strided_slice_nodedef = - [](int begin_mask = 0, int end_mask = 0, int ellipsis_mask = 0, - int new_axis_mask = 0, int shrink_axis_mask = 0) -> NodeDef { + [](int64 begin_mask = 0, int64 end_mask = 0, int64 ellipsis_mask = 0, + int64 new_axis_mask = 0, int64 shrink_axis_mask = 0) -> NodeDef { Scope s = Scope::NewRootScope(); auto input = ops::Placeholder(s.WithOpName("input"), DT_FLOAT); auto begin = ops::Placeholder(s.WithOpName("begin"), DT_INT32); -- GitLab From 00afc7bb81d6d36a0f619c08c011abe08965a25b Mon Sep 17 00:00:00 2001 From: Thomas Joerg Date: Mon, 25 Feb 2019 23:32:50 -0800 Subject: [PATCH 535/766] [XLA:GPU] Retire the flops-to-bytes heuristic for fusion. Benchmarks suggest that this heuristic rarely triggers in practice and is not effective when it does. It is only used by FusionMerger but no other fusion pass. Removing this heuristic reduces code complexity and is a step towards more consistency in fusion passes. PiperOrigin-RevId: 235667025 --- .../compiler/xla/service/gpu/fusion_merger.cc | 35 ------------ .../compiler/xla/service/gpu/fusion_merger.h | 2 - .../xla/service/gpu/fusion_merger_test.cc | 56 ------------------- 3 files changed, 93 deletions(-) diff --git a/tensorflow/compiler/xla/service/gpu/fusion_merger.cc b/tensorflow/compiler/xla/service/gpu/fusion_merger.cc index 91930eccdf..c6b30cdb1c 100644 --- a/tensorflow/compiler/xla/service/gpu/fusion_merger.cc +++ b/tensorflow/compiler/xla/service/gpu/fusion_merger.cc @@ -95,27 +95,6 @@ double CalculateBytesReadByFusionInstruction(HloInstruction* fusion) { return bytes; } -// Returns the flops to bytes transferred ratio of instruction 'fusion'. -double CalculateFlopsToBytesRatio(HloInstruction* fusion) { - CHECK_EQ(HloOpcode::kFusion, fusion->opcode()); - // Calculate total bytes transferred in/out. - double bytes = CalculateBytesReadByFusionInstruction(fusion); - // Add bytes written to root instructions buffer. - if (fusion->IsMultiOutputFusion()) { - for (auto& operand : fusion->fused_expression_root()->operands()) { - bytes += ShapeUtil::ByteSizeOf(operand->shape()); - } - } else { - bytes += ShapeUtil::ByteSizeOf(fusion->fused_expression_root()->shape()); - } - // Calculate flops for all fused instructions. Use a null shape size function - // because we don't care about bytes accessed by the ops. - HloCostAnalysis analysis([](const Shape& shape) { return 0; }); - TF_CHECK_OK(fusion->fused_expression_root()->Accept(&analysis)); - // Return flops / bytes. - return bytes > 0.0 ? analysis.flop_count() / bytes : analysis.flop_count(); -} - // Returns bytes transferred by instruction 'fusion', including the bytes // that would be read by all users. double GetCurrentBytesTransferred(HloInstruction* fusion) { @@ -169,7 +148,6 @@ class FusionInstructionMerger { int num_fail_not_loop_fusion_ = 0; int num_fail_merge_all_users_ = 0; int num_fail_expensive_fused_instruction_ = 0; - int num_fail_flops_to_byte_ratio_ = 0; int num_fail_net_bytes_transferred_ratio_ = 0; TF_DISALLOW_COPY_AND_ASSIGN(FusionInstructionMerger); @@ -190,15 +168,12 @@ Status FusionInstructionMerger::Run() { << " not_loop_fusion: " << num_fail_not_loop_fusion_ << " merge_all_users: " << num_fail_merge_all_users_ << " expensive_instruction: " << num_fail_expensive_fused_instruction_ - << " flops_to_byte_ratio: " << num_fail_flops_to_byte_ratio_ << " net_bytes_transferred: " << num_fail_net_bytes_transferred_ratio_ << " }"; return Status::OK(); } Status FusionInstructionMerger::HandleFusion(HloInstruction* fusion) { - VLOG(3) << "FusionInstructionMerger ENTRY fusion: " << fusion->name() - << " flops_to_bytes_ratio: " << CalculateFlopsToBytesRatio(fusion); ++total_visited_; // Skip 'fusion' instruction if there are no users into which we can merge. if (fusion->users().empty()) { @@ -256,15 +231,6 @@ Status FusionInstructionMerger::HandleFusion(HloInstruction* fusion) { return Status::OK(); } - // Skip 'fusion' instruction if its flops to bytes transferred ratio - // exceeds the threshold value. - if (CalculateFlopsToBytesRatio(fusion) > - FusionMerger::GetThresholdFlopsToBytesRatio()) { - VLOG(3) << "Not merging " << fusion->name() - << ": flops-to-bytes ratio is not favorable."; - ++num_fail_flops_to_byte_ratio_; - return Status::OK(); - } // Skip 'fusion' instruction if merging it into all users would result in a // net increase in bytes transferred (currently allowing the net bytes // transferred to be exceeded up to ~10% in exhange for eliminating the @@ -288,7 +254,6 @@ Status FusionInstructionMerger::HandleFusion(HloInstruction* fusion) { } ++total_merged_; VLOG(2) << "Merged fusion instruction: " << fusion->name() - << " flops_to_bytes_ratio: " << CalculateFlopsToBytesRatio(fusion) << " merged_to_current_bytes_ratio: " << merged_to_current_bytes_ratio << " into users { " << absl::StrJoin(users, ", ", diff --git a/tensorflow/compiler/xla/service/gpu/fusion_merger.h b/tensorflow/compiler/xla/service/gpu/fusion_merger.h index f19996edfe..a49d68002f 100644 --- a/tensorflow/compiler/xla/service/gpu/fusion_merger.h +++ b/tensorflow/compiler/xla/service/gpu/fusion_merger.h @@ -37,8 +37,6 @@ class FusionMerger : public HloModulePass { absl::string_view name() const override { return "fusion merger"; } StatusOr Run(HloModule* module) override; - - static double GetThresholdFlopsToBytesRatio() { return 1.0; } }; } // namespace gpu diff --git a/tensorflow/compiler/xla/service/gpu/fusion_merger_test.cc b/tensorflow/compiler/xla/service/gpu/fusion_merger_test.cc index 7cc869ed9e..96ec060d53 100644 --- a/tensorflow/compiler/xla/service/gpu/fusion_merger_test.cc +++ b/tensorflow/compiler/xla/service/gpu/fusion_merger_test.cc @@ -99,62 +99,6 @@ ENTRY MergeSharedFusionInstruction.Computation0 { EXPECT_EQ(7, operand2->fused_instruction_count()); } -// Tests that we do not merge a fusion instruction that above flops to bytes -// threshold. -// -// Fusion2 is not merged because it exceeds the threshold flops-to-bytes ratio. -TEST_F(FusionMergerTest, FlopsToBytesRatioThresholdExceeded) { - auto module = ParseHloString(R"( -HloModule FlopsToBytesRatioThresholdExceeded - -comp.2 { - state.param_1.1 = (f32[4]{0}, f32[4]{0}) parameter(0) - get-tuple-element.3 = f32[4]{0} get-tuple-element(state.param_1.1), index=0 - get-tuple-element.4 = f32[4]{0} get-tuple-element(state.param_1.1), index=2 - multiply.29 = f32[4]{0} multiply(get-tuple-element.3, get-tuple-element.4) - multiply.30 = f32[4]{0} multiply(get-tuple-element.3, multiply.29) - multiply.31 = f32[4]{0} multiply(get-tuple-element.3, multiply.30) - multiply.32 = f32[4]{0} multiply(get-tuple-element.3, multiply.31) - multiply.33 = f32[4]{0} multiply(get-tuple-element.3, multiply.32) - multiply.34 = f32[4]{0} multiply(get-tuple-element.3, multiply.33) - multiply.35 = f32[4]{0} multiply(get-tuple-element.3, multiply.34) - multiply.36 = f32[4]{0} multiply(get-tuple-element.3, multiply.35) - multiply.37 = f32[4]{0} multiply(get-tuple-element.3, multiply.36) - multiply.38 = f32[4]{0} multiply(get-tuple-element.3, multiply.37) - multiply.39 = f32[4]{0} multiply(get-tuple-element.3, multiply.38) - multiply.40 = f32[4]{0} multiply(get-tuple-element.3, multiply.39) - ROOT multiply.41 = f32[4]{0} multiply(get-tuple-element.3, multiply.40) -} - -comp.1 { - multiply.12.param_1.1 = f32[4]{0} parameter(1) - constant.param_1.3 = f32[4]{0} parameter(0) - add.3 = f32[4]{0} add(multiply.12.param_1.1, constant.param_1.3) - ROOT multiply.16 = f32[4]{0} multiply(add.3, constant.param_1.3) -} - -comp { - multiply.12.param_1 = f32[4]{0} parameter(1) - constant.param_1.1 = f32[4]{0} parameter(0) - multiply.15 = f32[4]{0} multiply(multiply.12.param_1, constant.param_1.1) - ROOT add.2 = f32[4]{0} add(multiply.15, constant.param_1.1) -} - -ENTRY FlopsToBytesRatioThresholdExceeded.Computation1 { - constant = f32[4]{0} constant({1, 1, 1, 1}) - state = (f32[4]{0}, f32[4]{0}) parameter(0) - fusion.2 = f32[4]{0} fusion(state), kind=kLoop, calls=comp.2 - fusion.3 = f32[4]{0} fusion(constant, fusion.2), kind=kLoop, calls=comp.1 - fusion.4 = f32[4]{0} fusion(constant, fusion.2), kind=kLoop, calls=comp - ROOT tuple = (f32[4]{0}, f32[4]{0}) tuple(fusion.3, fusion.4) -})") - .ValueOrDie(); - // Run fusion merger pass, which should detect that the flops/bytes of the - // shared fusion instruction exceeds the threshold ratio, and therefore - // cannot be merged with other fusion instructions. - EXPECT_FALSE(FusionMerger().Run(module.get()).ValueOrDie()); -} - // Tests that threshold for bytes transferred if merged is exceeded. // // Fusion2 is not merged because it exceeds the threshold bytes transferred. -- GitLab From 5ea6e863e5079aaa222f889213b16528166846f0 Mon Sep 17 00:00:00 2001 From: Siju Date: Tue, 26 Feb 2019 13:30:40 +0530 Subject: [PATCH 536/766] Update graph_properties_test.cc --- tensorflow/core/grappler/costs/graph_properties_test.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/core/grappler/costs/graph_properties_test.cc b/tensorflow/core/grappler/costs/graph_properties_test.cc index fa6b05bd15..ce8d367a34 100644 --- a/tensorflow/core/grappler/costs/graph_properties_test.cc +++ b/tensorflow/core/grappler/costs/graph_properties_test.cc @@ -1726,7 +1726,7 @@ TEST_F(GraphPropertiesTest, StridedSliceOfShapeWithShrinkAxisMask) { GrapplerItem item; TF_CHECK_OK(scope.ToGraphDef(&item.graph)); - // Without aggresive shape inference, it cannot infer output value of + // Without aggressive shape inference, it cannot infer output value of // StridedSlice with ShrinkAxisMask. { GraphProperties properties(item); @@ -1736,7 +1736,7 @@ TEST_F(GraphPropertiesTest, StridedSliceOfShapeWithShrinkAxisMask) { EXPECT_FALSE(properties.GetOutputProperties("slice").at(0).has_value()); } - // InferStatically with aggresive shape inference can infer output value of + // InferStatically with aggressive shape inference can infer output value of // StridedSlice with ShrinkAxisMask. { GraphProperties properties(item); -- GitLab From 2242681b130c58d926b89a8c51d43664e8fb0c8b Mon Sep 17 00:00:00 2001 From: Siju Date: Tue, 26 Feb 2019 13:37:24 +0530 Subject: [PATCH 537/766] Update resolve_constant_unary.cc --- .../lite/toco/graph_transformations/resolve_constant_unary.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/toco/graph_transformations/resolve_constant_unary.cc b/tensorflow/lite/toco/graph_transformations/resolve_constant_unary.cc index 43070b063c..2d9035bc72 100644 --- a/tensorflow/lite/toco/graph_transformations/resolve_constant_unary.cc +++ b/tensorflow/lite/toco/graph_transformations/resolve_constant_unary.cc @@ -30,7 +30,7 @@ namespace toco { namespace { // Using the function reducer, reduce input along all axes in axes. -// Put the reduced data in output, which should aleady be appropriately sized. +// Put the reduced data in output, which should already be appropriately sized. // check_output_shape is set to what this code computes the final shape // to be, so it can be cross checked with the shape computation logic. void ReduceGeneric(bool keep_dims, const std::vector& axes, -- GitLab From 4c52b16bd8187934c8f2d8015262957aad5d8547 Mon Sep 17 00:00:00 2001 From: Siju Date: Tue, 26 Feb 2019 13:38:53 +0530 Subject: [PATCH 538/766] Update partially_decluster_pass.cc --- tensorflow/compiler/jit/partially_decluster_pass.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/compiler/jit/partially_decluster_pass.cc b/tensorflow/compiler/jit/partially_decluster_pass.cc index e1fd2aaee2..3a7872847d 100644 --- a/tensorflow/compiler/jit/partially_decluster_pass.cc +++ b/tensorflow/compiler/jit/partially_decluster_pass.cc @@ -272,7 +272,7 @@ Status MustCompileNode(const Node* n, bool* must_compile) { // We assume here that the extra repeated (repeated compared to a clustered f // where it will always be constant folded) host-side computation of f does not // regress performance in any significant manner. We will have to revisit this -// algorith with a more complex cost model if this assumption turns out to be +// algorithm with a more complex cost model if this assumption turns out to be // incorrect. Status PartiallyDeclusterGraph(Graph* graph) { std::vector compile_time_const_nodes(graph->num_node_ids()); -- GitLab From ea6399ae50b1f59c3dd59f5b3d84a1fb3d45dee7 Mon Sep 17 00:00:00 2001 From: Siju Date: Tue, 26 Feb 2019 13:40:21 +0530 Subject: [PATCH 539/766] Update trt_allocator.cc --- tensorflow/compiler/tf2tensorrt/utils/trt_allocator.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/compiler/tf2tensorrt/utils/trt_allocator.cc b/tensorflow/compiler/tf2tensorrt/utils/trt_allocator.cc index 1e9ab1202f..a18f758a55 100644 --- a/tensorflow/compiler/tf2tensorrt/utils/trt_allocator.cc +++ b/tensorflow/compiler/tf2tensorrt/utils/trt_allocator.cc @@ -72,7 +72,7 @@ void* TRTDeviceAllocator::allocate(uint64_t size, uint64_t alignment, uint32_t flags) { if (size == 0) return nullptr; // WAR for allocator alignment requirement. Certain cuda API calls require GPU - // memory with alignemtn to cudaDeviceProp::textureAlignment. + // memory with alignment to cudaDeviceProp::textureAlignment. // See issue #20856 alignment = 512; assert((alignment & (alignment - 1)) == 0); // zero or a power of 2. -- GitLab From 7d14b0e3beeea2b59d17145309918c41c6e5505c Mon Sep 17 00:00:00 2001 From: Siju Date: Tue, 26 Feb 2019 13:50:14 +0530 Subject: [PATCH 540/766] Update batchnorm_expander_test.cc --- tensorflow/compiler/xla/service/batchnorm_expander_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/compiler/xla/service/batchnorm_expander_test.cc b/tensorflow/compiler/xla/service/batchnorm_expander_test.cc index 8e8fbbd935..34b516184f 100644 --- a/tensorflow/compiler/xla/service/batchnorm_expander_test.cc +++ b/tensorflow/compiler/xla/service/batchnorm_expander_test.cc @@ -60,7 +60,7 @@ TEST_F(BatchNormExpanderTest, BatchNormTraining) { HloComputation::Builder builder(TestName()); HloInstruction* param0 = builder.AddInstruction( - HloInstruction::CreateParameter(0, input_shape, "activiation")); + HloInstruction::CreateParameter(0, input_shape, "activation")); HloInstruction* param1 = builder.AddInstruction( HloInstruction::CreateParameter(1, scale_shape, "scale")); -- GitLab From 11df474e859a4beae19380fb5124d188c2130142 Mon Sep 17 00:00:00 2001 From: Siju Date: Tue, 26 Feb 2019 13:52:34 +0530 Subject: [PATCH 541/766] Update spectral_normalization_impl.py --- .../features/python/spectral_normalization_impl.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tensorflow/contrib/gan/python/features/python/spectral_normalization_impl.py b/tensorflow/contrib/gan/python/features/python/spectral_normalization_impl.py index 0cc653f0a7..3764c43cdf 100644 --- a/tensorflow/contrib/gan/python/features/python/spectral_normalization_impl.py +++ b/tensorflow/contrib/gan/python/features/python/spectral_normalization_impl.py @@ -53,7 +53,7 @@ def compute_spectral_norm(w_tensor, power_iteration_rounds=1, name=None): Args: w_tensor: The weight matrix whose spectral norm should be computed. power_iteration_rounds: The number of iterations of the power method to - perform. A higher number yeilds a better approximation. + perform. A higher number yields a better approximation. name: An optional scope name. Returns: @@ -105,7 +105,7 @@ def spectral_normalize(w, power_iteration_rounds=1, name=None): Args: w: The weight matrix to be normalized. power_iteration_rounds: The number of iterations of the power method to - perform. A higher number yeilds a better approximation. + perform. A higher number yields a better approximation. name: An optional scope name. Returns: @@ -126,7 +126,7 @@ def spectral_norm_regularizer(scale, power_iteration_rounds=1, scope=None): Args: scale: A scalar multiplier. 0.0 disables the regularizer. power_iteration_rounds: The number of iterations of the power method to - perform. A higher number yeilds a better approximation. + perform. A higher number yields a better approximation. scope: An optional scope name. Returns: @@ -221,7 +221,7 @@ def spectral_normalization_custom_getter(name_filter=_default_name_filter, name_filter: Optionally, a method that takes a Variable name as input and returns whether this Variable should be normalized. power_iteration_rounds: The number of iterations of the power method to - perform per step. A higher number yeilds a better approximation of the + perform per step. A higher number yields a better approximation of the true spectral norm. Returns: @@ -294,7 +294,7 @@ def keras_spectral_normalization(name_filter=_default_name_filter, name_filter: Optionally, a method that takes a Variable name as input and returns whether this Variable should be normalized. power_iteration_rounds: The number of iterations of the power method to - perform per step. A higher number yeilds a better approximation of the + perform per step. A higher number yields a better approximation of the true spectral norm. Yields: -- GitLab From 3c8afbcc47233dc311370438a50230d3c70ef3df Mon Sep 17 00:00:00 2001 From: Siju Date: Tue, 26 Feb 2019 13:53:21 +0530 Subject: [PATCH 542/766] Update hierarchical_tree_broadcaster_test.cc --- .../core/common_runtime/hierarchical_tree_broadcaster_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/core/common_runtime/hierarchical_tree_broadcaster_test.cc b/tensorflow/core/common_runtime/hierarchical_tree_broadcaster_test.cc index 12af4a8201..d17e4b08e0 100644 --- a/tensorflow/core/common_runtime/hierarchical_tree_broadcaster_test.cc +++ b/tensorflow/core/common_runtime/hierarchical_tree_broadcaster_test.cc @@ -504,7 +504,7 @@ class HierarchicalTreeBroadcasterTest : public ::testing::Test { // instance may succeed while others fail, even if a transmission // failure occurs early in the operation chain. So, when an abort // is specified we need to verify that at least one Op fails with - // the expected status and any Op that succeeds yeilds the correct + // the expected status and any Op that succeeds yields the correct // value. if (fail_after > 0) { mutex_lock l(mu_); -- GitLab From 17e7ad4ba42cb1c21d784c10ebd93ae36ca2a4d7 Mon Sep 17 00:00:00 2001 From: Siju Date: Tue, 26 Feb 2019 14:17:37 +0530 Subject: [PATCH 543/766] Update op_hint.py --- tensorflow/lite/python/op_hint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/python/op_hint.py b/tensorflow/lite/python/op_hint.py index ae1f5f094b..bf24b9e00b 100644 --- a/tensorflow/lite/python/op_hint.py +++ b/tensorflow/lite/python/op_hint.py @@ -234,7 +234,7 @@ class OpHint(object): and OpHint.AGGREGATE_STACK. Note, aggregate is only valid if tag is specified. index_override: Specify what input/output index should this be in the - final stub. i.e. add(arg0, index=1); add(arg1, index=0) wil make the + final stub. i.e. add(arg0, index=1); add(arg1, index=0) will make the final stub be as stub_func(inputs[arg1, arg0], outputs=[]) rather than the default call order based ordering. -- GitLab From 3a023c2c76566dd88270f00a45ae4869dc22af7e Mon Sep 17 00:00:00 2001 From: Siju Date: Tue, 26 Feb 2019 14:18:27 +0530 Subject: [PATCH 544/766] Update xrt_state_ops.cc --- tensorflow/compiler/xrt/ops/xrt_state_ops.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/compiler/xrt/ops/xrt_state_ops.cc b/tensorflow/compiler/xrt/ops/xrt_state_ops.cc index 8832270fb2..87546fce4e 100644 --- a/tensorflow/compiler/xrt/ops/xrt_state_ops.cc +++ b/tensorflow/compiler/xrt/ops/xrt_state_ops.cc @@ -53,7 +53,7 @@ The shapes can differ from the corresponding input one, as long as the total number of elements matches. In other words, it is possible to feed an input tensor with shape {8} and have a corresponding shape {2,2,2}. layouts: A vector holding the requested layout in minor-to-major sequence. -If empty, the default layout wil be used. +If empty, the default layout will be used. For a tuple, the layouts vector holds a linearized minor-to-major numbers for all the tuple leaves, in the order they appear within the tuple. The elements within the layouts sequence corresponding to a given tuple -- GitLab From 4fce06dbd945e220cf2e331699cfb3d4cdb23e73 Mon Sep 17 00:00:00 2001 From: Siju Date: Tue, 26 Feb 2019 14:19:11 +0530 Subject: [PATCH 545/766] Update model_fn.py --- tensorflow/contrib/learn/python/learn/estimators/model_fn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/contrib/learn/python/learn/estimators/model_fn.py b/tensorflow/contrib/learn/python/learn/estimators/model_fn.py index dcb161180c..96adc8b83b 100644 --- a/tensorflow/contrib/learn/python/learn/estimators/model_fn.py +++ b/tensorflow/contrib/learn/python/learn/estimators/model_fn.py @@ -219,7 +219,7 @@ class ModelFnOps( used if a Servo request does not explicitly mention which head to infer on. Pass the key of the output alternative here that you want to designate as default. A separate ExportOutpout for this default head - wil be added to the export_outputs dict with the special key + will be added to the export_outputs dict with the special key signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY, unless there is already an enry in output_alternatives with this special key. -- GitLab From e5afc00873bc58ed7cd7a7a7b36dc32eb8090e37 Mon Sep 17 00:00:00 2001 From: Anna R Date: Tue, 26 Feb 2019 00:56:50 -0800 Subject: [PATCH 546/766] Update tf_upgrade_v2 to auto-update *Classifier's and *Regressors, as well as tf.initializers.uniform_unit_scaling. Also, fix names accessor in tf_export.py. PiperOrigin-RevId: 235674971 --- tensorflow/python/util/tf_export.py | 4 +- tensorflow/tools/compatibility/renames_v2.py | 23 ++-- tensorflow/tools/compatibility/reorders_v2.py | 11 ++ .../compatibility/testdata/test_file_v1_12.py | 34 ++++-- .../tools/compatibility/tf_upgrade_v2.py | 112 +++++++++++++----- .../tools/compatibility/tf_upgrade_v2_test.py | 40 ++++++- .../update/generate_v2_reorders_map.py | 11 +- 7 files changed, 179 insertions(+), 56 deletions(-) diff --git a/tensorflow/python/util/tf_export.py b/tensorflow/python/util/tf_export.py index 7b44d72ab9..ff87f97dd4 100644 --- a/tensorflow/python/util/tf_export.py +++ b/tensorflow/python/util/tf_export.py @@ -170,7 +170,7 @@ def get_v1_names(symbol): estimator_api_attr_v1 = API_ATTRS_V1[ESTIMATOR_API_NAME].names keras_api_attr_v1 = API_ATTRS_V1[KERAS_API_NAME].names - if not hasattr(symbol, tensorflow_api_attr_v1): + if not hasattr(symbol, '__dict__'): return names_v1 if tensorflow_api_attr_v1 in symbol.__dict__: names_v1.extend(getattr(symbol, tensorflow_api_attr_v1)) @@ -196,7 +196,7 @@ def get_v2_names(symbol): estimator_api_attr = API_ATTRS[ESTIMATOR_API_NAME].names keras_api_attr = API_ATTRS[KERAS_API_NAME].names - if not hasattr(symbol, tensorflow_api_attr): + if not hasattr(symbol, '__dict__'): return names_v2 if tensorflow_api_attr in symbol.__dict__: names_v2.extend(getattr(symbol, tensorflow_api_attr)) diff --git a/tensorflow/tools/compatibility/renames_v2.py b/tensorflow/tools/compatibility/renames_v2.py index d399b3f321..18bc7046e5 100644 --- a/tensorflow/tools/compatibility/renames_v2.py +++ b/tensorflow/tools/compatibility/renames_v2.py @@ -180,6 +180,8 @@ renames = { 'tf.encode_base64': 'tf.io.encode_base64', 'tf.erf': 'tf.math.erf', 'tf.erfc': 'tf.math.erfc', + 'tf.estimator.inputs.numpy_input_fn': 'tf.compat.v1.estimator.inputs.numpy_input_fn', + 'tf.estimator.inputs.pandas_input_fn': 'tf.compat.v1.estimator.inputs.pandas_input_fn', 'tf.expm1': 'tf.math.expm1', 'tf.fake_quant_with_min_max_args': 'tf.quantization.fake_quant_with_min_max_args', 'tf.fake_quant_with_min_max_args_gradient': 'tf.quantization.fake_quant_with_min_max_args_gradient', @@ -264,23 +266,18 @@ renames = { 'tf.is_numeric_tensor': 'tf.debugging.is_numeric_tensor', 'tf.is_strictly_increasing': 'tf.math.is_strictly_increasing', 'tf.is_variable_initialized': 'tf.compat.v1.is_variable_initialized', - 'tf.keras.initializers.Identity': 'tf.compat.v1.keras.initializers.Identity', - 'tf.keras.initializers.Orthogonal': 'tf.compat.v1.keras.initializers.Orthogonal', - 'tf.keras.initializers.TruncatedNormal': 'tf.compat.v1.keras.initializers.TruncatedNormal', - 'tf.keras.initializers.VarianceScaling': 'tf.compat.v1.keras.initializers.VarianceScaling', - 'tf.keras.initializers.glorot_normal': 'tf.compat.v1.keras.initializers.glorot_normal', - 'tf.keras.initializers.glorot_uniform': 'tf.compat.v1.keras.initializers.glorot_uniform', - 'tf.keras.initializers.he_normal': 'tf.compat.v1.keras.initializers.he_normal', - 'tf.keras.initializers.he_uniform': 'tf.compat.v1.keras.initializers.he_uniform', - 'tf.keras.initializers.identity': 'tf.compat.v1.keras.initializers.identity', - 'tf.keras.initializers.lecun_normal': 'tf.compat.v1.keras.initializers.lecun_normal', - 'tf.keras.initializers.lecun_uniform': 'tf.compat.v1.keras.initializers.lecun_uniform', + 'tf.keras.backend.get_session': 'tf.compat.v1.keras.backend.get_session', 'tf.keras.initializers.normal': 'tf.compat.v1.keras.initializers.normal', - 'tf.keras.initializers.orthogonal': 'tf.compat.v1.keras.initializers.orthogonal', 'tf.keras.initializers.random_normal': 'tf.compat.v1.keras.initializers.random_normal', 'tf.keras.initializers.random_uniform': 'tf.compat.v1.keras.initializers.random_uniform', 'tf.keras.initializers.truncated_normal': 'tf.compat.v1.keras.initializers.truncated_normal', 'tf.keras.initializers.uniform': 'tf.compat.v1.keras.initializers.uniform', + 'tf.keras.layers.CuDNNGRU': 'tf.compat.v1.keras.layers.CuDNNGRU', + 'tf.keras.layers.CuDNNLSTM': 'tf.compat.v1.keras.layers.CuDNNLSTM', + 'tf.keras.losses.cosine': 'tf.keras.losses.cosine_similarity', + 'tf.keras.losses.cosine_proximity': 'tf.keras.losses.cosine_similarity', + 'tf.keras.metrics.cosine': 'tf.keras.losses.cosine_similarity', + 'tf.keras.metrics.cosine_proximity': 'tf.keras.losses.cosine_similarity', 'tf.layers.AveragePooling1D': 'tf.compat.v1.layers.AveragePooling1D', 'tf.layers.AveragePooling2D': 'tf.compat.v1.layers.AveragePooling2D', 'tf.layers.AveragePooling3D': 'tf.compat.v1.layers.AveragePooling3D', @@ -520,7 +517,6 @@ renames = { 'tf.saved_model.experimental.save': 'tf.saved_model.save', 'tf.saved_model.get_tensor_from_tensor_info': 'tf.compat.v1.saved_model.get_tensor_from_tensor_info', 'tf.saved_model.is_valid_signature': 'tf.compat.v1.saved_model.is_valid_signature', - 'tf.saved_model.load': 'tf.compat.v1.saved_model.load', 'tf.saved_model.loader.load': 'tf.compat.v1.saved_model.loader.load', 'tf.saved_model.loader.maybe_saved_model_directory': 'tf.compat.v1.saved_model.loader.maybe_saved_model_directory', 'tf.saved_model.main_op.main_op': 'tf.compat.v1.saved_model.main_op.main_op', @@ -641,7 +637,6 @@ renames = { 'tf.svd': 'tf.linalg.svd', 'tf.tables_initializer': 'tf.compat.v1.tables_initializer', 'tf.test.StubOutForTesting': 'tf.compat.v1.test.StubOutForTesting', - 'tf.test.compute_gradient': 'tf.compat.v1.test.compute_gradient', 'tf.test.compute_gradient_error': 'tf.compat.v1.test.compute_gradient_error', 'tf.test.get_temp_dir': 'tf.compat.v1.test.get_temp_dir', 'tf.test.mock': 'tf.compat.v1.test.mock', diff --git a/tensorflow/tools/compatibility/reorders_v2.py b/tensorflow/tools/compatibility/reorders_v2.py index 14c2d0c09f..436daf1196 100644 --- a/tensorflow/tools/compatibility/reorders_v2.py +++ b/tensorflow/tools/compatibility/reorders_v2.py @@ -35,10 +35,19 @@ reorders = { 'tf.convert_to_tensor': ['value', 'dtype', 'name', 'preferred_dtype', 'dtype_hint'], 'tf.decode_csv': ['records', 'record_defaults', 'field_delim', 'use_quote_delim', 'name', 'na_value', 'select_cols'], 'tf.depth_to_space': ['input', 'block_size', 'name', 'data_format'], + 'tf.estimator.BaselineClassifier': ['model_dir', 'n_classes', 'weight_column', 'label_vocabulary', 'optimizer', 'config', 'loss_reduction'], + 'tf.estimator.BaselineRegressor': ['model_dir', 'label_dimension', 'weight_column', 'optimizer', 'config', 'loss_reduction'], + 'tf.estimator.DNNClassifier': ['hidden_units', 'feature_columns', 'model_dir', 'n_classes', 'weight_column', 'label_vocabulary', 'optimizer', 'activation_fn', 'dropout', 'input_layer_partitioner', 'config', 'warm_start_from', 'loss_reduction', 'batch_norm'], + 'tf.estimator.DNNLinearCombinedClassifier': ['model_dir', 'linear_feature_columns', 'linear_optimizer', 'dnn_feature_columns', 'dnn_optimizer', 'dnn_hidden_units', 'dnn_activation_fn', 'dnn_dropout', 'n_classes', 'weight_column', 'label_vocabulary', 'input_layer_partitioner', 'config', 'warm_start_from', 'loss_reduction', 'batch_norm', 'linear_sparse_combiner'], + 'tf.estimator.DNNLinearCombinedRegressor': ['model_dir', 'linear_feature_columns', 'linear_optimizer', 'dnn_feature_columns', 'dnn_optimizer', 'dnn_hidden_units', 'dnn_activation_fn', 'dnn_dropout', 'label_dimension', 'weight_column', 'input_layer_partitioner', 'config', 'warm_start_from', 'loss_reduction', 'batch_norm', 'linear_sparse_combiner'], + 'tf.estimator.DNNRegressor': ['hidden_units', 'feature_columns', 'model_dir', 'label_dimension', 'weight_column', 'optimizer', 'activation_fn', 'dropout', 'input_layer_partitioner', 'config', 'warm_start_from', 'loss_reduction', 'batch_norm'], + 'tf.estimator.LinearClassifier': ['feature_columns', 'model_dir', 'n_classes', 'weight_column', 'label_vocabulary', 'optimizer', 'config', 'partitioner', 'warm_start_from', 'loss_reduction', 'sparse_combiner'], + 'tf.estimator.LinearRegressor': ['feature_columns', 'model_dir', 'label_dimension', 'weight_column', 'optimizer', 'config', 'partitioner', 'warm_start_from', 'loss_reduction', 'sparse_combiner'], 'tf.feature_column.categorical_column_with_vocabulary_file': ['key', 'vocabulary_file', 'vocabulary_size', 'num_oov_buckets', 'default_value', 'dtype'], 'tf.gradients': ['ys', 'xs', 'grad_ys', 'name', 'colocate_gradients_with_ops', 'gate_gradients', 'aggregation_method', 'stop_gradients', 'unconnected_gradients'], 'tf.hessians': ['ys', 'xs', 'name', 'colocate_gradients_with_ops', 'gate_gradients', 'aggregation_method'], 'tf.image.sample_distorted_bounding_box': ['image_size', 'bounding_boxes', 'seed', 'seed2', 'min_object_covered', 'aspect_ratio_range', 'area_range', 'max_attempts', 'use_image_if_no_bounding_boxes', 'name'], + 'tf.initializers.uniform_unit_scaling': ['factor', 'seed', 'dtype'], 'tf.io.decode_csv': ['records', 'record_defaults', 'field_delim', 'use_quote_delim', 'name', 'na_value', 'select_cols'], 'tf.io.parse_example': ['serialized', 'features', 'name', 'example_names'], 'tf.io.parse_single_example': ['serialized', 'features', 'name', 'example_names'], @@ -59,6 +68,7 @@ reorders = { 'tf.math.reduce_sum': ['input_tensor', 'axis', 'keepdims', 'name', 'reduction_indices', 'keep_dims'], 'tf.multinomial': ['logits', 'num_samples', 'seed', 'name', 'output_dtype'], 'tf.nn.avg_pool': ['value', 'ksize', 'strides', 'padding', 'data_format', 'name', 'input'], + 'tf.nn.avg_pool2d': ['value', 'ksize', 'strides', 'padding', 'data_format', 'name', 'input'], 'tf.nn.conv1d': ['value', 'filters', 'stride', 'padding', 'use_cudnn_on_gpu', 'data_format', 'name', 'input', 'dilations'], 'tf.nn.conv2d': ['input', 'filter', 'strides', 'padding', 'use_cudnn_on_gpu', 'data_format', 'dilations', 'name', 'filters'], 'tf.nn.conv2d_backprop_input': ['input_sizes', 'filter', 'out_backprop', 'strides', 'padding', 'use_cudnn_on_gpu', 'data_format', 'dilations', 'name', 'filters'], @@ -126,5 +136,6 @@ reorders = { 'tf.test.assert_equal_graph_def': ['actual', 'expected', 'checkpoint_v2'], 'tf.transpose': ['a', 'perm', 'name', 'conjugate'], 'tf.tuple': ['tensors', 'name', 'control_inputs'], + 'tf.uniform_unit_scaling_initializer': ['factor', 'seed', 'dtype'], 'tf.while_loop': ['cond', 'body', 'loop_vars', 'shape_invariants', 'parallel_iterations', 'back_prop', 'swap_memory', 'name', 'maximum_iterations', 'return_same_structure'] } diff --git a/tensorflow/tools/compatibility/testdata/test_file_v1_12.py b/tensorflow/tools/compatibility/testdata/test_file_v1_12.py index 2663762aa7..3f86596f9b 100644 --- a/tensorflow/tools/compatibility/testdata/test_file_v1_12.py +++ b/tensorflow/tools/compatibility/testdata/test_file_v1_12.py @@ -28,13 +28,10 @@ class TestUpgrade(test_util.TensorFlowTestCase): def setUp(self): tf.enable_eager_execution() - @test_util.run_v1_only("b/120545219") def testRenames(self): - with self.cached_session(): - self.assertAllClose(1.04719755, tf.acos(0.5)) - self.assertAllClose(0.5, tf.rsqrt(4.0)) + self.assertAllClose(1.04719755, tf.acos(0.5)) + self.assertAllClose(0.5, tf.rsqrt(4.0)) - @test_util.run_v1_only("b/120545219") def testSerializeSparseTensor(self): sp_input = tf.SparseTensor( indices=tf.constant([[1]], dtype=tf.int64), @@ -46,7 +43,6 @@ class TestUpgrade(test_util.TensorFlowTestCase): self.assertEqual((3,), serialized_sp.shape) self.assertTrue(serialized_sp[0].numpy()) # check non-empty - @test_util.run_v1_only("b/120545219") def testSerializeManySparse(self): sp_input = tf.SparseTensor( indices=tf.constant([[0, 1]], dtype=tf.int64), @@ -58,7 +54,6 @@ class TestUpgrade(test_util.TensorFlowTestCase): sp_input, 'serialize_name', tf.string) self.assertEqual((1, 3), serialized_sp.shape) - @test_util.run_v1_only("b/120545219") def testArgMaxMin(self): self.assertAllClose( [1], @@ -70,7 +65,6 @@ class TestUpgrade(test_util.TensorFlowTestCase): [0], tf.argmin([[1, 3, 2]], name='abc', dimension=1)) - @test_util.run_v1_only("b/120545219") def testSoftmaxCrossEntropyWithLogits(self): out = tf.nn.softmax_cross_entropy_with_logits( logits=[0.1, 0.8], labels=[0, 1]) @@ -79,6 +73,30 @@ class TestUpgrade(test_util.TensorFlowTestCase): logits=[0.1, 0.8], labels=[0, 1]) self.assertAllClose(out, 0.40318608) + def testLinearClassifier(self): + feature_column = tf.feature_column.numeric_column( + 'feature', shape=(1,)) + + classifier = tf.estimator.LinearClassifier( + n_classes=2, feature_columns=[feature_column]) + + data = {'feature': [1, 20, 3]} + target = [0, 1, 0] + classifier.train( + input_fn=lambda: (data, target), + steps=100) + scores = classifier.evaluate( + input_fn=lambda: (data, target), + steps=100) + self.assertGreater(scores['accuracy'], 0.99) + + def testUniformUnitScalingInitializer(self): + init = tf.initializers.uniform_unit_scaling(0.5, seed=1) + self.assertArrayNear( + [-0.45200047, 0.72815341], + init((2,)).numpy(), + err=1e-6) + if __name__ == "__main__": test_lib.main() diff --git a/tensorflow/tools/compatibility/tf_upgrade_v2.py b/tensorflow/tools/compatibility/tf_upgrade_v2.py index c3ca68be60..6832a3a0d7 100644 --- a/tensorflow/tools/compatibility/tf_upgrade_v2.py +++ b/tensorflow/tools/compatibility/tf_upgrade_v2.py @@ -781,7 +781,9 @@ class TFAPIChangeSpec(ast_edits.APIChangeSpec): "tf.data.experimental.map_and_batch_with_legacy_function": "tf.compat.v1.data.experimental.map_and_batch_with_legacy_function", "tf.nn.conv2d_backprop_input": - "tf.nn.conv2d_transpose" + "tf.nn.conv2d_transpose", + "tf.test.compute_gradient": + "tf.compat.v1.test.compute_gradient", } # pylint: enable=line-too-long @@ -895,6 +897,16 @@ class TFAPIChangeSpec(ast_edits.APIChangeSpec): "tf.hessians", "tf.nn.max_pool", "tf.nn.avg_pool", + "tf.estimator.LinearClassifier", + "tf.estimator.LinearRegressor", + "tf.estimator.DNNLinearCombinedClassifier", + "tf.estimator.DNNLinearCombinedRegressor", + "tf.estimator.DNNRegressor", + "tf.estimator.DNNClassifier", + "tf.estimator.BaselineClassifier", + "tf.estimator.BaselineRegressor", + "tf.initializers.uniform_unit_scaling", + "tf.uniform_unit_scaling_initializer", } # Manual mapping of function names to be reordered to their list of argument @@ -978,13 +990,6 @@ class TFAPIChangeSpec(ast_edits.APIChangeSpec): "`partition_strategy` has been removed from . " " The 'div' strategy will be used by default.") - # TODO(b/118888586): add default value change to update script. - default_loss_reduction_changed = ( - ast_edits.WARNING, - "default value of loss_reduction has been changed to " - "SUM_OVER_BATCH_SIZE.\n" - ) - # make change instead uniform_unit_scaling_initializer_comment = ( ast_edits.ERROR, @@ -1112,22 +1117,6 @@ class TFAPIChangeSpec(ast_edits.APIChangeSpec): decay_function_comment, "tf.train.noisy_linear_cosine_decay": decay_function_comment, - "tf.estimator.LinearClassifier": - default_loss_reduction_changed, - "tf.estimator.LinearRegressor": - default_loss_reduction_changed, - "tf.estimator.DNNLinearCombinedClassifier": - default_loss_reduction_changed, - "tf.estimator.DNNLinearCombinedRegressor": - default_loss_reduction_changed, - "tf.estimator.DNNRegressor": - default_loss_reduction_changed, - "tf.estimator.DNNClassifier": - default_loss_reduction_changed, - "tf.estimator.BaselineClassifier": - default_loss_reduction_changed, - "tf.estimator.BaselineRegressor": - default_loss_reduction_changed, "tf.nn.nce_loss": deprecate_partition_strategy_comment, "tf.nn.safe_embedding_lookup_sparse": @@ -1202,10 +1191,6 @@ class TFAPIChangeSpec(ast_edits.APIChangeSpec): initializers_no_dtype_comment, "tf.initializers.glorot_normal": initializers_no_dtype_comment, - "tf.initializers.uniform_unit_scaling": - uniform_unit_scaling_initializer_comment, - "tf.uniform_unit_scaling_initializer": - uniform_unit_scaling_initializer_comment, "tf.losses.absolute_difference": losses_comment, "tf.losses.add_loss": @@ -1472,6 +1457,20 @@ class TFAPIChangeSpec(ast_edits.APIChangeSpec): "tf.contrib.summary.histogram": _add_summary_step_transformer, "tf.contrib.summary.image": _add_summary_step_transformer, "tf.contrib.summary.scalar": _add_summary_step_transformer, + "tf.estimator.LinearClassifier": _add_loss_reduction_transformer, + "tf.estimator.LinearRegressor": _add_loss_reduction_transformer, + "tf.estimator.DNNLinearCombinedClassifier": + _add_loss_reduction_transformer, + "tf.estimator.DNNLinearCombinedRegressor": + _add_loss_reduction_transformer, + "tf.estimator.DNNRegressor": _add_loss_reduction_transformer, + "tf.estimator.DNNClassifier": _add_loss_reduction_transformer, + "tf.estimator.BaselineClassifier": _add_loss_reduction_transformer, + "tf.estimator.BaselineRegressor": _add_loss_reduction_transformer, + "tf.initializers.uniform_unit_scaling": + _add_uniform_scaling_initializer_transformer, + "tf.uniform_unit_scaling_initializer": + _add_uniform_scaling_initializer_transformer, } self.module_deprecations = { @@ -1884,3 +1883,60 @@ def _add_summary_step_transformer(parent, node, full_name, name, logs): "Summary API writing function %s now requires a 'step' argument; " "inserting default of %s." % (full_name or name, default_value))) return node + + +def _add_loss_reduction_transformer(parent, node, full_name, name, logs): + """Adds a loss_reduction argument if not specified. + + Default value for tf.estimator.*Classifier and tf.estimator.*Regressor + loss_reduction argument changed to SUM_OVER_BATCH_SIZE. So, we update + existing calls to use the old default value `tf.losses.Reduction.SUM`. + + Note: to apply this transformation, symbol must be added + to reordered_function_names above. + """ + for keyword_arg in node.keywords: + if keyword_arg.arg == "loss_reduction": + return node + # TODO(annarev): this should be updated to tf.keras.losses.Reduction.SUM + # once b/125525822 is fixed. + default_value = "tf.compat.v1.losses.Reduction.SUM" + # Parse with pasta instead of ast to avoid emitting a spurious trailing \n. + ast_value = pasta.parse(default_value) + node.keywords.append(ast.keyword(arg="loss_reduction", value=ast_value)) + logs.append(( + ast_edits.INFO, node.lineno, node.col_offset, + "%s: Default value of loss_reduction has been changed to " + "SUM_OVER_BATCH_SIZE; inserting old default value %s.\n" + % (full_name or name, default_value))) + return node + +def _add_uniform_scaling_initializer_transformer( + parent, node, full_name, name, logs): + """Updates references to uniform_unit_scaling_initializer. + + Transforms: + tf.uniform_unit_scaling_initializer(factor, seed, dtype) to + tf.keras.initializers.VarianceScaling( + scale=factor, distribution="uniform", seed=seed) + + Note: to apply this transformation, symbol must be added + to reordered_function_names above. + """ + for keyword_arg in node.keywords: + if keyword_arg.arg == "factor": + keyword_arg.arg = "scale" + + distribution_value = "\"uniform\"" + # Parse with pasta instead of ast to avoid emitting a spurious trailing \n. + ast_value = pasta.parse(distribution_value) + node.keywords.append(ast.keyword(arg="distribution", value=ast_value)) + + lineno = node.func.value.lineno + col_offset = node.func.value.col_offset + node.func.value = ast_edits.full_name_node("tf.keras.initializers") + node.func.value.lineno = lineno + node.func.value.col_offset = col_offset + node.func.attr = "VarianceScaling" + return node + diff --git a/tensorflow/tools/compatibility/tf_upgrade_v2_test.py b/tensorflow/tools/compatibility/tf_upgrade_v2_test.py index 57d938f749..89fe045bd5 100644 --- a/tensorflow/tools/compatibility/tf_upgrade_v2_test.py +++ b/tensorflow/tools/compatibility/tf_upgrade_v2_test.py @@ -493,11 +493,30 @@ bazel-bin/tensorflow/tools/compatibility/update/generate_v2_reorders_map ] for c in classes: ns = "tf.estimator." + c - text = ns + "(a, b)" + text = ns + "()" + expected_text = ns + "(loss_reduction=tf.compat.v1.losses.Reduction.SUM)" + _, report, errors, new_text = self._upgrade(text) + self.assertEqual(expected_text, new_text) + + text = ns + "(loss_reduction=TEST)" + expected_text = ns + "(loss_reduction=TEST)" _, report, errors, new_text = self._upgrade(text) self.assertEqual(text, new_text) - self.assertIn("%s requires manual check" % ns, errors[0]) - self.assertIn("loss_reduction has been changed", report) + text = "tf.estimator.BaselineClassifier(m, c, w, v, o, c, lr)" + expected_text = ( + "tf.estimator.BaselineClassifier(" + + "model_dir=m, n_classes=c, weight_column=w, label_vocabulary=v, " + "optimizer=o, config=c, loss_reduction=lr)") + _, report, errors, new_text = self._upgrade(text) + self.assertEqual(expected_text, new_text) + + text = "tf.estimator.BaselineClassifier(model_dir=model_dir)" + expected_text = ("tf.estimator.BaselineClassifier(" + + "model_dir=model_dir, " + "loss_reduction=tf.compat.v1.losses.Reduction.SUM)") + _, report, errors, new_text = self._upgrade(text) + self.assertEqual(expected_text, new_text) + def testExtractGlimpse(self): text = ("tf.image.extract_glimpse(x, size, off, False, " @@ -1343,6 +1362,21 @@ def _log_prob(self, x): _, _, _, new_text = self._upgrade(text) self.assertEqual(expected, new_text) + def test_uniform_unit_scaling_initializer(self): + text = "tf.uniform_unit_scaling_initializer(0.5)" + expected_text = ( + "tf.keras.initializers.VarianceScaling(" + + "scale=0.5, distribution=\"uniform\")") + _, _, _, new_text = self._upgrade(text) + self.assertEqual(expected_text, new_text) + + text = "tf.initializers.uniform_unit_scaling(0.5)" + expected_text = ( + "tf.keras.initializers.VarianceScaling(" + + "scale=0.5, distribution=\"uniform\")") + _, _, _, new_text = self._upgrade(text) + self.assertEqual(expected_text, new_text) + class TestUpgradeFiles(test_util.TensorFlowTestCase): diff --git a/tensorflow/tools/compatibility/update/generate_v2_reorders_map.py b/tensorflow/tools/compatibility/update/generate_v2_reorders_map.py index 0eb942d396..c4fd8aab90 100644 --- a/tensorflow/tools/compatibility/update/generate_v2_reorders_map.py +++ b/tensorflow/tools/compatibility/update/generate_v2_reorders_map.py @@ -86,7 +86,16 @@ def collect_function_arg_names(function_names): matches_function_names = any( name in function_names for name in api_names_v1) if matches_function_names: - arg_list = tf_inspect.getargspec(attr)[0] + if tf_inspect.isclass(attr): + # Get constructor arguments if attr is a class + arg_list = tf_inspect.getargspec( + getattr(attr, '__init__'))[0] + arg_list = arg_list[1:] # skip 'self' argument + else: + # Get function arguments. + # getargspec returns a tuple of (args, varargs, keywords, defaults) + # we just look at args. + arg_list = tf_inspect.getargspec(attr)[0] for name in api_names_v1: function_to_args[name] = arg_list -- GitLab From 2dae893f00aaee6dbb6271c4cd24adae83ad6aa2 Mon Sep 17 00:00:00 2001 From: Nick Felt Date: Tue, 26 Feb 2019 00:58:47 -0800 Subject: [PATCH 547/766] Introduce settable default step for TF 2.0 summary API (experimental) This adds tf.summary.experimental.set_step() and tf.summary.experimental.get_step() functions to the TF 2.0 summary API. Their purpose is to allow setting a default value for the `step` parameter that had up until now been required on the new summary API writing functions like scalar(), image(), etc. Those parameters are now optional, though if there is no default step configured an exception will still be raised. The step can be manually set to a constant value in each iteration of the train loop, or you can set the step to a tf.Variable and then update the variable independently. Only the latter works well across a tf.function boundary because constant values will be captured when the function is traced, so updates outside the function won't be reflected inside it. PiperOrigin-RevId: 235675144 --- tensorflow/python/eager/context.py | 23 +- tensorflow/python/framework/test_util.py | 10 +- .../python/kernel_tests/summary_ops_test.py | 290 ++++++++++++++++-- tensorflow/python/ops/summary_ops_v2.py | 93 +++++- .../tools/api/generator/api_init_files.bzl | 1 + .../v2/tensorflow.summary.experimental.pbtxt | 11 + .../api/golden/v2/tensorflow.summary.pbtxt | 18 +- 7 files changed, 391 insertions(+), 55 deletions(-) create mode 100644 tensorflow/tools/api/golden/v2/tensorflow.summary.experimental.pbtxt diff --git a/tensorflow/python/eager/context.py b/tensorflow/python/eager/context.py index e25243c7bb..2202bc966a 100644 --- a/tensorflow/python/eager/context.py +++ b/tensorflow/python/eager/context.py @@ -143,7 +143,8 @@ class _EagerContext(threading.local): self.is_eager = default_execution_mode == EAGER_MODE self.scope_name = "" self.summary_writer_resource = None - self.recording_summaries = None + self.summary_recording = None + self.summary_step = None self.scalar_cache = {} self._ones_rank_cache = None self._zeros_cache = None @@ -526,14 +527,24 @@ class Context(object): self._eager_context.summary_writer_resource = resource @property - def recording_summaries(self): + def summary_recording(self): """Returns summary recording condition.""" - return self._eager_context.recording_summaries + return self._eager_context.summary_recording - @recording_summaries.setter - def recording_summaries(self, condition): + @summary_recording.setter + def summary_recording(self, condition): """Sets summary recording condition.""" - self._eager_context.recording_summaries = condition + self._eager_context.summary_recording = condition + + @property + def summary_step(self): + """Returns summary step variable.""" + return self._eager_context.summary_step + + @summary_step.setter + def summary_step(self, step): + """Sets summary step variable.""" + self._eager_context.summary_step = step @property def device_name(self): diff --git a/tensorflow/python/framework/test_util.py b/tensorflow/python/framework/test_util.py index 1d267830ff..303e263009 100644 --- a/tensorflow/python/framework/test_util.py +++ b/tensorflow/python/framework/test_util.py @@ -1012,10 +1012,12 @@ def py_func_if_in_function(f): if not ops.get_default_graph()._building_function: return f(*args, **kwds) - tensor_args, tensor_indices = zip(*[(x, i) - for i, x in enumerate(args) - if isinstance(x, (ops.Tensor, - variables.Variable))]) + tensor_args = [] + tensor_indices = [] + for i, arg in enumerate(args): + if isinstance(arg, (ops.Tensor, variables.Variable)): + tensor_args.append(arg) + tensor_indices.append(i) def inner_f(*inner_tensor_args): my_args = list(args) diff --git a/tensorflow/python/kernel_tests/summary_ops_test.py b/tensorflow/python/kernel_tests/summary_ops_test.py index dead337cfb..c3cdf9f758 100644 --- a/tensorflow/python/kernel_tests/summary_ops_test.py +++ b/tensorflow/python/kernel_tests/summary_ops_test.py @@ -167,8 +167,145 @@ class SummaryOpsCoreTest(test_util.TensorFlowTestCase): @test_util.also_run_as_tf_function def testWrite_noDefaultWriter(self): - with context.eager_mode(): - self.assertFalse(summary_ops.write('tag', 42, step=0)) + # Use assertAllEqual instead of assertFalse since it works in a defun. + self.assertAllEqual(False, summary_ops.write('tag', 42, step=0)) + + @test_util.also_run_as_tf_function + def testWrite_noStep_okayIfAlsoNoDefaultWriter(self): + # Use assertAllEqual instead of assertFalse since it works in a defun. + self.assertAllEqual(False, summary_ops.write('tag', 42)) + + @test_util.also_run_as_tf_function + def testWrite_noStep(self): + logdir = self.get_temp_dir() + with summary_ops.create_file_writer(logdir).as_default(): + with self.assertRaisesRegex(ValueError, 'No step set'): + summary_ops.write('tag', 42) + + def testWrite_usingDefaultStep(self): + logdir = self.get_temp_dir() + try: + with context.eager_mode(): + with summary_ops.create_file_writer(logdir).as_default(): + summary_ops.set_step(1) + summary_ops.write('tag', 1.0) + summary_ops.set_step(2) + summary_ops.write('tag', 1.0) + mystep = variables.Variable(10, dtype=dtypes.int64) + summary_ops.set_step(mystep) + summary_ops.write('tag', 1.0) + mystep.assign_add(1) + summary_ops.write('tag', 1.0) + events = events_from_logdir(logdir) + self.assertEqual(5, len(events)) + self.assertEqual(1, events[1].step) + self.assertEqual(2, events[2].step) + self.assertEqual(10, events[3].step) + self.assertEqual(11, events[4].step) + finally: + # Reset to default state for other tests. + summary_ops.set_step(None) + + def testWrite_usingDefaultStepConstant_fromFunction(self): + logdir = self.get_temp_dir() + try: + with context.eager_mode(): + writer = summary_ops.create_file_writer(logdir) + @def_function.function + def f(): + with writer.as_default(): + summary_ops.write('tag', 1.0) + summary_ops.set_step(1) + f() + summary_ops.set_step(2) + f() + events = events_from_logdir(logdir) + self.assertEqual(3, len(events)) + self.assertEqual(1, events[1].step) + # The step value will still be 1 because the value was captured at the + # time the function was first traced. + self.assertEqual(1, events[2].step) + finally: + # Reset to default state for other tests. + summary_ops.set_step(None) + + def testWrite_usingDefaultStepVariable_fromFunction(self): + logdir = self.get_temp_dir() + try: + with context.eager_mode(): + writer = summary_ops.create_file_writer(logdir) + @def_function.function + def f(): + with writer.as_default(): + summary_ops.write('tag', 1.0) + mystep = variables.Variable(0, dtype=dtypes.int64) + summary_ops.set_step(mystep) + f() + mystep.assign_add(1) + f() + mystep.assign(10) + f() + events = events_from_logdir(logdir) + self.assertEqual(4, len(events)) + self.assertEqual(0, events[1].step) + self.assertEqual(1, events[2].step) + self.assertEqual(10, events[3].step) + finally: + # Reset to default state for other tests. + summary_ops.set_step(None) + + def testWrite_usingDefaultStepConstant_fromLegacyGraph(self): + logdir = self.get_temp_dir() + try: + with context.graph_mode(): + writer = summary_ops.create_file_writer(logdir) + summary_ops.set_step(1) + with writer.as_default(): + write_op = summary_ops.write('tag', 1.0) + summary_ops.set_step(2) + with self.cached_session() as sess: + sess.run(writer.init()) + sess.run(write_op) + sess.run(write_op) + sess.run(writer.flush()) + events = events_from_logdir(logdir) + self.assertEqual(3, len(events)) + self.assertEqual(1, events[1].step) + # The step value will still be 1 because the value was captured at the + # time the graph was constructed. + self.assertEqual(1, events[2].step) + finally: + # Reset to default state for other tests. + summary_ops.set_step(None) + + def testWrite_usingDefaultStepVariable_fromLegacyGraph(self): + logdir = self.get_temp_dir() + try: + with context.graph_mode(): + writer = summary_ops.create_file_writer(logdir) + mystep = variables.Variable(0, dtype=dtypes.int64) + summary_ops.set_step(mystep) + with writer.as_default(): + write_op = summary_ops.write('tag', 1.0) + first_assign_op = mystep.assign_add(1) + second_assign_op = mystep.assign(10) + with self.cached_session() as sess: + sess.run(writer.init()) + sess.run(mystep.initializer) + sess.run(write_op) + sess.run(first_assign_op) + sess.run(write_op) + sess.run(second_assign_op) + sess.run(write_op) + sess.run(writer.flush()) + events = events_from_logdir(logdir) + self.assertEqual(4, len(events)) + self.assertEqual(0, events[1].step) + self.assertEqual(1, events[2].step) + self.assertEqual(10, events[3].step) + finally: + # Reset to default state for other tests. + summary_ops.set_step(None) def testWrite_recordIf_constant(self): logdir = self.get_temp_dir() @@ -269,6 +406,59 @@ class SummaryOpsCoreTest(test_util.TensorFlowTestCase): self.assertEqual(2, events[2].step) self.assertEqual(4, events[3].step) + @test_util.also_run_as_tf_function + def testGetSetStep(self): + try: + self.assertIsNone(summary_ops.get_step()) + summary_ops.set_step(1) + # Use assertAllEqual instead of assertEqual since it works in a defun. + self.assertAllEqual(1, summary_ops.get_step()) + summary_ops.set_step(constant_op.constant(2)) + self.assertAllEqual(2, summary_ops.get_step()) + finally: + # Reset to default state for other tests. + summary_ops.set_step(None) + + def testGetSetStep_variable(self): + with context.eager_mode(): + try: + mystep = variables.Variable(0) + summary_ops.set_step(mystep) + self.assertAllEqual(0, summary_ops.get_step().read_value()) + mystep.assign_add(1) + self.assertAllEqual(1, summary_ops.get_step().read_value()) + # Check that set_step() properly maintains reference to variable. + del mystep + self.assertAllEqual(1, summary_ops.get_step().read_value()) + summary_ops.get_step().assign_add(1) + self.assertAllEqual(2, summary_ops.get_step().read_value()) + finally: + # Reset to default state for other tests. + summary_ops.set_step(None) + + def testGetSetStep_variable_fromFunction(self): + with context.eager_mode(): + try: + @def_function.function + def set_step(step): + summary_ops.set_step(step) + return summary_ops.get_step() + @def_function.function + def get_and_increment(): + summary_ops.get_step().assign_add(1) + return summary_ops.get_step() + mystep = variables.Variable(0) + self.assertAllEqual(0, set_step(mystep)) + self.assertAllEqual(0, summary_ops.get_step().read_value()) + self.assertAllEqual(1, get_and_increment()) + self.assertAllEqual(2, get_and_increment()) + # Check that set_step() properly maintains reference to variable. + del mystep + self.assertAllEqual(3, get_and_increment()) + finally: + # Reset to default state for other tests. + summary_ops.set_step(None) + @test_util.also_run_as_tf_function def testSummaryScope(self): with summary_ops.summary_scope('foo') as (tag, scope): @@ -585,7 +775,7 @@ class SummaryOpsTest(test_util.TensorFlowTestCase): summary_ops.run_metadata(*args, **kwargs) writer.close() events = events_from_logdir(logdir) - return events[1].summary + return events[1] def run_metadata_graphs(self, *args, **kwargs): assert context.executing_eagerly() @@ -595,7 +785,7 @@ class SummaryOpsTest(test_util.TensorFlowTestCase): summary_ops.run_metadata_graphs(*args, **kwargs) writer.close() events = events_from_logdir(logdir) - return events[1].summary + return events[1] def create_run_metadata(self): step_stats = step_stats_pb2.StepStats(dev_stats=[ @@ -620,27 +810,27 @@ class SummaryOpsTest(test_util.TensorFlowTestCase): events = events_from_logdir(logdir) # The first event contains no summary values. The written content goes to # the second event. - return events[1].summary + return events[1] - def run_trace(self, f): + def run_trace(self, f, step=1): assert context.executing_eagerly() logdir = self.get_temp_dir() writer = summary_ops.create_file_writer(logdir) summary_ops.enable_trace(graph=True, profiler=False) with writer.as_default(): f() - summary_ops.export_trace(name='foo', step=1) + summary_ops.export_trace(name='foo', step=step) writer.close() events = events_from_logdir(logdir) - return events[1].summary + return events[1] @test_util.run_v2_only def testRunMetadata_usesNameAsTag(self): meta = config_pb2.RunMetadata() with ops.name_scope('foo'): - summary = self.run_metadata(name='my_name', data=meta, step=1) - first_val = summary.value[0] + event = self.run_metadata(name='my_name', data=meta, step=1) + first_val = event.summary.value[0] self.assertEqual('foo/my_name', first_val.tag) @@ -653,8 +843,8 @@ class SummaryOpsTest(test_util.TensorFlowTestCase): } """ meta = config_pb2.RunMetadata() - summary = self.run_metadata(name='my_name', data=meta, step=1) - actual_summary_metadata = summary.value[0].metadata + event = self.run_metadata(name='my_name', data=meta, step=1) + actual_summary_metadata = event.summary.value[0].metadata self.assertProtoEquals(expected_summary_metadata, actual_summary_metadata) @test_util.run_v2_only @@ -677,20 +867,31 @@ class SummaryOpsTest(test_util.TensorFlowTestCase): } """ meta = self.create_run_metadata() - summary = self.run_metadata(name='my_name', data=meta, step=1) - first_val = summary.value[0] + event = self.run_metadata(name='my_name', data=meta, step=1) + first_val = event.summary.value[0] actual_run_metadata = config_pb2.RunMetadata.FromString( first_val.tensor.string_val[0]) self.assertProtoEquals(expected_run_metadata, actual_run_metadata) + @test_util.run_v2_only + def testRunMetadata_usesDefaultStep(self): + meta = config_pb2.RunMetadata() + try: + summary_ops.set_step(42) + event = self.run_metadata(name='my_name', data=meta) + self.assertEqual(42, event.step) + finally: + # Reset to default state for other tests. + summary_ops.set_step(None) + @test_util.run_v2_only def testRunMetadataGraph_usesNameAsTag(self): meta = config_pb2.RunMetadata() with ops.name_scope('foo'): - summary = self.run_metadata_graphs(name='my_name', data=meta, step=1) - first_val = summary.value[0] + event = self.run_metadata_graphs(name='my_name', data=meta, step=1) + first_val = event.summary.value[0] self.assertEqual('foo/my_name', first_val.tag) @@ -703,8 +904,8 @@ class SummaryOpsTest(test_util.TensorFlowTestCase): } """ meta = config_pb2.RunMetadata() - summary = self.run_metadata_graphs(name='my_name', data=meta, step=1) - actual_summary_metadata = summary.value[0].metadata + event = self.run_metadata_graphs(name='my_name', data=meta, step=1) + actual_summary_metadata = event.summary.value[0].metadata self.assertProtoEquals(expected_summary_metadata, actual_summary_metadata) @test_util.run_v2_only @@ -720,22 +921,46 @@ class SummaryOpsTest(test_util.TensorFlowTestCase): """ meta = self.create_run_metadata() - summary = self.run_metadata_graphs(name='my_name', data=meta, step=1) - first_val = summary.value[0] + event = self.run_metadata_graphs(name='my_name', data=meta, step=1) + first_val = event.summary.value[0] actual_run_metadata = config_pb2.RunMetadata.FromString( first_val.tensor.string_val[0]) self.assertProtoEquals(expected_run_metadata, actual_run_metadata) + @test_util.run_v2_only + def testRunMetadataGraph_usesDefaultStep(self): + meta = config_pb2.RunMetadata() + try: + summary_ops.set_step(42) + event = self.run_metadata_graphs(name='my_name', data=meta) + self.assertEqual(42, event.step) + finally: + # Reset to default state for other tests. + summary_ops.set_step(None) + @test_util.run_v2_only def testKerasModel(self): model = Sequential( [Dense(10, input_shape=(100,)), Activation('relu', name='my_relu')]) - summary = self.keras_model(name='my_name', data=model, step=1) - first_val = summary.value[0] + event = self.keras_model(name='my_name', data=model, step=1) + first_val = event.summary.value[0] self.assertEqual(model.to_json(), first_val.tensor.string_val[0]) + @test_util.run_v2_only + def testKerasModel_usesDefaultStep(self): + model = Sequential( + [Dense(10, input_shape=(100,)), + Activation('relu', name='my_relu')]) + try: + summary_ops.set_step(42) + event = self.keras_model(name='my_name', data=model) + self.assertEqual(42, event.step) + finally: + # Reset to default state for other tests. + summary_ops.set_step(None) + @test_util.run_v2_only def testTrace(self): @@ -745,9 +970,9 @@ class SummaryOpsTest(test_util.TensorFlowTestCase): y = constant_op.constant(3) return x**y - summary = self.run_trace(f) + event = self.run_trace(f) - first_val = summary.value[0] + first_val = event.summary.value[0] actual_run_metadata = config_pb2.RunMetadata.FromString( first_val.tensor.string_val[0]) @@ -792,6 +1017,23 @@ class SummaryOpsTest(test_util.TensorFlowTestCase): str(mock_log.call_args), 'Can only export trace while executing eagerly.') + @test_util.run_v2_only + def testTrace_usesDefaultStep(self): + + @def_function.function + def f(): + x = constant_op.constant(2) + y = constant_op.constant(3) + return x**y + + try: + summary_ops.set_step(42) + event = self.run_trace(f, step=None) + self.assertEqual(42, event.step) + finally: + # Reset to default state for other tests. + summary_ops.set_step(None) + def events_from_file(filepath): """Returns all events in a single event file. diff --git a/tensorflow/python/ops/summary_ops_v2.py b/tensorflow/python/ops/summary_ops_v2.py index 4ed2bd6b7d..fac4445a40 100644 --- a/tensorflow/python/ops/summary_ops_v2.py +++ b/tensorflow/python/ops/summary_ops_v2.py @@ -63,7 +63,7 @@ _USER_NAME_PATTERNS = re.compile(r"^[a-z]([-a-z0-9]{0,29}[a-z0-9])?$", re.I) def _should_record_summaries_internal(): """Returns boolean Tensor if summaries should/shouldn't be recorded, or None. """ - condition = context.context().recording_summaries + condition = context.context().summary_recording return condition() if callable(condition) else condition @@ -99,12 +99,12 @@ def record_if(condition): Returns a context manager that sets this value on enter and restores the previous value on exit. """ - old = context.context().recording_summaries + old = context.context().summary_recording try: - context.context().recording_summaries = condition + context.context().summary_recording = condition yield finally: - context.context().recording_summaries = old + context.context().summary_recording = old # TODO(apassos) consider how to handle local step here. @@ -129,6 +129,36 @@ def never_record_summaries(): return record_if(False) +@tf_export("summary.experimental.get_step", v1=[]) +def get_step(): + """Returns the default summary step for the current thread. + + Returns: + The step set by `tf.summary.experimental.set_step()` if one has been set, + otherwise None. + """ + return context.context().summary_step + + +@tf_export("summary.experimental.set_step", v1=[]) +def set_step(step): + """Sets the default summary step for the current thread. + + For convenience, this function sets a default value for the `step` parameter + used in summary-writing functions elsewhere in the API so that it need not + be explicitly passed in every such invocation. The value can be a constant + or a variable, and can be retrieved via `tf.summary.experimental.get_step()`. + + Note: when using this with @tf.functions, the step value will be captured at + the time the function is traced, so changes to the step outside the function + will not be reflected inside the function unless using a `tf.Variable` step. + + Args: + step: An `int64`-castable default step value, or None to unset. + """ + context.context().summary_step = step + + @tf_export("summary.SummaryWriter", v1=[]) @six.add_metaclass(abc.ABCMeta) class SummaryWriter(object): @@ -544,7 +574,7 @@ def summary_scope(name, default_name="summary", values=None): @tf_export("summary.write", v1=[]) -def write(tag, tensor, step, metadata=None, name=None): +def write(tag, tensor, step=None, metadata=None, name=None): """Writes a generic summary to the default SummaryWriter if one exists. This exists primarily to support the definition of type-specific summary ops @@ -555,17 +585,28 @@ def write(tag, tensor, step, metadata=None, name=None): tag: string tag used to identify the summary (e.g. in TensorBoard), usually generated with `tf.summary.summary_scope` tensor: the Tensor holding the summary data to write - step: `int64`-castable monotonic step value for this summary + step: Explicit `int64`-castable monotonic step value for this summary. If + omitted, this defaults to `tf.summary.experimental.get_step()`, which must + not be None. metadata: Optional SummaryMetadata, as a proto or serialized bytes name: Optional string name for this op. Returns: True on success, or false if no summary was written because no default summary writer was available. + + Raises: + ValueError: if a default writer exists, but no step was provided and + `tf.summary.experimental.get_step()` is None. """ with ops.name_scope(name, "write_summary") as scope: if context.context().summary_writer_resource is None: return constant_op.constant(False) + if step is None: + step = get_step() + if step is None: + raise ValueError("No step set via 'step' argument or " + "tf.summary.experimental.set_step()") if metadata is None: serialized_metadata = b"" elif hasattr(metadata, "SerializeToString"): @@ -877,7 +918,7 @@ def _check_create_file_writer_args(inside_function, **kwargs): "create_file_writer()." % (arg_name, arg)) -def run_metadata(name, data, step): +def run_metadata(name, data, step=None): """Writes entire RunMetadata summary. A RunMetadata can contain DeviceStats, partition graphs, and function graphs. @@ -887,11 +928,17 @@ def run_metadata(name, data, step): name: A name for this summary. The summary tag used for TensorBoard will be this name prefixed by any active name scopes. data: A RunMetadata proto to write. - step: Required `int64`-castable monotonic step value. + step: Explicit `int64`-castable monotonic step value for this summary. If + omitted, this defaults to `tf.summary.experimental.get_step()`, which must + not be None. Returns: True on success, or false if no summary was written because no default summary writer was available. + + Raises: + ValueError: if a default writer exists, but no step was provided and + `tf.summary.experimental.get_step()` is None. """ summary_metadata = summary_pb2.SummaryMetadata() # Hard coding a plugin name. Please refer to go/tb-plugin-name-hardcode for @@ -911,18 +958,24 @@ def run_metadata(name, data, step): metadata=summary_metadata) -def run_metadata_graphs(name, data, step): +def run_metadata_graphs(name, data, step=None): """Writes graphs from a RunMetadata summary. Args: name: A name for this summary. The summary tag used for TensorBoard will be this name prefixed by any active name scopes. data: A RunMetadata proto to write. - step: Required `int64`-castable monotonic step value. + step: Explicit `int64`-castable monotonic step value for this summary. If + omitted, this defaults to `tf.summary.experimental.get_step()`, which must + not be None. Returns: True on success, or false if no summary was written because no default summary writer was available. + + Raises: + ValueError: if a default writer exists, but no step was provided and + `tf.summary.experimental.get_step()` is None. """ summary_metadata = summary_pb2.SummaryMetadata() # Hard coding a plugin name. Please refer to go/tb-plugin-name-hardcode for @@ -946,7 +999,7 @@ def run_metadata_graphs(name, data, step): metadata=summary_metadata) -def keras_model(name, data, step): +def keras_model(name, data, step=None): """Writes a Keras model as JSON to as a Summary. Writing the Keras model configuration allows the TensorBoard graph plugin to @@ -956,11 +1009,17 @@ def keras_model(name, data, step): name: A name for this summary. The summary tag used for TensorBoard will be this name prefixed by any active name scopes. data: A Keras Model to write. - step: Required `int64`-castable monotonic step value. + step: Explicit `int64`-castable monotonic step value for this summary. If + omitted, this defaults to `tf.summary.experimental.get_step()`, which must + not be None. Returns: True on success, or false if no summary was written because no default summary writer was available. + + Raises: + ValueError: if a default writer exists, but no step was provided and + `tf.summary.experimental.get_step()` is None. """ summary_metadata = summary_pb2.SummaryMetadata() # Hard coding a plugin name. Please refer to go/tb-plugin-name-hardcode for @@ -1015,17 +1074,23 @@ def enable_trace(graph=True, profiler=False): @tf_export("summary.export_trace", v1=[]) -def export_trace(name, step, profiler_outdir=None): +def export_trace(name, step=None, profiler_outdir=None): """Exports trace as a Summary and/or profile file. Args: name: A name for the summary to be written. - step: Required `int64`-castable monotonic step value. + step: Explicit `int64`-castable monotonic step value for this summary. If + omitted, this defaults to `tf.summary.experimental.get_step()`, which must + not be None. profiler_outdir: Output directory for profiler. It is required when profiler is enabled when trace was started. Otherwise, it is ignored. Returns: None + + Raises: + ValueError: if a default writer exists, but no step was provided and + `tf.summary.experimental.get_step()` is None. """ # TODO(stephanlee): See if we can remove profiler_outdir and infer it from # the SummaryWriter's logdir. diff --git a/tensorflow/python/tools/api/generator/api_init_files.bzl b/tensorflow/python/tools/api/generator/api_init_files.bzl index 5a5bb501e9..3c7344ceba 100644 --- a/tensorflow/python/tools/api/generator/api_init_files.bzl +++ b/tensorflow/python/tools/api/generator/api_init_files.bzl @@ -45,6 +45,7 @@ TENSORFLOW_API_INIT_FILES = [ "sparse/__init__.py", "strings/__init__.py", "summary/__init__.py", + "summary/experimental/__init__.py", "sysconfig/__init__.py", "test/__init__.py", "train/__init__.py", diff --git a/tensorflow/tools/api/golden/v2/tensorflow.summary.experimental.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.summary.experimental.pbtxt new file mode 100644 index 0000000000..016d753744 --- /dev/null +++ b/tensorflow/tools/api/golden/v2/tensorflow.summary.experimental.pbtxt @@ -0,0 +1,11 @@ +path: "tensorflow.summary.experimental" +tf_module { + member_method { + name: "get_step" + argspec: "args=[], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "set_step" + argspec: "args=[\'step\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.summary.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.summary.pbtxt index d8650bd4ce..c7afcd7f21 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.summary.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.summary.pbtxt @@ -4,9 +4,13 @@ tf_module { name: "SummaryWriter" mtype: "" } + member { + name: "experimental" + mtype: "" + } member_method { name: "audio" - argspec: "args=[\'name\', \'data\', \'sample_rate\', \'step\', \'max_outputs\', \'encoding\', \'description\'], varargs=None, keywords=None, defaults=[\'3\', \'None\', \'None\'], " + argspec: "args=[\'name\', \'data\', \'sample_rate\', \'step\', \'max_outputs\', \'encoding\', \'description\'], varargs=None, keywords=None, defaults=[\'None\', \'3\', \'None\', \'None\'], " } member_method { name: "create_file_writer" @@ -26,7 +30,7 @@ tf_module { } member_method { name: "export_trace" - argspec: "args=[\'name\', \'step\', \'profiler_outdir\'], varargs=None, keywords=None, defaults=[\'None\'], " + argspec: "args=[\'name\', \'step\', \'profiler_outdir\'], varargs=None, keywords=None, defaults=[\'None\', \'None\'], " } member_method { name: "flush" @@ -34,11 +38,11 @@ tf_module { } member_method { name: "histogram" - argspec: "args=[\'name\', \'data\', \'step\', \'buckets\', \'description\'], varargs=None, keywords=None, defaults=[\'None\', \'None\'], " + argspec: "args=[\'name\', \'data\', \'step\', \'buckets\', \'description\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'None\'], " } member_method { name: "image" - argspec: "args=[\'name\', \'data\', \'step\', \'max_outputs\', \'description\'], varargs=None, keywords=None, defaults=[\'3\', \'None\'], " + argspec: "args=[\'name\', \'data\', \'step\', \'max_outputs\', \'description\'], varargs=None, keywords=None, defaults=[\'None\', \'3\', \'None\'], " } member_method { name: "import_event" @@ -50,7 +54,7 @@ tf_module { } member_method { name: "scalar" - argspec: "args=[\'name\', \'data\', \'step\', \'description\'], varargs=None, keywords=None, defaults=[\'None\'], " + argspec: "args=[\'name\', \'data\', \'step\', \'description\'], varargs=None, keywords=None, defaults=[\'None\', \'None\'], " } member_method { name: "summary_scope" @@ -58,10 +62,10 @@ tf_module { } member_method { name: "text" - argspec: "args=[\'name\', \'data\', \'step\', \'description\'], varargs=None, keywords=None, defaults=[\'None\'], " + argspec: "args=[\'name\', \'data\', \'step\', \'description\'], varargs=None, keywords=None, defaults=[\'None\', \'None\'], " } member_method { name: "write" - argspec: "args=[\'tag\', \'tensor\', \'step\', \'metadata\', \'name\'], varargs=None, keywords=None, defaults=[\'None\', \'None\'], " + argspec: "args=[\'tag\', \'tensor\', \'step\', \'metadata\', \'name\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'None\'], " } } -- GitLab From 348eaa0d0af4106479ef4754277630cc4c4141c0 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 26 Feb 2019 01:02:40 -0800 Subject: [PATCH 548/766] compat: Update forward compatibility horizon to 2019-02-26 PiperOrigin-RevId: 235675690 --- tensorflow/python/compat/compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/compat/compat.py b/tensorflow/python/compat/compat.py index d949d20368..72fc9fcea0 100644 --- a/tensorflow/python/compat/compat.py +++ b/tensorflow/python/compat/compat.py @@ -27,7 +27,7 @@ import datetime from tensorflow.python.util import tf_contextlib from tensorflow.python.util.tf_export import tf_export -_FORWARD_COMPATIBILITY_HORIZON = datetime.date(2019, 2, 25) +_FORWARD_COMPATIBILITY_HORIZON = datetime.date(2019, 2, 26) @tf_export("compat.forward_compatible") -- GitLab From 08b57db26d325cd3152a42f78f7d1e037ece89ce Mon Sep 17 00:00:00 2001 From: Tom Hennigan Date: Tue, 26 Feb 2019 01:53:00 -0800 Subject: [PATCH 549/766] Fix typo in docstring. PiperOrigin-RevId: 235681704 --- tensorflow/python/module/module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/module/module.py b/tensorflow/python/module/module.py index a015e9687d..6406bc8f14 100644 --- a/tensorflow/python/module/module.py +++ b/tensorflow/python/module/module.py @@ -77,7 +77,7 @@ class Module(tracking.AutoTrackable): ... self.layers = [] ... with self.name_scope: ... for size in sizes: - ... self.layers.append(Dense(input_size=size, output_size=size)) + ... self.layers.append(Dense(input_size=input_size, output_size=size)) ... input_size = size ... ... @tf.Module.with_name_scope -- GitLab From 2e846b14e15d9bf04892e935e293dad1f2ec037a Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 26 Feb 2019 01:59:10 -0800 Subject: [PATCH 550/766] Delete unused toolchain directory. PiperOrigin-RevId: 235682311 --- tensorflow/contrib/makefile/Makefile | 1 - tensorflow/opensource_only.files | 6 - third_party/toolchains/gpus/crosstool/BUILD | 77 - .../toolchains/gpus/crosstool/CROSSTOOL | 1432 ----------------- third_party/toolchains/gpus/cuda/BUILD | 1268 --------------- .../toolchains/gpus/cuda/build_defs.bzl | 35 - .../toolchains/gpus/cuda/cuda/cuda_config.h | 27 - third_party/toolchains/gpus/py/BUILD | 177 -- 8 files changed, 3023 deletions(-) delete mode 100644 third_party/toolchains/gpus/crosstool/BUILD delete mode 100644 third_party/toolchains/gpus/crosstool/CROSSTOOL delete mode 100644 third_party/toolchains/gpus/cuda/BUILD delete mode 100644 third_party/toolchains/gpus/cuda/build_defs.bzl delete mode 100644 third_party/toolchains/gpus/cuda/cuda/cuda_config.h delete mode 100644 third_party/toolchains/gpus/py/BUILD diff --git a/tensorflow/contrib/makefile/Makefile b/tensorflow/contrib/makefile/Makefile index 7ea6e34cf5..d22548d500 100644 --- a/tensorflow/contrib/makefile/Makefile +++ b/tensorflow/contrib/makefile/Makefile @@ -439,7 +439,6 @@ $(MARCH_OPTION) \ -Itensorflow/core/kernels \ -I$(MAKEFILE_DIR)/downloads/cub \ -I$(MAKEFILE_DIR)/downloads/cub/cub_archive/cub/device \ --Ithird_party/toolchains/gpus/cuda \ -I$(JETPACK)/cuda/include \ -I$(JETPACK) \ -I$(JETPACK)/cuDNN/aarch64 \ diff --git a/tensorflow/opensource_only.files b/tensorflow/opensource_only.files index 9264939b3b..3be09b70f1 100644 --- a/tensorflow/opensource_only.files +++ b/tensorflow/opensource_only.files @@ -60,12 +60,6 @@ tensorflow/third_party/toolchains/preconfig/win_1803/bazel_018/BUILD tensorflow/third_party/toolchains/preconfig/win_1803/bazel_018/dummy_toolchain.bzl tensorflow/third_party/toolchains/preconfig/win_1803/py36/BUILD tensorflow/third_party/toolchains/preconfig/win_1803/BUILD -tensorflow/third_party/toolchains/gpus/cuda/build_defs.bzl -tensorflow/third_party/toolchains/gpus/cuda/BUILD -tensorflow/third_party/toolchains/gpus/cuda/cuda/cuda_config.h -tensorflow/third_party/toolchains/gpus/crosstool/BUILD -tensorflow/third_party/toolchains/gpus/crosstool/CROSSTOOL -tensorflow/third_party/toolchains/gpus/py/BUILD tensorflow/third_party/toolchains/cpus/arm/arm_compiler_configure.bzl tensorflow/third_party/toolchains/cpus/arm/CROSSTOOL.tpl tensorflow/third_party/toolchains/cpus/arm/BUILD diff --git a/third_party/toolchains/gpus/crosstool/BUILD b/third_party/toolchains/gpus/crosstool/BUILD deleted file mode 100644 index bb0b6b3bbb..0000000000 --- a/third_party/toolchains/gpus/crosstool/BUILD +++ /dev/null @@ -1,77 +0,0 @@ -# A build file to configure cc toolchain for GPU build used with Bazel remote -# execution service -# DO NOT EDIT: automatically generated BUILD file - -licenses(["restricted"]) - -package(default_visibility = ["//visibility:public"]) - -cc_toolchain_suite( - name = "toolchain", - toolchains = { - "local|compiler": ":cc-compiler-local", - "darwin|compiler": ":cc-compiler-darwin", - "x64_windows|msvc-cl": ":cc-compiler-windows", - }, -) - -cc_toolchain( - name = "cc-compiler-local", - all_files = ":empty", - compiler_files = ":empty", - cpu = "local", - dwp_files = ":empty", - dynamic_runtime_libs = [":empty"], - linker_files = ":empty", - objcopy_files = ":empty", - static_runtime_libs = [":empty"], - strip_files = ":empty", - # To support linker flags that need to go to the start of command line - # we need the toolchain to support parameter files. Parameter files are - # last on the command line and contain all shared libraries to link, so all - # regular options will be left of them. - supports_param_files = 1, -) - -cc_toolchain( - name = "cc-compiler-darwin", - all_files = ":empty", - compiler_files = ":empty", - cpu = "darwin", - dwp_files = ":empty", - dynamic_runtime_libs = [":empty"], - linker_files = ":empty", - objcopy_files = ":empty", - static_runtime_libs = [":empty"], - strip_files = ":empty", - supports_param_files = 0, -) - -cc_toolchain( - name = "cc-compiler-windows", - all_files = ":empty", - compiler_files = ":empty", - cpu = "x64_windows", - dwp_files = ":empty", - dynamic_runtime_libs = [":empty"], - linker_files = ":empty", - objcopy_files = ":empty", - static_runtime_libs = [":empty"], - strip_files = ":empty", - supports_param_files = 1, -) - -filegroup( - name = "empty", - srcs = [], -) - -filegroup( - name = "crosstool_wrapper_driver_is_not_gcc", - srcs = ["clang/bin/crosstool_wrapper_driver_is_not_gcc"], -) - -filegroup( - name = "windows_msvc_wrapper_files", - srcs = glob(["windows/msvc_*"]), -) diff --git a/third_party/toolchains/gpus/crosstool/CROSSTOOL b/third_party/toolchains/gpus/crosstool/CROSSTOOL deleted file mode 100644 index b8eeb31ecb..0000000000 --- a/third_party/toolchains/gpus/crosstool/CROSSTOOL +++ /dev/null @@ -1,1432 +0,0 @@ -# A crosstool configuration for GPU build used with Bazel remote -# execution service -# DO NOT EDIT: automatically generated file - -major_version: "local" -minor_version: "" -default_target_cpu: "same_as_host" - -default_toolchain { - cpu: "k8" - toolchain_identifier: "local_linux" -} -default_toolchain { - cpu: "piii" - toolchain_identifier: "local_linux" -} -default_toolchain { - cpu: "arm" - toolchain_identifier: "local_linux" -} -default_toolchain { - cpu: "darwin" - toolchain_identifier: "local_darwin" -} -default_toolchain { - cpu: "ppc" - toolchain_identifier: "local_linux" -} -default_toolchain { - cpu: "x64_windows" - toolchain_identifier: "local_windows" -} - -toolchain { - abi_version: "local" - abi_libc_version: "local" - compiler: "compiler" - host_system_name: "local" - needsPic: true - target_libc: "local" - target_cpu: "local" - target_system_name: "local" - toolchain_identifier: "local_linux" - - feature { - name: "c++11" - flag_set { - action: "c++-compile" - flag_group { - flag: "-std=c++11" - } - } - } - - feature { - name: "stdlib" - flag_set { - action: "c++-link-executable" - action: "c++-link-dynamic-library" - action: "c++-link-nodeps-dynamic-library" - flag_group { - flag: "-lstdc++" - } - } - } - - feature { - name: "determinism" - flag_set { - action: "c-compile" - action: "c++-compile" - flag_group { - # Make C++ compilation deterministic. Use linkstamping instead of these - # compiler symbols. - flag: "-Wno-builtin-macro-redefined" - flag: "-D__DATE__=\"redacted\"" - flag: "-D__TIMESTAMP__=\"redacted\"" - flag: "-D__TIME__=\"redacted\"" - } - } - } - - feature { - name: "alwayslink" - flag_set { - action: "c++-link-dynamic-library" - action: "c++-link-nodeps-dynamic-library" - action: "c++-link-executable" - flag_group { - flag: "-Wl,-no-as-needed" - } - } - } - - # This feature will be enabled for builds that support pic by bazel. - feature { - name: "pic" - flag_set { - action: "c-compile" - action: "c++-compile" - flag_group { - expand_if_all_available: "pic" - flag: "-fPIC" - } - flag_group { - expand_if_none_available: "pic" - flag: "-fPIE" - } - } - } - - # Security hardening on by default. - feature { - name: "hardening" - flag_set { - action: "c-compile" - action: "c++-compile" - flag_group { - # Conservative choice; -D_FORTIFY_SOURCE=2 may be unsafe in some cases. - # We need to undef it before redefining it as some distributions now - # have it enabled by default. - flag: "-U_FORTIFY_SOURCE" - flag: "-D_FORTIFY_SOURCE=1" - flag: "-fstack-protector" - } - } - flag_set { - action: "c++-link-dynamic-library" - action: "c++-link-nodeps-dynamic-library" - flag_group { - flag: "-Wl,-z,relro,-z,now" - } - } - flag_set { - action: "c++-link-executable" - flag_group { - flag: "-pie" - flag: "-Wl,-z,relro,-z,now" - } - } - } - - feature { - name: "warnings" - flag_set { - action: "c-compile" - action: "c++-compile" - flag_group { - # All warnings are enabled. Maybe enable -Werror as well? - flag: "-Wall" - - # Some parts of the codebase set -Werror and hit this warning, so - # switch it off for now. - flag: "-Wno-invalid-partial-specialization" - - } - } - } - - # Keep stack frames for debugging, even in opt mode. - feature { - name: "frame-pointer" - flag_set { - action: "c-compile" - action: "c++-compile" - flag_group { - flag: "-fno-omit-frame-pointer" - } - } - } - - feature { - name: "build-id" - flag_set { - action: "c++-link-executable" - action: "c++-link-dynamic-library" - action: "c++-link-nodeps-dynamic-library" - flag_group { - # Stamp the binary with a unique identifier. - flag: "-Wl,--build-id=md5" - flag: "-Wl,--hash-style=gnu" - } - } - } - - feature { - name: "no-canonical-prefixes" - flag_set { - action: "c-compile" - action: "c++-compile" - action: "c++-link-executable" - action: "c++-link-dynamic-library" - action: "c++-link-nodeps-dynamic-library" - flag_group { - flag:"-no-canonical-prefixes" - } - } - } - - feature { - name: "disable-assertions" - flag_set { - action: "c-compile" - action: "c++-compile" - flag_group { - flag: "-DNDEBUG" - } - } - } - - feature { - name: "linker-bin-path" - - flag_set { - action: "c++-link-executable" - action: "c++-link-dynamic-library" - action: "c++-link-nodeps-dynamic-library" - flag_group { - flag: "-B/usr/bin/" - } - } - } - - feature { - name: "common" - implies: "stdlib" - implies: "c++11" - implies: "determinism" - implies: "alwayslink" - implies: "hardening" - implies: "warnings" - implies: "frame-pointer" - implies: "build-id" - implies: "no-canonical-prefixes" - implies: "linker-bin-path" - } - - feature { - name: "opt" - implies: "common" - implies: "disable-assertions" - - flag_set { - action: "c-compile" - action: "c++-compile" - flag_group { - # No debug symbols. - # Maybe we should enable https://gcc.gnu.org/wiki/DebugFission for opt - # or even generally? However, that can't happen here, as it requires - # special handling in Bazel. - flag: "-g0" - - # Conservative choice for -O - # -O3 can increase binary size and even slow down the resulting binaries. - # Profile first and / or use FDO if you need better performance than this. - flag: "-O2" - - # Removal of unused code and data at link time (can this increase binary size in some cases?). - flag: "-ffunction-sections" - flag: "-fdata-sections" - } - } - flag_set { - action: "c++-link-dynamic-library" - action: "c++-link-nodeps-dynamic-library" - action: "c++-link-executable" - flag_group { - flag: "-Wl,--gc-sections" - } - } - } - - feature { - name: "fastbuild" - implies: "common" - } - - feature { - name: "dbg" - implies: "common" - flag_set { - action: "c-compile" - action: "c++-compile" - flag_group { - flag: "-g" - } - } - } - - # Set clang as a C/C++ compiler. - tool_path { name: "gcc" path: "/usr/local/bin/clang" } - - # Use the default system toolchain for everything else. - tool_path { name: "ar" path: "/usr/bin/ar" } - tool_path { name: "compat-ld" path: "/usr/bin/ld" } - tool_path { name: "cpp" path: "/usr/bin/cpp" } - tool_path { name: "dwp" path: "/usr/bin/dwp" } - tool_path { name: "gcov" path: "/usr/bin/gcov" } - tool_path { name: "ld" path: "/usr/bin/ld" } - tool_path { name: "nm" path: "/usr/bin/nm" } - tool_path { name: "objcopy" path: "/usr/bin/objcopy" } - tool_path { name: "objdump" path: "/usr/bin/objdump" } - tool_path { name: "strip" path: "/usr/bin/strip" } - - # Enabled dynamic linking. - linking_mode_flags { mode: DYNAMIC } - - cxx_builtin_include_directory: "/usr/include/c++/5.4.0" - cxx_builtin_include_directory: "/usr/include/x86_64-linux-gnu/c++/5.4.0" - cxx_builtin_include_directory: "/usr/include/c++/5.4.0/backward" - cxx_builtin_include_directory: "/usr/local/include" - cxx_builtin_include_directory: "/usr/local/lib/clang/7.0.0/include" - cxx_builtin_include_directory: "/usr/include/x86_64-linux-gnu" - cxx_builtin_include_directory: "/usr/include" -} - -toolchain { - abi_version: "local" - abi_libc_version: "local" - compiler: "compiler" - host_system_name: "local" - needsPic: true - target_libc: "macosx" - target_cpu: "darwin" - target_system_name: "local" - toolchain_identifier: "local_darwin" - feature { - name: "c++11" - flag_set { - action: "c++-compile" - flag_group { - flag: "-std=c++11" - } - } - } - - feature { - name: "stdlib" - flag_set { - action: "c++-link-executable" - action: "c++-link-dynamic-library" - action: "c++-link-nodeps-dynamic-library" - flag_group { - flag: "-lc++" - } - } - } - - feature { - name: "determinism" - flag_set { - action: "c-compile" - action: "c++-compile" - flag_group { - # Make C++ compilation deterministic. Use linkstamping instead of these - # compiler symbols. - flag: "-Wno-builtin-macro-redefined" - flag: "-D__DATE__=\"redacted\"" - flag: "-D__TIMESTAMP__=\"redacted\"" - flag: "-D__TIME__=\"redacted\"" - } - } - } - - # This feature will be enabled for builds that support pic by bazel. - feature { - name: "pic" - flag_set { - action: "c-compile" - action: "c++-compile" - flag_group { - expand_if_all_available: "pic" - flag: "-fPIC" - } - flag_group { - expand_if_none_available: "pic" - flag: "-fPIE" - } - } - } - - # Security hardening on by default. - feature { - name: "hardening" - flag_set { - action: "c-compile" - action: "c++-compile" - flag_group { - # Conservative choice; -D_FORTIFY_SOURCE=2 may be unsafe in some cases. - # We need to undef it before redefining it as some distributions now - # have it enabled by default. - flag: "-U_FORTIFY_SOURCE" - flag: "-D_FORTIFY_SOURCE=1" - flag: "-fstack-protector" - } - } - flag_set { - action: "c++-link-executable" - flag_group { - flag: "-pie" - } - } - } - - feature { - name: "warnings" - flag_set { - action: "c-compile" - action: "c++-compile" - flag_group { - # All warnings are enabled. Maybe enable -Werror as well? - flag: "-Wall" - - # Some parts of the codebase set -Werror and hit this warning, so - # switch it off for now. - flag: "-Wno-invalid-partial-specialization" - - } - } - } - - # Keep stack frames for debugging, even in opt mode. - feature { - name: "frame-pointer" - flag_set { - action: "c-compile" - action: "c++-compile" - flag_group { - flag: "-fno-omit-frame-pointer" - } - } - } - - feature { - name: "no-canonical-prefixes" - flag_set { - action: "c-compile" - action: "c++-compile" - action: "c++-link-executable" - action: "c++-link-dynamic-library" - action: "c++-link-nodeps-dynamic-library" - flag_group { - flag:"-no-canonical-prefixes" - } - } - } - - feature { - name: "disable-assertions" - flag_set { - action: "c-compile" - action: "c++-compile" - flag_group { - flag: "-DNDEBUG" - } - } - } - - feature { - name: "linker-bin-path" - - flag_set { - action: "c++-link-executable" - action: "c++-link-dynamic-library" - action: "c++-link-nodeps-dynamic-library" - flag_group { - flag: "-B/usr/bin/" - } - } - } - - feature { - name: "undefined-dynamic" - flag_set { - action: "c++-link-dynamic-library" - action: "c++-link-nodeps-dynamic-library" - action: "c++-link-executable" - flag_group { - flag: "-undefined" - flag: "dynamic_lookup" - } - } - } - - feature { - name: "common" - implies: "stdlib" - implies: "c++11" - implies: "determinism" - implies: "hardening" - implies: "warnings" - implies: "frame-pointer" - implies: "no-canonical-prefixes" - implies: "linker-bin-path" - implies: "undefined-dynamic" - } - - feature { - name: "opt" - implies: "common" - implies: "disable-assertions" - - flag_set { - action: "c-compile" - action: "c++-compile" - flag_group { - # No debug symbols. - # Maybe we should enable https://gcc.gnu.org/wiki/DebugFission for opt - # or even generally? However, that can't happen here, as it requires - # special handling in Bazel. - flag: "-g0" - - # Conservative choice for -O - # -O3 can increase binary size and even slow down the resulting binaries. - # Profile first and / or use FDO if you need better performance than this. - flag: "-O2" - - # Removal of unused code and data at link time (can this increase binary size in some cases?). - flag: "-ffunction-sections" - flag: "-fdata-sections" - } - } - } - - feature { - name: "fastbuild" - implies: "common" - } - - feature { - name: "dbg" - implies: "common" - flag_set { - action: "c-compile" - action: "c++-compile" - flag_group { - flag: "-g" - } - } - } - - # Set clang as a C/C++ compiler. - tool_path { name: "gcc" path: "/usr/local/bin/clang" } - - # Use the default system toolchain for everything else. - tool_path { name: "ar" path: "/usr/bin/libtool" } - tool_path { name: "compat-ld" path: "/usr/bin/ld" } - tool_path { name: "cpp" path: "/usr/bin/cpp" } - tool_path { name: "dwp" path: "/usr/bin/dwp" } - tool_path { name: "gcov" path: "/usr/bin/gcov" } - tool_path { name: "ld" path: "/usr/bin/ld" } - tool_path { name: "nm" path: "/usr/bin/nm" } - tool_path { name: "objcopy" path: "/usr/bin/objcopy" } - tool_path { name: "objdump" path: "/usr/bin/objdump" } - tool_path { name: "strip" path: "/usr/bin/strip" } - - # Enabled dynamic linking. - linking_mode_flags { mode: DYNAMIC } - - cxx_builtin_include_directory: "/usr/include/c++/5.4.0" - cxx_builtin_include_directory: "/usr/include/x86_64-linux-gnu/c++/5.4.0" - cxx_builtin_include_directory: "/usr/include/c++/5.4.0/backward" - cxx_builtin_include_directory: "/usr/local/include" - cxx_builtin_include_directory: "/usr/local/lib/clang/7.0.0/include" - cxx_builtin_include_directory: "/usr/include/x86_64-linux-gnu" - cxx_builtin_include_directory: "/usr/include" -} - -toolchain { - toolchain_identifier: "local_windows" - host_system_name: "local" - target_system_name: "local" - - abi_version: "local" - abi_libc_version: "local" - target_cpu: "x64_windows" - compiler: "msvc-cl" - target_libc: "msvcrt" - - - - tool_path { - name: "ar" - path: "" - } - tool_path { - name: "ml" - path: "" - } - tool_path { - name: "cpp" - path: "" - } - tool_path { - name: "gcc" - path: "" - } - tool_path { - name: "gcov" - path: "wrapper/bin/msvc_nop.bat" - } - tool_path { - name: "ld" - path: "" - } - tool_path { - name: "nm" - path: "wrapper/bin/msvc_nop.bat" - } - tool_path { - name: "objcopy" - path: "wrapper/bin/msvc_nop.bat" - } - tool_path { - name: "objdump" - path: "wrapper/bin/msvc_nop.bat" - } - tool_path { - name: "strip" - path: "wrapper/bin/msvc_nop.bat" - } - supports_interface_shared_objects: true - - # TODO(pcloudy): Review those flags below, they should be defined by cl.exe - compiler_flag: "/DCOMPILER_MSVC" - - # Don't define min/max macros in windows.h. - compiler_flag: "/DNOMINMAX" - - # Platform defines. - compiler_flag: "/D_WIN32_WINNT=0x0600" - # Turn off warning messages. - compiler_flag: "/D_CRT_SECURE_NO_DEPRECATE" - compiler_flag: "/D_CRT_SECURE_NO_WARNINGS" - compiler_flag: "/D_SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS" - - # Useful options to have on for compilation. - # Increase the capacity of object files to 2^32 sections. - compiler_flag: "/bigobj" - # Allocate 500MB for precomputed headers. - compiler_flag: "/Zm500" - # Use unsigned char by default. - compiler_flag: "/J" - # Use function level linking. - compiler_flag: "/Gy" - # Use string pooling. - compiler_flag: "/GF" - # Catch C++ exceptions only and tell the compiler to assume that functions declared - # as extern "C" never throw a C++ exception. - compiler_flag: "/EHsc" - - # Globally disabled warnings. - # Don't warn about elements of array being be default initialized. - compiler_flag: "/wd4351" - # Don't warn about no matching delete found. - compiler_flag: "/wd4291" - # Don't warn about diamond inheritance patterns. - compiler_flag: "/wd4250" - # Don't warn about insecure functions (e.g. non _s functions). - compiler_flag: "/wd4996" - - linker_flag: "/MACHINE:X64" - - feature { - name: "no_legacy_features" - } - - # Suppress startup banner. - feature { - name: "nologo" - flag_set { - action: "c-compile" - action: "c++-compile" - action: "c++-module-compile" - action: "c++-module-codegen" - action: "c++-header-parsing" - action: "assemble" - action: "preprocess-assemble" - action: "c++-link-executable" - action: "c++-link-dynamic-library" - action: "c++-link-nodeps-dynamic-library" - action: "c++-link-static-library" - flag_group { - flag: "/nologo" - } - } - } - - feature { - name: 'has_configured_linker_path' - } - - # This feature indicates strip is not supported, building stripped binary will just result a copy of orignial binary - feature { - name: 'no_stripping' - } - - # This feature indicates this is a toolchain targeting Windows. - feature { - name: 'targets_windows' - implies: 'copy_dynamic_libraries_to_binary' - enabled: true - } - - feature { - name: 'copy_dynamic_libraries_to_binary' - } - - action_config { - config_name: 'assemble' - action_name: 'assemble' - tool { - tool_path: '' - } - implies: 'compiler_input_flags' - implies: 'compiler_output_flags' - implies: 'nologo' - implies: 'msvc_env' - implies: 'sysroot' - } - - action_config { - config_name: 'preprocess-assemble' - action_name: 'preprocess-assemble' - tool { - tool_path: '' - } - implies: 'compiler_input_flags' - implies: 'compiler_output_flags' - implies: 'nologo' - implies: 'msvc_env' - implies: 'sysroot' - } - - action_config { - config_name: 'c-compile' - action_name: 'c-compile' - tool { - tool_path: '' - } - implies: 'compiler_input_flags' - implies: 'compiler_output_flags' - implies: 'legacy_compile_flags' - implies: 'nologo' - implies: 'msvc_env' - implies: 'parse_showincludes' - implies: 'user_compile_flags' - implies: 'sysroot' - implies: 'unfiltered_compile_flags' - } - - action_config { - config_name: 'c++-compile' - action_name: 'c++-compile' - tool { - tool_path: '' - } - implies: 'compiler_input_flags' - implies: 'compiler_output_flags' - implies: 'legacy_compile_flags' - implies: 'nologo' - implies: 'msvc_env' - implies: 'parse_showincludes' - implies: 'user_compile_flags' - implies: 'sysroot' - implies: 'unfiltered_compile_flags' - } - - action_config { - config_name: 'c++-link-executable' - action_name: 'c++-link-executable' - tool { - tool_path: '' - } - implies: 'nologo' - implies: 'linkstamps' - implies: 'output_execpath_flags' - implies: 'input_param_flags' - implies: 'user_link_flags' - implies: 'legacy_link_flags' - implies: 'linker_subsystem_flag' - implies: 'linker_param_file' - implies: 'msvc_env' - implies: 'no_stripping' - } - - action_config { - config_name: 'c++-link-dynamic-library' - action_name: 'c++-link-dynamic-library' - tool { - tool_path: '' - } - implies: 'nologo' - implies: 'shared_flag' - implies: 'linkstamps' - implies: 'output_execpath_flags' - implies: 'input_param_flags' - implies: 'user_link_flags' - implies: 'legacy_link_flags' - implies: 'linker_subsystem_flag' - implies: 'linker_param_file' - implies: 'msvc_env' - implies: 'no_stripping' - implies: 'has_configured_linker_path' - implies: 'def_file' - } - - action_config { - config_name: 'c++-link-nodeps-dynamic-library' - action_name: 'c++-link-nodeps-dynamic-library' - tool { - tool_path: '' - } - implies: 'nologo' - implies: 'shared_flag' - implies: 'linkstamps' - implies: 'output_execpath_flags' - implies: 'input_param_flags' - implies: 'user_link_flags' - implies: 'legacy_link_flags' - implies: 'linker_subsystem_flag' - implies: 'linker_param_file' - implies: 'msvc_env' - implies: 'no_stripping' - implies: 'has_configured_linker_path' - implies: 'def_file' - } - - action_config { - config_name: 'c++-link-static-library' - action_name: 'c++-link-static-library' - tool { - tool_path: '' - } - implies: 'nologo' - implies: 'archiver_flags' - implies: 'input_param_flags' - implies: 'linker_param_file' - implies: 'msvc_env' - } - - # TODO(b/65151735): Remove legacy_compile_flags feature when legacy fields are - # not used in this crosstool - feature { - name: 'legacy_compile_flags' - flag_set { - expand_if_all_available: 'legacy_compile_flags' - action: 'preprocess-assemble' - action: 'c-compile' - action: 'c++-compile' - action: 'c++-header-parsing' - action: 'c++-module-compile' - action: 'c++-module-codegen' - flag_group { - iterate_over: 'legacy_compile_flags' - flag: '%{legacy_compile_flags}' - } - } - } - - feature { - name: "msvc_env" - env_set { - action: "c-compile" - action: "c++-compile" - action: "c++-module-compile" - action: "c++-module-codegen" - action: "c++-header-parsing" - action: "assemble" - action: "preprocess-assemble" - action: "c++-link-executable" - action: "c++-link-dynamic-library" - action: "c++-link-nodeps-dynamic-library" - action: "c++-link-static-library" - env_entry { - key: "PATH" - value: "" - } - env_entry { - key: "INCLUDE" - value: "" - } - env_entry { - key: "LIB" - value: "" - } - env_entry { - key: "TMP" - value: "" - } - env_entry { - key: "TEMP" - value: "" - } - } - } - - feature { - name: 'include_paths' - flag_set { - action: "assemble" - action: 'preprocess-assemble' - action: 'c-compile' - action: 'c++-compile' - action: 'c++-header-parsing' - action: 'c++-module-compile' - flag_group { - iterate_over: 'quote_include_paths' - flag: '/I%{quote_include_paths}' - } - flag_group { - iterate_over: 'include_paths' - flag: '/I%{include_paths}' - } - flag_group { - iterate_over: 'system_include_paths' - flag: '/I%{system_include_paths}' - } - } - } - - feature { - name: "preprocessor_defines" - flag_set { - action: "assemble" - action: "preprocess-assemble" - action: "c-compile" - action: "c++-compile" - action: "c++-header-parsing" - action: "c++-module-compile" - flag_group { - flag: "/D%{preprocessor_defines}" - iterate_over: "preprocessor_defines" - } - } - } - - # Tell Bazel to parse the output of /showIncludes - feature { - name: 'parse_showincludes' - flag_set { - action: 'preprocess-assemble' - action: 'c-compile' - action: 'c++-compile' - action: 'c++-module-compile' - action: 'c++-header-parsing' - flag_group { - flag: "/showIncludes" - } - } - } - - - feature { - name: 'generate_pdb_file' - requires: { - feature: 'dbg' - } - requires: { - feature: 'fastbuild' - } - } - - feature { - name: 'shared_flag' - flag_set { - action: 'c++-link-dynamic-library' - action: "c++-link-nodeps-dynamic-library" - flag_group { - flag: '/DLL' - } - } - } - - feature { - name: 'linkstamps' - flag_set { - action: 'c++-link-executable' - action: 'c++-link-dynamic-library' - action: "c++-link-nodeps-dynamic-library" - expand_if_all_available: 'linkstamp_paths' - flag_group { - iterate_over: 'linkstamp_paths' - flag: '%{linkstamp_paths}' - } - } - } - - feature { - name: 'output_execpath_flags' - flag_set { - expand_if_all_available: 'output_execpath' - action: 'c++-link-executable' - action: 'c++-link-dynamic-library' - action: "c++-link-nodeps-dynamic-library" - flag_group { - flag: '/OUT:%{output_execpath}' - } - } - } - - feature { - name: 'archiver_flags' - flag_set { - expand_if_all_available: 'output_execpath' - action: 'c++-link-static-library' - flag_group { - flag: '/OUT:%{output_execpath}' - } - } - } - - feature { - name: 'input_param_flags' - flag_set { - expand_if_all_available: 'interface_library_output_path' - action: 'c++-link-dynamic-library' - action: "c++-link-nodeps-dynamic-library" - flag_group { - flag: "/IMPLIB:%{interface_library_output_path}" - } - } - flag_set { - expand_if_all_available: 'libopts' - action: 'c++-link-executable' - action: 'c++-link-dynamic-library' - action: "c++-link-nodeps-dynamic-library" - flag_group { - iterate_over: 'libopts' - flag: '%{libopts}' - } - } - flag_set { - expand_if_all_available: 'libraries_to_link' - action: 'c++-link-executable' - action: 'c++-link-dynamic-library' - action: "c++-link-nodeps-dynamic-library" - action: 'c++-link-static-library' - flag_group { - iterate_over: 'libraries_to_link' - flag_group { - expand_if_equal: { - variable: 'libraries_to_link.type' - value: 'object_file_group' - } - iterate_over: 'libraries_to_link.object_files' - flag_group { - flag: '%{libraries_to_link.object_files}' - } - } - flag_group { - expand_if_equal: { - variable: 'libraries_to_link.type' - value: 'object_file' - } - flag_group { - flag: '%{libraries_to_link.name}' - } - } - flag_group { - expand_if_equal: { - variable: 'libraries_to_link.type' - value: 'interface_library' - } - flag_group { - flag: '%{libraries_to_link.name}' - } - } - flag_group { - expand_if_equal: { - variable: 'libraries_to_link.type' - value: 'static_library' - } - flag_group { - expand_if_false: 'libraries_to_link.is_whole_archive' - flag: '%{libraries_to_link.name}' - } - flag_group { - expand_if_true: 'libraries_to_link.is_whole_archive' - flag: '/WHOLEARCHIVE:%{libraries_to_link.name}' - } - } - } - } - } - - # Since this feature is declared earlier in the CROSSTOOL than - # "user_link_flags", this feature will be applied prior to it anwyhere they - # are both implied. And since "user_link_flags" contains the linkopts from - # the build rule, this allows the user to override the /SUBSYSTEM in the BUILD - # file. - feature { - name: 'linker_subsystem_flag' - flag_set { - action: 'c++-link-executable' - action: 'c++-link-dynamic-library' - action: "c++-link-nodeps-dynamic-library" - flag_group { - flag: '/SUBSYSTEM:CONSOLE' - } - } - } - - # The "user_link_flags" contains user-defined linkopts (from build rules) - # so it should be defined after features that declare user-overridable flags. - # For example the "linker_subsystem_flag" defines a default "/SUBSYSTEM" flag - # but we want to let the user override it, therefore "link_flag_subsystem" is - # defined earlier in the CROSSTOOL file than "user_link_flags". - feature { - name: 'user_link_flags' - flag_set { - expand_if_all_available: 'user_link_flags' - action: 'c++-link-executable' - action: 'c++-link-dynamic-library' - action: "c++-link-nodeps-dynamic-library" - flag_group { - iterate_over: 'user_link_flags' - flag: '%{user_link_flags}' - } - } - } - feature { - name: 'legacy_link_flags' - flag_set { - expand_if_all_available: 'legacy_link_flags' - action: 'c++-link-executable' - action: 'c++-link-dynamic-library' - action: "c++-link-nodeps-dynamic-library" - flag_group { - iterate_over: 'legacy_link_flags' - flag: '%{legacy_link_flags}' - } - } - } - - feature { - name: 'linker_param_file' - flag_set { - expand_if_all_available: 'linker_param_file' - action: 'c++-link-executable' - action: 'c++-link-dynamic-library' - action: "c++-link-nodeps-dynamic-library" - action: 'c++-link-static-library' - flag_group { - flag: '@%{linker_param_file}' - } - } - } - - feature { - name: 'static_link_msvcrt' - } - - feature { - name: 'static_link_msvcrt_no_debug' - flag_set { - action: 'c-compile' - action: 'c++-compile' - flag_group { - flag: "/MT" - } - } - flag_set { - action: 'c++-link-executable' - action: 'c++-link-dynamic-library' - action: "c++-link-nodeps-dynamic-library" - flag_group { - flag: "/DEFAULTLIB:libcmt.lib" - } - } - requires: { feature: 'fastbuild'} - requires: { feature: 'opt'} - } - - feature { - name: 'dynamic_link_msvcrt_no_debug' - flag_set { - action: 'c-compile' - action: 'c++-compile' - flag_group { - flag: "/MD" - } - } - flag_set { - action: 'c++-link-executable' - action: 'c++-link-dynamic-library' - action: "c++-link-nodeps-dynamic-library" - flag_group { - flag: "/DEFAULTLIB:msvcrt.lib" - } - } - requires: { feature: 'fastbuild'} - requires: { feature: 'opt'} - } - - feature { - name: 'static_link_msvcrt_debug' - flag_set { - action: 'c-compile' - action: 'c++-compile' - flag_group { - flag: "/MTd" - } - } - flag_set { - action: 'c++-link-executable' - action: 'c++-link-dynamic-library' - action: "c++-link-nodeps-dynamic-library" - flag_group { - flag: "/DEFAULTLIB:libcmtd.lib" - } - } - requires: { feature: 'dbg'} - } - - feature { - name: 'dynamic_link_msvcrt_debug' - flag_set { - action: 'c-compile' - action: 'c++-compile' - flag_group { - flag: "/MDd" - } - } - flag_set { - action: 'c++-link-executable' - action: 'c++-link-dynamic-library' - action: "c++-link-nodeps-dynamic-library" - flag_group { - flag: "/DEFAULTLIB:msvcrtd.lib" - } - } - requires: { feature: 'dbg'} - } - - feature { - name: 'dbg' - flag_set { - action: 'c-compile' - action: 'c++-compile' - flag_group { - flag: "/Od" - flag: "/Z7" - flag: "/DDEBUG" - } - } - flag_set { - action: 'c++-link-executable' - action: 'c++-link-dynamic-library' - action: "c++-link-nodeps-dynamic-library" - flag_group { - flag: "/DEBUG:FULL" - flag: "/INCREMENTAL:NO" - } - } - implies: 'generate_pdb_file' - } - - feature { - name: 'fastbuild' - flag_set { - action: 'c-compile' - action: 'c++-compile' - flag_group { - flag: "/Od" - flag: "/Z7" - flag: "/DDEBUG" - } - } - flag_set { - action: 'c++-link-executable' - action: 'c++-link-dynamic-library' - action: "c++-link-nodeps-dynamic-library" - flag_group { - flag: "/DEBUG:FASTLINK" - flag: "/INCREMENTAL:NO" - } - } - implies: 'generate_pdb_file' - } - - feature { - name: 'opt' - flag_set { - action: 'c-compile' - action: 'c++-compile' - flag_group { - flag: "/O2" - flag: "/DNDEBUG" - } - } - } - - feature { - name: 'user_compile_flags' - flag_set { - expand_if_all_available: 'user_compile_flags' - action: 'preprocess-assemble' - action: 'c-compile' - action: 'c++-compile' - action: 'c++-header-parsing' - action: 'c++-module-compile' - action: 'c++-module-codegen' - flag_group { - iterate_over: 'user_compile_flags' - flag: '%{user_compile_flags}' - } - } - } - - feature { - name: 'sysroot' - flag_set { - expand_if_all_available: 'sysroot' - action: 'assemble' - action: 'preprocess-assemble' - action: 'c-compile' - action: 'c++-compile' - action: 'c++-header-parsing' - action: 'c++-module-compile' - action: 'c++-module-codegen' - action: 'c++-link-executable' - action: 'c++-link-dynamic-library' - action: "c++-link-nodeps-dynamic-library" - flag_group { - iterate_over: 'sysroot' - flag: '--sysroot=%{sysroot}' - } - } - } - - feature { - name: 'unfiltered_compile_flags' - flag_set { - expand_if_all_available: 'unfiltered_compile_flags' - action: 'preprocess-assemble' - action: 'c-compile' - action: 'c++-compile' - action: 'c++-header-parsing' - action: 'c++-module-compile' - action: 'c++-module-codegen' - flag_group { - iterate_over: 'unfiltered_compile_flags' - flag: '%{unfiltered_compile_flags}' - } - } - } - - feature { - name: 'compiler_output_flags' - flag_set { - action: 'assemble' - flag_group { - expand_if_all_available: 'output_file' - expand_if_none_available: 'output_assembly_file' - expand_if_none_available: 'output_preprocess_file' - flag: '/Fo%{output_file}' - flag: '/Zi' - } - } - flag_set { - action: 'preprocess-assemble' - action: 'c-compile' - action: 'c++-compile' - action: 'c++-header-parsing' - action: 'c++-module-compile' - action: 'c++-module-codegen' - flag_group { - expand_if_all_available: 'output_file' - expand_if_none_available: 'output_assembly_file' - expand_if_none_available: 'output_preprocess_file' - flag: '/Fo%{output_file}' - } - flag_group { - expand_if_all_available: 'output_file' - expand_if_all_available: 'output_assembly_file' - flag: '/Fa%{output_file}' - } - flag_group { - expand_if_all_available: 'output_file' - expand_if_all_available: 'output_preprocess_file' - flag: '/P' - flag: '/Fi%{output_file}' - } - } - } - - feature { - name: 'compiler_input_flags' - flag_set { - action: 'assemble' - action: 'preprocess-assemble' - action: 'c-compile' - action: 'c++-compile' - action: 'c++-header-parsing' - action: 'c++-module-compile' - action: 'c++-module-codegen' - flag_group { - expand_if_all_available: 'source_file' - flag: '/c' - flag: '%{source_file}' - } - } - } - - feature { - name : 'def_file', - flag_set { - expand_if_all_available: 'def_file_path' - action: 'c++-link-executable' - action: 'c++-link-dynamic-library' - action: "c++-link-nodeps-dynamic-library" - flag_group { - flag: "/DEF:%{def_file_path}" - # We can specify a different DLL name in DEF file, /ignore:4070 suppresses - # the warning message about DLL name doesn't match the default one. - # See https://msdn.microsoft.com/en-us/library/sfkk2fz7.aspx - flag: "/ignore:4070" - } - } - } - - feature { - name: 'windows_export_all_symbols' - } - - feature { - name: 'no_windows_export_all_symbols' - } - - linking_mode_flags { mode: DYNAMIC } -} \ No newline at end of file diff --git a/third_party/toolchains/gpus/cuda/BUILD b/third_party/toolchains/gpus/cuda/BUILD deleted file mode 100644 index 8bb22c0269..0000000000 --- a/third_party/toolchains/gpus/cuda/BUILD +++ /dev/null @@ -1,1268 +0,0 @@ -# A build file to configure cuda remote repository used with Bazel remote -# execution service -# DO NOT EDIT: automatically generated BUILD file - -licenses(["restricted"]) # MPL2, portions GPL v3, LGPL v3, BSD-like - -package(default_visibility = ["//visibility:public"]) - -config_setting( - name = "using_nvcc", - values = { - "define": "using_cuda_nvcc=true", - }, -) - -config_setting( - name = "using_clang", - values = { - "define": "using_cuda_clang=true", - }, -) - -# Equivalent to using_clang && -c opt. -config_setting( - name = "using_clang_opt", - values = { - "define": "using_cuda_clang=true", - "compilation_mode": "opt", - }, -) - -config_setting( - name = "darwin", - values = {"cpu": "darwin"}, - visibility = ["//visibility:public"], -) - -config_setting( - name = "freebsd", - values = {"cpu": "freebsd"}, - visibility = ["//visibility:public"], -) - -cc_library( - name = "cuda_headers", - hdrs = [ - "cuda/cuda_config.h", - ":cuda-include", - ":cudnn-include", - ], - includes = [ - ".", - "cuda/include", - "cuda/include/crt", - ], - visibility = ["//visibility:public"], -) - -cc_library( - name = "cudart_static", - srcs = ["cuda/lib/libcudart_static.a"], - includes = [ - ".", - "cuda/include", - ], - linkopts = select({ - ":freebsd": [], - "//conditions:default": ["-ldl"], - }) + [ - "-lpthread", - "-lrt", - ], - visibility = ["//visibility:public"], -) - -cc_library( - name = "cuda_driver", - srcs = ["cuda/lib/libcuda.so"], - includes = [ - ".", - "cuda/include", - ], - visibility = ["//visibility:public"], -) - -cc_library( - name = "cudart", - srcs = ["cuda/lib/libcudart.so.10.0"], - data = ["cuda/lib/libcudart.so.10.0"], - includes = [ - ".", - "cuda/include", - ], - linkstatic = 1, - visibility = ["//visibility:public"], -) - -cc_library( - name = "cublas", - srcs = ["cuda/lib/libcublas.so.10.0"], - data = ["cuda/lib/libcublas.so.10.0"], - includes = [ - ".", - "cuda/include", - ], - linkstatic = 1, - visibility = ["//visibility:public"], -) - -cc_library( - name = "cusolver", - srcs = ["cuda/lib/libcusolver.so.10.0"], - data = ["cuda/lib/libcusolver.so.10.0"], - includes = [ - ".", - "cuda/include", - ], - linkopts = ["-lgomp"], - linkstatic = 1, - visibility = ["//visibility:public"], -) - -cc_library( - name = "cudnn", - srcs = ["cuda/lib/libcudnn.so.7"], - data = ["cuda/lib/libcudnn.so.7"], - includes = [ - ".", - "cuda/include", - ], - linkstatic = 1, - visibility = ["//visibility:public"], -) - -cc_library( - name = "cudnn_header", - includes = [ - ".", - "cuda/include", - ], - visibility = ["//visibility:public"], -) - -cc_library( - name = "cufft", - srcs = ["cuda/lib/libcufft.so.10.0"], - data = ["cuda/lib/libcufft.so.10.0"], - includes = [ - ".", - "cuda/include", - ], - linkstatic = 1, - visibility = ["//visibility:public"], -) - -cc_library( - name = "curand", - srcs = ["cuda/lib/libcurand.so.10.0"], - data = ["cuda/lib/libcurand.so.10.0"], - includes = [ - ".", - "cuda/include", - ], - linkstatic = 1, - visibility = ["//visibility:public"], -) - -cc_library( - name = "cuda", - visibility = ["//visibility:public"], - deps = [ - ":cublas", - ":cuda_headers", - ":cudart", - ":cudnn", - ":cufft", - ":curand", - ], -) - -cc_library( - name = "cupti_headers", - hdrs = [ - "cuda/cuda_config.h", - ":cuda-extras", - ], - includes = [ - ".", - "cuda/extras/CUPTI/include/", - ], - visibility = ["//visibility:public"], -) - -cc_library( - name = "cupti_dsos", - data = ["cuda/lib/libcupti.so.10.0"], - includes = [ - ".", - "cuda/include", - ], - visibility = ["//visibility:public"], -) - -cc_library( - name = "libdevice_root", - data = [":cuda-nvvm"], - visibility = ["//visibility:public"], -) - -genrule( - name = "cuda-include", - outs = [ - "cuda/include/CL/cl.h", - "cuda/include/CL/cl.hpp", - "cuda/include/CL/cl_egl.h", - "cuda/include/CL/cl_ext.h", - "cuda/include/CL/cl_gl.h", - "cuda/include/CL/cl_gl_ext.h", - "cuda/include/CL/cl_platform.h", - "cuda/include/CL/opencl.h", - "cuda/include/builtin_types.h", - "cuda/include/channel_descriptor.h", - "cuda/include/common_functions.h", - "cuda/include/cooperative_groups.h", - "cuda/include/cooperative_groups_helpers.h", - "cuda/include/crt/common_functions.h", - "cuda/include/crt/device_double_functions.h", - "cuda/include/crt/device_double_functions.hpp", - "cuda/include/crt/device_functions.h", - "cuda/include/crt/device_functions.hpp", - "cuda/include/crt/func_macro.h", - "cuda/include/crt/host_config.h", - "cuda/include/crt/host_defines.h", - "cuda/include/crt/host_runtime.h", - "cuda/include/crt/math_functions.h", - "cuda/include/crt/math_functions.hpp", - "cuda/include/crt/mma.h", - "cuda/include/crt/mma.hpp", - "cuda/include/crt/nvfunctional", - "cuda/include/crt/sm_70_rt.h", - "cuda/include/crt/sm_70_rt.hpp", - "cuda/include/crt/storage_class.h", - "cuda/include/cuComplex.h", - "cuda/include/cublas.h", - "cuda/include/cublasXt.h", - "cuda/include/cublas_api.h", - "cuda/include/cublas_v2.h", - "cuda/include/cuda.h", - "cuda/include/cudaEGL.h", - "cuda/include/cudaGL.h", - "cuda/include/cudaProfiler.h", - "cuda/include/cudaVDPAU.h", - "cuda/include/cuda_device_runtime_api.h", - "cuda/include/cuda_fp16.h", - "cuda/include/cuda_fp16.hpp", - "cuda/include/cuda_gl_interop.h", - "cuda/include/cuda_occupancy.h", - "cuda/include/cuda_profiler_api.h", - "cuda/include/cuda_runtime.h", - "cuda/include/cuda_runtime_api.h", - "cuda/include/cuda_surface_types.h", - "cuda/include/cuda_texture_types.h", - "cuda/include/cuda_vdpau_interop.h", - "cuda/include/cudalibxt.h", - "cuda/include/cudnn.h", - "cuda/include/cufft.h", - "cuda/include/cufftXt.h", - "cuda/include/cufftw.h", - "cuda/include/curand.h", - "cuda/include/curand_discrete.h", - "cuda/include/curand_discrete2.h", - "cuda/include/curand_globals.h", - "cuda/include/curand_kernel.h", - "cuda/include/curand_lognormal.h", - "cuda/include/curand_mrg32k3a.h", - "cuda/include/curand_mtgp32.h", - "cuda/include/curand_mtgp32_host.h", - "cuda/include/curand_mtgp32_kernel.h", - "cuda/include/curand_mtgp32dc_p_11213.h", - "cuda/include/curand_normal.h", - "cuda/include/curand_normal_static.h", - "cuda/include/curand_philox4x32_x.h", - "cuda/include/curand_poisson.h", - "cuda/include/curand_precalc.h", - "cuda/include/curand_uniform.h", - "cuda/include/cusolverDn.h", - "cuda/include/cusolverRf.h", - "cuda/include/cusolverSp.h", - "cuda/include/cusolverSp_LOWLEVEL_PREVIEW.h", - "cuda/include/cusolver_common.h", - "cuda/include/cusparse.h", - "cuda/include/cusparse_v2.h", - "cuda/include/device_atomic_functions.h", - "cuda/include/device_atomic_functions.hpp", - "cuda/include/device_double_functions.h", - "cuda/include/device_double_functions.hpp", - "cuda/include/device_functions.h", - "cuda/include/device_functions.hpp", - "cuda/include/device_functions_decls.h", - "cuda/include/device_launch_parameters.h", - "cuda/include/device_types.h", - "cuda/include/driver_functions.h", - "cuda/include/driver_types.h", - "cuda/include/dynlink_cuda.h", - "cuda/include/dynlink_cuda_cuda.h", - "cuda/include/dynlink_cuviddec.h", - "cuda/include/dynlink_nvcuvid.h", - "cuda/include/fatBinaryCtl.h", - "cuda/include/fatbinary.h", - "cuda/include/host_config.h", - "cuda/include/host_defines.h", - "cuda/include/library_types.h", - "cuda/include/math_constants.h", - "cuda/include/math_functions.h", - "cuda/include/math_functions.hpp", - "cuda/include/math_functions_dbl_ptx3.h", - "cuda/include/math_functions_dbl_ptx3.hpp", - "cuda/include/mma.h", - "cuda/include/npp.h", - "cuda/include/nppcore.h", - "cuda/include/nppdefs.h", - "cuda/include/nppi.h", - "cuda/include/nppi_arithmetic_and_logical_operations.h", - "cuda/include/nppi_color_conversion.h", - "cuda/include/nppi_compression_functions.h", - "cuda/include/nppi_computer_vision.h", - "cuda/include/nppi_data_exchange_and_initialization.h", - "cuda/include/nppi_filtering_functions.h", - "cuda/include/nppi_geometry_transforms.h", - "cuda/include/nppi_linear_transforms.h", - "cuda/include/nppi_morphological_operations.h", - "cuda/include/nppi_statistics_functions.h", - "cuda/include/nppi_support_functions.h", - "cuda/include/nppi_threshold_and_compare_operations.h", - "cuda/include/npps.h", - "cuda/include/npps_arithmetic_and_logical_operations.h", - "cuda/include/npps_conversion_functions.h", - "cuda/include/npps_filtering_functions.h", - "cuda/include/npps_initialization.h", - "cuda/include/npps_statistics_functions.h", - "cuda/include/npps_support_functions.h", - "cuda/include/nppversion.h", - "cuda/include/nvToolsExt.h", - "cuda/include/nvToolsExtCuda.h", - "cuda/include/nvToolsExtCudaRt.h", - "cuda/include/nvToolsExtMeta.h", - "cuda/include/nvToolsExtSync.h", - "cuda/include/nvblas.h", - "cuda/include/nvfunctional", - "cuda/include/nvgraph.h", - "cuda/include/nvml.h", - "cuda/include/nvrtc.h", - "cuda/include/sm_20_atomic_functions.h", - "cuda/include/sm_20_atomic_functions.hpp", - "cuda/include/sm_20_intrinsics.h", - "cuda/include/sm_20_intrinsics.hpp", - "cuda/include/sm_30_intrinsics.h", - "cuda/include/sm_30_intrinsics.hpp", - "cuda/include/sm_32_atomic_functions.h", - "cuda/include/sm_32_atomic_functions.hpp", - "cuda/include/sm_32_intrinsics.h", - "cuda/include/sm_32_intrinsics.hpp", - "cuda/include/sm_35_atomic_functions.h", - "cuda/include/sm_35_intrinsics.h", - "cuda/include/sm_60_atomic_functions.h", - "cuda/include/sm_60_atomic_functions.hpp", - "cuda/include/sm_61_intrinsics.h", - "cuda/include/sm_61_intrinsics.hpp", - "cuda/include/sobol_direction_vectors.h", - "cuda/include/surface_functions.h", - "cuda/include/surface_functions.hpp", - "cuda/include/surface_indirect_functions.h", - "cuda/include/surface_indirect_functions.hpp", - "cuda/include/surface_types.h", - "cuda/include/texture_fetch_functions.h", - "cuda/include/texture_fetch_functions.hpp", - "cuda/include/texture_indirect_functions.h", - "cuda/include/texture_indirect_functions.hpp", - "cuda/include/texture_types.h", - "cuda/include/thrust/adjacent_difference.h", - "cuda/include/thrust/advance.h", - "cuda/include/thrust/binary_search.h", - "cuda/include/thrust/complex.h", - "cuda/include/thrust/copy.h", - "cuda/include/thrust/count.h", - "cuda/include/thrust/detail/adjacent_difference.inl", - "cuda/include/thrust/detail/advance.inl", - "cuda/include/thrust/detail/allocator/allocator_traits.h", - "cuda/include/thrust/detail/allocator/allocator_traits.inl", - "cuda/include/thrust/detail/allocator/copy_construct_range.h", - "cuda/include/thrust/detail/allocator/copy_construct_range.inl", - "cuda/include/thrust/detail/allocator/default_construct_range.h", - "cuda/include/thrust/detail/allocator/default_construct_range.inl", - "cuda/include/thrust/detail/allocator/destroy_range.h", - "cuda/include/thrust/detail/allocator/destroy_range.inl", - "cuda/include/thrust/detail/allocator/fill_construct_range.h", - "cuda/include/thrust/detail/allocator/fill_construct_range.inl", - "cuda/include/thrust/detail/allocator/malloc_allocator.h", - "cuda/include/thrust/detail/allocator/malloc_allocator.inl", - "cuda/include/thrust/detail/allocator/no_throw_allocator.h", - "cuda/include/thrust/detail/allocator/tagged_allocator.h", - "cuda/include/thrust/detail/allocator/tagged_allocator.inl", - "cuda/include/thrust/detail/allocator/temporary_allocator.h", - "cuda/include/thrust/detail/allocator/temporary_allocator.inl", - "cuda/include/thrust/detail/binary_search.inl", - "cuda/include/thrust/detail/complex/arithmetic.h", - "cuda/include/thrust/detail/complex/c99math.h", - "cuda/include/thrust/detail/complex/catrig.h", - "cuda/include/thrust/detail/complex/catrigf.h", - "cuda/include/thrust/detail/complex/ccosh.h", - "cuda/include/thrust/detail/complex/ccoshf.h", - "cuda/include/thrust/detail/complex/cexp.h", - "cuda/include/thrust/detail/complex/cexpf.h", - "cuda/include/thrust/detail/complex/clog.h", - "cuda/include/thrust/detail/complex/clogf.h", - "cuda/include/thrust/detail/complex/complex.inl", - "cuda/include/thrust/detail/complex/cpow.h", - "cuda/include/thrust/detail/complex/cpowf.h", - "cuda/include/thrust/detail/complex/cproj.h", - "cuda/include/thrust/detail/complex/csinh.h", - "cuda/include/thrust/detail/complex/csinhf.h", - "cuda/include/thrust/detail/complex/csqrt.h", - "cuda/include/thrust/detail/complex/csqrtf.h", - "cuda/include/thrust/detail/complex/ctanh.h", - "cuda/include/thrust/detail/complex/ctanhf.h", - "cuda/include/thrust/detail/complex/math_private.h", - "cuda/include/thrust/detail/complex/stream.h", - "cuda/include/thrust/detail/config.h", - "cuda/include/thrust/detail/config/compiler.h", - "cuda/include/thrust/detail/config/compiler_fence.h", - "cuda/include/thrust/detail/config/config.h", - "cuda/include/thrust/detail/config/debug.h", - "cuda/include/thrust/detail/config/device_system.h", - "cuda/include/thrust/detail/config/exec_check_disable.h", - "cuda/include/thrust/detail/config/forceinline.h", - "cuda/include/thrust/detail/config/global_workarounds.h", - "cuda/include/thrust/detail/config/host_device.h", - "cuda/include/thrust/detail/config/host_system.h", - "cuda/include/thrust/detail/config/simple_defines.h", - "cuda/include/thrust/detail/contiguous_storage.h", - "cuda/include/thrust/detail/contiguous_storage.inl", - "cuda/include/thrust/detail/copy.h", - "cuda/include/thrust/detail/copy.inl", - "cuda/include/thrust/detail/copy_if.h", - "cuda/include/thrust/detail/copy_if.inl", - "cuda/include/thrust/detail/count.inl", - "cuda/include/thrust/detail/cstdint.h", - "cuda/include/thrust/detail/device_delete.inl", - "cuda/include/thrust/detail/device_free.inl", - "cuda/include/thrust/detail/device_malloc.inl", - "cuda/include/thrust/detail/device_new.inl", - "cuda/include/thrust/detail/device_ptr.inl", - "cuda/include/thrust/detail/device_reference.inl", - "cuda/include/thrust/detail/device_vector.inl", - "cuda/include/thrust/detail/dispatch/is_trivial_copy.h", - "cuda/include/thrust/detail/distance.inl", - "cuda/include/thrust/detail/equal.inl", - "cuda/include/thrust/detail/execute_with_allocator.h", - "cuda/include/thrust/detail/execution_policy.h", - "cuda/include/thrust/detail/extrema.inl", - "cuda/include/thrust/detail/fill.inl", - "cuda/include/thrust/detail/find.inl", - "cuda/include/thrust/detail/for_each.inl", - "cuda/include/thrust/detail/function.h", - "cuda/include/thrust/detail/functional.inl", - "cuda/include/thrust/detail/functional/actor.h", - "cuda/include/thrust/detail/functional/actor.inl", - "cuda/include/thrust/detail/functional/argument.h", - "cuda/include/thrust/detail/functional/composite.h", - "cuda/include/thrust/detail/functional/operators.h", - "cuda/include/thrust/detail/functional/operators/arithmetic_operators.h", - "cuda/include/thrust/detail/functional/operators/assignment_operator.h", - "cuda/include/thrust/detail/functional/operators/bitwise_operators.h", - "cuda/include/thrust/detail/functional/operators/compound_assignment_operators.h", - "cuda/include/thrust/detail/functional/operators/logical_operators.h", - "cuda/include/thrust/detail/functional/operators/operator_adaptors.h", - "cuda/include/thrust/detail/functional/operators/relational_operators.h", - "cuda/include/thrust/detail/functional/placeholder.h", - "cuda/include/thrust/detail/functional/value.h", - "cuda/include/thrust/detail/gather.inl", - "cuda/include/thrust/detail/generate.inl", - "cuda/include/thrust/detail/get_iterator_value.h", - "cuda/include/thrust/detail/host_vector.inl", - "cuda/include/thrust/detail/inner_product.inl", - "cuda/include/thrust/detail/integer_math.h", - "cuda/include/thrust/detail/integer_traits.h", - "cuda/include/thrust/detail/internal_functional.h", - "cuda/include/thrust/detail/logical.inl", - "cuda/include/thrust/detail/malloc_and_free.h", - "cuda/include/thrust/detail/merge.inl", - "cuda/include/thrust/detail/minmax.h", - "cuda/include/thrust/detail/mismatch.inl", - "cuda/include/thrust/detail/mpl/math.h", - "cuda/include/thrust/detail/numeric_traits.h", - "cuda/include/thrust/detail/overlapped_copy.h", - "cuda/include/thrust/detail/pair.inl", - "cuda/include/thrust/detail/partition.inl", - "cuda/include/thrust/detail/pointer.h", - "cuda/include/thrust/detail/pointer.inl", - "cuda/include/thrust/detail/range/head_flags.h", - "cuda/include/thrust/detail/range/tail_flags.h", - "cuda/include/thrust/detail/raw_pointer_cast.h", - "cuda/include/thrust/detail/raw_reference_cast.h", - "cuda/include/thrust/detail/reduce.inl", - "cuda/include/thrust/detail/reference.h", - "cuda/include/thrust/detail/reference.inl", - "cuda/include/thrust/detail/reference_forward_declaration.h", - "cuda/include/thrust/detail/remove.inl", - "cuda/include/thrust/detail/replace.inl", - "cuda/include/thrust/detail/reverse.inl", - "cuda/include/thrust/detail/scan.inl", - "cuda/include/thrust/detail/scatter.inl", - "cuda/include/thrust/detail/seq.h", - "cuda/include/thrust/detail/sequence.inl", - "cuda/include/thrust/detail/set_operations.inl", - "cuda/include/thrust/detail/sort.inl", - "cuda/include/thrust/detail/static_assert.h", - "cuda/include/thrust/detail/static_map.h", - "cuda/include/thrust/detail/swap.h", - "cuda/include/thrust/detail/swap.inl", - "cuda/include/thrust/detail/swap_ranges.inl", - "cuda/include/thrust/detail/tabulate.inl", - "cuda/include/thrust/detail/temporary_array.h", - "cuda/include/thrust/detail/temporary_array.inl", - "cuda/include/thrust/detail/temporary_buffer.h", - "cuda/include/thrust/detail/transform.inl", - "cuda/include/thrust/detail/transform_reduce.inl", - "cuda/include/thrust/detail/transform_scan.inl", - "cuda/include/thrust/detail/trivial_sequence.h", - "cuda/include/thrust/detail/tuple.inl", - "cuda/include/thrust/detail/tuple_meta_transform.h", - "cuda/include/thrust/detail/tuple_transform.h", - "cuda/include/thrust/detail/type_traits.h", - "cuda/include/thrust/detail/type_traits/algorithm/intermediate_type_from_function_and_iterators.h", - "cuda/include/thrust/detail/type_traits/function_traits.h", - "cuda/include/thrust/detail/type_traits/has_member_function.h", - "cuda/include/thrust/detail/type_traits/has_nested_type.h", - "cuda/include/thrust/detail/type_traits/has_trivial_assign.h", - "cuda/include/thrust/detail/type_traits/is_call_possible.h", - "cuda/include/thrust/detail/type_traits/is_metafunction_defined.h", - "cuda/include/thrust/detail/type_traits/iterator/is_discard_iterator.h", - "cuda/include/thrust/detail/type_traits/iterator/is_output_iterator.h", - "cuda/include/thrust/detail/type_traits/minimum_type.h", - "cuda/include/thrust/detail/type_traits/pointer_traits.h", - "cuda/include/thrust/detail/type_traits/result_of_adaptable_function.h", - "cuda/include/thrust/detail/uninitialized_copy.inl", - "cuda/include/thrust/detail/uninitialized_fill.inl", - "cuda/include/thrust/detail/unique.inl", - "cuda/include/thrust/detail/use_default.h", - "cuda/include/thrust/detail/util/align.h", - "cuda/include/thrust/detail/util/blocking.h", - "cuda/include/thrust/detail/vector_base.h", - "cuda/include/thrust/detail/vector_base.inl", - "cuda/include/thrust/device_allocator.h", - "cuda/include/thrust/device_delete.h", - "cuda/include/thrust/device_free.h", - "cuda/include/thrust/device_malloc.h", - "cuda/include/thrust/device_malloc_allocator.h", - "cuda/include/thrust/device_new.h", - "cuda/include/thrust/device_new_allocator.h", - "cuda/include/thrust/device_ptr.h", - "cuda/include/thrust/device_reference.h", - "cuda/include/thrust/device_vector.h", - "cuda/include/thrust/distance.h", - "cuda/include/thrust/equal.h", - "cuda/include/thrust/execution_policy.h", - "cuda/include/thrust/extrema.h", - "cuda/include/thrust/fill.h", - "cuda/include/thrust/find.h", - "cuda/include/thrust/for_each.h", - "cuda/include/thrust/functional.h", - "cuda/include/thrust/gather.h", - "cuda/include/thrust/generate.h", - "cuda/include/thrust/host_vector.h", - "cuda/include/thrust/inner_product.h", - "cuda/include/thrust/iterator/constant_iterator.h", - "cuda/include/thrust/iterator/counting_iterator.h", - "cuda/include/thrust/iterator/detail/any_assign.h", - "cuda/include/thrust/iterator/detail/any_system_tag.h", - "cuda/include/thrust/iterator/detail/constant_iterator_base.h", - "cuda/include/thrust/iterator/detail/counting_iterator.inl", - "cuda/include/thrust/iterator/detail/device_system_tag.h", - "cuda/include/thrust/iterator/detail/discard_iterator_base.h", - "cuda/include/thrust/iterator/detail/distance_from_result.h", - "cuda/include/thrust/iterator/detail/host_system_tag.h", - "cuda/include/thrust/iterator/detail/is_iterator_category.h", - "cuda/include/thrust/iterator/detail/is_trivial_iterator.h", - "cuda/include/thrust/iterator/detail/iterator_adaptor_base.h", - "cuda/include/thrust/iterator/detail/iterator_category_to_system.h", - "cuda/include/thrust/iterator/detail/iterator_category_to_traversal.h", - "cuda/include/thrust/iterator/detail/iterator_category_with_system_and_traversal.h", - "cuda/include/thrust/iterator/detail/iterator_facade_category.h", - "cuda/include/thrust/iterator/detail/iterator_traits.inl", - "cuda/include/thrust/iterator/detail/iterator_traversal_tags.h", - "cuda/include/thrust/iterator/detail/join_iterator.h", - "cuda/include/thrust/iterator/detail/minimum_category.h", - "cuda/include/thrust/iterator/detail/minimum_system.h", - "cuda/include/thrust/iterator/detail/normal_iterator.h", - "cuda/include/thrust/iterator/detail/permutation_iterator_base.h", - "cuda/include/thrust/iterator/detail/retag.h", - "cuda/include/thrust/iterator/detail/reverse_iterator.inl", - "cuda/include/thrust/iterator/detail/reverse_iterator_base.h", - "cuda/include/thrust/iterator/detail/tagged_iterator.h", - "cuda/include/thrust/iterator/detail/transform_iterator.inl", - "cuda/include/thrust/iterator/detail/transform_output_iterator.inl", - "cuda/include/thrust/iterator/detail/tuple_of_iterator_references.h", - "cuda/include/thrust/iterator/detail/universal_categories.h", - "cuda/include/thrust/iterator/detail/zip_iterator.inl", - "cuda/include/thrust/iterator/detail/zip_iterator_base.h", - "cuda/include/thrust/iterator/discard_iterator.h", - "cuda/include/thrust/iterator/iterator_adaptor.h", - "cuda/include/thrust/iterator/iterator_categories.h", - "cuda/include/thrust/iterator/iterator_facade.h", - "cuda/include/thrust/iterator/iterator_traits.h", - "cuda/include/thrust/iterator/permutation_iterator.h", - "cuda/include/thrust/iterator/retag.h", - "cuda/include/thrust/iterator/reverse_iterator.h", - "cuda/include/thrust/iterator/transform_iterator.h", - "cuda/include/thrust/iterator/transform_output_iterator.h", - "cuda/include/thrust/iterator/zip_iterator.h", - "cuda/include/thrust/logical.h", - "cuda/include/thrust/memory.h", - "cuda/include/thrust/merge.h", - "cuda/include/thrust/mismatch.h", - "cuda/include/thrust/pair.h", - "cuda/include/thrust/partition.h", - "cuda/include/thrust/random.h", - "cuda/include/thrust/random/detail/discard_block_engine.inl", - "cuda/include/thrust/random/detail/linear_congruential_engine.inl", - "cuda/include/thrust/random/detail/linear_congruential_engine_discard.h", - "cuda/include/thrust/random/detail/linear_feedback_shift_engine.inl", - "cuda/include/thrust/random/detail/linear_feedback_shift_engine_wordmask.h", - "cuda/include/thrust/random/detail/mod.h", - "cuda/include/thrust/random/detail/normal_distribution.inl", - "cuda/include/thrust/random/detail/normal_distribution_base.h", - "cuda/include/thrust/random/detail/random_core_access.h", - "cuda/include/thrust/random/detail/subtract_with_carry_engine.inl", - "cuda/include/thrust/random/detail/uniform_int_distribution.inl", - "cuda/include/thrust/random/detail/uniform_real_distribution.inl", - "cuda/include/thrust/random/detail/xor_combine_engine.inl", - "cuda/include/thrust/random/detail/xor_combine_engine_max.h", - "cuda/include/thrust/random/discard_block_engine.h", - "cuda/include/thrust/random/linear_congruential_engine.h", - "cuda/include/thrust/random/linear_feedback_shift_engine.h", - "cuda/include/thrust/random/normal_distribution.h", - "cuda/include/thrust/random/subtract_with_carry_engine.h", - "cuda/include/thrust/random/uniform_int_distribution.h", - "cuda/include/thrust/random/uniform_real_distribution.h", - "cuda/include/thrust/random/xor_combine_engine.h", - "cuda/include/thrust/reduce.h", - "cuda/include/thrust/remove.h", - "cuda/include/thrust/replace.h", - "cuda/include/thrust/reverse.h", - "cuda/include/thrust/scan.h", - "cuda/include/thrust/scatter.h", - "cuda/include/thrust/sequence.h", - "cuda/include/thrust/set_operations.h", - "cuda/include/thrust/sort.h", - "cuda/include/thrust/swap.h", - "cuda/include/thrust/system/cpp/detail/adjacent_difference.h", - "cuda/include/thrust/system/cpp/detail/assign_value.h", - "cuda/include/thrust/system/cpp/detail/binary_search.h", - "cuda/include/thrust/system/cpp/detail/copy.h", - "cuda/include/thrust/system/cpp/detail/copy_if.h", - "cuda/include/thrust/system/cpp/detail/count.h", - "cuda/include/thrust/system/cpp/detail/equal.h", - "cuda/include/thrust/system/cpp/detail/execution_policy.h", - "cuda/include/thrust/system/cpp/detail/extrema.h", - "cuda/include/thrust/system/cpp/detail/fill.h", - "cuda/include/thrust/system/cpp/detail/find.h", - "cuda/include/thrust/system/cpp/detail/for_each.h", - "cuda/include/thrust/system/cpp/detail/gather.h", - "cuda/include/thrust/system/cpp/detail/generate.h", - "cuda/include/thrust/system/cpp/detail/get_value.h", - "cuda/include/thrust/system/cpp/detail/inner_product.h", - "cuda/include/thrust/system/cpp/detail/iter_swap.h", - "cuda/include/thrust/system/cpp/detail/logical.h", - "cuda/include/thrust/system/cpp/detail/malloc_and_free.h", - "cuda/include/thrust/system/cpp/detail/memory.inl", - "cuda/include/thrust/system/cpp/detail/merge.h", - "cuda/include/thrust/system/cpp/detail/mismatch.h", - "cuda/include/thrust/system/cpp/detail/par.h", - "cuda/include/thrust/system/cpp/detail/partition.h", - "cuda/include/thrust/system/cpp/detail/reduce.h", - "cuda/include/thrust/system/cpp/detail/reduce_by_key.h", - "cuda/include/thrust/system/cpp/detail/remove.h", - "cuda/include/thrust/system/cpp/detail/replace.h", - "cuda/include/thrust/system/cpp/detail/reverse.h", - "cuda/include/thrust/system/cpp/detail/scan.h", - "cuda/include/thrust/system/cpp/detail/scan_by_key.h", - "cuda/include/thrust/system/cpp/detail/scatter.h", - "cuda/include/thrust/system/cpp/detail/sequence.h", - "cuda/include/thrust/system/cpp/detail/set_operations.h", - "cuda/include/thrust/system/cpp/detail/sort.h", - "cuda/include/thrust/system/cpp/detail/swap_ranges.h", - "cuda/include/thrust/system/cpp/detail/tabulate.h", - "cuda/include/thrust/system/cpp/detail/temporary_buffer.h", - "cuda/include/thrust/system/cpp/detail/transform.h", - "cuda/include/thrust/system/cpp/detail/transform_reduce.h", - "cuda/include/thrust/system/cpp/detail/transform_scan.h", - "cuda/include/thrust/system/cpp/detail/uninitialized_copy.h", - "cuda/include/thrust/system/cpp/detail/uninitialized_fill.h", - "cuda/include/thrust/system/cpp/detail/unique.h", - "cuda/include/thrust/system/cpp/detail/unique_by_key.h", - "cuda/include/thrust/system/cpp/detail/vector.inl", - "cuda/include/thrust/system/cpp/execution_policy.h", - "cuda/include/thrust/system/cpp/memory.h", - "cuda/include/thrust/system/cpp/vector.h", - "cuda/include/thrust/system/cuda/config.h", - "cuda/include/thrust/system/cuda/detail/adjacent_difference.h", - "cuda/include/thrust/system/cuda/detail/assign_value.h", - "cuda/include/thrust/system/cuda/detail/binary_search.h", - "cuda/include/thrust/system/cuda/detail/copy.h", - "cuda/include/thrust/system/cuda/detail/copy_if.h", - "cuda/include/thrust/system/cuda/detail/core/agent_launcher.h", - "cuda/include/thrust/system/cuda/detail/core/alignment.h", - "cuda/include/thrust/system/cuda/detail/core/triple_chevron_launch.h", - "cuda/include/thrust/system/cuda/detail/core/util.h", - "cuda/include/thrust/system/cuda/detail/count.h", - "cuda/include/thrust/system/cuda/detail/cross_system.h", - "cuda/include/thrust/system/cuda/detail/cub/agent/agent_histogram.cuh", - "cuda/include/thrust/system/cuda/detail/cub/agent/agent_radix_sort_downsweep.cuh", - "cuda/include/thrust/system/cuda/detail/cub/agent/agent_radix_sort_upsweep.cuh", - "cuda/include/thrust/system/cuda/detail/cub/agent/agent_reduce.cuh", - "cuda/include/thrust/system/cuda/detail/cub/agent/agent_reduce_by_key.cuh", - "cuda/include/thrust/system/cuda/detail/cub/agent/agent_rle.cuh", - "cuda/include/thrust/system/cuda/detail/cub/agent/agent_scan.cuh", - "cuda/include/thrust/system/cuda/detail/cub/agent/agent_segment_fixup.cuh", - "cuda/include/thrust/system/cuda/detail/cub/agent/agent_select_if.cuh", - "cuda/include/thrust/system/cuda/detail/cub/agent/agent_spmv_csrt.cuh", - "cuda/include/thrust/system/cuda/detail/cub/agent/agent_spmv_orig.cuh", - "cuda/include/thrust/system/cuda/detail/cub/agent/agent_spmv_row_based.cuh", - "cuda/include/thrust/system/cuda/detail/cub/agent/single_pass_scan_operators.cuh", - "cuda/include/thrust/system/cuda/detail/cub/block/block_adjacent_difference.cuh", - "cuda/include/thrust/system/cuda/detail/cub/block/block_discontinuity.cuh", - "cuda/include/thrust/system/cuda/detail/cub/block/block_exchange.cuh", - "cuda/include/thrust/system/cuda/detail/cub/block/block_histogram.cuh", - "cuda/include/thrust/system/cuda/detail/cub/block/block_load.cuh", - "cuda/include/thrust/system/cuda/detail/cub/block/block_radix_rank.cuh", - "cuda/include/thrust/system/cuda/detail/cub/block/block_radix_sort.cuh", - "cuda/include/thrust/system/cuda/detail/cub/block/block_raking_layout.cuh", - "cuda/include/thrust/system/cuda/detail/cub/block/block_reduce.cuh", - "cuda/include/thrust/system/cuda/detail/cub/block/block_scan.cuh", - "cuda/include/thrust/system/cuda/detail/cub/block/block_shuffle.cuh", - "cuda/include/thrust/system/cuda/detail/cub/block/block_store.cuh", - "cuda/include/thrust/system/cuda/detail/cub/block/specializations/block_histogram_atomic.cuh", - "cuda/include/thrust/system/cuda/detail/cub/block/specializations/block_histogram_sort.cuh", - "cuda/include/thrust/system/cuda/detail/cub/block/specializations/block_reduce_raking.cuh", - "cuda/include/thrust/system/cuda/detail/cub/block/specializations/block_reduce_raking_commutative_only.cuh", - "cuda/include/thrust/system/cuda/detail/cub/block/specializations/block_reduce_warp_reductions.cuh", - "cuda/include/thrust/system/cuda/detail/cub/block/specializations/block_scan_raking.cuh", - "cuda/include/thrust/system/cuda/detail/cub/block/specializations/block_scan_warp_scans.cuh", - "cuda/include/thrust/system/cuda/detail/cub/block/specializations/block_scan_warp_scans2.cuh", - "cuda/include/thrust/system/cuda/detail/cub/block/specializations/block_scan_warp_scans3.cuh", - "cuda/include/thrust/system/cuda/detail/cub/cub.cuh", - "cuda/include/thrust/system/cuda/detail/cub/device/device_histogram.cuh", - "cuda/include/thrust/system/cuda/detail/cub/device/device_partition.cuh", - "cuda/include/thrust/system/cuda/detail/cub/device/device_radix_sort.cuh", - "cuda/include/thrust/system/cuda/detail/cub/device/device_reduce.cuh", - "cuda/include/thrust/system/cuda/detail/cub/device/device_run_length_encode.cuh", - "cuda/include/thrust/system/cuda/detail/cub/device/device_scan.cuh", - "cuda/include/thrust/system/cuda/detail/cub/device/device_segmented_radix_sort.cuh", - "cuda/include/thrust/system/cuda/detail/cub/device/device_segmented_reduce.cuh", - "cuda/include/thrust/system/cuda/detail/cub/device/device_select.cuh", - "cuda/include/thrust/system/cuda/detail/cub/device/device_spmv.cuh", - "cuda/include/thrust/system/cuda/detail/cub/device/dispatch/dispatch_histogram.cuh", - "cuda/include/thrust/system/cuda/detail/cub/device/dispatch/dispatch_radix_sort.cuh", - "cuda/include/thrust/system/cuda/detail/cub/device/dispatch/dispatch_reduce.cuh", - "cuda/include/thrust/system/cuda/detail/cub/device/dispatch/dispatch_reduce_by_key.cuh", - "cuda/include/thrust/system/cuda/detail/cub/device/dispatch/dispatch_rle.cuh", - "cuda/include/thrust/system/cuda/detail/cub/device/dispatch/dispatch_scan.cuh", - "cuda/include/thrust/system/cuda/detail/cub/device/dispatch/dispatch_select_if.cuh", - "cuda/include/thrust/system/cuda/detail/cub/device/dispatch/dispatch_spmv_csrt.cuh", - "cuda/include/thrust/system/cuda/detail/cub/device/dispatch/dispatch_spmv_orig.cuh", - "cuda/include/thrust/system/cuda/detail/cub/device/dispatch/dispatch_spmv_row_based.cuh", - "cuda/include/thrust/system/cuda/detail/cub/grid/grid_barrier.cuh", - "cuda/include/thrust/system/cuda/detail/cub/grid/grid_even_share.cuh", - "cuda/include/thrust/system/cuda/detail/cub/grid/grid_mapping.cuh", - "cuda/include/thrust/system/cuda/detail/cub/grid/grid_queue.cuh", - "cuda/include/thrust/system/cuda/detail/cub/host/mutex.cuh", - "cuda/include/thrust/system/cuda/detail/cub/iterator/arg_index_input_iterator.cuh", - "cuda/include/thrust/system/cuda/detail/cub/iterator/cache_modified_input_iterator.cuh", - "cuda/include/thrust/system/cuda/detail/cub/iterator/cache_modified_output_iterator.cuh", - "cuda/include/thrust/system/cuda/detail/cub/iterator/constant_input_iterator.cuh", - "cuda/include/thrust/system/cuda/detail/cub/iterator/counting_input_iterator.cuh", - "cuda/include/thrust/system/cuda/detail/cub/iterator/discard_output_iterator.cuh", - "cuda/include/thrust/system/cuda/detail/cub/iterator/tex_obj_input_iterator.cuh", - "cuda/include/thrust/system/cuda/detail/cub/iterator/tex_ref_input_iterator.cuh", - "cuda/include/thrust/system/cuda/detail/cub/iterator/transform_input_iterator.cuh", - "cuda/include/thrust/system/cuda/detail/cub/thread/thread_load.cuh", - "cuda/include/thrust/system/cuda/detail/cub/thread/thread_operators.cuh", - "cuda/include/thrust/system/cuda/detail/cub/thread/thread_reduce.cuh", - "cuda/include/thrust/system/cuda/detail/cub/thread/thread_scan.cuh", - "cuda/include/thrust/system/cuda/detail/cub/thread/thread_search.cuh", - "cuda/include/thrust/system/cuda/detail/cub/thread/thread_store.cuh", - "cuda/include/thrust/system/cuda/detail/cub/util_allocator.cuh", - "cuda/include/thrust/system/cuda/detail/cub/util_arch.cuh", - "cuda/include/thrust/system/cuda/detail/cub/util_debug.cuh", - "cuda/include/thrust/system/cuda/detail/cub/util_device.cuh", - "cuda/include/thrust/system/cuda/detail/cub/util_macro.cuh", - "cuda/include/thrust/system/cuda/detail/cub/util_namespace.cuh", - "cuda/include/thrust/system/cuda/detail/cub/util_ptx.cuh", - "cuda/include/thrust/system/cuda/detail/cub/util_type.cuh", - "cuda/include/thrust/system/cuda/detail/cub/warp/specializations/warp_reduce_shfl.cuh", - "cuda/include/thrust/system/cuda/detail/cub/warp/specializations/warp_reduce_smem.cuh", - "cuda/include/thrust/system/cuda/detail/cub/warp/specializations/warp_scan_shfl.cuh", - "cuda/include/thrust/system/cuda/detail/cub/warp/specializations/warp_scan_smem.cuh", - "cuda/include/thrust/system/cuda/detail/cub/warp/warp_reduce.cuh", - "cuda/include/thrust/system/cuda/detail/cub/warp/warp_scan.cuh", - "cuda/include/thrust/system/cuda/detail/equal.h", - "cuda/include/thrust/system/cuda/detail/error.inl", - "cuda/include/thrust/system/cuda/detail/execution_policy.h", - "cuda/include/thrust/system/cuda/detail/extrema.h", - "cuda/include/thrust/system/cuda/detail/fill.h", - "cuda/include/thrust/system/cuda/detail/find.h", - "cuda/include/thrust/system/cuda/detail/for_each.h", - "cuda/include/thrust/system/cuda/detail/gather.h", - "cuda/include/thrust/system/cuda/detail/generate.h", - "cuda/include/thrust/system/cuda/detail/get_value.h", - "cuda/include/thrust/system/cuda/detail/guarded_cuda_runtime_api.h", - "cuda/include/thrust/system/cuda/detail/guarded_driver_types.h", - "cuda/include/thrust/system/cuda/detail/inner_product.h", - "cuda/include/thrust/system/cuda/detail/internal/copy_cross_system.h", - "cuda/include/thrust/system/cuda/detail/internal/copy_device_to_device.h", - "cuda/include/thrust/system/cuda/detail/iter_swap.h", - "cuda/include/thrust/system/cuda/detail/logical.h", - "cuda/include/thrust/system/cuda/detail/malloc_and_free.h", - "cuda/include/thrust/system/cuda/detail/memory.inl", - "cuda/include/thrust/system/cuda/detail/memory_buffer.h", - "cuda/include/thrust/system/cuda/detail/merge.h", - "cuda/include/thrust/system/cuda/detail/mismatch.h", - "cuda/include/thrust/system/cuda/detail/par.h", - "cuda/include/thrust/system/cuda/detail/par_to_seq.h", - "cuda/include/thrust/system/cuda/detail/parallel_for.h", - "cuda/include/thrust/system/cuda/detail/partition.h", - "cuda/include/thrust/system/cuda/detail/reduce.h", - "cuda/include/thrust/system/cuda/detail/reduce_by_key.h", - "cuda/include/thrust/system/cuda/detail/remove.h", - "cuda/include/thrust/system/cuda/detail/replace.h", - "cuda/include/thrust/system/cuda/detail/reverse.h", - "cuda/include/thrust/system/cuda/detail/scan.h", - "cuda/include/thrust/system/cuda/detail/scan_by_key.h", - "cuda/include/thrust/system/cuda/detail/scatter.h", - "cuda/include/thrust/system/cuda/detail/sequence.h", - "cuda/include/thrust/system/cuda/detail/set_operations.h", - "cuda/include/thrust/system/cuda/detail/sort.h", - "cuda/include/thrust/system/cuda/detail/swap_ranges.h", - "cuda/include/thrust/system/cuda/detail/tabulate.h", - "cuda/include/thrust/system/cuda/detail/temporary_buffer.h", - "cuda/include/thrust/system/cuda/detail/terminate.h", - "cuda/include/thrust/system/cuda/detail/transform.h", - "cuda/include/thrust/system/cuda/detail/transform_reduce.h", - "cuda/include/thrust/system/cuda/detail/transform_scan.h", - "cuda/include/thrust/system/cuda/detail/uninitialized_copy.h", - "cuda/include/thrust/system/cuda/detail/uninitialized_fill.h", - "cuda/include/thrust/system/cuda/detail/unique.h", - "cuda/include/thrust/system/cuda/detail/unique_by_key.h", - "cuda/include/thrust/system/cuda/detail/util.h", - "cuda/include/thrust/system/cuda/detail/vector.inl", - "cuda/include/thrust/system/cuda/error.h", - "cuda/include/thrust/system/cuda/execution_policy.h", - "cuda/include/thrust/system/cuda/experimental/pinned_allocator.h", - "cuda/include/thrust/system/cuda/memory.h", - "cuda/include/thrust/system/cuda/vector.h", - "cuda/include/thrust/system/detail/adl/adjacent_difference.h", - "cuda/include/thrust/system/detail/adl/assign_value.h", - "cuda/include/thrust/system/detail/adl/binary_search.h", - "cuda/include/thrust/system/detail/adl/copy.h", - "cuda/include/thrust/system/detail/adl/copy_if.h", - "cuda/include/thrust/system/detail/adl/count.h", - "cuda/include/thrust/system/detail/adl/equal.h", - "cuda/include/thrust/system/detail/adl/extrema.h", - "cuda/include/thrust/system/detail/adl/fill.h", - "cuda/include/thrust/system/detail/adl/find.h", - "cuda/include/thrust/system/detail/adl/for_each.h", - "cuda/include/thrust/system/detail/adl/gather.h", - "cuda/include/thrust/system/detail/adl/generate.h", - "cuda/include/thrust/system/detail/adl/get_value.h", - "cuda/include/thrust/system/detail/adl/inner_product.h", - "cuda/include/thrust/system/detail/adl/iter_swap.h", - "cuda/include/thrust/system/detail/adl/logical.h", - "cuda/include/thrust/system/detail/adl/malloc_and_free.h", - "cuda/include/thrust/system/detail/adl/merge.h", - "cuda/include/thrust/system/detail/adl/mismatch.h", - "cuda/include/thrust/system/detail/adl/partition.h", - "cuda/include/thrust/system/detail/adl/reduce.h", - "cuda/include/thrust/system/detail/adl/reduce_by_key.h", - "cuda/include/thrust/system/detail/adl/remove.h", - "cuda/include/thrust/system/detail/adl/replace.h", - "cuda/include/thrust/system/detail/adl/reverse.h", - "cuda/include/thrust/system/detail/adl/scan.h", - "cuda/include/thrust/system/detail/adl/scan_by_key.h", - "cuda/include/thrust/system/detail/adl/scatter.h", - "cuda/include/thrust/system/detail/adl/sequence.h", - "cuda/include/thrust/system/detail/adl/set_operations.h", - "cuda/include/thrust/system/detail/adl/sort.h", - "cuda/include/thrust/system/detail/adl/swap_ranges.h", - "cuda/include/thrust/system/detail/adl/tabulate.h", - "cuda/include/thrust/system/detail/adl/temporary_buffer.h", - "cuda/include/thrust/system/detail/adl/transform.h", - "cuda/include/thrust/system/detail/adl/transform_reduce.h", - "cuda/include/thrust/system/detail/adl/transform_scan.h", - "cuda/include/thrust/system/detail/adl/uninitialized_copy.h", - "cuda/include/thrust/system/detail/adl/uninitialized_fill.h", - "cuda/include/thrust/system/detail/adl/unique.h", - "cuda/include/thrust/system/detail/adl/unique_by_key.h", - "cuda/include/thrust/system/detail/bad_alloc.h", - "cuda/include/thrust/system/detail/errno.h", - "cuda/include/thrust/system/detail/error_category.inl", - "cuda/include/thrust/system/detail/error_code.inl", - "cuda/include/thrust/system/detail/error_condition.inl", - "cuda/include/thrust/system/detail/generic/adjacent_difference.h", - "cuda/include/thrust/system/detail/generic/adjacent_difference.inl", - "cuda/include/thrust/system/detail/generic/advance.h", - "cuda/include/thrust/system/detail/generic/advance.inl", - "cuda/include/thrust/system/detail/generic/binary_search.h", - "cuda/include/thrust/system/detail/generic/binary_search.inl", - "cuda/include/thrust/system/detail/generic/copy.h", - "cuda/include/thrust/system/detail/generic/copy.inl", - "cuda/include/thrust/system/detail/generic/copy_if.h", - "cuda/include/thrust/system/detail/generic/copy_if.inl", - "cuda/include/thrust/system/detail/generic/count.h", - "cuda/include/thrust/system/detail/generic/count.inl", - "cuda/include/thrust/system/detail/generic/distance.h", - "cuda/include/thrust/system/detail/generic/distance.inl", - "cuda/include/thrust/system/detail/generic/equal.h", - "cuda/include/thrust/system/detail/generic/equal.inl", - "cuda/include/thrust/system/detail/generic/extrema.h", - "cuda/include/thrust/system/detail/generic/extrema.inl", - "cuda/include/thrust/system/detail/generic/fill.h", - "cuda/include/thrust/system/detail/generic/find.h", - "cuda/include/thrust/system/detail/generic/find.inl", - "cuda/include/thrust/system/detail/generic/for_each.h", - "cuda/include/thrust/system/detail/generic/gather.h", - "cuda/include/thrust/system/detail/generic/gather.inl", - "cuda/include/thrust/system/detail/generic/generate.h", - "cuda/include/thrust/system/detail/generic/generate.inl", - "cuda/include/thrust/system/detail/generic/inner_product.h", - "cuda/include/thrust/system/detail/generic/inner_product.inl", - "cuda/include/thrust/system/detail/generic/logical.h", - "cuda/include/thrust/system/detail/generic/memory.h", - "cuda/include/thrust/system/detail/generic/memory.inl", - "cuda/include/thrust/system/detail/generic/merge.h", - "cuda/include/thrust/system/detail/generic/merge.inl", - "cuda/include/thrust/system/detail/generic/mismatch.h", - "cuda/include/thrust/system/detail/generic/mismatch.inl", - "cuda/include/thrust/system/detail/generic/partition.h", - "cuda/include/thrust/system/detail/generic/partition.inl", - "cuda/include/thrust/system/detail/generic/reduce.h", - "cuda/include/thrust/system/detail/generic/reduce.inl", - "cuda/include/thrust/system/detail/generic/reduce_by_key.h", - "cuda/include/thrust/system/detail/generic/reduce_by_key.inl", - "cuda/include/thrust/system/detail/generic/remove.h", - "cuda/include/thrust/system/detail/generic/remove.inl", - "cuda/include/thrust/system/detail/generic/replace.h", - "cuda/include/thrust/system/detail/generic/replace.inl", - "cuda/include/thrust/system/detail/generic/reverse.h", - "cuda/include/thrust/system/detail/generic/reverse.inl", - "cuda/include/thrust/system/detail/generic/scalar/binary_search.h", - "cuda/include/thrust/system/detail/generic/scalar/binary_search.inl", - "cuda/include/thrust/system/detail/generic/scan.h", - "cuda/include/thrust/system/detail/generic/scan.inl", - "cuda/include/thrust/system/detail/generic/scan_by_key.h", - "cuda/include/thrust/system/detail/generic/scan_by_key.inl", - "cuda/include/thrust/system/detail/generic/scatter.h", - "cuda/include/thrust/system/detail/generic/scatter.inl", - "cuda/include/thrust/system/detail/generic/select_system.h", - "cuda/include/thrust/system/detail/generic/sequence.h", - "cuda/include/thrust/system/detail/generic/sequence.inl", - "cuda/include/thrust/system/detail/generic/set_operations.h", - "cuda/include/thrust/system/detail/generic/set_operations.inl", - "cuda/include/thrust/system/detail/generic/sort.h", - "cuda/include/thrust/system/detail/generic/sort.inl", - "cuda/include/thrust/system/detail/generic/swap_ranges.h", - "cuda/include/thrust/system/detail/generic/swap_ranges.inl", - "cuda/include/thrust/system/detail/generic/tabulate.h", - "cuda/include/thrust/system/detail/generic/tabulate.inl", - "cuda/include/thrust/system/detail/generic/tag.h", - "cuda/include/thrust/system/detail/generic/temporary_buffer.h", - "cuda/include/thrust/system/detail/generic/temporary_buffer.inl", - "cuda/include/thrust/system/detail/generic/transform.h", - "cuda/include/thrust/system/detail/generic/transform.inl", - "cuda/include/thrust/system/detail/generic/transform_reduce.h", - "cuda/include/thrust/system/detail/generic/transform_reduce.inl", - "cuda/include/thrust/system/detail/generic/transform_scan.h", - "cuda/include/thrust/system/detail/generic/transform_scan.inl", - "cuda/include/thrust/system/detail/generic/type_traits.h", - "cuda/include/thrust/system/detail/generic/uninitialized_copy.h", - "cuda/include/thrust/system/detail/generic/uninitialized_copy.inl", - "cuda/include/thrust/system/detail/generic/uninitialized_fill.h", - "cuda/include/thrust/system/detail/generic/uninitialized_fill.inl", - "cuda/include/thrust/system/detail/generic/unique.h", - "cuda/include/thrust/system/detail/generic/unique.inl", - "cuda/include/thrust/system/detail/generic/unique_by_key.h", - "cuda/include/thrust/system/detail/generic/unique_by_key.inl", - "cuda/include/thrust/system/detail/internal/decompose.h", - "cuda/include/thrust/system/detail/sequential/adjacent_difference.h", - "cuda/include/thrust/system/detail/sequential/assign_value.h", - "cuda/include/thrust/system/detail/sequential/binary_search.h", - "cuda/include/thrust/system/detail/sequential/copy.h", - "cuda/include/thrust/system/detail/sequential/copy.inl", - "cuda/include/thrust/system/detail/sequential/copy_backward.h", - "cuda/include/thrust/system/detail/sequential/copy_if.h", - "cuda/include/thrust/system/detail/sequential/count.h", - "cuda/include/thrust/system/detail/sequential/equal.h", - "cuda/include/thrust/system/detail/sequential/execution_policy.h", - "cuda/include/thrust/system/detail/sequential/extrema.h", - "cuda/include/thrust/system/detail/sequential/fill.h", - "cuda/include/thrust/system/detail/sequential/find.h", - "cuda/include/thrust/system/detail/sequential/for_each.h", - "cuda/include/thrust/system/detail/sequential/gather.h", - "cuda/include/thrust/system/detail/sequential/general_copy.h", - "cuda/include/thrust/system/detail/sequential/generate.h", - "cuda/include/thrust/system/detail/sequential/get_value.h", - "cuda/include/thrust/system/detail/sequential/inner_product.h", - "cuda/include/thrust/system/detail/sequential/insertion_sort.h", - "cuda/include/thrust/system/detail/sequential/iter_swap.h", - "cuda/include/thrust/system/detail/sequential/logical.h", - "cuda/include/thrust/system/detail/sequential/malloc_and_free.h", - "cuda/include/thrust/system/detail/sequential/merge.h", - "cuda/include/thrust/system/detail/sequential/merge.inl", - "cuda/include/thrust/system/detail/sequential/mismatch.h", - "cuda/include/thrust/system/detail/sequential/partition.h", - "cuda/include/thrust/system/detail/sequential/reduce.h", - "cuda/include/thrust/system/detail/sequential/reduce_by_key.h", - "cuda/include/thrust/system/detail/sequential/remove.h", - "cuda/include/thrust/system/detail/sequential/replace.h", - "cuda/include/thrust/system/detail/sequential/reverse.h", - "cuda/include/thrust/system/detail/sequential/scan.h", - "cuda/include/thrust/system/detail/sequential/scan_by_key.h", - "cuda/include/thrust/system/detail/sequential/scatter.h", - "cuda/include/thrust/system/detail/sequential/sequence.h", - "cuda/include/thrust/system/detail/sequential/set_operations.h", - "cuda/include/thrust/system/detail/sequential/sort.h", - "cuda/include/thrust/system/detail/sequential/sort.inl", - "cuda/include/thrust/system/detail/sequential/stable_merge_sort.h", - "cuda/include/thrust/system/detail/sequential/stable_merge_sort.inl", - "cuda/include/thrust/system/detail/sequential/stable_primitive_sort.h", - "cuda/include/thrust/system/detail/sequential/stable_primitive_sort.inl", - "cuda/include/thrust/system/detail/sequential/stable_radix_sort.h", - "cuda/include/thrust/system/detail/sequential/stable_radix_sort.inl", - "cuda/include/thrust/system/detail/sequential/swap_ranges.h", - "cuda/include/thrust/system/detail/sequential/tabulate.h", - "cuda/include/thrust/system/detail/sequential/temporary_buffer.h", - "cuda/include/thrust/system/detail/sequential/transform.h", - "cuda/include/thrust/system/detail/sequential/transform_reduce.h", - "cuda/include/thrust/system/detail/sequential/transform_scan.h", - "cuda/include/thrust/system/detail/sequential/trivial_copy.h", - "cuda/include/thrust/system/detail/sequential/uninitialized_copy.h", - "cuda/include/thrust/system/detail/sequential/uninitialized_fill.h", - "cuda/include/thrust/system/detail/sequential/unique.h", - "cuda/include/thrust/system/detail/sequential/unique_by_key.h", - "cuda/include/thrust/system/detail/system_error.inl", - "cuda/include/thrust/system/error_code.h", - "cuda/include/thrust/system/omp/detail/adjacent_difference.h", - "cuda/include/thrust/system/omp/detail/assign_value.h", - "cuda/include/thrust/system/omp/detail/binary_search.h", - "cuda/include/thrust/system/omp/detail/copy.h", - "cuda/include/thrust/system/omp/detail/copy.inl", - "cuda/include/thrust/system/omp/detail/copy_if.h", - "cuda/include/thrust/system/omp/detail/copy_if.inl", - "cuda/include/thrust/system/omp/detail/count.h", - "cuda/include/thrust/system/omp/detail/default_decomposition.h", - "cuda/include/thrust/system/omp/detail/default_decomposition.inl", - "cuda/include/thrust/system/omp/detail/equal.h", - "cuda/include/thrust/system/omp/detail/execution_policy.h", - "cuda/include/thrust/system/omp/detail/extrema.h", - "cuda/include/thrust/system/omp/detail/fill.h", - "cuda/include/thrust/system/omp/detail/find.h", - "cuda/include/thrust/system/omp/detail/for_each.h", - "cuda/include/thrust/system/omp/detail/for_each.inl", - "cuda/include/thrust/system/omp/detail/gather.h", - "cuda/include/thrust/system/omp/detail/generate.h", - "cuda/include/thrust/system/omp/detail/get_value.h", - "cuda/include/thrust/system/omp/detail/inner_product.h", - "cuda/include/thrust/system/omp/detail/iter_swap.h", - "cuda/include/thrust/system/omp/detail/logical.h", - "cuda/include/thrust/system/omp/detail/malloc_and_free.h", - "cuda/include/thrust/system/omp/detail/memory.inl", - "cuda/include/thrust/system/omp/detail/merge.h", - "cuda/include/thrust/system/omp/detail/mismatch.h", - "cuda/include/thrust/system/omp/detail/par.h", - "cuda/include/thrust/system/omp/detail/partition.h", - "cuda/include/thrust/system/omp/detail/partition.inl", - "cuda/include/thrust/system/omp/detail/reduce.h", - "cuda/include/thrust/system/omp/detail/reduce.inl", - "cuda/include/thrust/system/omp/detail/reduce_by_key.h", - "cuda/include/thrust/system/omp/detail/reduce_by_key.inl", - "cuda/include/thrust/system/omp/detail/reduce_intervals.h", - "cuda/include/thrust/system/omp/detail/reduce_intervals.inl", - "cuda/include/thrust/system/omp/detail/remove.h", - "cuda/include/thrust/system/omp/detail/remove.inl", - "cuda/include/thrust/system/omp/detail/replace.h", - "cuda/include/thrust/system/omp/detail/reverse.h", - "cuda/include/thrust/system/omp/detail/scan.h", - "cuda/include/thrust/system/omp/detail/scan_by_key.h", - "cuda/include/thrust/system/omp/detail/scatter.h", - "cuda/include/thrust/system/omp/detail/sequence.h", - "cuda/include/thrust/system/omp/detail/set_operations.h", - "cuda/include/thrust/system/omp/detail/sort.h", - "cuda/include/thrust/system/omp/detail/sort.inl", - "cuda/include/thrust/system/omp/detail/swap_ranges.h", - "cuda/include/thrust/system/omp/detail/tabulate.h", - "cuda/include/thrust/system/omp/detail/temporary_buffer.h", - "cuda/include/thrust/system/omp/detail/transform.h", - "cuda/include/thrust/system/omp/detail/transform_reduce.h", - "cuda/include/thrust/system/omp/detail/transform_scan.h", - "cuda/include/thrust/system/omp/detail/uninitialized_copy.h", - "cuda/include/thrust/system/omp/detail/uninitialized_fill.h", - "cuda/include/thrust/system/omp/detail/unique.h", - "cuda/include/thrust/system/omp/detail/unique.inl", - "cuda/include/thrust/system/omp/detail/unique_by_key.h", - "cuda/include/thrust/system/omp/detail/unique_by_key.inl", - "cuda/include/thrust/system/omp/detail/vector.inl", - "cuda/include/thrust/system/omp/execution_policy.h", - "cuda/include/thrust/system/omp/memory.h", - "cuda/include/thrust/system/omp/vector.h", - "cuda/include/thrust/system/system_error.h", - "cuda/include/thrust/system/tbb/detail/adjacent_difference.h", - "cuda/include/thrust/system/tbb/detail/assign_value.h", - "cuda/include/thrust/system/tbb/detail/binary_search.h", - "cuda/include/thrust/system/tbb/detail/copy.h", - "cuda/include/thrust/system/tbb/detail/copy.inl", - "cuda/include/thrust/system/tbb/detail/copy_if.h", - "cuda/include/thrust/system/tbb/detail/copy_if.inl", - "cuda/include/thrust/system/tbb/detail/count.h", - "cuda/include/thrust/system/tbb/detail/equal.h", - "cuda/include/thrust/system/tbb/detail/execution_policy.h", - "cuda/include/thrust/system/tbb/detail/extrema.h", - "cuda/include/thrust/system/tbb/detail/fill.h", - "cuda/include/thrust/system/tbb/detail/find.h", - "cuda/include/thrust/system/tbb/detail/for_each.h", - "cuda/include/thrust/system/tbb/detail/for_each.inl", - "cuda/include/thrust/system/tbb/detail/gather.h", - "cuda/include/thrust/system/tbb/detail/generate.h", - "cuda/include/thrust/system/tbb/detail/get_value.h", - "cuda/include/thrust/system/tbb/detail/inner_product.h", - "cuda/include/thrust/system/tbb/detail/iter_swap.h", - "cuda/include/thrust/system/tbb/detail/logical.h", - "cuda/include/thrust/system/tbb/detail/malloc_and_free.h", - "cuda/include/thrust/system/tbb/detail/memory.inl", - "cuda/include/thrust/system/tbb/detail/merge.h", - "cuda/include/thrust/system/tbb/detail/merge.inl", - "cuda/include/thrust/system/tbb/detail/mismatch.h", - "cuda/include/thrust/system/tbb/detail/par.h", - "cuda/include/thrust/system/tbb/detail/partition.h", - "cuda/include/thrust/system/tbb/detail/partition.inl", - "cuda/include/thrust/system/tbb/detail/reduce.h", - "cuda/include/thrust/system/tbb/detail/reduce.inl", - "cuda/include/thrust/system/tbb/detail/reduce_by_key.h", - "cuda/include/thrust/system/tbb/detail/reduce_by_key.inl", - "cuda/include/thrust/system/tbb/detail/reduce_intervals.h", - "cuda/include/thrust/system/tbb/detail/remove.h", - "cuda/include/thrust/system/tbb/detail/remove.inl", - "cuda/include/thrust/system/tbb/detail/replace.h", - "cuda/include/thrust/system/tbb/detail/reverse.h", - "cuda/include/thrust/system/tbb/detail/scan.h", - "cuda/include/thrust/system/tbb/detail/scan.inl", - "cuda/include/thrust/system/tbb/detail/scan_by_key.h", - "cuda/include/thrust/system/tbb/detail/scatter.h", - "cuda/include/thrust/system/tbb/detail/sequence.h", - "cuda/include/thrust/system/tbb/detail/set_operations.h", - "cuda/include/thrust/system/tbb/detail/sort.h", - "cuda/include/thrust/system/tbb/detail/sort.inl", - "cuda/include/thrust/system/tbb/detail/swap_ranges.h", - "cuda/include/thrust/system/tbb/detail/tabulate.h", - "cuda/include/thrust/system/tbb/detail/temporary_buffer.h", - "cuda/include/thrust/system/tbb/detail/transform.h", - "cuda/include/thrust/system/tbb/detail/transform_reduce.h", - "cuda/include/thrust/system/tbb/detail/transform_scan.h", - "cuda/include/thrust/system/tbb/detail/uninitialized_copy.h", - "cuda/include/thrust/system/tbb/detail/uninitialized_fill.h", - "cuda/include/thrust/system/tbb/detail/unique.h", - "cuda/include/thrust/system/tbb/detail/unique.inl", - "cuda/include/thrust/system/tbb/detail/unique_by_key.h", - "cuda/include/thrust/system/tbb/detail/unique_by_key.inl", - "cuda/include/thrust/system/tbb/detail/vector.inl", - "cuda/include/thrust/system/tbb/execution_policy.h", - "cuda/include/thrust/system/tbb/memory.h", - "cuda/include/thrust/system/tbb/vector.h", - "cuda/include/thrust/system_error.h", - "cuda/include/thrust/tabulate.h", - "cuda/include/thrust/transform.h", - "cuda/include/thrust/transform_reduce.h", - "cuda/include/thrust/transform_scan.h", - "cuda/include/thrust/tuple.h", - "cuda/include/thrust/uninitialized_copy.h", - "cuda/include/thrust/uninitialized_fill.h", - "cuda/include/thrust/unique.h", - "cuda/include/thrust/version.h", - "cuda/include/vector_functions.h", - "cuda/include/vector_functions.hpp", - "cuda/include/vector_types.h", - ], - cmd = """ -if [ -d "$(@D)/extras" ]; then rm $(@D)/extras -drf; fi && if [ -d "$(@D)/include" ]; then rm $(@D)/include -drf; fi && if [ -d "$(@D)/lib" ]; then rm $(@D)/lib -drf; fi && if [ -d "$(@D)/nvvm" ]; then rm $(@D)/nvvm -drf; fi && cp "/usr/local/cuda-10.0/include/CL/cl.h" "$(@D)/cuda/include/CL/cl.h" && cp "/usr/local/cuda-10.0/include/CL/cl.hpp" "$(@D)/cuda/include/CL/cl.hpp" && cp "/usr/local/cuda-10.0/include/CL/cl_egl.h" "$(@D)/cuda/include/CL/cl_egl.h" && cp "/usr/local/cuda-10.0/include/CL/cl_ext.h" "$(@D)/cuda/include/CL/cl_ext.h" && cp "/usr/local/cuda-10.0/include/CL/cl_gl.h" "$(@D)/cuda/include/CL/cl_gl.h" && cp "/usr/local/cuda-10.0/include/CL/cl_gl_ext.h" "$(@D)/cuda/include/CL/cl_gl_ext.h" && cp "/usr/local/cuda-10.0/include/CL/cl_platform.h" "$(@D)/cuda/include/CL/cl_platform.h" && cp "/usr/local/cuda-10.0/include/CL/opencl.h" "$(@D)/cuda/include/CL/opencl.h" && cp "/usr/local/cuda-10.0/include/builtin_types.h" "$(@D)/cuda/include/builtin_types.h" && cp "/usr/local/cuda-10.0/include/channel_descriptor.h" "$(@D)/cuda/include/channel_descriptor.h" && cp "/usr/local/cuda-10.0/include/common_functions.h" "$(@D)/cuda/include/common_functions.h" && cp "/usr/local/cuda-10.0/include/cooperative_groups.h" "$(@D)/cuda/include/cooperative_groups.h" && cp "/usr/local/cuda-10.0/include/cooperative_groups_helpers.h" "$(@D)/cuda/include/cooperative_groups_helpers.h" && cp "/usr/local/cuda-10.0/include/crt/common_functions.h" "$(@D)/cuda/include/crt/common_functions.h" && cp "/usr/local/cuda-10.0/include/crt/device_double_functions.h" "$(@D)/cuda/include/crt/device_double_functions.h" && cp "/usr/local/cuda-10.0/include/crt/device_double_functions.hpp" "$(@D)/cuda/include/crt/device_double_functions.hpp" && cp "/usr/local/cuda-10.0/include/crt/device_functions.h" "$(@D)/cuda/include/crt/device_functions.h" && cp "/usr/local/cuda-10.0/include/crt/device_functions.hpp" "$(@D)/cuda/include/crt/device_functions.hpp" && cp "/usr/local/cuda-10.0/include/crt/func_macro.h" "$(@D)/cuda/include/crt/func_macro.h" && cp "/usr/local/cuda-10.0/include/crt/host_config.h" "$(@D)/cuda/include/crt/host_config.h" && cp "/usr/local/cuda-10.0/include/crt/host_defines.h" "$(@D)/cuda/include/crt/host_defines.h" && cp "/usr/local/cuda-10.0/include/crt/host_runtime.h" "$(@D)/cuda/include/crt/host_runtime.h" && cp "/usr/local/cuda-10.0/include/crt/math_functions.h" "$(@D)/cuda/include/crt/math_functions.h" && cp "/usr/local/cuda-10.0/include/crt/math_functions.hpp" "$(@D)/cuda/include/crt/math_functions.hpp" && cp "/usr/local/cuda-10.0/include/crt/mma.h" "$(@D)/cuda/include/crt/mma.h" && cp "/usr/local/cuda-10.0/include/crt/mma.hpp" "$(@D)/cuda/include/crt/mma.hpp" && cp "/usr/local/cuda-10.0/include/crt/nvfunctional" "$(@D)/cuda/include/crt/nvfunctional" && cp "/usr/local/cuda-10.0/include/crt/sm_70_rt.h" "$(@D)/cuda/include/crt/sm_70_rt.h" && cp "/usr/local/cuda-10.0/include/crt/sm_70_rt.hpp" "$(@D)/cuda/include/crt/sm_70_rt.hpp" && cp "/usr/local/cuda-10.0/include/crt/storage_class.h" "$(@D)/cuda/include/crt/storage_class.h" && cp "/usr/local/cuda-10.0/include/cuComplex.h" "$(@D)/cuda/include/cuComplex.h" && cp "/usr/local/cuda-10.0/include/cublas.h" "$(@D)/cuda/include/cublas.h" && cp "/usr/local/cuda-10.0/include/cublasXt.h" "$(@D)/cuda/include/cublasXt.h" && cp "/usr/local/cuda-10.0/include/cublas_api.h" "$(@D)/cuda/include/cublas_api.h" && cp "/usr/local/cuda-10.0/include/cublas_v2.h" "$(@D)/cuda/include/cublas_v2.h" && cp "/usr/local/cuda-10.0/include/cuda.h" "$(@D)/cuda/include/cuda.h" && cp "/usr/local/cuda-10.0/include/cudaEGL.h" "$(@D)/cuda/include/cudaEGL.h" && cp "/usr/local/cuda-10.0/include/cudaGL.h" "$(@D)/cuda/include/cudaGL.h" && cp "/usr/local/cuda-10.0/include/cudaProfiler.h" "$(@D)/cuda/include/cudaProfiler.h" && cp "/usr/local/cuda-10.0/include/cudaVDPAU.h" "$(@D)/cuda/include/cudaVDPAU.h" && cp "/usr/local/cuda-10.0/include/cuda_device_runtime_api.h" "$(@D)/cuda/include/cuda_device_runtime_api.h" && cp "/usr/local/cuda-10.0/include/cuda_fp16.h" "$(@D)/cuda/include/cuda_fp16.h" && cp "/usr/local/cuda-10.0/include/cuda_fp16.hpp" "$(@D)/cuda/include/cuda_fp16.hpp" && cp "/usr/local/cuda-10.0/include/cuda_gl_interop.h" "$(@D)/cuda/include/cuda_gl_interop.h" && cp "/usr/local/cuda-10.0/include/cuda_occupancy.h" "$(@D)/cuda/include/cuda_occupancy.h" && cp "/usr/local/cuda-10.0/include/cuda_profiler_api.h" "$(@D)/cuda/include/cuda_profiler_api.h" && cp "/usr/local/cuda-10.0/include/cuda_runtime.h" "$(@D)/cuda/include/cuda_runtime.h" && cp "/usr/local/cuda-10.0/include/cuda_runtime_api.h" "$(@D)/cuda/include/cuda_runtime_api.h" && cp "/usr/local/cuda-10.0/include/cuda_surface_types.h" "$(@D)/cuda/include/cuda_surface_types.h" && cp "/usr/local/cuda-10.0/include/cuda_texture_types.h" "$(@D)/cuda/include/cuda_texture_types.h" && cp "/usr/local/cuda-10.0/include/cuda_vdpau_interop.h" "$(@D)/cuda/include/cuda_vdpau_interop.h" && cp "/usr/local/cuda-10.0/include/cudalibxt.h" "$(@D)/cuda/include/cudalibxt.h" && cp "/usr/local/cuda-10.0/include/cudnn.h" "$(@D)/cuda/include/cudnn.h" && cp "/usr/local/cuda-10.0/include/cufft.h" "$(@D)/cuda/include/cufft.h" && cp "/usr/local/cuda-10.0/include/cufftXt.h" "$(@D)/cuda/include/cufftXt.h" && cp "/usr/local/cuda-10.0/include/cufftw.h" "$(@D)/cuda/include/cufftw.h" && cp "/usr/local/cuda-10.0/include/curand.h" "$(@D)/cuda/include/curand.h" && cp "/usr/local/cuda-10.0/include/curand_discrete.h" "$(@D)/cuda/include/curand_discrete.h" && cp "/usr/local/cuda-10.0/include/curand_discrete2.h" "$(@D)/cuda/include/curand_discrete2.h" && cp "/usr/local/cuda-10.0/include/curand_globals.h" "$(@D)/cuda/include/curand_globals.h" && cp "/usr/local/cuda-10.0/include/curand_kernel.h" "$(@D)/cuda/include/curand_kernel.h" && cp "/usr/local/cuda-10.0/include/curand_lognormal.h" "$(@D)/cuda/include/curand_lognormal.h" && cp "/usr/local/cuda-10.0/include/curand_mrg32k3a.h" "$(@D)/cuda/include/curand_mrg32k3a.h" && cp "/usr/local/cuda-10.0/include/curand_mtgp32.h" "$(@D)/cuda/include/curand_mtgp32.h" && cp "/usr/local/cuda-10.0/include/curand_mtgp32_host.h" "$(@D)/cuda/include/curand_mtgp32_host.h" && cp "/usr/local/cuda-10.0/include/curand_mtgp32_kernel.h" "$(@D)/cuda/include/curand_mtgp32_kernel.h" && cp "/usr/local/cuda-10.0/include/curand_mtgp32dc_p_11213.h" "$(@D)/cuda/include/curand_mtgp32dc_p_11213.h" && cp "/usr/local/cuda-10.0/include/curand_normal.h" "$(@D)/cuda/include/curand_normal.h" && cp "/usr/local/cuda-10.0/include/curand_normal_static.h" "$(@D)/cuda/include/curand_normal_static.h" && cp "/usr/local/cuda-10.0/include/curand_philox4x32_x.h" "$(@D)/cuda/include/curand_philox4x32_x.h" && cp "/usr/local/cuda-10.0/include/curand_poisson.h" "$(@D)/cuda/include/curand_poisson.h" && cp "/usr/local/cuda-10.0/include/curand_precalc.h" "$(@D)/cuda/include/curand_precalc.h" && cp "/usr/local/cuda-10.0/include/curand_uniform.h" "$(@D)/cuda/include/curand_uniform.h" && cp "/usr/local/cuda-10.0/include/cusolverDn.h" "$(@D)/cuda/include/cusolverDn.h" && cp "/usr/local/cuda-10.0/include/cusolverRf.h" "$(@D)/cuda/include/cusolverRf.h" && cp "/usr/local/cuda-10.0/include/cusolverSp.h" "$(@D)/cuda/include/cusolverSp.h" && cp "/usr/local/cuda-10.0/include/cusolverSp_LOWLEVEL_PREVIEW.h" "$(@D)/cuda/include/cusolverSp_LOWLEVEL_PREVIEW.h" && cp "/usr/local/cuda-10.0/include/cusolver_common.h" "$(@D)/cuda/include/cusolver_common.h" && cp "/usr/local/cuda-10.0/include/cusparse.h" "$(@D)/cuda/include/cusparse.h" && cp "/usr/local/cuda-10.0/include/cusparse_v2.h" "$(@D)/cuda/include/cusparse_v2.h" && cp "/usr/local/cuda-10.0/include/device_atomic_functions.h" "$(@D)/cuda/include/device_atomic_functions.h" && cp "/usr/local/cuda-10.0/include/device_atomic_functions.hpp" "$(@D)/cuda/include/device_atomic_functions.hpp" && cp "/usr/local/cuda-10.0/include/device_double_functions.h" "$(@D)/cuda/include/device_double_functions.h" && cp "/usr/local/cuda-10.0/include/device_double_functions.hpp" "$(@D)/cuda/include/device_double_functions.hpp" && cp "/usr/local/cuda-10.0/include/device_functions.h" "$(@D)/cuda/include/device_functions.h" && cp "/usr/local/cuda-10.0/include/device_functions.hpp" "$(@D)/cuda/include/device_functions.hpp" && cp "/usr/local/cuda-10.0/include/device_functions_decls.h" "$(@D)/cuda/include/device_functions_decls.h" && cp "/usr/local/cuda-10.0/include/device_launch_parameters.h" "$(@D)/cuda/include/device_launch_parameters.h" && cp "/usr/local/cuda-10.0/include/device_types.h" "$(@D)/cuda/include/device_types.h" && cp "/usr/local/cuda-10.0/include/driver_functions.h" "$(@D)/cuda/include/driver_functions.h" && cp "/usr/local/cuda-10.0/include/driver_types.h" "$(@D)/cuda/include/driver_types.h" && cp "/usr/local/cuda-10.0/include/dynlink_cuda.h" "$(@D)/cuda/include/dynlink_cuda.h" && cp "/usr/local/cuda-10.0/include/dynlink_cuda_cuda.h" "$(@D)/cuda/include/dynlink_cuda_cuda.h" && cp "/usr/local/cuda-10.0/include/dynlink_cuviddec.h" "$(@D)/cuda/include/dynlink_cuviddec.h" && cp "/usr/local/cuda-10.0/include/dynlink_nvcuvid.h" "$(@D)/cuda/include/dynlink_nvcuvid.h" && cp "/usr/local/cuda-10.0/include/fatBinaryCtl.h" "$(@D)/cuda/include/fatBinaryCtl.h" && cp "/usr/local/cuda-10.0/include/fatbinary.h" "$(@D)/cuda/include/fatbinary.h" && cp "/usr/local/cuda-10.0/include/host_config.h" "$(@D)/cuda/include/host_config.h" && cp "/usr/local/cuda-10.0/include/host_defines.h" "$(@D)/cuda/include/host_defines.h" && cp "/usr/local/cuda-10.0/include/library_types.h" "$(@D)/cuda/include/library_types.h" && cp "/usr/local/cuda-10.0/include/math_constants.h" "$(@D)/cuda/include/math_constants.h" && cp "/usr/local/cuda-10.0/include/math_functions.h" "$(@D)/cuda/include/math_functions.h" && cp "/usr/local/cuda-10.0/include/math_functions.hpp" "$(@D)/cuda/include/math_functions.hpp" && cp "/usr/local/cuda-10.0/include/math_functions_dbl_ptx3.h" "$(@D)/cuda/include/math_functions_dbl_ptx3.h" && cp "/usr/local/cuda-10.0/include/math_functions_dbl_ptx3.hpp" "$(@D)/cuda/include/math_functions_dbl_ptx3.hpp" && cp "/usr/local/cuda-10.0/include/mma.h" "$(@D)/cuda/include/mma.h" && cp "/usr/local/cuda-10.0/include/npp.h" "$(@D)/cuda/include/npp.h" && cp "/usr/local/cuda-10.0/include/nppcore.h" "$(@D)/cuda/include/nppcore.h" && cp "/usr/local/cuda-10.0/include/nppdefs.h" "$(@D)/cuda/include/nppdefs.h" && cp "/usr/local/cuda-10.0/include/nppi.h" "$(@D)/cuda/include/nppi.h" && cp "/usr/local/cuda-10.0/include/nppi_arithmetic_and_logical_operations.h" "$(@D)/cuda/include/nppi_arithmetic_and_logical_operations.h" && cp "/usr/local/cuda-10.0/include/nppi_color_conversion.h" "$(@D)/cuda/include/nppi_color_conversion.h" && cp "/usr/local/cuda-10.0/include/nppi_compression_functions.h" "$(@D)/cuda/include/nppi_compression_functions.h" && cp "/usr/local/cuda-10.0/include/nppi_computer_vision.h" "$(@D)/cuda/include/nppi_computer_vision.h" && cp "/usr/local/cuda-10.0/include/nppi_data_exchange_and_initialization.h" "$(@D)/cuda/include/nppi_data_exchange_and_initialization.h" && cp "/usr/local/cuda-10.0/include/nppi_filtering_functions.h" "$(@D)/cuda/include/nppi_filtering_functions.h" && cp "/usr/local/cuda-10.0/include/nppi_geometry_transforms.h" "$(@D)/cuda/include/nppi_geometry_transforms.h" && cp "/usr/local/cuda-10.0/include/nppi_linear_transforms.h" "$(@D)/cuda/include/nppi_linear_transforms.h" && cp "/usr/local/cuda-10.0/include/nppi_morphological_operations.h" "$(@D)/cuda/include/nppi_morphological_operations.h" && cp "/usr/local/cuda-10.0/include/nppi_statistics_functions.h" "$(@D)/cuda/include/nppi_statistics_functions.h" && cp "/usr/local/cuda-10.0/include/nppi_support_functions.h" "$(@D)/cuda/include/nppi_support_functions.h" && cp "/usr/local/cuda-10.0/include/nppi_threshold_and_compare_operations.h" "$(@D)/cuda/include/nppi_threshold_and_compare_operations.h" && cp "/usr/local/cuda-10.0/include/npps.h" "$(@D)/cuda/include/npps.h" && cp "/usr/local/cuda-10.0/include/npps_arithmetic_and_logical_operations.h" "$(@D)/cuda/include/npps_arithmetic_and_logical_operations.h" && cp "/usr/local/cuda-10.0/include/npps_conversion_functions.h" "$(@D)/cuda/include/npps_conversion_functions.h" && cp "/usr/local/cuda-10.0/include/npps_filtering_functions.h" "$(@D)/cuda/include/npps_filtering_functions.h" && cp "/usr/local/cuda-10.0/include/npps_initialization.h" "$(@D)/cuda/include/npps_initialization.h" && cp "/usr/local/cuda-10.0/include/npps_statistics_functions.h" "$(@D)/cuda/include/npps_statistics_functions.h" && cp "/usr/local/cuda-10.0/include/npps_support_functions.h" "$(@D)/cuda/include/npps_support_functions.h" && cp "/usr/local/cuda-10.0/include/nppversion.h" "$(@D)/cuda/include/nppversion.h" && cp "/usr/local/cuda-10.0/include/nvToolsExt.h" "$(@D)/cuda/include/nvToolsExt.h" && cp "/usr/local/cuda-10.0/include/nvToolsExtCuda.h" "$(@D)/cuda/include/nvToolsExtCuda.h" && cp "/usr/local/cuda-10.0/include/nvToolsExtCudaRt.h" "$(@D)/cuda/include/nvToolsExtCudaRt.h" && cp "/usr/local/cuda-10.0/include/nvToolsExtMeta.h" "$(@D)/cuda/include/nvToolsExtMeta.h" && cp "/usr/local/cuda-10.0/include/nvToolsExtSync.h" "$(@D)/cuda/include/nvToolsExtSync.h" && cp "/usr/local/cuda-10.0/include/nvblas.h" "$(@D)/cuda/include/nvblas.h" && cp "/usr/local/cuda-10.0/include/nvfunctional" "$(@D)/cuda/include/nvfunctional" && cp "/usr/local/cuda-10.0/include/nvgraph.h" "$(@D)/cuda/include/nvgraph.h" && cp "/usr/local/cuda-10.0/include/nvml.h" "$(@D)/cuda/include/nvml.h" && cp "/usr/local/cuda-10.0/include/nvrtc.h" "$(@D)/cuda/include/nvrtc.h" && cp "/usr/local/cuda-10.0/include/sm_20_atomic_functions.h" "$(@D)/cuda/include/sm_20_atomic_functions.h" && cp "/usr/local/cuda-10.0/include/sm_20_atomic_functions.hpp" "$(@D)/cuda/include/sm_20_atomic_functions.hpp" && cp "/usr/local/cuda-10.0/include/sm_20_intrinsics.h" "$(@D)/cuda/include/sm_20_intrinsics.h" && cp "/usr/local/cuda-10.0/include/sm_20_intrinsics.hpp" "$(@D)/cuda/include/sm_20_intrinsics.hpp" && cp "/usr/local/cuda-10.0/include/sm_30_intrinsics.h" "$(@D)/cuda/include/sm_30_intrinsics.h" && cp "/usr/local/cuda-10.0/include/sm_30_intrinsics.hpp" "$(@D)/cuda/include/sm_30_intrinsics.hpp" && cp "/usr/local/cuda-10.0/include/sm_32_atomic_functions.h" "$(@D)/cuda/include/sm_32_atomic_functions.h" && cp "/usr/local/cuda-10.0/include/sm_32_atomic_functions.hpp" "$(@D)/cuda/include/sm_32_atomic_functions.hpp" && cp "/usr/local/cuda-10.0/include/sm_32_intrinsics.h" "$(@D)/cuda/include/sm_32_intrinsics.h" && cp "/usr/local/cuda-10.0/include/sm_32_intrinsics.hpp" "$(@D)/cuda/include/sm_32_intrinsics.hpp" && cp "/usr/local/cuda-10.0/include/sm_35_atomic_functions.h" "$(@D)/cuda/include/sm_35_atomic_functions.h" && cp "/usr/local/cuda-10.0/include/sm_35_intrinsics.h" "$(@D)/cuda/include/sm_35_intrinsics.h" && cp "/usr/local/cuda-10.0/include/sm_60_atomic_functions.h" "$(@D)/cuda/include/sm_60_atomic_functions.h" && cp "/usr/local/cuda-10.0/include/sm_60_atomic_functions.hpp" "$(@D)/cuda/include/sm_60_atomic_functions.hpp" && cp "/usr/local/cuda-10.0/include/sm_61_intrinsics.h" "$(@D)/cuda/include/sm_61_intrinsics.h" && cp "/usr/local/cuda-10.0/include/sm_61_intrinsics.hpp" "$(@D)/cuda/include/sm_61_intrinsics.hpp" && cp "/usr/local/cuda-10.0/include/sobol_direction_vectors.h" "$(@D)/cuda/include/sobol_direction_vectors.h" && cp "/usr/local/cuda-10.0/include/surface_functions.h" "$(@D)/cuda/include/surface_functions.h" && cp "/usr/local/cuda-10.0/include/surface_functions.hpp" "$(@D)/cuda/include/surface_functions.hpp" && cp "/usr/local/cuda-10.0/include/surface_indirect_functions.h" "$(@D)/cuda/include/surface_indirect_functions.h" && cp "/usr/local/cuda-10.0/include/surface_indirect_functions.hpp" "$(@D)/cuda/include/surface_indirect_functions.hpp" && cp "/usr/local/cuda-10.0/include/surface_types.h" "$(@D)/cuda/include/surface_types.h" && cp "/usr/local/cuda-10.0/include/texture_fetch_functions.h" "$(@D)/cuda/include/texture_fetch_functions.h" && cp "/usr/local/cuda-10.0/include/texture_fetch_functions.hpp" "$(@D)/cuda/include/texture_fetch_functions.hpp" && cp "/usr/local/cuda-10.0/include/texture_indirect_functions.h" "$(@D)/cuda/include/texture_indirect_functions.h" && cp "/usr/local/cuda-10.0/include/texture_indirect_functions.hpp" "$(@D)/cuda/include/texture_indirect_functions.hpp" && cp "/usr/local/cuda-10.0/include/texture_types.h" "$(@D)/cuda/include/texture_types.h" && cp "/usr/local/cuda-10.0/include/thrust/adjacent_difference.h" "$(@D)/cuda/include/thrust/adjacent_difference.h" && cp "/usr/local/cuda-10.0/include/thrust/advance.h" "$(@D)/cuda/include/thrust/advance.h" && cp "/usr/local/cuda-10.0/include/thrust/binary_search.h" "$(@D)/cuda/include/thrust/binary_search.h" && cp "/usr/local/cuda-10.0/include/thrust/complex.h" "$(@D)/cuda/include/thrust/complex.h" && cp "/usr/local/cuda-10.0/include/thrust/copy.h" "$(@D)/cuda/include/thrust/copy.h" && cp "/usr/local/cuda-10.0/include/thrust/count.h" "$(@D)/cuda/include/thrust/count.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/adjacent_difference.inl" "$(@D)/cuda/include/thrust/detail/adjacent_difference.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/advance.inl" "$(@D)/cuda/include/thrust/detail/advance.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/allocator/allocator_traits.h" "$(@D)/cuda/include/thrust/detail/allocator/allocator_traits.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/allocator/allocator_traits.inl" "$(@D)/cuda/include/thrust/detail/allocator/allocator_traits.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/allocator/copy_construct_range.h" "$(@D)/cuda/include/thrust/detail/allocator/copy_construct_range.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/allocator/copy_construct_range.inl" "$(@D)/cuda/include/thrust/detail/allocator/copy_construct_range.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/allocator/default_construct_range.h" "$(@D)/cuda/include/thrust/detail/allocator/default_construct_range.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/allocator/default_construct_range.inl" "$(@D)/cuda/include/thrust/detail/allocator/default_construct_range.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/allocator/destroy_range.h" "$(@D)/cuda/include/thrust/detail/allocator/destroy_range.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/allocator/destroy_range.inl" "$(@D)/cuda/include/thrust/detail/allocator/destroy_range.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/allocator/fill_construct_range.h" "$(@D)/cuda/include/thrust/detail/allocator/fill_construct_range.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/allocator/fill_construct_range.inl" "$(@D)/cuda/include/thrust/detail/allocator/fill_construct_range.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/allocator/malloc_allocator.h" "$(@D)/cuda/include/thrust/detail/allocator/malloc_allocator.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/allocator/malloc_allocator.inl" "$(@D)/cuda/include/thrust/detail/allocator/malloc_allocator.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/allocator/no_throw_allocator.h" "$(@D)/cuda/include/thrust/detail/allocator/no_throw_allocator.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/allocator/tagged_allocator.h" "$(@D)/cuda/include/thrust/detail/allocator/tagged_allocator.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/allocator/tagged_allocator.inl" "$(@D)/cuda/include/thrust/detail/allocator/tagged_allocator.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/allocator/temporary_allocator.h" "$(@D)/cuda/include/thrust/detail/allocator/temporary_allocator.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/allocator/temporary_allocator.inl" "$(@D)/cuda/include/thrust/detail/allocator/temporary_allocator.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/binary_search.inl" "$(@D)/cuda/include/thrust/detail/binary_search.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/complex/arithmetic.h" "$(@D)/cuda/include/thrust/detail/complex/arithmetic.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/complex/c99math.h" "$(@D)/cuda/include/thrust/detail/complex/c99math.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/complex/catrig.h" "$(@D)/cuda/include/thrust/detail/complex/catrig.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/complex/catrigf.h" "$(@D)/cuda/include/thrust/detail/complex/catrigf.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/complex/ccosh.h" "$(@D)/cuda/include/thrust/detail/complex/ccosh.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/complex/ccoshf.h" "$(@D)/cuda/include/thrust/detail/complex/ccoshf.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/complex/cexp.h" "$(@D)/cuda/include/thrust/detail/complex/cexp.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/complex/cexpf.h" "$(@D)/cuda/include/thrust/detail/complex/cexpf.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/complex/clog.h" "$(@D)/cuda/include/thrust/detail/complex/clog.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/complex/clogf.h" "$(@D)/cuda/include/thrust/detail/complex/clogf.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/complex/complex.inl" "$(@D)/cuda/include/thrust/detail/complex/complex.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/complex/cpow.h" "$(@D)/cuda/include/thrust/detail/complex/cpow.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/complex/cpowf.h" "$(@D)/cuda/include/thrust/detail/complex/cpowf.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/complex/cproj.h" "$(@D)/cuda/include/thrust/detail/complex/cproj.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/complex/csinh.h" "$(@D)/cuda/include/thrust/detail/complex/csinh.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/complex/csinhf.h" "$(@D)/cuda/include/thrust/detail/complex/csinhf.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/complex/csqrt.h" "$(@D)/cuda/include/thrust/detail/complex/csqrt.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/complex/csqrtf.h" "$(@D)/cuda/include/thrust/detail/complex/csqrtf.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/complex/ctanh.h" "$(@D)/cuda/include/thrust/detail/complex/ctanh.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/complex/ctanhf.h" "$(@D)/cuda/include/thrust/detail/complex/ctanhf.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/complex/math_private.h" "$(@D)/cuda/include/thrust/detail/complex/math_private.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/complex/stream.h" "$(@D)/cuda/include/thrust/detail/complex/stream.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/config.h" "$(@D)/cuda/include/thrust/detail/config.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/config/compiler.h" "$(@D)/cuda/include/thrust/detail/config/compiler.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/config/compiler_fence.h" "$(@D)/cuda/include/thrust/detail/config/compiler_fence.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/config/config.h" "$(@D)/cuda/include/thrust/detail/config/config.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/config/debug.h" "$(@D)/cuda/include/thrust/detail/config/debug.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/config/device_system.h" "$(@D)/cuda/include/thrust/detail/config/device_system.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/config/exec_check_disable.h" "$(@D)/cuda/include/thrust/detail/config/exec_check_disable.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/config/forceinline.h" "$(@D)/cuda/include/thrust/detail/config/forceinline.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/config/global_workarounds.h" "$(@D)/cuda/include/thrust/detail/config/global_workarounds.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/config/host_device.h" "$(@D)/cuda/include/thrust/detail/config/host_device.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/config/host_system.h" "$(@D)/cuda/include/thrust/detail/config/host_system.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/config/simple_defines.h" "$(@D)/cuda/include/thrust/detail/config/simple_defines.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/contiguous_storage.h" "$(@D)/cuda/include/thrust/detail/contiguous_storage.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/contiguous_storage.inl" "$(@D)/cuda/include/thrust/detail/contiguous_storage.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/copy.h" "$(@D)/cuda/include/thrust/detail/copy.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/copy.inl" "$(@D)/cuda/include/thrust/detail/copy.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/copy_if.h" "$(@D)/cuda/include/thrust/detail/copy_if.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/copy_if.inl" "$(@D)/cuda/include/thrust/detail/copy_if.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/count.inl" "$(@D)/cuda/include/thrust/detail/count.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/cstdint.h" "$(@D)/cuda/include/thrust/detail/cstdint.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/device_delete.inl" "$(@D)/cuda/include/thrust/detail/device_delete.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/device_free.inl" "$(@D)/cuda/include/thrust/detail/device_free.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/device_malloc.inl" "$(@D)/cuda/include/thrust/detail/device_malloc.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/device_new.inl" "$(@D)/cuda/include/thrust/detail/device_new.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/device_ptr.inl" "$(@D)/cuda/include/thrust/detail/device_ptr.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/device_reference.inl" "$(@D)/cuda/include/thrust/detail/device_reference.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/device_vector.inl" "$(@D)/cuda/include/thrust/detail/device_vector.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/dispatch/is_trivial_copy.h" "$(@D)/cuda/include/thrust/detail/dispatch/is_trivial_copy.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/distance.inl" "$(@D)/cuda/include/thrust/detail/distance.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/equal.inl" "$(@D)/cuda/include/thrust/detail/equal.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/execute_with_allocator.h" "$(@D)/cuda/include/thrust/detail/execute_with_allocator.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/execution_policy.h" "$(@D)/cuda/include/thrust/detail/execution_policy.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/extrema.inl" "$(@D)/cuda/include/thrust/detail/extrema.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/fill.inl" "$(@D)/cuda/include/thrust/detail/fill.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/find.inl" "$(@D)/cuda/include/thrust/detail/find.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/for_each.inl" "$(@D)/cuda/include/thrust/detail/for_each.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/function.h" "$(@D)/cuda/include/thrust/detail/function.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/functional.inl" "$(@D)/cuda/include/thrust/detail/functional.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/functional/actor.h" "$(@D)/cuda/include/thrust/detail/functional/actor.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/functional/actor.inl" "$(@D)/cuda/include/thrust/detail/functional/actor.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/functional/argument.h" "$(@D)/cuda/include/thrust/detail/functional/argument.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/functional/composite.h" "$(@D)/cuda/include/thrust/detail/functional/composite.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/functional/operators.h" "$(@D)/cuda/include/thrust/detail/functional/operators.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/functional/operators/arithmetic_operators.h" "$(@D)/cuda/include/thrust/detail/functional/operators/arithmetic_operators.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/functional/operators/assignment_operator.h" "$(@D)/cuda/include/thrust/detail/functional/operators/assignment_operator.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/functional/operators/bitwise_operators.h" "$(@D)/cuda/include/thrust/detail/functional/operators/bitwise_operators.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/functional/operators/compound_assignment_operators.h" "$(@D)/cuda/include/thrust/detail/functional/operators/compound_assignment_operators.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/functional/operators/logical_operators.h" "$(@D)/cuda/include/thrust/detail/functional/operators/logical_operators.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/functional/operators/operator_adaptors.h" "$(@D)/cuda/include/thrust/detail/functional/operators/operator_adaptors.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/functional/operators/relational_operators.h" "$(@D)/cuda/include/thrust/detail/functional/operators/relational_operators.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/functional/placeholder.h" "$(@D)/cuda/include/thrust/detail/functional/placeholder.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/functional/value.h" "$(@D)/cuda/include/thrust/detail/functional/value.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/gather.inl" "$(@D)/cuda/include/thrust/detail/gather.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/generate.inl" "$(@D)/cuda/include/thrust/detail/generate.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/get_iterator_value.h" "$(@D)/cuda/include/thrust/detail/get_iterator_value.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/host_vector.inl" "$(@D)/cuda/include/thrust/detail/host_vector.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/inner_product.inl" "$(@D)/cuda/include/thrust/detail/inner_product.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/integer_math.h" "$(@D)/cuda/include/thrust/detail/integer_math.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/integer_traits.h" "$(@D)/cuda/include/thrust/detail/integer_traits.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/internal_functional.h" "$(@D)/cuda/include/thrust/detail/internal_functional.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/logical.inl" "$(@D)/cuda/include/thrust/detail/logical.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/malloc_and_free.h" "$(@D)/cuda/include/thrust/detail/malloc_and_free.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/merge.inl" "$(@D)/cuda/include/thrust/detail/merge.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/minmax.h" "$(@D)/cuda/include/thrust/detail/minmax.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/mismatch.inl" "$(@D)/cuda/include/thrust/detail/mismatch.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/mpl/math.h" "$(@D)/cuda/include/thrust/detail/mpl/math.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/numeric_traits.h" "$(@D)/cuda/include/thrust/detail/numeric_traits.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/overlapped_copy.h" "$(@D)/cuda/include/thrust/detail/overlapped_copy.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/pair.inl" "$(@D)/cuda/include/thrust/detail/pair.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/partition.inl" "$(@D)/cuda/include/thrust/detail/partition.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/pointer.h" "$(@D)/cuda/include/thrust/detail/pointer.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/pointer.inl" "$(@D)/cuda/include/thrust/detail/pointer.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/range/head_flags.h" "$(@D)/cuda/include/thrust/detail/range/head_flags.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/range/tail_flags.h" "$(@D)/cuda/include/thrust/detail/range/tail_flags.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/raw_pointer_cast.h" "$(@D)/cuda/include/thrust/detail/raw_pointer_cast.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/raw_reference_cast.h" "$(@D)/cuda/include/thrust/detail/raw_reference_cast.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/reduce.inl" "$(@D)/cuda/include/thrust/detail/reduce.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/reference.h" "$(@D)/cuda/include/thrust/detail/reference.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/reference.inl" "$(@D)/cuda/include/thrust/detail/reference.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/reference_forward_declaration.h" "$(@D)/cuda/include/thrust/detail/reference_forward_declaration.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/remove.inl" "$(@D)/cuda/include/thrust/detail/remove.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/replace.inl" "$(@D)/cuda/include/thrust/detail/replace.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/reverse.inl" "$(@D)/cuda/include/thrust/detail/reverse.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/scan.inl" "$(@D)/cuda/include/thrust/detail/scan.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/scatter.inl" "$(@D)/cuda/include/thrust/detail/scatter.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/seq.h" "$(@D)/cuda/include/thrust/detail/seq.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/sequence.inl" "$(@D)/cuda/include/thrust/detail/sequence.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/set_operations.inl" "$(@D)/cuda/include/thrust/detail/set_operations.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/sort.inl" "$(@D)/cuda/include/thrust/detail/sort.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/static_assert.h" "$(@D)/cuda/include/thrust/detail/static_assert.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/static_map.h" "$(@D)/cuda/include/thrust/detail/static_map.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/swap.h" "$(@D)/cuda/include/thrust/detail/swap.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/swap.inl" "$(@D)/cuda/include/thrust/detail/swap.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/swap_ranges.inl" "$(@D)/cuda/include/thrust/detail/swap_ranges.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/tabulate.inl" "$(@D)/cuda/include/thrust/detail/tabulate.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/temporary_array.h" "$(@D)/cuda/include/thrust/detail/temporary_array.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/temporary_array.inl" "$(@D)/cuda/include/thrust/detail/temporary_array.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/temporary_buffer.h" "$(@D)/cuda/include/thrust/detail/temporary_buffer.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/transform.inl" "$(@D)/cuda/include/thrust/detail/transform.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/transform_reduce.inl" "$(@D)/cuda/include/thrust/detail/transform_reduce.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/transform_scan.inl" "$(@D)/cuda/include/thrust/detail/transform_scan.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/trivial_sequence.h" "$(@D)/cuda/include/thrust/detail/trivial_sequence.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/tuple.inl" "$(@D)/cuda/include/thrust/detail/tuple.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/tuple_meta_transform.h" "$(@D)/cuda/include/thrust/detail/tuple_meta_transform.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/tuple_transform.h" "$(@D)/cuda/include/thrust/detail/tuple_transform.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/type_traits.h" "$(@D)/cuda/include/thrust/detail/type_traits.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/type_traits/algorithm/intermediate_type_from_function_and_iterators.h" "$(@D)/cuda/include/thrust/detail/type_traits/algorithm/intermediate_type_from_function_and_iterators.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/type_traits/function_traits.h" "$(@D)/cuda/include/thrust/detail/type_traits/function_traits.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/type_traits/has_member_function.h" "$(@D)/cuda/include/thrust/detail/type_traits/has_member_function.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/type_traits/has_nested_type.h" "$(@D)/cuda/include/thrust/detail/type_traits/has_nested_type.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/type_traits/has_trivial_assign.h" "$(@D)/cuda/include/thrust/detail/type_traits/has_trivial_assign.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/type_traits/is_call_possible.h" "$(@D)/cuda/include/thrust/detail/type_traits/is_call_possible.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/type_traits/is_metafunction_defined.h" "$(@D)/cuda/include/thrust/detail/type_traits/is_metafunction_defined.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/type_traits/iterator/is_discard_iterator.h" "$(@D)/cuda/include/thrust/detail/type_traits/iterator/is_discard_iterator.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/type_traits/iterator/is_output_iterator.h" "$(@D)/cuda/include/thrust/detail/type_traits/iterator/is_output_iterator.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/type_traits/minimum_type.h" "$(@D)/cuda/include/thrust/detail/type_traits/minimum_type.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/type_traits/pointer_traits.h" "$(@D)/cuda/include/thrust/detail/type_traits/pointer_traits.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/type_traits/result_of_adaptable_function.h" "$(@D)/cuda/include/thrust/detail/type_traits/result_of_adaptable_function.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/uninitialized_copy.inl" "$(@D)/cuda/include/thrust/detail/uninitialized_copy.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/uninitialized_fill.inl" "$(@D)/cuda/include/thrust/detail/uninitialized_fill.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/unique.inl" "$(@D)/cuda/include/thrust/detail/unique.inl" && cp "/usr/local/cuda-10.0/include/thrust/detail/use_default.h" "$(@D)/cuda/include/thrust/detail/use_default.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/util/align.h" "$(@D)/cuda/include/thrust/detail/util/align.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/util/blocking.h" "$(@D)/cuda/include/thrust/detail/util/blocking.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/vector_base.h" "$(@D)/cuda/include/thrust/detail/vector_base.h" && cp "/usr/local/cuda-10.0/include/thrust/detail/vector_base.inl" "$(@D)/cuda/include/thrust/detail/vector_base.inl" && cp "/usr/local/cuda-10.0/include/thrust/device_allocator.h" "$(@D)/cuda/include/thrust/device_allocator.h" && cp "/usr/local/cuda-10.0/include/thrust/device_delete.h" "$(@D)/cuda/include/thrust/device_delete.h" && cp "/usr/local/cuda-10.0/include/thrust/device_free.h" "$(@D)/cuda/include/thrust/device_free.h" && cp "/usr/local/cuda-10.0/include/thrust/device_malloc.h" "$(@D)/cuda/include/thrust/device_malloc.h" && cp "/usr/local/cuda-10.0/include/thrust/device_malloc_allocator.h" "$(@D)/cuda/include/thrust/device_malloc_allocator.h" && cp "/usr/local/cuda-10.0/include/thrust/device_new.h" "$(@D)/cuda/include/thrust/device_new.h" && cp "/usr/local/cuda-10.0/include/thrust/device_new_allocator.h" "$(@D)/cuda/include/thrust/device_new_allocator.h" && cp "/usr/local/cuda-10.0/include/thrust/device_ptr.h" "$(@D)/cuda/include/thrust/device_ptr.h" && cp "/usr/local/cuda-10.0/include/thrust/device_reference.h" "$(@D)/cuda/include/thrust/device_reference.h" && cp "/usr/local/cuda-10.0/include/thrust/device_vector.h" "$(@D)/cuda/include/thrust/device_vector.h" && cp "/usr/local/cuda-10.0/include/thrust/distance.h" "$(@D)/cuda/include/thrust/distance.h" && cp "/usr/local/cuda-10.0/include/thrust/equal.h" "$(@D)/cuda/include/thrust/equal.h" && cp "/usr/local/cuda-10.0/include/thrust/execution_policy.h" "$(@D)/cuda/include/thrust/execution_policy.h" && cp "/usr/local/cuda-10.0/include/thrust/extrema.h" "$(@D)/cuda/include/thrust/extrema.h" && cp "/usr/local/cuda-10.0/include/thrust/fill.h" "$(@D)/cuda/include/thrust/fill.h" && cp "/usr/local/cuda-10.0/include/thrust/find.h" "$(@D)/cuda/include/thrust/find.h" && cp "/usr/local/cuda-10.0/include/thrust/for_each.h" "$(@D)/cuda/include/thrust/for_each.h" && cp "/usr/local/cuda-10.0/include/thrust/functional.h" "$(@D)/cuda/include/thrust/functional.h" && cp "/usr/local/cuda-10.0/include/thrust/gather.h" "$(@D)/cuda/include/thrust/gather.h" && cp "/usr/local/cuda-10.0/include/thrust/generate.h" "$(@D)/cuda/include/thrust/generate.h" && cp "/usr/local/cuda-10.0/include/thrust/host_vector.h" "$(@D)/cuda/include/thrust/host_vector.h" && cp "/usr/local/cuda-10.0/include/thrust/inner_product.h" "$(@D)/cuda/include/thrust/inner_product.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/constant_iterator.h" "$(@D)/cuda/include/thrust/iterator/constant_iterator.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/counting_iterator.h" "$(@D)/cuda/include/thrust/iterator/counting_iterator.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/detail/any_assign.h" "$(@D)/cuda/include/thrust/iterator/detail/any_assign.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/detail/any_system_tag.h" "$(@D)/cuda/include/thrust/iterator/detail/any_system_tag.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/detail/constant_iterator_base.h" "$(@D)/cuda/include/thrust/iterator/detail/constant_iterator_base.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/detail/counting_iterator.inl" "$(@D)/cuda/include/thrust/iterator/detail/counting_iterator.inl" && cp "/usr/local/cuda-10.0/include/thrust/iterator/detail/device_system_tag.h" "$(@D)/cuda/include/thrust/iterator/detail/device_system_tag.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/detail/discard_iterator_base.h" "$(@D)/cuda/include/thrust/iterator/detail/discard_iterator_base.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/detail/distance_from_result.h" "$(@D)/cuda/include/thrust/iterator/detail/distance_from_result.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/detail/host_system_tag.h" "$(@D)/cuda/include/thrust/iterator/detail/host_system_tag.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/detail/is_iterator_category.h" "$(@D)/cuda/include/thrust/iterator/detail/is_iterator_category.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/detail/is_trivial_iterator.h" "$(@D)/cuda/include/thrust/iterator/detail/is_trivial_iterator.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/detail/iterator_adaptor_base.h" "$(@D)/cuda/include/thrust/iterator/detail/iterator_adaptor_base.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/detail/iterator_category_to_system.h" "$(@D)/cuda/include/thrust/iterator/detail/iterator_category_to_system.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/detail/iterator_category_to_traversal.h" "$(@D)/cuda/include/thrust/iterator/detail/iterator_category_to_traversal.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/detail/iterator_category_with_system_and_traversal.h" "$(@D)/cuda/include/thrust/iterator/detail/iterator_category_with_system_and_traversal.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/detail/iterator_facade_category.h" "$(@D)/cuda/include/thrust/iterator/detail/iterator_facade_category.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/detail/iterator_traits.inl" "$(@D)/cuda/include/thrust/iterator/detail/iterator_traits.inl" && cp "/usr/local/cuda-10.0/include/thrust/iterator/detail/iterator_traversal_tags.h" "$(@D)/cuda/include/thrust/iterator/detail/iterator_traversal_tags.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/detail/join_iterator.h" "$(@D)/cuda/include/thrust/iterator/detail/join_iterator.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/detail/minimum_category.h" "$(@D)/cuda/include/thrust/iterator/detail/minimum_category.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/detail/minimum_system.h" "$(@D)/cuda/include/thrust/iterator/detail/minimum_system.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/detail/normal_iterator.h" "$(@D)/cuda/include/thrust/iterator/detail/normal_iterator.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/detail/permutation_iterator_base.h" "$(@D)/cuda/include/thrust/iterator/detail/permutation_iterator_base.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/detail/retag.h" "$(@D)/cuda/include/thrust/iterator/detail/retag.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/detail/reverse_iterator.inl" "$(@D)/cuda/include/thrust/iterator/detail/reverse_iterator.inl" && cp "/usr/local/cuda-10.0/include/thrust/iterator/detail/reverse_iterator_base.h" "$(@D)/cuda/include/thrust/iterator/detail/reverse_iterator_base.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/detail/tagged_iterator.h" "$(@D)/cuda/include/thrust/iterator/detail/tagged_iterator.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/detail/transform_iterator.inl" "$(@D)/cuda/include/thrust/iterator/detail/transform_iterator.inl" && cp "/usr/local/cuda-10.0/include/thrust/iterator/detail/transform_output_iterator.inl" "$(@D)/cuda/include/thrust/iterator/detail/transform_output_iterator.inl" && cp "/usr/local/cuda-10.0/include/thrust/iterator/detail/tuple_of_iterator_references.h" "$(@D)/cuda/include/thrust/iterator/detail/tuple_of_iterator_references.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/detail/universal_categories.h" "$(@D)/cuda/include/thrust/iterator/detail/universal_categories.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/detail/zip_iterator.inl" "$(@D)/cuda/include/thrust/iterator/detail/zip_iterator.inl" && cp "/usr/local/cuda-10.0/include/thrust/iterator/detail/zip_iterator_base.h" "$(@D)/cuda/include/thrust/iterator/detail/zip_iterator_base.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/discard_iterator.h" "$(@D)/cuda/include/thrust/iterator/discard_iterator.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/iterator_adaptor.h" "$(@D)/cuda/include/thrust/iterator/iterator_adaptor.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/iterator_categories.h" "$(@D)/cuda/include/thrust/iterator/iterator_categories.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/iterator_facade.h" "$(@D)/cuda/include/thrust/iterator/iterator_facade.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/iterator_traits.h" "$(@D)/cuda/include/thrust/iterator/iterator_traits.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/permutation_iterator.h" "$(@D)/cuda/include/thrust/iterator/permutation_iterator.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/retag.h" "$(@D)/cuda/include/thrust/iterator/retag.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/reverse_iterator.h" "$(@D)/cuda/include/thrust/iterator/reverse_iterator.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/transform_iterator.h" "$(@D)/cuda/include/thrust/iterator/transform_iterator.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/transform_output_iterator.h" "$(@D)/cuda/include/thrust/iterator/transform_output_iterator.h" && cp "/usr/local/cuda-10.0/include/thrust/iterator/zip_iterator.h" "$(@D)/cuda/include/thrust/iterator/zip_iterator.h" && cp "/usr/local/cuda-10.0/include/thrust/logical.h" "$(@D)/cuda/include/thrust/logical.h" && cp "/usr/local/cuda-10.0/include/thrust/memory.h" "$(@D)/cuda/include/thrust/memory.h" && cp "/usr/local/cuda-10.0/include/thrust/merge.h" "$(@D)/cuda/include/thrust/merge.h" && cp "/usr/local/cuda-10.0/include/thrust/mismatch.h" "$(@D)/cuda/include/thrust/mismatch.h" && cp "/usr/local/cuda-10.0/include/thrust/pair.h" "$(@D)/cuda/include/thrust/pair.h" && cp "/usr/local/cuda-10.0/include/thrust/partition.h" "$(@D)/cuda/include/thrust/partition.h" && cp "/usr/local/cuda-10.0/include/thrust/random.h" "$(@D)/cuda/include/thrust/random.h" && cp "/usr/local/cuda-10.0/include/thrust/random/detail/discard_block_engine.inl" "$(@D)/cuda/include/thrust/random/detail/discard_block_engine.inl" && cp "/usr/local/cuda-10.0/include/thrust/random/detail/linear_congruential_engine.inl" "$(@D)/cuda/include/thrust/random/detail/linear_congruential_engine.inl" && cp "/usr/local/cuda-10.0/include/thrust/random/detail/linear_congruential_engine_discard.h" "$(@D)/cuda/include/thrust/random/detail/linear_congruential_engine_discard.h" && cp "/usr/local/cuda-10.0/include/thrust/random/detail/linear_feedback_shift_engine.inl" "$(@D)/cuda/include/thrust/random/detail/linear_feedback_shift_engine.inl" && cp "/usr/local/cuda-10.0/include/thrust/random/detail/linear_feedback_shift_engine_wordmask.h" "$(@D)/cuda/include/thrust/random/detail/linear_feedback_shift_engine_wordmask.h" && cp "/usr/local/cuda-10.0/include/thrust/random/detail/mod.h" "$(@D)/cuda/include/thrust/random/detail/mod.h" && cp "/usr/local/cuda-10.0/include/thrust/random/detail/normal_distribution.inl" "$(@D)/cuda/include/thrust/random/detail/normal_distribution.inl" && cp "/usr/local/cuda-10.0/include/thrust/random/detail/normal_distribution_base.h" "$(@D)/cuda/include/thrust/random/detail/normal_distribution_base.h" && cp "/usr/local/cuda-10.0/include/thrust/random/detail/random_core_access.h" "$(@D)/cuda/include/thrust/random/detail/random_core_access.h" && cp "/usr/local/cuda-10.0/include/thrust/random/detail/subtract_with_carry_engine.inl" "$(@D)/cuda/include/thrust/random/detail/subtract_with_carry_engine.inl" && cp "/usr/local/cuda-10.0/include/thrust/random/detail/uniform_int_distribution.inl" "$(@D)/cuda/include/thrust/random/detail/uniform_int_distribution.inl" && cp "/usr/local/cuda-10.0/include/thrust/random/detail/uniform_real_distribution.inl" "$(@D)/cuda/include/thrust/random/detail/uniform_real_distribution.inl" && cp "/usr/local/cuda-10.0/include/thrust/random/detail/xor_combine_engine.inl" "$(@D)/cuda/include/thrust/random/detail/xor_combine_engine.inl" && cp "/usr/local/cuda-10.0/include/thrust/random/detail/xor_combine_engine_max.h" "$(@D)/cuda/include/thrust/random/detail/xor_combine_engine_max.h" && cp "/usr/local/cuda-10.0/include/thrust/random/discard_block_engine.h" "$(@D)/cuda/include/thrust/random/discard_block_engine.h" && cp "/usr/local/cuda-10.0/include/thrust/random/linear_congruential_engine.h" "$(@D)/cuda/include/thrust/random/linear_congruential_engine.h" && cp "/usr/local/cuda-10.0/include/thrust/random/linear_feedback_shift_engine.h" "$(@D)/cuda/include/thrust/random/linear_feedback_shift_engine.h" && cp "/usr/local/cuda-10.0/include/thrust/random/normal_distribution.h" "$(@D)/cuda/include/thrust/random/normal_distribution.h" && cp "/usr/local/cuda-10.0/include/thrust/random/subtract_with_carry_engine.h" "$(@D)/cuda/include/thrust/random/subtract_with_carry_engine.h" && cp "/usr/local/cuda-10.0/include/thrust/random/uniform_int_distribution.h" "$(@D)/cuda/include/thrust/random/uniform_int_distribution.h" && cp "/usr/local/cuda-10.0/include/thrust/random/uniform_real_distribution.h" "$(@D)/cuda/include/thrust/random/uniform_real_distribution.h" && cp "/usr/local/cuda-10.0/include/thrust/random/xor_combine_engine.h" "$(@D)/cuda/include/thrust/random/xor_combine_engine.h" && cp "/usr/local/cuda-10.0/include/thrust/reduce.h" "$(@D)/cuda/include/thrust/reduce.h" && cp "/usr/local/cuda-10.0/include/thrust/remove.h" "$(@D)/cuda/include/thrust/remove.h" && cp "/usr/local/cuda-10.0/include/thrust/replace.h" "$(@D)/cuda/include/thrust/replace.h" && cp "/usr/local/cuda-10.0/include/thrust/reverse.h" "$(@D)/cuda/include/thrust/reverse.h" && cp "/usr/local/cuda-10.0/include/thrust/scan.h" "$(@D)/cuda/include/thrust/scan.h" && cp "/usr/local/cuda-10.0/include/thrust/scatter.h" "$(@D)/cuda/include/thrust/scatter.h" && cp "/usr/local/cuda-10.0/include/thrust/sequence.h" "$(@D)/cuda/include/thrust/sequence.h" && cp "/usr/local/cuda-10.0/include/thrust/set_operations.h" "$(@D)/cuda/include/thrust/set_operations.h" && cp "/usr/local/cuda-10.0/include/thrust/sort.h" "$(@D)/cuda/include/thrust/sort.h" && cp "/usr/local/cuda-10.0/include/thrust/swap.h" "$(@D)/cuda/include/thrust/swap.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/adjacent_difference.h" "$(@D)/cuda/include/thrust/system/cpp/detail/adjacent_difference.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/assign_value.h" "$(@D)/cuda/include/thrust/system/cpp/detail/assign_value.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/binary_search.h" "$(@D)/cuda/include/thrust/system/cpp/detail/binary_search.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/copy.h" "$(@D)/cuda/include/thrust/system/cpp/detail/copy.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/copy_if.h" "$(@D)/cuda/include/thrust/system/cpp/detail/copy_if.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/count.h" "$(@D)/cuda/include/thrust/system/cpp/detail/count.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/equal.h" "$(@D)/cuda/include/thrust/system/cpp/detail/equal.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/execution_policy.h" "$(@D)/cuda/include/thrust/system/cpp/detail/execution_policy.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/extrema.h" "$(@D)/cuda/include/thrust/system/cpp/detail/extrema.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/fill.h" "$(@D)/cuda/include/thrust/system/cpp/detail/fill.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/find.h" "$(@D)/cuda/include/thrust/system/cpp/detail/find.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/for_each.h" "$(@D)/cuda/include/thrust/system/cpp/detail/for_each.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/gather.h" "$(@D)/cuda/include/thrust/system/cpp/detail/gather.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/generate.h" "$(@D)/cuda/include/thrust/system/cpp/detail/generate.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/get_value.h" "$(@D)/cuda/include/thrust/system/cpp/detail/get_value.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/inner_product.h" "$(@D)/cuda/include/thrust/system/cpp/detail/inner_product.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/iter_swap.h" "$(@D)/cuda/include/thrust/system/cpp/detail/iter_swap.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/logical.h" "$(@D)/cuda/include/thrust/system/cpp/detail/logical.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/malloc_and_free.h" "$(@D)/cuda/include/thrust/system/cpp/detail/malloc_and_free.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/memory.inl" "$(@D)/cuda/include/thrust/system/cpp/detail/memory.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/merge.h" "$(@D)/cuda/include/thrust/system/cpp/detail/merge.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/mismatch.h" "$(@D)/cuda/include/thrust/system/cpp/detail/mismatch.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/par.h" "$(@D)/cuda/include/thrust/system/cpp/detail/par.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/partition.h" "$(@D)/cuda/include/thrust/system/cpp/detail/partition.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/reduce.h" "$(@D)/cuda/include/thrust/system/cpp/detail/reduce.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/reduce_by_key.h" "$(@D)/cuda/include/thrust/system/cpp/detail/reduce_by_key.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/remove.h" "$(@D)/cuda/include/thrust/system/cpp/detail/remove.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/replace.h" "$(@D)/cuda/include/thrust/system/cpp/detail/replace.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/reverse.h" "$(@D)/cuda/include/thrust/system/cpp/detail/reverse.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/scan.h" "$(@D)/cuda/include/thrust/system/cpp/detail/scan.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/scan_by_key.h" "$(@D)/cuda/include/thrust/system/cpp/detail/scan_by_key.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/scatter.h" "$(@D)/cuda/include/thrust/system/cpp/detail/scatter.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/sequence.h" "$(@D)/cuda/include/thrust/system/cpp/detail/sequence.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/set_operations.h" "$(@D)/cuda/include/thrust/system/cpp/detail/set_operations.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/sort.h" "$(@D)/cuda/include/thrust/system/cpp/detail/sort.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/swap_ranges.h" "$(@D)/cuda/include/thrust/system/cpp/detail/swap_ranges.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/tabulate.h" "$(@D)/cuda/include/thrust/system/cpp/detail/tabulate.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/temporary_buffer.h" "$(@D)/cuda/include/thrust/system/cpp/detail/temporary_buffer.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/transform.h" "$(@D)/cuda/include/thrust/system/cpp/detail/transform.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/transform_reduce.h" "$(@D)/cuda/include/thrust/system/cpp/detail/transform_reduce.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/transform_scan.h" "$(@D)/cuda/include/thrust/system/cpp/detail/transform_scan.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/uninitialized_copy.h" "$(@D)/cuda/include/thrust/system/cpp/detail/uninitialized_copy.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/uninitialized_fill.h" "$(@D)/cuda/include/thrust/system/cpp/detail/uninitialized_fill.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/unique.h" "$(@D)/cuda/include/thrust/system/cpp/detail/unique.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/unique_by_key.h" "$(@D)/cuda/include/thrust/system/cpp/detail/unique_by_key.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/detail/vector.inl" "$(@D)/cuda/include/thrust/system/cpp/detail/vector.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/execution_policy.h" "$(@D)/cuda/include/thrust/system/cpp/execution_policy.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/memory.h" "$(@D)/cuda/include/thrust/system/cpp/memory.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cpp/vector.h" "$(@D)/cuda/include/thrust/system/cpp/vector.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/config.h" "$(@D)/cuda/include/thrust/system/cuda/config.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/adjacent_difference.h" "$(@D)/cuda/include/thrust/system/cuda/detail/adjacent_difference.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/assign_value.h" "$(@D)/cuda/include/thrust/system/cuda/detail/assign_value.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/binary_search.h" "$(@D)/cuda/include/thrust/system/cuda/detail/binary_search.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/copy.h" "$(@D)/cuda/include/thrust/system/cuda/detail/copy.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/copy_if.h" "$(@D)/cuda/include/thrust/system/cuda/detail/copy_if.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/core/agent_launcher.h" "$(@D)/cuda/include/thrust/system/cuda/detail/core/agent_launcher.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/core/alignment.h" "$(@D)/cuda/include/thrust/system/cuda/detail/core/alignment.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/core/triple_chevron_launch.h" "$(@D)/cuda/include/thrust/system/cuda/detail/core/triple_chevron_launch.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/core/util.h" "$(@D)/cuda/include/thrust/system/cuda/detail/core/util.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/count.h" "$(@D)/cuda/include/thrust/system/cuda/detail/count.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cross_system.h" "$(@D)/cuda/include/thrust/system/cuda/detail/cross_system.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/agent/agent_histogram.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/agent/agent_histogram.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/agent/agent_radix_sort_downsweep.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/agent/agent_radix_sort_downsweep.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/agent/agent_radix_sort_upsweep.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/agent/agent_radix_sort_upsweep.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/agent/agent_reduce.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/agent/agent_reduce.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/agent/agent_reduce_by_key.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/agent/agent_reduce_by_key.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/agent/agent_rle.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/agent/agent_rle.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/agent/agent_scan.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/agent/agent_scan.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/agent/agent_segment_fixup.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/agent/agent_segment_fixup.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/agent/agent_select_if.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/agent/agent_select_if.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/agent/agent_spmv_csrt.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/agent/agent_spmv_csrt.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/agent/agent_spmv_orig.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/agent/agent_spmv_orig.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/agent/agent_spmv_row_based.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/agent/agent_spmv_row_based.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/agent/single_pass_scan_operators.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/agent/single_pass_scan_operators.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/block/block_adjacent_difference.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/block/block_adjacent_difference.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/block/block_discontinuity.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/block/block_discontinuity.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/block/block_exchange.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/block/block_exchange.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/block/block_histogram.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/block/block_histogram.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/block/block_load.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/block/block_load.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/block/block_radix_rank.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/block/block_radix_rank.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/block/block_radix_sort.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/block/block_radix_sort.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/block/block_raking_layout.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/block/block_raking_layout.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/block/block_reduce.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/block/block_reduce.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/block/block_scan.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/block/block_scan.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/block/block_shuffle.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/block/block_shuffle.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/block/block_store.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/block/block_store.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/block/specializations/block_histogram_atomic.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/block/specializations/block_histogram_atomic.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/block/specializations/block_histogram_sort.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/block/specializations/block_histogram_sort.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/block/specializations/block_reduce_raking.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/block/specializations/block_reduce_raking.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/block/specializations/block_reduce_raking_commutative_only.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/block/specializations/block_reduce_raking_commutative_only.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/block/specializations/block_reduce_warp_reductions.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/block/specializations/block_reduce_warp_reductions.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/block/specializations/block_scan_raking.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/block/specializations/block_scan_raking.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/block/specializations/block_scan_warp_scans.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/block/specializations/block_scan_warp_scans.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/block/specializations/block_scan_warp_scans2.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/block/specializations/block_scan_warp_scans2.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/block/specializations/block_scan_warp_scans3.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/block/specializations/block_scan_warp_scans3.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/cub.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/cub.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/device/device_histogram.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/device/device_histogram.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/device/device_partition.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/device/device_partition.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/device/device_radix_sort.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/device/device_radix_sort.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/device/device_reduce.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/device/device_reduce.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/device/device_run_length_encode.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/device/device_run_length_encode.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/device/device_scan.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/device/device_scan.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/device/device_segmented_radix_sort.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/device/device_segmented_radix_sort.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/device/device_segmented_reduce.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/device/device_segmented_reduce.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/device/device_select.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/device/device_select.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/device/device_spmv.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/device/device_spmv.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/device/dispatch/dispatch_histogram.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/device/dispatch/dispatch_histogram.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/device/dispatch/dispatch_radix_sort.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/device/dispatch/dispatch_radix_sort.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/device/dispatch/dispatch_reduce.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/device/dispatch/dispatch_reduce.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/device/dispatch/dispatch_reduce_by_key.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/device/dispatch/dispatch_reduce_by_key.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/device/dispatch/dispatch_rle.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/device/dispatch/dispatch_rle.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/device/dispatch/dispatch_scan.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/device/dispatch/dispatch_scan.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/device/dispatch/dispatch_select_if.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/device/dispatch/dispatch_select_if.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/device/dispatch/dispatch_spmv_csrt.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/device/dispatch/dispatch_spmv_csrt.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/device/dispatch/dispatch_spmv_orig.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/device/dispatch/dispatch_spmv_orig.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/device/dispatch/dispatch_spmv_row_based.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/device/dispatch/dispatch_spmv_row_based.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/grid/grid_barrier.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/grid/grid_barrier.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/grid/grid_even_share.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/grid/grid_even_share.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/grid/grid_mapping.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/grid/grid_mapping.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/grid/grid_queue.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/grid/grid_queue.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/host/mutex.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/host/mutex.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/iterator/arg_index_input_iterator.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/iterator/arg_index_input_iterator.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/iterator/cache_modified_input_iterator.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/iterator/cache_modified_input_iterator.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/iterator/cache_modified_output_iterator.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/iterator/cache_modified_output_iterator.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/iterator/constant_input_iterator.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/iterator/constant_input_iterator.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/iterator/counting_input_iterator.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/iterator/counting_input_iterator.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/iterator/discard_output_iterator.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/iterator/discard_output_iterator.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/iterator/tex_obj_input_iterator.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/iterator/tex_obj_input_iterator.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/iterator/tex_ref_input_iterator.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/iterator/tex_ref_input_iterator.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/iterator/transform_input_iterator.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/iterator/transform_input_iterator.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/thread/thread_load.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/thread/thread_load.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/thread/thread_operators.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/thread/thread_operators.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/thread/thread_reduce.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/thread/thread_reduce.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/thread/thread_scan.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/thread/thread_scan.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/thread/thread_search.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/thread/thread_search.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/thread/thread_store.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/thread/thread_store.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/util_allocator.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/util_allocator.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/util_arch.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/util_arch.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/util_debug.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/util_debug.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/util_device.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/util_device.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/util_macro.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/util_macro.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/util_namespace.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/util_namespace.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/util_ptx.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/util_ptx.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/util_type.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/util_type.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/warp/specializations/warp_reduce_shfl.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/warp/specializations/warp_reduce_shfl.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/warp/specializations/warp_reduce_smem.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/warp/specializations/warp_reduce_smem.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/warp/specializations/warp_scan_shfl.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/warp/specializations/warp_scan_shfl.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/warp/specializations/warp_scan_smem.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/warp/specializations/warp_scan_smem.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/warp/warp_reduce.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/warp/warp_reduce.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/cub/warp/warp_scan.cuh" "$(@D)/cuda/include/thrust/system/cuda/detail/cub/warp/warp_scan.cuh" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/equal.h" "$(@D)/cuda/include/thrust/system/cuda/detail/equal.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/error.inl" "$(@D)/cuda/include/thrust/system/cuda/detail/error.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/execution_policy.h" "$(@D)/cuda/include/thrust/system/cuda/detail/execution_policy.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/extrema.h" "$(@D)/cuda/include/thrust/system/cuda/detail/extrema.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/fill.h" "$(@D)/cuda/include/thrust/system/cuda/detail/fill.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/find.h" "$(@D)/cuda/include/thrust/system/cuda/detail/find.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/for_each.h" "$(@D)/cuda/include/thrust/system/cuda/detail/for_each.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/gather.h" "$(@D)/cuda/include/thrust/system/cuda/detail/gather.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/generate.h" "$(@D)/cuda/include/thrust/system/cuda/detail/generate.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/get_value.h" "$(@D)/cuda/include/thrust/system/cuda/detail/get_value.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/guarded_cuda_runtime_api.h" "$(@D)/cuda/include/thrust/system/cuda/detail/guarded_cuda_runtime_api.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/guarded_driver_types.h" "$(@D)/cuda/include/thrust/system/cuda/detail/guarded_driver_types.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/inner_product.h" "$(@D)/cuda/include/thrust/system/cuda/detail/inner_product.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/internal/copy_cross_system.h" "$(@D)/cuda/include/thrust/system/cuda/detail/internal/copy_cross_system.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/internal/copy_device_to_device.h" "$(@D)/cuda/include/thrust/system/cuda/detail/internal/copy_device_to_device.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/iter_swap.h" "$(@D)/cuda/include/thrust/system/cuda/detail/iter_swap.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/logical.h" "$(@D)/cuda/include/thrust/system/cuda/detail/logical.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/malloc_and_free.h" "$(@D)/cuda/include/thrust/system/cuda/detail/malloc_and_free.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/memory.inl" "$(@D)/cuda/include/thrust/system/cuda/detail/memory.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/memory_buffer.h" "$(@D)/cuda/include/thrust/system/cuda/detail/memory_buffer.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/merge.h" "$(@D)/cuda/include/thrust/system/cuda/detail/merge.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/mismatch.h" "$(@D)/cuda/include/thrust/system/cuda/detail/mismatch.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/par.h" "$(@D)/cuda/include/thrust/system/cuda/detail/par.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/par_to_seq.h" "$(@D)/cuda/include/thrust/system/cuda/detail/par_to_seq.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/parallel_for.h" "$(@D)/cuda/include/thrust/system/cuda/detail/parallel_for.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/partition.h" "$(@D)/cuda/include/thrust/system/cuda/detail/partition.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/reduce.h" "$(@D)/cuda/include/thrust/system/cuda/detail/reduce.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/reduce_by_key.h" "$(@D)/cuda/include/thrust/system/cuda/detail/reduce_by_key.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/remove.h" "$(@D)/cuda/include/thrust/system/cuda/detail/remove.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/replace.h" "$(@D)/cuda/include/thrust/system/cuda/detail/replace.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/reverse.h" "$(@D)/cuda/include/thrust/system/cuda/detail/reverse.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/scan.h" "$(@D)/cuda/include/thrust/system/cuda/detail/scan.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/scan_by_key.h" "$(@D)/cuda/include/thrust/system/cuda/detail/scan_by_key.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/scatter.h" "$(@D)/cuda/include/thrust/system/cuda/detail/scatter.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/sequence.h" "$(@D)/cuda/include/thrust/system/cuda/detail/sequence.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/set_operations.h" "$(@D)/cuda/include/thrust/system/cuda/detail/set_operations.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/sort.h" "$(@D)/cuda/include/thrust/system/cuda/detail/sort.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/swap_ranges.h" "$(@D)/cuda/include/thrust/system/cuda/detail/swap_ranges.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/tabulate.h" "$(@D)/cuda/include/thrust/system/cuda/detail/tabulate.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/temporary_buffer.h" "$(@D)/cuda/include/thrust/system/cuda/detail/temporary_buffer.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/terminate.h" "$(@D)/cuda/include/thrust/system/cuda/detail/terminate.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/transform.h" "$(@D)/cuda/include/thrust/system/cuda/detail/transform.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/transform_reduce.h" "$(@D)/cuda/include/thrust/system/cuda/detail/transform_reduce.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/transform_scan.h" "$(@D)/cuda/include/thrust/system/cuda/detail/transform_scan.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/uninitialized_copy.h" "$(@D)/cuda/include/thrust/system/cuda/detail/uninitialized_copy.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/uninitialized_fill.h" "$(@D)/cuda/include/thrust/system/cuda/detail/uninitialized_fill.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/unique.h" "$(@D)/cuda/include/thrust/system/cuda/detail/unique.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/unique_by_key.h" "$(@D)/cuda/include/thrust/system/cuda/detail/unique_by_key.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/util.h" "$(@D)/cuda/include/thrust/system/cuda/detail/util.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/detail/vector.inl" "$(@D)/cuda/include/thrust/system/cuda/detail/vector.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/error.h" "$(@D)/cuda/include/thrust/system/cuda/error.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/execution_policy.h" "$(@D)/cuda/include/thrust/system/cuda/execution_policy.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/experimental/pinned_allocator.h" "$(@D)/cuda/include/thrust/system/cuda/experimental/pinned_allocator.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/memory.h" "$(@D)/cuda/include/thrust/system/cuda/memory.h" && cp "/usr/local/cuda-10.0/include/thrust/system/cuda/vector.h" "$(@D)/cuda/include/thrust/system/cuda/vector.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/adjacent_difference.h" "$(@D)/cuda/include/thrust/system/detail/adl/adjacent_difference.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/assign_value.h" "$(@D)/cuda/include/thrust/system/detail/adl/assign_value.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/binary_search.h" "$(@D)/cuda/include/thrust/system/detail/adl/binary_search.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/copy.h" "$(@D)/cuda/include/thrust/system/detail/adl/copy.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/copy_if.h" "$(@D)/cuda/include/thrust/system/detail/adl/copy_if.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/count.h" "$(@D)/cuda/include/thrust/system/detail/adl/count.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/equal.h" "$(@D)/cuda/include/thrust/system/detail/adl/equal.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/extrema.h" "$(@D)/cuda/include/thrust/system/detail/adl/extrema.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/fill.h" "$(@D)/cuda/include/thrust/system/detail/adl/fill.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/find.h" "$(@D)/cuda/include/thrust/system/detail/adl/find.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/for_each.h" "$(@D)/cuda/include/thrust/system/detail/adl/for_each.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/gather.h" "$(@D)/cuda/include/thrust/system/detail/adl/gather.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/generate.h" "$(@D)/cuda/include/thrust/system/detail/adl/generate.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/get_value.h" "$(@D)/cuda/include/thrust/system/detail/adl/get_value.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/inner_product.h" "$(@D)/cuda/include/thrust/system/detail/adl/inner_product.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/iter_swap.h" "$(@D)/cuda/include/thrust/system/detail/adl/iter_swap.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/logical.h" "$(@D)/cuda/include/thrust/system/detail/adl/logical.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/malloc_and_free.h" "$(@D)/cuda/include/thrust/system/detail/adl/malloc_and_free.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/merge.h" "$(@D)/cuda/include/thrust/system/detail/adl/merge.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/mismatch.h" "$(@D)/cuda/include/thrust/system/detail/adl/mismatch.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/partition.h" "$(@D)/cuda/include/thrust/system/detail/adl/partition.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/reduce.h" "$(@D)/cuda/include/thrust/system/detail/adl/reduce.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/reduce_by_key.h" "$(@D)/cuda/include/thrust/system/detail/adl/reduce_by_key.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/remove.h" "$(@D)/cuda/include/thrust/system/detail/adl/remove.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/replace.h" "$(@D)/cuda/include/thrust/system/detail/adl/replace.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/reverse.h" "$(@D)/cuda/include/thrust/system/detail/adl/reverse.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/scan.h" "$(@D)/cuda/include/thrust/system/detail/adl/scan.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/scan_by_key.h" "$(@D)/cuda/include/thrust/system/detail/adl/scan_by_key.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/scatter.h" "$(@D)/cuda/include/thrust/system/detail/adl/scatter.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/sequence.h" "$(@D)/cuda/include/thrust/system/detail/adl/sequence.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/set_operations.h" "$(@D)/cuda/include/thrust/system/detail/adl/set_operations.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/sort.h" "$(@D)/cuda/include/thrust/system/detail/adl/sort.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/swap_ranges.h" "$(@D)/cuda/include/thrust/system/detail/adl/swap_ranges.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/tabulate.h" "$(@D)/cuda/include/thrust/system/detail/adl/tabulate.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/temporary_buffer.h" "$(@D)/cuda/include/thrust/system/detail/adl/temporary_buffer.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/transform.h" "$(@D)/cuda/include/thrust/system/detail/adl/transform.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/transform_reduce.h" "$(@D)/cuda/include/thrust/system/detail/adl/transform_reduce.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/transform_scan.h" "$(@D)/cuda/include/thrust/system/detail/adl/transform_scan.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/uninitialized_copy.h" "$(@D)/cuda/include/thrust/system/detail/adl/uninitialized_copy.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/uninitialized_fill.h" "$(@D)/cuda/include/thrust/system/detail/adl/uninitialized_fill.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/unique.h" "$(@D)/cuda/include/thrust/system/detail/adl/unique.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/adl/unique_by_key.h" "$(@D)/cuda/include/thrust/system/detail/adl/unique_by_key.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/bad_alloc.h" "$(@D)/cuda/include/thrust/system/detail/bad_alloc.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/errno.h" "$(@D)/cuda/include/thrust/system/detail/errno.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/error_category.inl" "$(@D)/cuda/include/thrust/system/detail/error_category.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/error_code.inl" "$(@D)/cuda/include/thrust/system/detail/error_code.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/error_condition.inl" "$(@D)/cuda/include/thrust/system/detail/error_condition.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/adjacent_difference.h" "$(@D)/cuda/include/thrust/system/detail/generic/adjacent_difference.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/adjacent_difference.inl" "$(@D)/cuda/include/thrust/system/detail/generic/adjacent_difference.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/advance.h" "$(@D)/cuda/include/thrust/system/detail/generic/advance.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/advance.inl" "$(@D)/cuda/include/thrust/system/detail/generic/advance.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/binary_search.h" "$(@D)/cuda/include/thrust/system/detail/generic/binary_search.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/binary_search.inl" "$(@D)/cuda/include/thrust/system/detail/generic/binary_search.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/copy.h" "$(@D)/cuda/include/thrust/system/detail/generic/copy.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/copy.inl" "$(@D)/cuda/include/thrust/system/detail/generic/copy.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/copy_if.h" "$(@D)/cuda/include/thrust/system/detail/generic/copy_if.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/copy_if.inl" "$(@D)/cuda/include/thrust/system/detail/generic/copy_if.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/count.h" "$(@D)/cuda/include/thrust/system/detail/generic/count.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/count.inl" "$(@D)/cuda/include/thrust/system/detail/generic/count.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/distance.h" "$(@D)/cuda/include/thrust/system/detail/generic/distance.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/distance.inl" "$(@D)/cuda/include/thrust/system/detail/generic/distance.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/equal.h" "$(@D)/cuda/include/thrust/system/detail/generic/equal.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/equal.inl" "$(@D)/cuda/include/thrust/system/detail/generic/equal.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/extrema.h" "$(@D)/cuda/include/thrust/system/detail/generic/extrema.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/extrema.inl" "$(@D)/cuda/include/thrust/system/detail/generic/extrema.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/fill.h" "$(@D)/cuda/include/thrust/system/detail/generic/fill.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/find.h" "$(@D)/cuda/include/thrust/system/detail/generic/find.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/find.inl" "$(@D)/cuda/include/thrust/system/detail/generic/find.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/for_each.h" "$(@D)/cuda/include/thrust/system/detail/generic/for_each.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/gather.h" "$(@D)/cuda/include/thrust/system/detail/generic/gather.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/gather.inl" "$(@D)/cuda/include/thrust/system/detail/generic/gather.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/generate.h" "$(@D)/cuda/include/thrust/system/detail/generic/generate.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/generate.inl" "$(@D)/cuda/include/thrust/system/detail/generic/generate.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/inner_product.h" "$(@D)/cuda/include/thrust/system/detail/generic/inner_product.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/inner_product.inl" "$(@D)/cuda/include/thrust/system/detail/generic/inner_product.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/logical.h" "$(@D)/cuda/include/thrust/system/detail/generic/logical.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/memory.h" "$(@D)/cuda/include/thrust/system/detail/generic/memory.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/memory.inl" "$(@D)/cuda/include/thrust/system/detail/generic/memory.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/merge.h" "$(@D)/cuda/include/thrust/system/detail/generic/merge.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/merge.inl" "$(@D)/cuda/include/thrust/system/detail/generic/merge.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/mismatch.h" "$(@D)/cuda/include/thrust/system/detail/generic/mismatch.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/mismatch.inl" "$(@D)/cuda/include/thrust/system/detail/generic/mismatch.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/partition.h" "$(@D)/cuda/include/thrust/system/detail/generic/partition.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/partition.inl" "$(@D)/cuda/include/thrust/system/detail/generic/partition.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/reduce.h" "$(@D)/cuda/include/thrust/system/detail/generic/reduce.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/reduce.inl" "$(@D)/cuda/include/thrust/system/detail/generic/reduce.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/reduce_by_key.h" "$(@D)/cuda/include/thrust/system/detail/generic/reduce_by_key.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/reduce_by_key.inl" "$(@D)/cuda/include/thrust/system/detail/generic/reduce_by_key.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/remove.h" "$(@D)/cuda/include/thrust/system/detail/generic/remove.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/remove.inl" "$(@D)/cuda/include/thrust/system/detail/generic/remove.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/replace.h" "$(@D)/cuda/include/thrust/system/detail/generic/replace.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/replace.inl" "$(@D)/cuda/include/thrust/system/detail/generic/replace.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/reverse.h" "$(@D)/cuda/include/thrust/system/detail/generic/reverse.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/reverse.inl" "$(@D)/cuda/include/thrust/system/detail/generic/reverse.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/scalar/binary_search.h" "$(@D)/cuda/include/thrust/system/detail/generic/scalar/binary_search.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/scalar/binary_search.inl" "$(@D)/cuda/include/thrust/system/detail/generic/scalar/binary_search.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/scan.h" "$(@D)/cuda/include/thrust/system/detail/generic/scan.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/scan.inl" "$(@D)/cuda/include/thrust/system/detail/generic/scan.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/scan_by_key.h" "$(@D)/cuda/include/thrust/system/detail/generic/scan_by_key.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/scan_by_key.inl" "$(@D)/cuda/include/thrust/system/detail/generic/scan_by_key.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/scatter.h" "$(@D)/cuda/include/thrust/system/detail/generic/scatter.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/scatter.inl" "$(@D)/cuda/include/thrust/system/detail/generic/scatter.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/select_system.h" "$(@D)/cuda/include/thrust/system/detail/generic/select_system.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/sequence.h" "$(@D)/cuda/include/thrust/system/detail/generic/sequence.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/sequence.inl" "$(@D)/cuda/include/thrust/system/detail/generic/sequence.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/set_operations.h" "$(@D)/cuda/include/thrust/system/detail/generic/set_operations.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/set_operations.inl" "$(@D)/cuda/include/thrust/system/detail/generic/set_operations.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/sort.h" "$(@D)/cuda/include/thrust/system/detail/generic/sort.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/sort.inl" "$(@D)/cuda/include/thrust/system/detail/generic/sort.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/swap_ranges.h" "$(@D)/cuda/include/thrust/system/detail/generic/swap_ranges.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/swap_ranges.inl" "$(@D)/cuda/include/thrust/system/detail/generic/swap_ranges.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/tabulate.h" "$(@D)/cuda/include/thrust/system/detail/generic/tabulate.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/tabulate.inl" "$(@D)/cuda/include/thrust/system/detail/generic/tabulate.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/tag.h" "$(@D)/cuda/include/thrust/system/detail/generic/tag.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/temporary_buffer.h" "$(@D)/cuda/include/thrust/system/detail/generic/temporary_buffer.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/temporary_buffer.inl" "$(@D)/cuda/include/thrust/system/detail/generic/temporary_buffer.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/transform.h" "$(@D)/cuda/include/thrust/system/detail/generic/transform.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/transform.inl" "$(@D)/cuda/include/thrust/system/detail/generic/transform.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/transform_reduce.h" "$(@D)/cuda/include/thrust/system/detail/generic/transform_reduce.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/transform_reduce.inl" "$(@D)/cuda/include/thrust/system/detail/generic/transform_reduce.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/transform_scan.h" "$(@D)/cuda/include/thrust/system/detail/generic/transform_scan.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/transform_scan.inl" "$(@D)/cuda/include/thrust/system/detail/generic/transform_scan.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/type_traits.h" "$(@D)/cuda/include/thrust/system/detail/generic/type_traits.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/uninitialized_copy.h" "$(@D)/cuda/include/thrust/system/detail/generic/uninitialized_copy.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/uninitialized_copy.inl" "$(@D)/cuda/include/thrust/system/detail/generic/uninitialized_copy.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/uninitialized_fill.h" "$(@D)/cuda/include/thrust/system/detail/generic/uninitialized_fill.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/uninitialized_fill.inl" "$(@D)/cuda/include/thrust/system/detail/generic/uninitialized_fill.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/unique.h" "$(@D)/cuda/include/thrust/system/detail/generic/unique.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/unique.inl" "$(@D)/cuda/include/thrust/system/detail/generic/unique.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/unique_by_key.h" "$(@D)/cuda/include/thrust/system/detail/generic/unique_by_key.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/generic/unique_by_key.inl" "$(@D)/cuda/include/thrust/system/detail/generic/unique_by_key.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/internal/decompose.h" "$(@D)/cuda/include/thrust/system/detail/internal/decompose.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/adjacent_difference.h" "$(@D)/cuda/include/thrust/system/detail/sequential/adjacent_difference.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/assign_value.h" "$(@D)/cuda/include/thrust/system/detail/sequential/assign_value.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/binary_search.h" "$(@D)/cuda/include/thrust/system/detail/sequential/binary_search.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/copy.h" "$(@D)/cuda/include/thrust/system/detail/sequential/copy.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/copy.inl" "$(@D)/cuda/include/thrust/system/detail/sequential/copy.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/copy_backward.h" "$(@D)/cuda/include/thrust/system/detail/sequential/copy_backward.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/copy_if.h" "$(@D)/cuda/include/thrust/system/detail/sequential/copy_if.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/count.h" "$(@D)/cuda/include/thrust/system/detail/sequential/count.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/equal.h" "$(@D)/cuda/include/thrust/system/detail/sequential/equal.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/execution_policy.h" "$(@D)/cuda/include/thrust/system/detail/sequential/execution_policy.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/extrema.h" "$(@D)/cuda/include/thrust/system/detail/sequential/extrema.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/fill.h" "$(@D)/cuda/include/thrust/system/detail/sequential/fill.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/find.h" "$(@D)/cuda/include/thrust/system/detail/sequential/find.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/for_each.h" "$(@D)/cuda/include/thrust/system/detail/sequential/for_each.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/gather.h" "$(@D)/cuda/include/thrust/system/detail/sequential/gather.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/general_copy.h" "$(@D)/cuda/include/thrust/system/detail/sequential/general_copy.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/generate.h" "$(@D)/cuda/include/thrust/system/detail/sequential/generate.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/get_value.h" "$(@D)/cuda/include/thrust/system/detail/sequential/get_value.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/inner_product.h" "$(@D)/cuda/include/thrust/system/detail/sequential/inner_product.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/insertion_sort.h" "$(@D)/cuda/include/thrust/system/detail/sequential/insertion_sort.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/iter_swap.h" "$(@D)/cuda/include/thrust/system/detail/sequential/iter_swap.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/logical.h" "$(@D)/cuda/include/thrust/system/detail/sequential/logical.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/malloc_and_free.h" "$(@D)/cuda/include/thrust/system/detail/sequential/malloc_and_free.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/merge.h" "$(@D)/cuda/include/thrust/system/detail/sequential/merge.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/merge.inl" "$(@D)/cuda/include/thrust/system/detail/sequential/merge.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/mismatch.h" "$(@D)/cuda/include/thrust/system/detail/sequential/mismatch.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/partition.h" "$(@D)/cuda/include/thrust/system/detail/sequential/partition.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/reduce.h" "$(@D)/cuda/include/thrust/system/detail/sequential/reduce.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/reduce_by_key.h" "$(@D)/cuda/include/thrust/system/detail/sequential/reduce_by_key.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/remove.h" "$(@D)/cuda/include/thrust/system/detail/sequential/remove.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/replace.h" "$(@D)/cuda/include/thrust/system/detail/sequential/replace.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/reverse.h" "$(@D)/cuda/include/thrust/system/detail/sequential/reverse.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/scan.h" "$(@D)/cuda/include/thrust/system/detail/sequential/scan.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/scan_by_key.h" "$(@D)/cuda/include/thrust/system/detail/sequential/scan_by_key.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/scatter.h" "$(@D)/cuda/include/thrust/system/detail/sequential/scatter.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/sequence.h" "$(@D)/cuda/include/thrust/system/detail/sequential/sequence.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/set_operations.h" "$(@D)/cuda/include/thrust/system/detail/sequential/set_operations.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/sort.h" "$(@D)/cuda/include/thrust/system/detail/sequential/sort.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/sort.inl" "$(@D)/cuda/include/thrust/system/detail/sequential/sort.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/stable_merge_sort.h" "$(@D)/cuda/include/thrust/system/detail/sequential/stable_merge_sort.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/stable_merge_sort.inl" "$(@D)/cuda/include/thrust/system/detail/sequential/stable_merge_sort.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/stable_primitive_sort.h" "$(@D)/cuda/include/thrust/system/detail/sequential/stable_primitive_sort.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/stable_primitive_sort.inl" "$(@D)/cuda/include/thrust/system/detail/sequential/stable_primitive_sort.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/stable_radix_sort.h" "$(@D)/cuda/include/thrust/system/detail/sequential/stable_radix_sort.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/stable_radix_sort.inl" "$(@D)/cuda/include/thrust/system/detail/sequential/stable_radix_sort.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/swap_ranges.h" "$(@D)/cuda/include/thrust/system/detail/sequential/swap_ranges.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/tabulate.h" "$(@D)/cuda/include/thrust/system/detail/sequential/tabulate.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/temporary_buffer.h" "$(@D)/cuda/include/thrust/system/detail/sequential/temporary_buffer.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/transform.h" "$(@D)/cuda/include/thrust/system/detail/sequential/transform.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/transform_reduce.h" "$(@D)/cuda/include/thrust/system/detail/sequential/transform_reduce.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/transform_scan.h" "$(@D)/cuda/include/thrust/system/detail/sequential/transform_scan.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/trivial_copy.h" "$(@D)/cuda/include/thrust/system/detail/sequential/trivial_copy.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/uninitialized_copy.h" "$(@D)/cuda/include/thrust/system/detail/sequential/uninitialized_copy.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/uninitialized_fill.h" "$(@D)/cuda/include/thrust/system/detail/sequential/uninitialized_fill.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/unique.h" "$(@D)/cuda/include/thrust/system/detail/sequential/unique.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/sequential/unique_by_key.h" "$(@D)/cuda/include/thrust/system/detail/sequential/unique_by_key.h" && cp "/usr/local/cuda-10.0/include/thrust/system/detail/system_error.inl" "$(@D)/cuda/include/thrust/system/detail/system_error.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/error_code.h" "$(@D)/cuda/include/thrust/system/error_code.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/adjacent_difference.h" "$(@D)/cuda/include/thrust/system/omp/detail/adjacent_difference.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/assign_value.h" "$(@D)/cuda/include/thrust/system/omp/detail/assign_value.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/binary_search.h" "$(@D)/cuda/include/thrust/system/omp/detail/binary_search.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/copy.h" "$(@D)/cuda/include/thrust/system/omp/detail/copy.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/copy.inl" "$(@D)/cuda/include/thrust/system/omp/detail/copy.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/copy_if.h" "$(@D)/cuda/include/thrust/system/omp/detail/copy_if.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/copy_if.inl" "$(@D)/cuda/include/thrust/system/omp/detail/copy_if.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/count.h" "$(@D)/cuda/include/thrust/system/omp/detail/count.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/default_decomposition.h" "$(@D)/cuda/include/thrust/system/omp/detail/default_decomposition.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/default_decomposition.inl" "$(@D)/cuda/include/thrust/system/omp/detail/default_decomposition.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/equal.h" "$(@D)/cuda/include/thrust/system/omp/detail/equal.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/execution_policy.h" "$(@D)/cuda/include/thrust/system/omp/detail/execution_policy.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/extrema.h" "$(@D)/cuda/include/thrust/system/omp/detail/extrema.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/fill.h" "$(@D)/cuda/include/thrust/system/omp/detail/fill.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/find.h" "$(@D)/cuda/include/thrust/system/omp/detail/find.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/for_each.h" "$(@D)/cuda/include/thrust/system/omp/detail/for_each.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/for_each.inl" "$(@D)/cuda/include/thrust/system/omp/detail/for_each.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/gather.h" "$(@D)/cuda/include/thrust/system/omp/detail/gather.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/generate.h" "$(@D)/cuda/include/thrust/system/omp/detail/generate.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/get_value.h" "$(@D)/cuda/include/thrust/system/omp/detail/get_value.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/inner_product.h" "$(@D)/cuda/include/thrust/system/omp/detail/inner_product.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/iter_swap.h" "$(@D)/cuda/include/thrust/system/omp/detail/iter_swap.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/logical.h" "$(@D)/cuda/include/thrust/system/omp/detail/logical.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/malloc_and_free.h" "$(@D)/cuda/include/thrust/system/omp/detail/malloc_and_free.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/memory.inl" "$(@D)/cuda/include/thrust/system/omp/detail/memory.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/merge.h" "$(@D)/cuda/include/thrust/system/omp/detail/merge.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/mismatch.h" "$(@D)/cuda/include/thrust/system/omp/detail/mismatch.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/par.h" "$(@D)/cuda/include/thrust/system/omp/detail/par.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/partition.h" "$(@D)/cuda/include/thrust/system/omp/detail/partition.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/partition.inl" "$(@D)/cuda/include/thrust/system/omp/detail/partition.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/reduce.h" "$(@D)/cuda/include/thrust/system/omp/detail/reduce.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/reduce.inl" "$(@D)/cuda/include/thrust/system/omp/detail/reduce.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/reduce_by_key.h" "$(@D)/cuda/include/thrust/system/omp/detail/reduce_by_key.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/reduce_by_key.inl" "$(@D)/cuda/include/thrust/system/omp/detail/reduce_by_key.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/reduce_intervals.h" "$(@D)/cuda/include/thrust/system/omp/detail/reduce_intervals.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/reduce_intervals.inl" "$(@D)/cuda/include/thrust/system/omp/detail/reduce_intervals.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/remove.h" "$(@D)/cuda/include/thrust/system/omp/detail/remove.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/remove.inl" "$(@D)/cuda/include/thrust/system/omp/detail/remove.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/replace.h" "$(@D)/cuda/include/thrust/system/omp/detail/replace.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/reverse.h" "$(@D)/cuda/include/thrust/system/omp/detail/reverse.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/scan.h" "$(@D)/cuda/include/thrust/system/omp/detail/scan.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/scan_by_key.h" "$(@D)/cuda/include/thrust/system/omp/detail/scan_by_key.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/scatter.h" "$(@D)/cuda/include/thrust/system/omp/detail/scatter.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/sequence.h" "$(@D)/cuda/include/thrust/system/omp/detail/sequence.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/set_operations.h" "$(@D)/cuda/include/thrust/system/omp/detail/set_operations.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/sort.h" "$(@D)/cuda/include/thrust/system/omp/detail/sort.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/sort.inl" "$(@D)/cuda/include/thrust/system/omp/detail/sort.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/swap_ranges.h" "$(@D)/cuda/include/thrust/system/omp/detail/swap_ranges.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/tabulate.h" "$(@D)/cuda/include/thrust/system/omp/detail/tabulate.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/temporary_buffer.h" "$(@D)/cuda/include/thrust/system/omp/detail/temporary_buffer.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/transform.h" "$(@D)/cuda/include/thrust/system/omp/detail/transform.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/transform_reduce.h" "$(@D)/cuda/include/thrust/system/omp/detail/transform_reduce.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/transform_scan.h" "$(@D)/cuda/include/thrust/system/omp/detail/transform_scan.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/uninitialized_copy.h" "$(@D)/cuda/include/thrust/system/omp/detail/uninitialized_copy.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/uninitialized_fill.h" "$(@D)/cuda/include/thrust/system/omp/detail/uninitialized_fill.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/unique.h" "$(@D)/cuda/include/thrust/system/omp/detail/unique.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/unique.inl" "$(@D)/cuda/include/thrust/system/omp/detail/unique.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/unique_by_key.h" "$(@D)/cuda/include/thrust/system/omp/detail/unique_by_key.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/unique_by_key.inl" "$(@D)/cuda/include/thrust/system/omp/detail/unique_by_key.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/detail/vector.inl" "$(@D)/cuda/include/thrust/system/omp/detail/vector.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/execution_policy.h" "$(@D)/cuda/include/thrust/system/omp/execution_policy.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/memory.h" "$(@D)/cuda/include/thrust/system/omp/memory.h" && cp "/usr/local/cuda-10.0/include/thrust/system/omp/vector.h" "$(@D)/cuda/include/thrust/system/omp/vector.h" && cp "/usr/local/cuda-10.0/include/thrust/system/system_error.h" "$(@D)/cuda/include/thrust/system/system_error.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/adjacent_difference.h" "$(@D)/cuda/include/thrust/system/tbb/detail/adjacent_difference.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/assign_value.h" "$(@D)/cuda/include/thrust/system/tbb/detail/assign_value.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/binary_search.h" "$(@D)/cuda/include/thrust/system/tbb/detail/binary_search.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/copy.h" "$(@D)/cuda/include/thrust/system/tbb/detail/copy.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/copy.inl" "$(@D)/cuda/include/thrust/system/tbb/detail/copy.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/copy_if.h" "$(@D)/cuda/include/thrust/system/tbb/detail/copy_if.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/copy_if.inl" "$(@D)/cuda/include/thrust/system/tbb/detail/copy_if.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/count.h" "$(@D)/cuda/include/thrust/system/tbb/detail/count.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/equal.h" "$(@D)/cuda/include/thrust/system/tbb/detail/equal.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/execution_policy.h" "$(@D)/cuda/include/thrust/system/tbb/detail/execution_policy.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/extrema.h" "$(@D)/cuda/include/thrust/system/tbb/detail/extrema.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/fill.h" "$(@D)/cuda/include/thrust/system/tbb/detail/fill.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/find.h" "$(@D)/cuda/include/thrust/system/tbb/detail/find.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/for_each.h" "$(@D)/cuda/include/thrust/system/tbb/detail/for_each.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/for_each.inl" "$(@D)/cuda/include/thrust/system/tbb/detail/for_each.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/gather.h" "$(@D)/cuda/include/thrust/system/tbb/detail/gather.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/generate.h" "$(@D)/cuda/include/thrust/system/tbb/detail/generate.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/get_value.h" "$(@D)/cuda/include/thrust/system/tbb/detail/get_value.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/inner_product.h" "$(@D)/cuda/include/thrust/system/tbb/detail/inner_product.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/iter_swap.h" "$(@D)/cuda/include/thrust/system/tbb/detail/iter_swap.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/logical.h" "$(@D)/cuda/include/thrust/system/tbb/detail/logical.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/malloc_and_free.h" "$(@D)/cuda/include/thrust/system/tbb/detail/malloc_and_free.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/memory.inl" "$(@D)/cuda/include/thrust/system/tbb/detail/memory.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/merge.h" "$(@D)/cuda/include/thrust/system/tbb/detail/merge.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/merge.inl" "$(@D)/cuda/include/thrust/system/tbb/detail/merge.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/mismatch.h" "$(@D)/cuda/include/thrust/system/tbb/detail/mismatch.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/par.h" "$(@D)/cuda/include/thrust/system/tbb/detail/par.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/partition.h" "$(@D)/cuda/include/thrust/system/tbb/detail/partition.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/partition.inl" "$(@D)/cuda/include/thrust/system/tbb/detail/partition.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/reduce.h" "$(@D)/cuda/include/thrust/system/tbb/detail/reduce.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/reduce.inl" "$(@D)/cuda/include/thrust/system/tbb/detail/reduce.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/reduce_by_key.h" "$(@D)/cuda/include/thrust/system/tbb/detail/reduce_by_key.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/reduce_by_key.inl" "$(@D)/cuda/include/thrust/system/tbb/detail/reduce_by_key.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/reduce_intervals.h" "$(@D)/cuda/include/thrust/system/tbb/detail/reduce_intervals.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/remove.h" "$(@D)/cuda/include/thrust/system/tbb/detail/remove.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/remove.inl" "$(@D)/cuda/include/thrust/system/tbb/detail/remove.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/replace.h" "$(@D)/cuda/include/thrust/system/tbb/detail/replace.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/reverse.h" "$(@D)/cuda/include/thrust/system/tbb/detail/reverse.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/scan.h" "$(@D)/cuda/include/thrust/system/tbb/detail/scan.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/scan.inl" "$(@D)/cuda/include/thrust/system/tbb/detail/scan.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/scan_by_key.h" "$(@D)/cuda/include/thrust/system/tbb/detail/scan_by_key.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/scatter.h" "$(@D)/cuda/include/thrust/system/tbb/detail/scatter.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/sequence.h" "$(@D)/cuda/include/thrust/system/tbb/detail/sequence.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/set_operations.h" "$(@D)/cuda/include/thrust/system/tbb/detail/set_operations.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/sort.h" "$(@D)/cuda/include/thrust/system/tbb/detail/sort.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/sort.inl" "$(@D)/cuda/include/thrust/system/tbb/detail/sort.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/swap_ranges.h" "$(@D)/cuda/include/thrust/system/tbb/detail/swap_ranges.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/tabulate.h" "$(@D)/cuda/include/thrust/system/tbb/detail/tabulate.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/temporary_buffer.h" "$(@D)/cuda/include/thrust/system/tbb/detail/temporary_buffer.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/transform.h" "$(@D)/cuda/include/thrust/system/tbb/detail/transform.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/transform_reduce.h" "$(@D)/cuda/include/thrust/system/tbb/detail/transform_reduce.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/transform_scan.h" "$(@D)/cuda/include/thrust/system/tbb/detail/transform_scan.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/uninitialized_copy.h" "$(@D)/cuda/include/thrust/system/tbb/detail/uninitialized_copy.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/uninitialized_fill.h" "$(@D)/cuda/include/thrust/system/tbb/detail/uninitialized_fill.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/unique.h" "$(@D)/cuda/include/thrust/system/tbb/detail/unique.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/unique.inl" "$(@D)/cuda/include/thrust/system/tbb/detail/unique.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/unique_by_key.h" "$(@D)/cuda/include/thrust/system/tbb/detail/unique_by_key.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/unique_by_key.inl" "$(@D)/cuda/include/thrust/system/tbb/detail/unique_by_key.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/detail/vector.inl" "$(@D)/cuda/include/thrust/system/tbb/detail/vector.inl" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/execution_policy.h" "$(@D)/cuda/include/thrust/system/tbb/execution_policy.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/memory.h" "$(@D)/cuda/include/thrust/system/tbb/memory.h" && cp "/usr/local/cuda-10.0/include/thrust/system/tbb/vector.h" "$(@D)/cuda/include/thrust/system/tbb/vector.h" && cp "/usr/local/cuda-10.0/include/thrust/system_error.h" "$(@D)/cuda/include/thrust/system_error.h" && cp "/usr/local/cuda-10.0/include/thrust/tabulate.h" "$(@D)/cuda/include/thrust/tabulate.h" && cp "/usr/local/cuda-10.0/include/thrust/transform.h" "$(@D)/cuda/include/thrust/transform.h" && cp "/usr/local/cuda-10.0/include/thrust/transform_reduce.h" "$(@D)/cuda/include/thrust/transform_reduce.h" && cp "/usr/local/cuda-10.0/include/thrust/transform_scan.h" "$(@D)/cuda/include/thrust/transform_scan.h" && cp "/usr/local/cuda-10.0/include/thrust/tuple.h" "$(@D)/cuda/include/thrust/tuple.h" && cp "/usr/local/cuda-10.0/include/thrust/uninitialized_copy.h" "$(@D)/cuda/include/thrust/uninitialized_copy.h" && cp "/usr/local/cuda-10.0/include/thrust/uninitialized_fill.h" "$(@D)/cuda/include/thrust/uninitialized_fill.h" && cp "/usr/local/cuda-10.0/include/thrust/unique.h" "$(@D)/cuda/include/thrust/unique.h" && cp "/usr/local/cuda-10.0/include/thrust/version.h" "$(@D)/cuda/include/thrust/version.h" && cp "/usr/local/cuda-10.0/include/vector_functions.h" "$(@D)/cuda/include/vector_functions.h" && cp "/usr/local/cuda-10.0/include/vector_functions.hpp" "$(@D)/cuda/include/vector_functions.hpp" && cp "/usr/local/cuda-10.0/include/vector_types.h" "$(@D)/cuda/include/vector_types.h" - """, -) - -genrule( - name = "cuda-nvvm", - outs = [ - "cuda/nvvm/libdevice/libdevice.10.bc", - ], - cmd = """ -if [ -d "$(@D)/extras" ]; then rm $(@D)/extras -drf; fi && if [ -d "$(@D)/include" ]; then rm $(@D)/include -drf; fi && if [ -d "$(@D)/lib" ]; then rm $(@D)/lib -drf; fi && if [ -d "$(@D)/nvvm" ]; then rm $(@D)/nvvm -drf; fi && cp "/usr/local/cuda-10.0/nvvm/libdevice/libdevice.10.bc" "$(@D)//libdevice.10.bc" - """, -) - -genrule( - name = "cuda-extras", - outs = [ - "cuda/extras/CUPTI/include/GL/gl.h", - "cuda/extras/CUPTI/include/GL/glew.h", - "cuda/extras/CUPTI/include/GL/glext.h", - "cuda/extras/CUPTI/include/GL/glu.h", - "cuda/extras/CUPTI/include/GL/glut.h", - "cuda/extras/CUPTI/include/GL/glx.h", - "cuda/extras/CUPTI/include/GL/glxext.h", - "cuda/extras/CUPTI/include/GL/wglew.h", - "cuda/extras/CUPTI/include/GL/wglext.h", - "cuda/extras/CUPTI/include/cuda_stdint.h", - "cuda/extras/CUPTI/include/cupti.h", - "cuda/extras/CUPTI/include/cupti_activity.h", - "cuda/extras/CUPTI/include/cupti_callbacks.h", - "cuda/extras/CUPTI/include/cupti_driver_cbid.h", - "cuda/extras/CUPTI/include/cupti_events.h", - "cuda/extras/CUPTI/include/cupti_metrics.h", - "cuda/extras/CUPTI/include/cupti_nvtx_cbid.h", - "cuda/extras/CUPTI/include/cupti_result.h", - "cuda/extras/CUPTI/include/cupti_runtime_cbid.h", - "cuda/extras/CUPTI/include/cupti_version.h", - "cuda/extras/CUPTI/include/generated_cudaGL_meta.h", - "cuda/extras/CUPTI/include/generated_cudaVDPAU_meta.h", - "cuda/extras/CUPTI/include/generated_cuda_gl_interop_meta.h", - "cuda/extras/CUPTI/include/generated_cuda_meta.h", - "cuda/extras/CUPTI/include/generated_cuda_runtime_api_meta.h", - "cuda/extras/CUPTI/include/generated_cuda_vdpau_interop_meta.h", - "cuda/extras/CUPTI/include/generated_nvtx_meta.h", - "cuda/extras/CUPTI/include/openacc/cupti_openacc.h", - ], - cmd = """ -if [ -d "$(@D)/extras" ]; then rm $(@D)/extras -drf; fi && if [ -d "$(@D)/include" ]; then rm $(@D)/include -drf; fi && if [ -d "$(@D)/lib" ]; then rm $(@D)/lib -drf; fi && if [ -d "$(@D)/nvvm" ]; then rm $(@D)/nvvm -drf; fi && cp "/usr/local/cuda-10.0/extras/CUPTI/include/GL/gl.h" "$(@D)/cuda/extras/CUPTI/include/GL/gl.h" && cp "/usr/local/cuda-10.0/extras/CUPTI/include/GL/glew.h" "$(@D)/cuda/extras/CUPTI/include/GL/glew.h" && cp "/usr/local/cuda-10.0/extras/CUPTI/include/GL/glext.h" "$(@D)/cuda/extras/CUPTI/include/GL/glext.h" && cp "/usr/local/cuda-10.0/extras/CUPTI/include/GL/glu.h" "$(@D)/cuda/extras/CUPTI/include/GL/glu.h" && cp "/usr/local/cuda-10.0/extras/CUPTI/include/GL/glut.h" "$(@D)/cuda/extras/CUPTI/include/GL/glut.h" && cp "/usr/local/cuda-10.0/extras/CUPTI/include/GL/glx.h" "$(@D)/cuda/extras/CUPTI/include/GL/glx.h" && cp "/usr/local/cuda-10.0/extras/CUPTI/include/GL/glxext.h" "$(@D)/cuda/extras/CUPTI/include/GL/glxext.h" && cp "/usr/local/cuda-10.0/extras/CUPTI/include/GL/wglew.h" "$(@D)/cuda/extras/CUPTI/include/GL/wglew.h" && cp "/usr/local/cuda-10.0/extras/CUPTI/include/GL/wglext.h" "$(@D)/cuda/extras/CUPTI/include/GL/wglext.h" && cp "/usr/local/cuda-10.0/extras/CUPTI/include/cuda_stdint.h" "$(@D)/cuda/extras/CUPTI/include/cuda_stdint.h" && cp "/usr/local/cuda-10.0/extras/CUPTI/include/cupti.h" "$(@D)/cuda/extras/CUPTI/include/cupti.h" && cp "/usr/local/cuda-10.0/extras/CUPTI/include/cupti_activity.h" "$(@D)/cuda/extras/CUPTI/include/cupti_activity.h" && cp "/usr/local/cuda-10.0/extras/CUPTI/include/cupti_callbacks.h" "$(@D)/cuda/extras/CUPTI/include/cupti_callbacks.h" && cp "/usr/local/cuda-10.0/extras/CUPTI/include/cupti_driver_cbid.h" "$(@D)/cuda/extras/CUPTI/include/cupti_driver_cbid.h" && cp "/usr/local/cuda-10.0/extras/CUPTI/include/cupti_events.h" "$(@D)/cuda/extras/CUPTI/include/cupti_events.h" && cp "/usr/local/cuda-10.0/extras/CUPTI/include/cupti_metrics.h" "$(@D)/cuda/extras/CUPTI/include/cupti_metrics.h" && cp "/usr/local/cuda-10.0/extras/CUPTI/include/cupti_nvtx_cbid.h" "$(@D)/cuda/extras/CUPTI/include/cupti_nvtx_cbid.h" && cp "/usr/local/cuda-10.0/extras/CUPTI/include/cupti_result.h" "$(@D)/cuda/extras/CUPTI/include/cupti_result.h" && cp "/usr/local/cuda-10.0/extras/CUPTI/include/cupti_runtime_cbid.h" "$(@D)/cuda/extras/CUPTI/include/cupti_runtime_cbid.h" && cp "/usr/local/cuda-10.0/extras/CUPTI/include/cupti_version.h" "$(@D)/cuda/extras/CUPTI/include/cupti_version.h" && cp "/usr/local/cuda-10.0/extras/CUPTI/include/generated_cudaGL_meta.h" "$(@D)/cuda/extras/CUPTI/include/generated_cudaGL_meta.h" && cp "/usr/local/cuda-10.0/extras/CUPTI/include/generated_cudaVDPAU_meta.h" "$(@D)/cuda/extras/CUPTI/include/generated_cudaVDPAU_meta.h" && cp "/usr/local/cuda-10.0/extras/CUPTI/include/generated_cuda_gl_interop_meta.h" "$(@D)/cuda/extras/CUPTI/include/generated_cuda_gl_interop_meta.h" && cp "/usr/local/cuda-10.0/extras/CUPTI/include/generated_cuda_meta.h" "$(@D)/cuda/extras/CUPTI/include/generated_cuda_meta.h" && cp "/usr/local/cuda-10.0/extras/CUPTI/include/generated_cuda_runtime_api_meta.h" "$(@D)/cuda/extras/CUPTI/include/generated_cuda_runtime_api_meta.h" && cp "/usr/local/cuda-10.0/extras/CUPTI/include/generated_cuda_vdpau_interop_meta.h" "$(@D)/cuda/extras/CUPTI/include/generated_cuda_vdpau_interop_meta.h" && cp "/usr/local/cuda-10.0/extras/CUPTI/include/generated_nvtx_meta.h" "$(@D)/cuda/extras/CUPTI/include/generated_nvtx_meta.h" && cp "/usr/local/cuda-10.0/extras/CUPTI/include/openacc/cupti_openacc.h" "$(@D)/cuda/extras/CUPTI/include/openacc/cupti_openacc.h" - """, -) - -genrule( - name = "cuda-lib", - outs = [ - "cuda/lib/libcuda.so", - "cuda/lib/libcudart.so.10.0", - "cuda/lib/libcudart_static.a", - "cuda/lib/libcublas.so.10.0", - "cuda/lib/libcusolver.so.10.0", - "cuda/lib/libcurand.so.10.0", - "cuda/lib/libcufft.so.10.0", - "cuda/lib/libcudnn.so.7", - "cuda/lib/libcupti.so.10.0", - ], - cmd = """ -if [ -d "$(@D)/extras" ]; then rm $(@D)/extras -drf; fi && if [ -d "$(@D)/include" ]; then rm $(@D)/include -drf; fi && if [ -d "$(@D)/lib" ]; then rm $(@D)/lib -drf; fi && if [ -d "$(@D)/nvvm" ]; then rm $(@D)/nvvm -drf; fi && cp "/usr/local/cuda-10.0/targets/x86_64-linux/lib/stubs/libcuda.so" "$(@D)/cuda/lib/libcuda.so" && cp "/usr/local/cuda-10.0/targets/x86_64-linux/lib/libcudart.so.10.0.176" "$(@D)/cuda/lib/libcudart.so.10.0" && cp "/usr/local/cuda-10.0/targets/x86_64-linux/lib/libcudart_static.a" "$(@D)/cuda/lib/libcudart_static.a" && cp "/usr/local/cuda-10.0/targets/x86_64-linux/lib/libcublas.so.10.0.480" "$(@D)/cuda/lib/libcublas.so.10.0" && cp "/usr/local/cuda-10.0/targets/x86_64-linux/lib/libcusolver.so.10.0.176" "$(@D)/cuda/lib/libcusolver.so.10.0" && cp "/usr/local/cuda-10.0/targets/x86_64-linux/lib/libcurand.so.10.0.176" "$(@D)/cuda/lib/libcurand.so.10.0" && cp "/usr/local/cuda-10.0/targets/x86_64-linux/lib/libcufft.so.10.0.176" "$(@D)/cuda/lib/libcufft.so.10.0" && cp "/usr/lib/x86_64-linux-gnu/libcudnn.so.7.2.1" "$(@D)/cuda/lib/libcudnn.so.7" && cp "/usr/local/cuda/extras/CUPTI/lib64/libcupti.so.10.0.176" "$(@D)/cuda/lib/libcupti.so.10.0" - """, -) - -filegroup( - name = "cudnn-include", - srcs = [], -) diff --git a/third_party/toolchains/gpus/cuda/build_defs.bzl b/third_party/toolchains/gpus/cuda/build_defs.bzl deleted file mode 100644 index 9210bfe016..0000000000 --- a/third_party/toolchains/gpus/cuda/build_defs.bzl +++ /dev/null @@ -1,35 +0,0 @@ -# Macros for building CUDA code used with Bazel remote -# execution service. -# DO NOT EDIT: automatically generated file - -# Macros for building CUDA code. -def if_cuda(if_true, if_false = []): - """Shorthand for select()'ing on whether we're building with CUDA. - - Returns a select statement which evaluates to if_true if we're building - with CUDA enabled. Otherwise, the select statement evaluates to if_false. - - """ - return select({ - "@local_config_cuda//cuda:using_nvcc": if_true, - "@local_config_cuda//cuda:using_clang": if_true, - "//conditions:default": if_false, - }) - -def cuda_default_copts(): - """Default options for all CUDA compilations.""" - return if_cuda(["-x", "cuda", "-DGOOGLE_CUDA=1"] + ["--cuda-gpu-arch=sm_30"]) - -def cuda_is_configured(): - """Returns true if CUDA was enabled during the configure process.""" - return True - -def if_cuda_is_configured(x): - """Tests if the CUDA was enabled during the configure process. - - Unlike if_cuda(), this does not require that we are building with - --config=cuda. Used to allow non-CUDA code to depend on CUDA libraries. - """ - if cuda_is_configured(): - return x - return [] diff --git a/third_party/toolchains/gpus/cuda/cuda/cuda_config.h b/third_party/toolchains/gpus/cuda/cuda/cuda_config.h deleted file mode 100644 index b05bfb7326..0000000000 --- a/third_party/toolchains/gpus/cuda/cuda/cuda_config.h +++ /dev/null @@ -1,27 +0,0 @@ -/* 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. -==============================================================================*/ - -// DO NOT EDIT: automatically generated file -#ifndef CUDA_CUDA_CONFIG_H_ -#define CUDA_CUDA_CONFIG_H_ - -#define TF_CUDA_CAPABILITIES CudaVersion("3.0") - -#define TF_CUDA_VERSION "10.0" -#define TF_CUDNN_VERSION "7" - -#define TF_CUDA_TOOLKIT_PATH "/usr/local/cuda-10.0" - -#endif // CUDA_CUDA_CONFIG_H_ diff --git a/third_party/toolchains/gpus/py/BUILD b/third_party/toolchains/gpus/py/BUILD deleted file mode 100644 index 1235988abb..0000000000 --- a/third_party/toolchains/gpus/py/BUILD +++ /dev/null @@ -1,177 +0,0 @@ -# A build file to configure python remote repository used with Bazel remote -# execution service -# DO NOT EDIT: automatically generated BUILD file - -licenses(["restricted"]) - -package(default_visibility = ["//visibility:public"]) - -# To build Python C/C++ extension on Windows, we need to link to python import library pythonXY.lib -# See https://docs.python.org/3/extending/windows.html -cc_import( - name = "python_lib", - interface_library = select({ - ":windows": ":python_import_lib", - # A placeholder for Unix platforms which makes --no_build happy. - "//conditions:default": "not-existing.lib", - }), - system_provided = 1, -) - -cc_library( - name = "python_headers", - hdrs = [":python_include"], - includes = ["python_include"], - deps = select({ - ":windows": [":python_lib"], - "//conditions:default": [], - }), -) - -cc_library( - name = "numpy_headers", - hdrs = [":numpy_include"], - includes = ["numpy_include"], -) - -config_setting( - name = "windows", - values = {"cpu": "x64_windows"}, - visibility = ["//visibility:public"], -) - -genrule( - name = "python_include", - outs = [ - "python_include/Python-ast.h", - "python_include/Python.h", - "python_include/abstract.h", - "python_include/asdl.h", - "python_include/ast.h", - "python_include/bitset.h", - "python_include/boolobject.h", - "python_include/bufferobject.h", - "python_include/bytearrayobject.h", - "python_include/bytes_methods.h", - "python_include/bytesobject.h", - "python_include/cStringIO.h", - "python_include/cellobject.h", - "python_include/ceval.h", - "python_include/classobject.h", - "python_include/cobject.h", - "python_include/code.h", - "python_include/codecs.h", - "python_include/compile.h", - "python_include/complexobject.h", - "python_include/datetime.h", - "python_include/descrobject.h", - "python_include/dictobject.h", - "python_include/dtoa.h", - "python_include/enumobject.h", - "python_include/errcode.h", - "python_include/eval.h", - "python_include/fileobject.h", - "python_include/floatobject.h", - "python_include/frameobject.h", - "python_include/funcobject.h", - "python_include/genobject.h", - "python_include/graminit.h", - "python_include/grammar.h", - "python_include/import.h", - "python_include/intobject.h", - "python_include/intrcheck.h", - "python_include/iterobject.h", - "python_include/listobject.h", - "python_include/longintrepr.h", - "python_include/longobject.h", - "python_include/marshal.h", - "python_include/memoryobject.h", - "python_include/metagrammar.h", - "python_include/methodobject.h", - "python_include/modsupport.h", - "python_include/moduleobject.h", - "python_include/node.h", - "python_include/object.h", - "python_include/objimpl.h", - "python_include/opcode.h", - "python_include/osdefs.h", - "python_include/parsetok.h", - "python_include/patchlevel.h", - "python_include/pgen.h", - "python_include/pgenheaders.h", - "python_include/py_curses.h", - "python_include/pyarena.h", - "python_include/pycapsule.h", - "python_include/pyconfig.h", - "python_include/pyctype.h", - "python_include/pydebug.h", - "python_include/pyerrors.h", - "python_include/pyexpat.h", - "python_include/pyfpe.h", - "python_include/pygetopt.h", - "python_include/pymacconfig.h", - "python_include/pymactoolbox.h", - "python_include/pymath.h", - "python_include/pymem.h", - "python_include/pyport.h", - "python_include/pystate.h", - "python_include/pystrcmp.h", - "python_include/pystrtod.h", - "python_include/pythonrun.h", - "python_include/pythread.h", - "python_include/rangeobject.h", - "python_include/setobject.h", - "python_include/sliceobject.h", - "python_include/stringobject.h", - "python_include/structmember.h", - "python_include/structseq.h", - "python_include/symtable.h", - "python_include/sysmodule.h", - "python_include/timefuncs.h", - "python_include/token.h", - "python_include/traceback.h", - "python_include/tupleobject.h", - "python_include/ucnhash.h", - "python_include/unicodeobject.h", - "python_include/warnings.h", - "python_include/weakrefobject.h", - ], - cmd = """ -cp "/usr/include/python2.7/Python-ast.h" "$(@D)/python_include/Python-ast.h" && cp "/usr/include/python2.7/Python.h" "$(@D)/python_include/Python.h" && cp "/usr/include/python2.7/abstract.h" "$(@D)/python_include/abstract.h" && cp "/usr/include/python2.7/asdl.h" "$(@D)/python_include/asdl.h" && cp "/usr/include/python2.7/ast.h" "$(@D)/python_include/ast.h" && cp "/usr/include/python2.7/bitset.h" "$(@D)/python_include/bitset.h" && cp "/usr/include/python2.7/boolobject.h" "$(@D)/python_include/boolobject.h" && cp "/usr/include/python2.7/bufferobject.h" "$(@D)/python_include/bufferobject.h" && cp "/usr/include/python2.7/bytearrayobject.h" "$(@D)/python_include/bytearrayobject.h" && cp "/usr/include/python2.7/bytes_methods.h" "$(@D)/python_include/bytes_methods.h" && cp "/usr/include/python2.7/bytesobject.h" "$(@D)/python_include/bytesobject.h" && cp "/usr/include/python2.7/cStringIO.h" "$(@D)/python_include/cStringIO.h" && cp "/usr/include/python2.7/cellobject.h" "$(@D)/python_include/cellobject.h" && cp "/usr/include/python2.7/ceval.h" "$(@D)/python_include/ceval.h" && cp "/usr/include/python2.7/classobject.h" "$(@D)/python_include/classobject.h" && cp "/usr/include/python2.7/cobject.h" "$(@D)/python_include/cobject.h" && cp "/usr/include/python2.7/code.h" "$(@D)/python_include/code.h" && cp "/usr/include/python2.7/codecs.h" "$(@D)/python_include/codecs.h" && cp "/usr/include/python2.7/compile.h" "$(@D)/python_include/compile.h" && cp "/usr/include/python2.7/complexobject.h" "$(@D)/python_include/complexobject.h" && cp "/usr/include/python2.7/datetime.h" "$(@D)/python_include/datetime.h" && cp "/usr/include/python2.7/descrobject.h" "$(@D)/python_include/descrobject.h" && cp "/usr/include/python2.7/dictobject.h" "$(@D)/python_include/dictobject.h" && cp "/usr/include/python2.7/dtoa.h" "$(@D)/python_include/dtoa.h" && cp "/usr/include/python2.7/enumobject.h" "$(@D)/python_include/enumobject.h" && cp "/usr/include/python2.7/errcode.h" "$(@D)/python_include/errcode.h" && cp "/usr/include/python2.7/eval.h" "$(@D)/python_include/eval.h" && cp "/usr/include/python2.7/fileobject.h" "$(@D)/python_include/fileobject.h" && cp "/usr/include/python2.7/floatobject.h" "$(@D)/python_include/floatobject.h" && cp "/usr/include/python2.7/frameobject.h" "$(@D)/python_include/frameobject.h" && cp "/usr/include/python2.7/funcobject.h" "$(@D)/python_include/funcobject.h" && cp "/usr/include/python2.7/genobject.h" "$(@D)/python_include/genobject.h" && cp "/usr/include/python2.7/graminit.h" "$(@D)/python_include/graminit.h" && cp "/usr/include/python2.7/grammar.h" "$(@D)/python_include/grammar.h" && cp "/usr/include/python2.7/import.h" "$(@D)/python_include/import.h" && cp "/usr/include/python2.7/intobject.h" "$(@D)/python_include/intobject.h" && cp "/usr/include/python2.7/intrcheck.h" "$(@D)/python_include/intrcheck.h" && cp "/usr/include/python2.7/iterobject.h" "$(@D)/python_include/iterobject.h" && cp "/usr/include/python2.7/listobject.h" "$(@D)/python_include/listobject.h" && cp "/usr/include/python2.7/longintrepr.h" "$(@D)/python_include/longintrepr.h" && cp "/usr/include/python2.7/longobject.h" "$(@D)/python_include/longobject.h" && cp "/usr/include/python2.7/marshal.h" "$(@D)/python_include/marshal.h" && cp "/usr/include/python2.7/memoryobject.h" "$(@D)/python_include/memoryobject.h" && cp "/usr/include/python2.7/metagrammar.h" "$(@D)/python_include/metagrammar.h" && cp "/usr/include/python2.7/methodobject.h" "$(@D)/python_include/methodobject.h" && cp "/usr/include/python2.7/modsupport.h" "$(@D)/python_include/modsupport.h" && cp "/usr/include/python2.7/moduleobject.h" "$(@D)/python_include/moduleobject.h" && cp "/usr/include/python2.7/node.h" "$(@D)/python_include/node.h" && cp "/usr/include/python2.7/object.h" "$(@D)/python_include/object.h" && cp "/usr/include/python2.7/objimpl.h" "$(@D)/python_include/objimpl.h" && cp "/usr/include/python2.7/opcode.h" "$(@D)/python_include/opcode.h" && cp "/usr/include/python2.7/osdefs.h" "$(@D)/python_include/osdefs.h" && cp "/usr/include/python2.7/parsetok.h" "$(@D)/python_include/parsetok.h" && cp "/usr/include/python2.7/patchlevel.h" "$(@D)/python_include/patchlevel.h" && cp "/usr/include/python2.7/pgen.h" "$(@D)/python_include/pgen.h" && cp "/usr/include/python2.7/pgenheaders.h" "$(@D)/python_include/pgenheaders.h" && cp "/usr/include/python2.7/py_curses.h" "$(@D)/python_include/py_curses.h" && cp "/usr/include/python2.7/pyarena.h" "$(@D)/python_include/pyarena.h" && cp "/usr/include/python2.7/pycapsule.h" "$(@D)/python_include/pycapsule.h" && cp "/usr/include/python2.7/pyconfig.h" "$(@D)/python_include/pyconfig.h" && cp "/usr/include/python2.7/pyctype.h" "$(@D)/python_include/pyctype.h" && cp "/usr/include/python2.7/pydebug.h" "$(@D)/python_include/pydebug.h" && cp "/usr/include/python2.7/pyerrors.h" "$(@D)/python_include/pyerrors.h" && cp "/usr/include/python2.7/pyexpat.h" "$(@D)/python_include/pyexpat.h" && cp "/usr/include/python2.7/pyfpe.h" "$(@D)/python_include/pyfpe.h" && cp "/usr/include/python2.7/pygetopt.h" "$(@D)/python_include/pygetopt.h" && cp "/usr/include/python2.7/pymacconfig.h" "$(@D)/python_include/pymacconfig.h" && cp "/usr/include/python2.7/pymactoolbox.h" "$(@D)/python_include/pymactoolbox.h" && cp "/usr/include/python2.7/pymath.h" "$(@D)/python_include/pymath.h" && cp "/usr/include/python2.7/pymem.h" "$(@D)/python_include/pymem.h" && cp "/usr/include/python2.7/pyport.h" "$(@D)/python_include/pyport.h" && cp "/usr/include/python2.7/pystate.h" "$(@D)/python_include/pystate.h" && cp "/usr/include/python2.7/pystrcmp.h" "$(@D)/python_include/pystrcmp.h" && cp "/usr/include/python2.7/pystrtod.h" "$(@D)/python_include/pystrtod.h" && cp "/usr/include/python2.7/pythonrun.h" "$(@D)/python_include/pythonrun.h" && cp "/usr/include/python2.7/pythread.h" "$(@D)/python_include/pythread.h" && cp "/usr/include/python2.7/rangeobject.h" "$(@D)/python_include/rangeobject.h" && cp "/usr/include/python2.7/setobject.h" "$(@D)/python_include/setobject.h" && cp "/usr/include/python2.7/sliceobject.h" "$(@D)/python_include/sliceobject.h" && cp "/usr/include/python2.7/stringobject.h" "$(@D)/python_include/stringobject.h" && cp "/usr/include/python2.7/structmember.h" "$(@D)/python_include/structmember.h" && cp "/usr/include/python2.7/structseq.h" "$(@D)/python_include/structseq.h" && cp "/usr/include/python2.7/symtable.h" "$(@D)/python_include/symtable.h" && cp "/usr/include/python2.7/sysmodule.h" "$(@D)/python_include/sysmodule.h" && cp "/usr/include/python2.7/timefuncs.h" "$(@D)/python_include/timefuncs.h" && cp "/usr/include/python2.7/token.h" "$(@D)/python_include/token.h" && cp "/usr/include/python2.7/traceback.h" "$(@D)/python_include/traceback.h" && cp "/usr/include/python2.7/tupleobject.h" "$(@D)/python_include/tupleobject.h" && cp "/usr/include/python2.7/ucnhash.h" "$(@D)/python_include/ucnhash.h" && cp "/usr/include/python2.7/unicodeobject.h" "$(@D)/python_include/unicodeobject.h" && cp "/usr/include/python2.7/warnings.h" "$(@D)/python_include/warnings.h" && cp "/usr/include/python2.7/weakrefobject.h" "$(@D)/python_include/weakrefobject.h" - """, -) - -genrule( - name = "numpy_include", - outs = [ - "numpy_include/numpy/__multiarray_api.h", - "numpy_include/numpy/__ufunc_api.h", - "numpy_include/numpy/_neighborhood_iterator_imp.h", - "numpy_include/numpy/_numpyconfig.h", - "numpy_include/numpy/arrayobject.h", - "numpy_include/numpy/arrayscalars.h", - "numpy_include/numpy/halffloat.h", - "numpy_include/numpy/multiarray_api.txt", - "numpy_include/numpy/ndarrayobject.h", - "numpy_include/numpy/ndarraytypes.h", - "numpy_include/numpy/noprefix.h", - "numpy_include/numpy/npy_1_7_deprecated_api.h", - "numpy_include/numpy/npy_3kcompat.h", - "numpy_include/numpy/npy_common.h", - "numpy_include/numpy/npy_cpu.h", - "numpy_include/numpy/npy_endian.h", - "numpy_include/numpy/npy_interrupt.h", - "numpy_include/numpy/npy_math.h", - "numpy_include/numpy/npy_no_deprecated_api.h", - "numpy_include/numpy/npy_os.h", - "numpy_include/numpy/numpyconfig.h", - "numpy_include/numpy/old_defines.h", - "numpy_include/numpy/oldnumeric.h", - "numpy_include/numpy/ufunc_api.txt", - "numpy_include/numpy/ufuncobject.h", - "numpy_include/numpy/utils.h", - ], - cmd = """ -cp "/usr/local/lib/python2.7/dist-packages/numpy/core/include/numpy/__multiarray_api.h" "$(@D)/numpy_include/numpy/__multiarray_api.h" && cp "/usr/local/lib/python2.7/dist-packages/numpy/core/include/numpy/__ufunc_api.h" "$(@D)/numpy_include/numpy/__ufunc_api.h" && cp "/usr/local/lib/python2.7/dist-packages/numpy/core/include/numpy/_neighborhood_iterator_imp.h" "$(@D)/numpy_include/numpy/_neighborhood_iterator_imp.h" && cp "/usr/local/lib/python2.7/dist-packages/numpy/core/include/numpy/_numpyconfig.h" "$(@D)/numpy_include/numpy/_numpyconfig.h" && cp "/usr/local/lib/python2.7/dist-packages/numpy/core/include/numpy/arrayobject.h" "$(@D)/numpy_include/numpy/arrayobject.h" && cp "/usr/local/lib/python2.7/dist-packages/numpy/core/include/numpy/arrayscalars.h" "$(@D)/numpy_include/numpy/arrayscalars.h" && cp "/usr/local/lib/python2.7/dist-packages/numpy/core/include/numpy/halffloat.h" "$(@D)/numpy_include/numpy/halffloat.h" && cp "/usr/local/lib/python2.7/dist-packages/numpy/core/include/numpy/multiarray_api.txt" "$(@D)/numpy_include/numpy/multiarray_api.txt" && cp "/usr/local/lib/python2.7/dist-packages/numpy/core/include/numpy/ndarrayobject.h" "$(@D)/numpy_include/numpy/ndarrayobject.h" && cp "/usr/local/lib/python2.7/dist-packages/numpy/core/include/numpy/ndarraytypes.h" "$(@D)/numpy_include/numpy/ndarraytypes.h" && cp "/usr/local/lib/python2.7/dist-packages/numpy/core/include/numpy/noprefix.h" "$(@D)/numpy_include/numpy/noprefix.h" && cp "/usr/local/lib/python2.7/dist-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h" "$(@D)/numpy_include/numpy/npy_1_7_deprecated_api.h" && cp "/usr/local/lib/python2.7/dist-packages/numpy/core/include/numpy/npy_3kcompat.h" "$(@D)/numpy_include/numpy/npy_3kcompat.h" && cp "/usr/local/lib/python2.7/dist-packages/numpy/core/include/numpy/npy_common.h" "$(@D)/numpy_include/numpy/npy_common.h" && cp "/usr/local/lib/python2.7/dist-packages/numpy/core/include/numpy/npy_cpu.h" "$(@D)/numpy_include/numpy/npy_cpu.h" && cp "/usr/local/lib/python2.7/dist-packages/numpy/core/include/numpy/npy_endian.h" "$(@D)/numpy_include/numpy/npy_endian.h" && cp "/usr/local/lib/python2.7/dist-packages/numpy/core/include/numpy/npy_interrupt.h" "$(@D)/numpy_include/numpy/npy_interrupt.h" && cp "/usr/local/lib/python2.7/dist-packages/numpy/core/include/numpy/npy_math.h" "$(@D)/numpy_include/numpy/npy_math.h" && cp "/usr/local/lib/python2.7/dist-packages/numpy/core/include/numpy/npy_no_deprecated_api.h" "$(@D)/numpy_include/numpy/npy_no_deprecated_api.h" && cp "/usr/local/lib/python2.7/dist-packages/numpy/core/include/numpy/npy_os.h" "$(@D)/numpy_include/numpy/npy_os.h" && cp "/usr/local/lib/python2.7/dist-packages/numpy/core/include/numpy/numpyconfig.h" "$(@D)/numpy_include/numpy/numpyconfig.h" && cp "/usr/local/lib/python2.7/dist-packages/numpy/core/include/numpy/old_defines.h" "$(@D)/numpy_include/numpy/old_defines.h" && cp "/usr/local/lib/python2.7/dist-packages/numpy/core/include/numpy/oldnumeric.h" "$(@D)/numpy_include/numpy/oldnumeric.h" && cp "/usr/local/lib/python2.7/dist-packages/numpy/core/include/numpy/ufunc_api.txt" "$(@D)/numpy_include/numpy/ufunc_api.txt" && cp "/usr/local/lib/python2.7/dist-packages/numpy/core/include/numpy/ufuncobject.h" "$(@D)/numpy_include/numpy/ufuncobject.h" && cp "/usr/local/lib/python2.7/dist-packages/numpy/core/include/numpy/utils.h" "$(@D)/numpy_include/numpy/utils.h" - """, -) -- GitLab From bf72395d44a6ffc3e281adc9b4157e37c0e2f7ca Mon Sep 17 00:00:00 2001 From: Siju Date: Tue, 26 Feb 2019 15:58:33 +0530 Subject: [PATCH 551/766] Update README.md --- tensorflow/lite/experimental/micro/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/experimental/micro/README.md index 4f7cfae5c5..9eeae442ee 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/experimental/micro/README.md @@ -304,7 +304,7 @@ Follow these steps to get the pushbutton yes/no example working on Apollo 3: this one second 5. The debugger will print out four numbers. They are the probabilites for 1. no speech - 2. unkown speech + 2. unknown speech 3. yes 4. no 6. The EVB LEDs will indicate detection. -- GitLab From c8814302770be9a58b5a09b73fd8a7261f8c0cfc Mon Sep 17 00:00:00 2001 From: Siju Date: Tue, 26 Feb 2019 16:01:46 +0530 Subject: [PATCH 552/766] Update demo_ios.md --- tensorflow/lite/g3doc/demo_ios.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/g3doc/demo_ios.md b/tensorflow/lite/g3doc/demo_ios.md index 33e74f1a40..b87616bb21 100644 --- a/tensorflow/lite/g3doc/demo_ios.md +++ b/tensorflow/lite/g3doc/demo_ios.md @@ -120,7 +120,7 @@ model = tflite::FlatBufferModel::BuildFromFile([graph_path UTF8String]); Behind the scenes, the model is loaded as a memory-mapped file. It offers faster load times and reduce the dirty pages in memory. -Construct a `BuiltinOpResolver` to use the TensorFliw Lite buildin ops. Then, +Construct a `BuiltinOpResolver` to use the TensorFlow Lite buildin ops. Then, create the interpreter object using `InterpreterBuilder` that takes the model file as argument as shown below. -- GitLab From 193b391512c78776fbbc808c21371d7a92acce4b Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 26 Feb 2019 02:39:52 -0800 Subject: [PATCH 553/766] Use the language "same structure layout" instead of "same structure" This removes confusion of whether results are stored "in place". PiperOrigin-RevId: 235686845 --- tensorflow/python/util/nest.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/tensorflow/python/util/nest.py b/tensorflow/python/util/nest.py index 9a1241cff3..8fb187fb5b 100644 --- a/tensorflow/python/util/nest.py +++ b/tensorflow/python/util/nest.py @@ -322,8 +322,8 @@ def flatten_dict_items(dictionary): Raises: TypeError: If the input is not a dictionary. - ValueError: If any key and value have not the same structure, or if keys are - not unique. + ValueError: If any key and value do not have the same structure layout, or + if keys are not unique. """ if not isinstance(dictionary, (dict, _collections.Mapping)): raise TypeError("input must be a dictionary") @@ -447,7 +447,7 @@ def map_structure(func, *structure, **kwargs): Applies `func(x[0], x[1], ...)` where x[i] is an entry in `structure[i]`. All structures in `structure` must have the same arity, - and the return value will contain the results in the same structure. + and the return value will contain results with the same structure layout. Args: func: A callable that accepts as many arguments as there are structures. @@ -513,9 +513,9 @@ def map_structure_with_paths(func, *structure, **kwargs): Applies `func(path, x[0], x[1], ..., **kwargs)` where x[i] is an entry in `structure[i]` and `path` is the common path to x[i] in the structures. All structures in `structure` must have the same arity, and the return value will - contain the results in the same structure. Special kwarg `check_types` - determines whether the types of iterables within the structure must be the - same-- see **kwargs definition below. + contain the results with the same structure layout. Special kwarg + `check_types` determines whether the types of iterables within the structure + must be the same-- see **kwargs definition below. Args: func: A callable with the signature func(path, *values, **kwargs) that is @@ -714,8 +714,8 @@ def flatten_up_to(shallow_tree, input_tree, check_types=True): of the nested structure. We achieve this by specifying a shallow structure, `shallow_tree`, we wish to flatten up to. - The input, `input_tree`, can be thought of as having the same structure as - `shallow_tree`, but with leaf nodes that are themselves tree structures. + The input, `input_tree`, can be thought of as having the same structure layout + as `shallow_tree`, but with leaf nodes that are themselves tree structures. Examples: @@ -793,8 +793,8 @@ def flatten_with_tuple_paths_up_to(shallow_tree, input_tree, check_types=True): of the nested structure. We achieve this by specifying a shallow structure, `shallow_tree`, we wish to flatten up to. - The input, `input_tree`, can be thought of as having the same structure as - `shallow_tree`, but with leaf nodes that are themselves tree structures. + The input, `input_tree`, can be thought of as having the same structure layout + as `shallow_tree`, but with leaf nodes that are themselves tree structures. Examples: @@ -881,7 +881,7 @@ def map_structure_up_to(shallow_tree, func, *inputs, **kwargs): achieve this by specifying a shallow structure, `shallow_tree` we wish to flatten up to. - The `inputs`, can be thought of as having the same structure as + The `inputs`, can be thought of as having the same structure layout as `shallow_tree`, but with leaf nodes that are themselves tree structures. This function therefore will return something with the same base structure as @@ -940,7 +940,7 @@ def map_structure_up_to(shallow_tree, func, *inputs, **kwargs): `input_tree`. Returns: - result of repeatedly applying `func`, with same structure as + result of repeatedly applying `func`, with the same structure layout as `shallow_tree`. """ return map_structure_with_tuple_paths_up_to( @@ -1012,7 +1012,8 @@ def map_structure_with_tuple_paths_up_to(shallow_tree, func, *inputs, **kwargs): `input_tree`. Returns: - Result of repeatedly applying `func`. Has same structure as `shallow_tree`. + Result of repeatedly applying `func`. Has the same structure layout as + `shallow_tree`. """ if not inputs: raise ValueError("Cannot map over no sequences") -- GitLab From 2de01b1a7334522314b410d53aeeb0262057369a Mon Sep 17 00:00:00 2001 From: Peter Hawkins Date: Tue, 26 Feb 2019 05:45:13 -0800 Subject: [PATCH 554/766] [XLA] Add support for generic parsing and printing of instruction attributes that are represented as a protocol buffer message. Currently for each new HLO operator we must define a parser and a pretty printer, which is tedious to write. However we already have a schema that describes the attributes of an operator, namely its protocol buffer representation. If we are careful to structure the attributes of each operator as a single protobuf message, then we can write generic printers and parsers using protocol buffer reflection. This change adds limited generic printer and parser support sufficient for the triangular solve HLO. Change in preparation for adding more linear algebra operators. PiperOrigin-RevId: 235705697 --- tensorflow/compiler/xla/service/BUILD | 1 + .../compiler/xla/service/hlo_instructions.cc | 50 +++++- tensorflow/compiler/xla/service/hlo_parser.cc | 158 ++++++++++++------ .../compiler/xla/service/hlo_parser_test.cc | 13 ++ 4 files changed, 164 insertions(+), 58 deletions(-) diff --git a/tensorflow/compiler/xla/service/BUILD b/tensorflow/compiler/xla/service/BUILD index f2e85a5abf..4a668d0c28 100644 --- a/tensorflow/compiler/xla/service/BUILD +++ b/tensorflow/compiler/xla/service/BUILD @@ -3756,6 +3756,7 @@ cc_library( "//tensorflow/compiler/xla:statusor", "//tensorflow/compiler/xla:util", "//tensorflow/compiler/xla:xla_data_proto", + "//tensorflow/core:lib", "//tensorflow/core:lib_internal", "@com_google_absl//absl/algorithm:container", "@com_google_absl//absl/memory", diff --git a/tensorflow/compiler/xla/service/hlo_instructions.cc b/tensorflow/compiler/xla/service/hlo_instructions.cc index 905a6fe08b..f8eef78531 100644 --- a/tensorflow/compiler/xla/service/hlo_instructions.cc +++ b/tensorflow/compiler/xla/service/hlo_instructions.cc @@ -30,6 +30,7 @@ limitations under the License. #include "tensorflow/compiler/xla/service/hlo_module.h" #include "tensorflow/compiler/xla/service/hlo_sharding_metadata.h" #include "tensorflow/compiler/xla/window_util.h" +#include "tensorflow/core/platform/protobuf.h" namespace xla { namespace { @@ -216,16 +217,49 @@ HloInstructionProto HloTriangularSolveInstruction::ToProto() const { return proto; } +namespace { + +// Converts a protocol buffer message (e.g., TriangularSolveOptions) to a vector +// of "key=value" attribute strings generically, using protocol buffer +// reflection. +// +// Currently implements a small subset of cases; feel free to add more as +// needed. +std::vector AttributeProtoToStringVector( + const tensorflow::protobuf::Message& message) { + const tensorflow::protobuf::Reflection* reflection = message.GetReflection(); + std::vector fields; + reflection->ListFields(message, &fields); + + std::vector output; + for (const tensorflow::protobuf::FieldDescriptor* field : fields) { + string s = absl::StrCat(field->name(), "="); + CHECK(!field->is_repeated()) << "Repeated fields aren't implemented"; + switch (field->type()) { + case tensorflow::protobuf::FieldDescriptor::TYPE_BOOL: { + bool val = reflection->GetBool(message, field); + absl::StrAppend(&s, val ? "true" : "false"); + break; + } + case tensorflow::protobuf::FieldDescriptor::TYPE_ENUM: { + const tensorflow::protobuf::EnumValueDescriptor* evd = + reflection->GetEnum(message, field); + absl::StrAppend(&s, evd->name()); + break; + } + default: + LOG(FATAL) << "Unimplemented field type: " << field->DebugString(); + } + output.push_back(std::move(s)); + } + return output; +} + +} // namespace + std::vector HloTriangularSolveInstruction::ExtraAttributesToStringImpl( const HloPrintOptions& options) const { - return { - StrCat("left_side=", - triangular_solve_options_.left_side() ? "true" : "false"), - StrCat("lower=", triangular_solve_options_.lower() ? "true" : "false"), - StrCat("unit_diagonal=", - triangular_solve_options_.unit_diagonal() ? "true" : "false"), - StrCat("transpose_a=", TriangularSolveOptions_Transpose_Name( - triangular_solve_options_.transpose_a()))}; + return AttributeProtoToStringVector(triangular_solve_options_); } bool HloTriangularSolveInstruction::IdenticalSlowPath( diff --git a/tensorflow/compiler/xla/service/hlo_parser.cc b/tensorflow/compiler/xla/service/hlo_parser.cc index 4aa1090f48..601748e54b 100644 --- a/tensorflow/compiler/xla/service/hlo_parser.cc +++ b/tensorflow/compiler/xla/service/hlo_parser.cc @@ -35,6 +35,7 @@ limitations under the License. #include "tensorflow/compiler/xla/shape_util.h" #include "tensorflow/compiler/xla/util.h" #include "tensorflow/core/lib/gtl/map_util.h" +#include "tensorflow/core/platform/protobuf.h" namespace xla { @@ -180,7 +181,6 @@ class HloParser { kBracedInt64ListList, kHloComputation, kFftType, - kTriangularSolveTranspose, kWindow, kConvolutionDimensionNumbers, kSharding, @@ -237,6 +237,21 @@ class HloParser { bool ParseAttributeHelper(const std::unordered_map& attrs, std::unordered_set* seen_attrs); + // Parses an attribute string into a protocol buffer `message`. + // Since proto3 has no notion of mandatory fields, `required_attrs` gives the + // set of mandatory attributes. + bool ParseAttributesAsProtoMessage( + const std::unordered_set& required_attrs, + tensorflow::protobuf::Message* message); + + // Parses one attribute. If it has already been seen, return error. Returns + // true and adds to seen_attrs on success. + // + // Do not call this except in ParseAttributesAsProtoMessage. + bool ParseAttributeAsProtoMessageHelper( + tensorflow::protobuf::Message* message, + std::unordered_set* seen_attrs); + // Parses a name and finds the corresponding hlo computation. bool ParseComputationName(HloComputation** value); // Parses a list of names and finds the corresponding hlo instructions. @@ -281,7 +296,6 @@ class HloParser { bool ParseTiles(std::vector* tiles); bool ParseOpcode(HloOpcode* result); bool ParseFftType(FftType* result); - bool ParseTriangularSolveTranspose(TriangularSolveOptions::Transpose* result); bool ParseFusionKind(HloInstruction::FusionKind* result); bool ParseRandomDistribution(RandomDistribution* result); bool ParsePrecision(PrecisionConfig::Precision* result); @@ -1104,32 +1118,12 @@ bool HloParser::ParseInstructionRhs(HloComputation::Builder* builder, break; } case HloOpcode::kTriangularSolve: { - optional left_side; - optional lower; - optional unit_diagonal; - optional transpose_a; - attrs["left_side"] = {/*required=*/false, AttrTy::kBool, &left_side}; - attrs["lower"] = {/*required=*/false, AttrTy::kBool, &lower}; - attrs["unit_diagonal"] = {/*required=*/false, AttrTy::kBool, - &unit_diagonal}; - attrs["transpose_a"] = {/*required=*/false, - AttrTy::kTriangularSolveTranspose, &transpose_a}; + TriangularSolveOptions options; if (!ParseOperands(&operands, /*expected_size=*/2) || - !ParseAttributes(attrs)) { + !ParseAttributesAsProtoMessage( + /*required_attrs=*/std::unordered_set(), &options)) { return false; } - TriangularSolveOptions options; - if (left_side) { - options.set_left_side(*left_side); - } - if (lower) { - options.set_lower(*lower); - } - if (unit_diagonal) { - options.set_unit_diagonal(*unit_diagonal); - } - options.set_transpose_a( - transpose_a ? *transpose_a : TriangularSolveOptions::NO_TRANSPOSE); instruction = builder->AddInstruction(HloInstruction::CreateTriangularSolve( shape, operands[0], operands[1], options)); @@ -2700,15 +2694,6 @@ bool HloParser::ParseAttributeHelper( static_cast*>(attr_out_ptr)->emplace(result); return true; } - case AttrTy::kTriangularSolveTranspose: { - TriangularSolveOptions::Transpose result; - if (!ParseTriangularSolveTranspose(&result)) { - return false; - } - static_cast*>(attr_out_ptr) - ->emplace(result); - return true; - } case AttrTy::kWindow: { Window result; if (!ParseWindow(&result, /*expect_outer_curlies=*/true)) { @@ -2861,6 +2846,95 @@ bool HloParser::ParseAttributeHelper( return true; } +// attributes ::= (',' attribute)* +bool HloParser::ParseAttributesAsProtoMessage( + const std::unordered_set& required_attrs, + tensorflow::protobuf::Message* message) { + LocTy loc = lexer_.GetLoc(); + std::unordered_set seen_attrs; + while (EatIfPresent(TokKind::kComma)) { + if (!ParseAttributeAsProtoMessageHelper(message, &seen_attrs)) { + return false; + } + } + // Check that all required attrs were seen. + for (const string& attr : required_attrs) { + if (seen_attrs.find(attr) == seen_attrs.end()) { + return Error(loc, + StrFormat("attribute %s is expected but not seen", attr)); + } + } + return true; +} + +bool HloParser::ParseAttributeAsProtoMessageHelper( + tensorflow::protobuf::Message* message, + std::unordered_set* seen_attrs) { + LocTy loc = lexer_.GetLoc(); + string name; + if (!ParseAttributeName(&name)) { + return Error(loc, "error parsing attributes"); + } + VLOG(1) << "Parsing attribute " << name; + if (!seen_attrs->insert(name).second) { + return Error(loc, StrFormat("attribute %s already exists", name)); + } + const tensorflow::protobuf::Descriptor* descriptor = message->GetDescriptor(); + const tensorflow::protobuf::FieldDescriptor* fd = + descriptor->FindFieldByName(name); + if (!fd) { + string allowed_attrs = "Allowed attributes: "; + + for (int i = 0; i < descriptor->field_count(); ++i) { + if (i == 0) { + absl::StrAppend(&allowed_attrs, descriptor->field(i)->name()); + } else { + absl::StrAppend(&allowed_attrs, ", ", descriptor->field(i)->name()); + } + } + return Error(loc, StrFormat("unexpected attribute \"%s\". %s", name, + allowed_attrs)); + } + const tensorflow::protobuf::Reflection* reflection = message->GetReflection(); + CHECK(!fd->is_repeated()); // Repeated fields not implemented. + bool success = [&] { + switch (fd->type()) { + case tensorflow::protobuf::FieldDescriptor::TYPE_BOOL: { + bool result; + if (!ParseBool(&result)) { + return false; + } + reflection->SetBool(message, fd, result); + return true; + } + case tensorflow::protobuf::FieldDescriptor::TYPE_ENUM: { + if (lexer_.GetKind() != TokKind::kIdent) { + return TokenError( + StrFormat("expects %s type", fd->enum_type()->name())); + } + string val = lexer_.GetStrVal(); + const tensorflow::protobuf::EnumValueDescriptor* evd = + fd->enum_type()->FindValueByName(val); + if (evd == nullptr) { + return TokenError(StrFormat("expects %s type but sees: %s", + fd->enum_type()->name(), val)); + } + reflection->SetEnum(message, fd, evd); + lexer_.Lex(); + return true; + } + default: + LOG(ERROR) << "Unimplemented protocol buffer type " + << fd->DebugString(); + return false; + } + }(); + if (!success) { + return Error(loc, StrFormat("error parsing attribute %s", name)); + } + return true; +} + bool HloParser::ParseComputationName(HloComputation** value) { string name; LocTy loc = lexer_.GetLoc(); @@ -3625,22 +3699,6 @@ bool HloParser::ParseFftType(FftType* result) { return true; } -bool HloParser::ParseTriangularSolveTranspose( - TriangularSolveOptions::Transpose* result) { - VLOG(1) << "ParseTriangularSolveTranspose"; - if (lexer_.GetKind() != TokKind::kIdent) { - return TokenError("expects triangular solve transpose type"); - } - string val = lexer_.GetStrVal(); - if (!TriangularSolveOptions_Transpose_Parse(val, result) || - !TriangularSolveOptions_Transpose_IsValid(*result)) { - return TokenError( - StrFormat("expects triangular solve transpose type but sees: %s", val)); - } - lexer_.Lex(); - return true; -} - bool HloParser::ParseFusionKind(HloInstruction::FusionKind* result) { VLOG(1) << "ParseFusionKind"; if (lexer_.GetKind() != TokKind::kIdent) { diff --git a/tensorflow/compiler/xla/service/hlo_parser_test.cc b/tensorflow/compiler/xla/service/hlo_parser_test.cc index 8e3f1e44b9..83f9960014 100644 --- a/tensorflow/compiler/xla/service/hlo_parser_test.cc +++ b/tensorflow/compiler/xla/service/hlo_parser_test.cc @@ -575,6 +575,19 @@ ENTRY %Transpose.v3 (input: c128[1,2,3]) -> c128[1,2,3] { ROOT %transpose = c128[1,2,3]{2,1,0} transpose(c128[1,2,3]{2,1,0} %input), dimensions={0,1,2} } +)" +}, +// Triangular solve +{ +"TriangularSolve", +R"(HloModule TriangularSolve_module + +ENTRY %SimpleRightLowerNotranspose.4 (a.1: f32[4,4], b.2: f32[3,4]) -> f32[3,4] { + %a.1 = f32[4,4]{1,0} parameter(0) + %b.2 = f32[3,4]{1,0} parameter(1) + ROOT %triangular-solve.3 = f32[3,4]{1,0} triangular-solve(f32[4,4]{1,0} %a.1, f32[3,4]{1,0} %b.2), lower=true, transpose_a=NO_TRANSPOSE +} + )" }, // Dynamic slice -- GitLab From 4ede94e8a3c817ecb80f41eafd73c0058e58c6d7 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 26 Feb 2019 06:41:00 -0800 Subject: [PATCH 555/766] Fix simple_features_generator_test with CMSIS specialization. PiperOrigin-RevId: 235712144 --- .../micro/examples/micro_speech/CMSIS/Makefile.inc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/Makefile.inc b/tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/Makefile.inc index 73b884f29d..22134152af 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/Makefile.inc +++ b/tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/Makefile.inc @@ -26,6 +26,9 @@ ifneq ($(filter CMSIS,$(ALL_TAGS)),) FEATURE_PROVIDER_TEST_SRCS += $(CMSIS_PREPROCESSOR_SRCS) FEATURE_PROVIDER_TEST_HDRS += $(CMSIS_PREPROCESSOR_HDRS) + SIMPLE_FEATURES_GENERATOR_TEST_SRCS += $(CMSIS_PREPROCESSOR_SRCS) + SIMPLE_FEATURES_GENERATOR_TEST_HDRS += $(CMSIS_PREPROCESSOR_HDRS) + MICRO_SPEECH_SRCS += $(CMSIS_PREPROCESSOR_SRCS) MICRO_SPEECH_HDRS += $(CMSIS_PREPROCESSOR_HDRS) -- GitLab From 458e0e300bbb8e9c9824a960fef13877983369f4 Mon Sep 17 00:00:00 2001 From: Peter Hawkins Date: Tue, 26 Feb 2019 06:49:31 -0800 Subject: [PATCH 556/766] [XLA:Python] Remove unused dependencies of xla_client.py on protocol buffers. PiperOrigin-RevId: 235713110 --- tensorflow/compiler/xla/python/BUILD | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tensorflow/compiler/xla/python/BUILD b/tensorflow/compiler/xla/python/BUILD index 55eacc1c16..92ace7877e 100644 --- a/tensorflow/compiler/xla/python/BUILD +++ b/tensorflow/compiler/xla/python/BUILD @@ -13,8 +13,6 @@ py_library( visibility = ["//visibility:public"], deps = [ ":pywrap_xla", - "//tensorflow/compiler/xla:xla_data_proto_py", - "//tensorflow/compiler/xla/service:hlo_proto_py", ], ) @@ -33,6 +31,7 @@ py_test( deps = [ ":custom_call_for_test", ":xla_client", + "//tensorflow/compiler/xla:xla_data_proto_py", "//tensorflow/python:platform_test", ], ) -- GitLab From c5739a813578636a75de131956510ed222e47ce6 Mon Sep 17 00:00:00 2001 From: Sachin Joglekar Date: Tue, 26 Feb 2019 07:55:30 -0800 Subject: [PATCH 557/766] FAQs page for TFLite PiperOrigin-RevId: 235721415 --- tensorflow/lite/g3doc/faq.md | 132 +++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 tensorflow/lite/g3doc/faq.md diff --git a/tensorflow/lite/g3doc/faq.md b/tensorflow/lite/g3doc/faq.md new file mode 100644 index 0000000000..5808391bdd --- /dev/null +++ b/tensorflow/lite/g3doc/faq.md @@ -0,0 +1,132 @@ +# Frequently Asked Questions + +If you don't find an answer to your question here, please look through our +detailed documentation for the topic or file a +[GitHub issue](https://github.com/tensorflow/tensorflow/issues). + +## Model Conversion + +#### What formats are supported for conversion from TensorFlow to TensorFlow Lite? + +The TensorFlow Lite converter supports the following formats: + +* SavedModels: + [TFLiteConverter.from_saved_model](convert/python_api.md#exporting_a_savedmodel_) +* Frozen GraphDefs generated by + [freeze_graph.py](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/tools/freeze_graph.py): + [TFLiteConverter.from_frozen_graph](convert/python_api#exporting_a_graphdef_from_file_) +* tf.keras HDF5 models: + [TFLiteConverter.from_keras_model_file](convert/python_api#exporting_a_tfkeras_file_) +* tf.Session: + [TFLiteConverter.from_session](python_api#exporting_a_graphdef_from_tfsession_) + +The recommended approach is to integrate the +[Python converter](convert/python_api.md) into your model pipeline in order to +detect compatibility issues early on. + +#### Why doesn't my model convert? + +Since the number of TensorFlow Lite operations is smaller than TensorFlow's, +some inference models may not be able to convert. For unimplemented operations, +take a look at the question on +[missing operators](faq.md#why-is-some-operator-not-implemented-in-tensorflow-lite). +For conversion issues not related to missing operations, search our +[GitHub issues](https://github.com/tensorflow/tensorflow/issues?q=label%3Acomp%3Alite+) +or file a [new one](https://github.com/tensorflow/tensorflow/issues). + +#### How do I determine the inputs/outputs for GraphDef protocol buffer? + +The recommended approach is to visualize the GraphDef with +[TensorBoard](https://www.tensorflow.org/guide/summaries_and_tensorboard) and +look for the inputs and outputs in the graph. + +To visualize a `.pb` file, use the +[`import_pb_to_tensorboard.py`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/tools/import_pb_to_tensorboard.py) +script like below: + +``` +python import_pb_to_tensorboard.py --model_dir --log_dir +``` + +#### How do I inspect a `.tflite` file? + +TensorFlow Lite models can be visualized using the +[visualize.py](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/tools/visualize.py) +script in our repository. + +* [Clone the TensorFlow repository](https://www.tensorflow.org/install/source) +* Run the `visualize.py` script with bazel: + +``` +bazel run //tensorflow/lite/tools:visualize model.tflite visualized_model.html +``` + +## Models & Operations + +#### Why are some operations not implemented in TensorFlow Lite? + +In order to keep TensorFlow Lite lightweight, only certain operations were used +in the converter. The [Compatibility Guide](tf_ops_compatibility.md) provides a +list of operations currently supported by TensorFlow Lite. + +If you don’t see a specific operation (or an equivalent) listed, it's likely +that it has not been prioritized. The team tracks requests for new operations on +GitHub [issue #21526](https://github.com/tensorflow/tensorflow/issues/21526). +Leave a comment if your request hasn’t already been mentioned. + +In the meanwhile, you could try implementing a +[custom operator](custom_operators.md) or using a different model that only +contains supported operators. If binary size is not a constraint, try using +TensorFlow Lite with [select TensorFlow ops](using_select_tf_ops.md). + +#### How do I test that a TensorFlow Lite model behaves the same as the original TensorFlow model? + +The best way to test the behavior of a TensorFlow Lite model is to use our API +with test data and compare the outputs to TensorFlow for the same inputs. Take a +look at our [Python Interpreter example](convert/python_api.md) that generates +random data to feed to the interpreter. + +## Optimization + +#### How do I reduce the size of my converted TensorFlow Lite model? + +[Post-training quantization](performance/post_training_quantization.md) can be +used during conversion to TensorFlow Lite to reduce the size of the model. +Post-training quantization quantizes weights to 8-bits of precision from +floating-point and dequantizes them during runtime to perform floating point +computations. However, note that this could have some accuracy implications. + +If retraining the model is an option, consider +[Quantization-aware training](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/quantize/README.md). +However, note that quantization-aware training is only available for a subset of +convolutional neural network architectures. + +For a deeper understanding of different optimization methods, look at +[Model optimization](performance/model_optimization.md). + +#### How do I optimize TensorFlow Lite performance for my machine learning task? + +The high-level process to optimize TensorFlow Lite performance looks something +like this: + +* *Make sure that you have the right model for the task.* For image + classification, check out our [list of hosted models](models.md). +* *Tweak the number of threads.* Many TensorFlow Lite operators support + multi-threaded kernels. You can use `SetNumThreads()` in the + [C++ API](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/interpreter.h#L345) + to do this. However, increasing threads results in performance variability + depending on the environment. +* *Use Hardware Accelerators.* TensorFlow Lite supports model acceleration for + specific hardware using delegates. For example, to use Android’s Neural + Networks API, call + [`UseNNAPI`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/interpreter.h#L343) + on the interpreter. Or take a look at our + [GPU delegate tutorial](performance/gpu.md). +* *(Advanced) Profile Model.* The Tensorflow Lite + [benchmarking tool](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/tools/benchmark) + has a built-in profiler that can show per-operator statistics. If you know + how you can optimize an operator’s performance for your specific platform, + you can implement a [custom operator](custom_operators.md). + +For a more in-depth discussion on how to optimize performance, take a look at +[Best Practices](performance/best_practices.md). -- GitLab From 91ec48b047d1be32846fad4203d4a78e9d68c38d Mon Sep 17 00:00:00 2001 From: "Joshua V. Dillon" Date: Tue, 26 Feb 2019 09:13:37 -0800 Subject: [PATCH 558/766] Correctly have `tf.get_static_value` return `None` for `tf.is_tensor(x) and not isinstance(x, tf.Tensor)`. PiperOrigin-RevId: 235733032 --- tensorflow/python/framework/tensor_util.py | 5 +++-- tensorflow/python/framework/tensor_util_test.py | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/framework/tensor_util.py b/tensorflow/python/framework/tensor_util.py index fe09e76997..08895f2366 100644 --- a/tensorflow/python/framework/tensor_util.py +++ b/tensorflow/python/framework/tensor_util.py @@ -22,7 +22,6 @@ import six from tensorflow.core.framework import tensor_pb2 from tensorflow.core.framework import tensor_shape_pb2 -from tensorflow.python import pywrap_tensorflow from tensorflow.python.framework import composite_tensor from tensorflow.python.framework import ops from tensorflow.python.framework import tensor_shape @@ -809,8 +808,10 @@ def constant_value(tensor, partial=False): # pylint: disable=invalid-name """ if isinstance(tensor, ops.EagerTensor): return tensor.numpy() - if not pywrap_tensorflow.IsTensor(tensor): + if not is_tensor(tensor): return tensor + if not isinstance(tensor, ops.Tensor): + return None ret = _ConstantValue(tensor, partial) if ret is not None: # The caller may now depend on the constant value of `tensor`, so we diff --git a/tensorflow/python/framework/tensor_util_test.py b/tensorflow/python/framework/tensor_util_test.py index e73df39035..92a0ae2cdc 100644 --- a/tensorflow/python/framework/tensor_util_test.py +++ b/tensorflow/python/framework/tensor_util_test.py @@ -30,6 +30,7 @@ from tensorflow.python.framework import test_util from tensorflow.python.ops import array_ops from tensorflow.python.ops import gen_state_ops from tensorflow.python.ops import math_ops +from tensorflow.python.ops import variables from tensorflow.python.platform import test @@ -953,6 +954,22 @@ class ConstantValueTest(test.TestCase): c_val = tensor_util.constant_value(tf_val) self.assertAllEqual(c_val, [[False, True], [True, False]]) + def testLiteral(self): + x = "hi" + self.assertIs(x, tensor_util.constant_value(x)) + + def testNumpyNdarray(self): + np_val = np.random.rand(3, 4, 7).astype(np.float32) + self.assertIs(np_val, tensor_util.constant_value(np_val)) + + def testVariable(self): + var = variables.Variable(1.0, name="variable_node") + self.assertIsNone(tensor_util.constant_value(var)) + + def testVariableV1(self): + var = variables.VariableV1(1.0, name="variable_node") + self.assertIsNone(tensor_util.constant_value(var)) + class ConstantValueAsShapeTest(test.TestCase): -- GitLab From 57e8d635b70e7d4cab30b4e5d6bc774ed7ce5ebc Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 26 Feb 2019 09:55:31 -0800 Subject: [PATCH 559/766] Removing excess namespace declarations. PiperOrigin-RevId: 235739960 --- .../distributed_runtime/rpc/eager/grpc_eager_service.h | 8 -------- .../distributed_runtime/rpc/grpc_master_service_impl.h | 8 -------- .../core/distributed_runtime/rpc/grpc_tensor_coding.h | 4 +--- .../distributed_runtime/rpc/grpc_worker_service_impl.h | 8 +++----- 4 files changed, 4 insertions(+), 24 deletions(-) diff --git a/tensorflow/core/distributed_runtime/rpc/eager/grpc_eager_service.h b/tensorflow/core/distributed_runtime/rpc/eager/grpc_eager_service.h index 521e0ac4fa..61b0237081 100644 --- a/tensorflow/core/distributed_runtime/rpc/eager/grpc_eager_service.h +++ b/tensorflow/core/distributed_runtime/rpc/eager/grpc_eager_service.h @@ -27,14 +27,6 @@ limitations under the License. #include "tensorflow/core/protobuf/eager_service.pb.h" -namespace grpc { -class CompletionQueue; -class Channel; -class RpcService; -class ServerCompletionQueue; -class ServerContext; -} // namespace grpc - namespace tensorflow { namespace eager { diff --git a/tensorflow/core/distributed_runtime/rpc/grpc_master_service_impl.h b/tensorflow/core/distributed_runtime/rpc/grpc_master_service_impl.h index 751f2633e7..6c8af761f2 100644 --- a/tensorflow/core/distributed_runtime/rpc/grpc_master_service_impl.h +++ b/tensorflow/core/distributed_runtime/rpc/grpc_master_service_impl.h @@ -27,14 +27,6 @@ limitations under the License. #include "tensorflow/core/protobuf/master.pb.h" -namespace grpc { -class CompletionQueue; -class Channel; -class RpcService; -class ServerCompletionQueue; -class ServerContext; -} // namespace grpc - namespace tensorflow { namespace grpc { diff --git a/tensorflow/core/distributed_runtime/rpc/grpc_tensor_coding.h b/tensorflow/core/distributed_runtime/rpc/grpc_tensor_coding.h index 344e95a672..9399687895 100644 --- a/tensorflow/core/distributed_runtime/rpc/grpc_tensor_coding.h +++ b/tensorflow/core/distributed_runtime/rpc/grpc_tensor_coding.h @@ -16,9 +16,7 @@ limitations under the License. #ifndef TENSORFLOW_CORE_DISTRIBUTED_RUNTIME_RPC_GRPC_TENSOR_CODING_H_ #define TENSORFLOW_CORE_DISTRIBUTED_RUNTIME_RPC_GRPC_TENSOR_CODING_H_ -namespace grpc { -class ByteBuffer; -} // namespace grpc +#include "grpcpp/impl/codegen/byte_buffer.h" namespace tensorflow { class Tensor; diff --git a/tensorflow/core/distributed_runtime/rpc/grpc_worker_service_impl.h b/tensorflow/core/distributed_runtime/rpc/grpc_worker_service_impl.h index d2ae4eeaee..c475153754 100644 --- a/tensorflow/core/distributed_runtime/rpc/grpc_worker_service_impl.h +++ b/tensorflow/core/distributed_runtime/rpc/grpc_worker_service_impl.h @@ -31,14 +31,11 @@ limitations under the License. #include "tensorflow/core/protobuf/worker.pb.h" namespace grpc { -class CompletionQueue; -class Channel; -class RpcService; -class ServerCompletionQueue; -class ServerContext; // Support parsing/unparsing of tensorflow::TensorResponse. // Wire-format is identical to RecvTensorResponse. +// This is specializing an existing template, so it's okay to do this in a +// namespace that we don't own. template <> class SerializationTraits { public: @@ -66,6 +63,7 @@ class SerializationTraits { return result; } }; + } // namespace grpc namespace tensorflow { -- GitLab From 3ae2c6691b7c6e0986d97b150c9283e5cc52c15f Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 26 Feb 2019 10:07:00 -0800 Subject: [PATCH 560/766] Adding 'half_pixel_centers' bool attribute (default False) to resize ops which when true, assumes that pixels are 0.5,0.5. This gives more correct resizes. The legacy resize ops will be called by the new resize wrapper with half_pixel_centers=True. PiperOrigin-RevId: 235742526 --- tensorflow/cc/gradients/image_grad.cc | 19 +- tensorflow/cc/gradients/image_grad_test.cc | 61 +++-- .../kernels/conv_ops_fused_image_transform.cc | 6 +- tensorflow/core/kernels/image_resizer_state.h | 43 +++- .../kernels/quantized_resize_bilinear_op.cc | 78 ++++--- .../quantized_resize_bilinear_op_test.cc | 145 ++++++------ tensorflow/core/kernels/resize_area_op.cc | 6 +- tensorflow/core/kernels/resize_bicubic_op.cc | 169 ++++++++++---- tensorflow/core/kernels/resize_bilinear_op.cc | 94 +++++--- tensorflow/core/kernels/resize_bilinear_op.h | 2 + .../core/kernels/resize_bilinear_op_gpu.cu.cc | 158 ++++++++++++- .../core/kernels/resize_bilinear_op_test.cc | 45 +++- .../kernels/resize_nearest_neighbor_op.cc | 158 +++++++++---- .../core/kernels/resize_nearest_neighbor_op.h | 6 +- .../resize_nearest_neighbor_op_gpu.cu.cc | 135 ++++++++++-- .../resize_nearest_neighbor_op_test.cc | 208 +++++++++++++++++- tensorflow/core/ops/image_ops.cc | 7 + tensorflow/python/ops/image_grad.py | 13 +- tensorflow/python/ops/image_ops_impl.py | 45 +++- .../api/golden/v1/tensorflow.image.pbtxt | 6 +- .../api/golden/v1/tensorflow.raw_ops.pbtxt | 14 +- .../api/golden/v2/tensorflow.raw_ops.pbtxt | 14 +- 22 files changed, 1130 insertions(+), 302 deletions(-) diff --git a/tensorflow/cc/gradients/image_grad.cc b/tensorflow/cc/gradients/image_grad.cc index 05c287bdc6..e7cdf861a5 100644 --- a/tensorflow/cc/gradients/image_grad.cc +++ b/tensorflow/cc/gradients/image_grad.cc @@ -29,13 +29,17 @@ Status ResizeNearestNeighborGradHelper(const Scope& scope, const Operation& op, bool align_corners; TF_RETURN_IF_ERROR( GetNodeAttr(op.node()->attrs(), "align_corners", &align_corners)); + bool half_pixel_centers; + TF_RETURN_IF_ERROR(GetNodeAttr(op.node()->attrs(), "half_pixel_centers", + &half_pixel_centers)); // The internal gradient implementation needs the shape of the input image. // x_shape = shape(x)[1:3] // = slice(shape(x), {1}, {3 - 1}) auto x_shape = Slice(scope, Shape(scope, op.input(0)), {1}, {2}); grad_outputs->push_back(internal::ResizeNearestNeighborGrad( scope, grad_inputs[0], x_shape, - internal::ResizeNearestNeighborGrad::AlignCorners(align_corners))); + internal::ResizeNearestNeighborGrad::AlignCorners(align_corners) + .HalfPixelCenters(half_pixel_centers))); grad_outputs->push_back(NoGradient()); return scope.status(); } @@ -47,9 +51,13 @@ Status ResizeBilinearGradHelper(const Scope& scope, const Operation& op, bool align_corners; TF_RETURN_IF_ERROR( GetNodeAttr(op.node()->attrs(), "align_corners", &align_corners)); + bool half_pixel_centers; + TF_RETURN_IF_ERROR(GetNodeAttr(op.node()->attrs(), "half_pixel_centers", + &half_pixel_centers)); grad_outputs->push_back(internal::ResizeBilinearGrad( scope, grad_inputs[0], op.input(0), - internal::ResizeBilinearGrad::AlignCorners(align_corners))); + internal::ResizeBilinearGrad::AlignCorners(align_corners) + .HalfPixelCenters(half_pixel_centers))); grad_outputs->push_back(NoGradient()); return scope.status(); } @@ -61,9 +69,14 @@ Status ResizeBicubicGradHelper(const Scope& scope, const Operation& op, bool align_corners; TF_RETURN_IF_ERROR( GetNodeAttr(op.node()->attrs(), "align_corners", &align_corners)); + bool half_pixel_centers; + TF_RETURN_IF_ERROR(GetNodeAttr(op.node()->attrs(), "half_pixel_centers", + &half_pixel_centers)); + grad_outputs->push_back(internal::ResizeBicubicGrad( scope, grad_inputs[0], op.input(0), - internal::ResizeBicubicGrad::AlignCorners(align_corners))); + internal::ResizeBicubicGrad::AlignCorners(align_corners) + .HalfPixelCenters(half_pixel_centers))); grad_outputs->push_back(NoGradient()); return scope.status(); } diff --git a/tensorflow/cc/gradients/image_grad_test.cc b/tensorflow/cc/gradients/image_grad_test.cc index 1d15022653..aeea0467b7 100644 --- a/tensorflow/cc/gradients/image_grad_test.cc +++ b/tensorflow/cc/gradients/image_grad_test.cc @@ -51,7 +51,8 @@ class ImageGradTest : public ::testing::Test { template void MakeOp(const OpType op_type, const Tensor& x_data, const Input& y_shape, - const bool align_corners, Output* x, Output* y) { + const bool align_corners, const bool half_pixel_centers, + Output* x, Output* y) { *x = Const(scope_, x_data); switch (op_type) { case RESIZE_NEAREST: @@ -61,22 +62,26 @@ class ImageGradTest : public ::testing::Test { return; case RESIZE_BILINEAR: *y = ResizeBilinear(scope_, *x, y_shape, - ResizeBilinear::AlignCorners(align_corners)); + ResizeBilinear::AlignCorners(align_corners) + .HalfPixelCenters(half_pixel_centers)); return; case RESIZE_BICUBIC: *y = ResizeBicubic(scope_, *x, y_shape, - ResizeBicubic::AlignCorners(align_corners)); + ResizeBicubic::AlignCorners(align_corners) + .HalfPixelCenters(half_pixel_centers)); return; } assert(false); } template - void TestResizedShapeForType(const OpType op_type, const bool align_corners) { + void TestResizedShapeForType(const OpType op_type, const bool align_corners, + const bool half_pixel_centers) { TensorShape x_shape({1, 2, 2, 1}); Tensor x_data = MakeData(x_shape); Output x, y; - MakeOp(op_type, x_data, {4, 6}, align_corners, &x, &y); + MakeOp(op_type, x_data, {4, 6}, align_corners, half_pixel_centers, &x, + &y); ClientSession session(scope_); std::vector outputs; @@ -86,44 +91,64 @@ class ImageGradTest : public ::testing::Test { } void TestResizedShape(OpType op_type) { - for (const bool align_corners : {true, false}) { - TestResizedShapeForType(op_type, align_corners); - TestResizedShapeForType(op_type, align_corners); - TestResizedShapeForType(op_type, align_corners); + for (const bool half_pixel_centers : {true, false}) { + for (const bool align_corners : {true, false}) { + if (half_pixel_centers && align_corners) { + continue; + } + TestResizedShapeForType(op_type, align_corners, + half_pixel_centers); + TestResizedShapeForType(op_type, align_corners, + half_pixel_centers); + TestResizedShapeForType(op_type, align_corners, + half_pixel_centers); + } } } template void TestResizeToSmallerAndAlign(const OpType op_type, - const bool align_corners) { + const bool align_corners, + const bool half_pixel_centers) { TensorShape x_shape({1, 4, 6, 1}); Tensor x_data = MakeData(x_shape); Output x, y; - MakeOp(op_type, x_data, {2, 3}, align_corners, &x, &y); + MakeOp(op_type, x_data, {2, 3}, align_corners, half_pixel_centers, &x, + &y); JAC_T max_error; TF_ASSERT_OK((ComputeGradientError( scope_, x, x_data, y, {1, 2, 3, 1}, &max_error))); - EXPECT_LT(max_error, 1e-3); + EXPECT_LT(max_error, 1.5e-3); } template void TestResizeToLargerAndAlign(const OpType op_type, - const bool align_corners) { + const bool align_corners, + const bool half_pixel_centers) { TensorShape x_shape({1, 2, 3, 1}); Tensor x_data = MakeData(x_shape); Output x, y; - MakeOp(op_type, x_data, {4, 6}, align_corners, &x, &y); + MakeOp(op_type, x_data, {4, 6}, align_corners, half_pixel_centers, &x, + &y); JAC_T max_error; TF_ASSERT_OK((ComputeGradientError( scope_, x, x_data, y, {1, 4, 6, 1}, &max_error))); - EXPECT_LT(max_error, 1e-3); + EXPECT_LT(max_error, 1.5e-3); } template void TestResize(OpType op_type) { - for (const bool align_corners : {true, false}) { - TestResizeToSmallerAndAlign(op_type, align_corners); - TestResizeToLargerAndAlign(op_type, align_corners); + for (const bool half_pixel_centers : {true, false}) { + for (const bool align_corners : {true, false}) { + // if (!half_pixel_centers) continue; + if (half_pixel_centers && align_corners) { + continue; + } + TestResizeToSmallerAndAlign(op_type, align_corners, + half_pixel_centers); + TestResizeToLargerAndAlign(op_type, align_corners, + half_pixel_centers); + } } } diff --git a/tensorflow/core/kernels/conv_ops_fused_image_transform.cc b/tensorflow/core/kernels/conv_ops_fused_image_transform.cc index 0542216a23..c1c3b555d6 100644 --- a/tensorflow/core/kernels/conv_ops_fused_image_transform.cc +++ b/tensorflow/core/kernels/conv_ops_fused_image_transform.cc @@ -102,7 +102,7 @@ void FusedConvParallelFor( // Holds the state needed for the resizing subtasks. template struct ResizeTaskParameters { - ResizeTaskParameters() : st(false) {} + ResizeTaskParameters() : st(false, false) {} int cache_height; T1* resize_cache; @@ -649,9 +649,9 @@ class FusedResizeConv2DUsingGemmOp : public OpKernel { OP_REQUIRES(context, (input.shape().num_elements() > 0), errors::InvalidArgument("Input tensor can't be empty")); - ImageResizerState st(false); + ImageResizerState st(false, false); if (DoResize) { - st = ImageResizerState(align_corners_); + st = ImageResizerState(align_corners_, false); st.ValidateAndCalculateOutputSize(context, input); if (!context->status().ok()) return; } else { diff --git a/tensorflow/core/kernels/image_resizer_state.h b/tensorflow/core/kernels/image_resizer_state.h index 8078c7036a..591acfb444 100644 --- a/tensorflow/core/kernels/image_resizer_state.h +++ b/tensorflow/core/kernels/image_resizer_state.h @@ -45,9 +45,29 @@ inline float CalculateResizeScale(int64 in_size, int64 out_size, : in_size / static_cast(out_size); } +// Half pixel scaler scales assuming that the pixel centers are at 0.5, i.e. the +// floating point coordinates of the top,left pixel is 0.5,0.5. +struct HalfPixelScaler { + inline float operator()(const int x, const float scale) const { + // Note that we subtract 0.5 from the return value, as the existing bilinear + // sampling code etc assumes pixels are in the old coordinate system. + return (static_cast(x) + 0.5f) * scale - 0.5f; + } +}; + +// Older incorrect scaling method that causes all resizes to have a slight +// translation leading to inconsistent results. For example, a flip then a +// resize gives different results then a resize then a flip. +struct LegacyScaler { + inline float operator()(const int x, const float scale) const { + return static_cast(x) * scale; + } +}; + struct ImageResizerState { - explicit ImageResizerState(bool align_corners) - : align_corners_(align_corners) {} + explicit ImageResizerState(bool align_corners, bool half_pixel_centers) + : align_corners_(align_corners), + half_pixel_centers_(half_pixel_centers) {} // ValidateAndCalculateOutputSize checks the bounds on the input tensors // and requested size, sets up some of the resizing state such as the @@ -56,6 +76,11 @@ struct ImageResizerState { // the context, which the caller must check. void ValidateAndCalculateOutputSize(OpKernelContext* context, const Tensor& input) { + OP_REQUIRES( + context, + !half_pixel_centers_ || (half_pixel_centers_ && !align_corners_), + errors::InvalidArgument("If half_pixel_centers is True, " + "align_corners must be False.")); OP_REQUIRES(context, input.dims() == 4, errors::InvalidArgument("input must be 4-dimensional", input.shape().DebugString())); @@ -127,14 +152,23 @@ struct ImageResizerState { private: bool align_corners_; + bool half_pixel_centers_; }; struct ImageResizerGradientState { - explicit ImageResizerGradientState(bool align_corners) - : align_corners_(align_corners) {} + explicit ImageResizerGradientState(bool align_corners, + bool half_pixel_centers) + : align_corners_(align_corners), + half_pixel_centers_(half_pixel_centers) {} void ValidateAndCreateOutput(OpKernelContext* context, const Tensor& input, const Tensor& original_image) { + OP_REQUIRES( + context, + !half_pixel_centers_ || (half_pixel_centers_ && !align_corners_), + errors::InvalidArgument("If half_pixel_centers is True, " + "align_corners must be False.")); + OP_REQUIRES(context, input.dims() == 4, errors::InvalidArgument("input_grad must be 4-dimensional", input.shape().DebugString())); @@ -187,6 +221,7 @@ struct ImageResizerGradientState { private: bool align_corners_; + bool half_pixel_centers_; }; } // namespace tensorflow diff --git a/tensorflow/core/kernels/quantized_resize_bilinear_op.cc b/tensorflow/core/kernels/quantized_resize_bilinear_op.cc index 9a1dcd0d49..59e33d1ac3 100644 --- a/tensorflow/core/kernels/quantized_resize_bilinear_op.cc +++ b/tensorflow/core/kernels/quantized_resize_bilinear_op.cc @@ -45,10 +45,11 @@ struct InterpolationCache { std::vector ilerp; }; -template +template inline void ComputeInterpolationWeights( const int64 out_size, const int64 in_size, const float scale, const int resolution, InterpolationCache* interpolation) { + const Scaler scaler; interpolation->lower.resize(out_size + 1); interpolation->upper.resize(out_size + 1); interpolation->lerp.resize(out_size + 1); @@ -57,26 +58,31 @@ inline void ComputeInterpolationWeights( interpolation->lower[out_size] = 0; interpolation->upper[out_size] = 0; for (int64 i = out_size - 1; i >= 0; --i) { - const float in = i * scale; - interpolation->lower[i] = static_cast(in); + const float in = scaler(i, scale); + const float in_f = std::floor(in); + interpolation->lower[i] = + std::max(static_cast(in_f), static_cast(0)); interpolation->upper[i] = - std::min(interpolation->lower[i] + 1, in_size - 1); - interpolation->lerp[i] = in - interpolation->lower[i]; - interpolation->ilerp[i] = static_cast( - (in - interpolation->lower[i]) * (1 << resolution)); + std::min(static_cast(std::ceil(in)), in_size - 1); + interpolation->lerp[i] = in - in_f; + interpolation->ilerp[i] = + static_cast((in - in_f) * (1 << resolution)); } } template -inline InterpolationCache BuildLerpCache(const int64 out_size, - const int64 in_size, - const float scale, - const int index_step, - const int resolution) { +inline InterpolationCache BuildLerpCache( + const int64 out_size, const int64 in_size, const float scale, + const int index_step, const int resolution, const bool half_pixel_centers) { InterpolationCache cache; // Compute the cached interpolation weights on the x and y dimensions. - ComputeInterpolationWeights(out_size, in_size, scale, resolution, - &cache); + if (half_pixel_centers) { + ComputeInterpolationWeights( + out_size, in_size, scale, resolution, &cache); + } else { + ComputeInterpolationWeights(out_size, in_size, scale, + resolution, &cache); + } CHECK(index_step > 0); if (index_step > 1) { for (int i = 0; i < cache.lower.size(); ++i) { @@ -464,13 +470,14 @@ void ResizeImageReference(typename TTypes::ConstTensor images, const int64 out_width, const int channels, const float height_scale, const float width_scale, const float in_min, const float in_max, + const bool half_pixel_centers, typename TTypes::Tensor* output) { CHECK_NOTNULL(output); - const InterpolationCache xs = - BuildLerpCache(out_width, in_width, width_scale, channels, 0); - const InterpolationCache ys = - BuildLerpCache(out_height, in_height, height_scale, 1, 0); + const InterpolationCache xs = BuildLerpCache( + out_width, in_width, width_scale, channels, 0, half_pixel_centers); + const InterpolationCache ys = BuildLerpCache( + out_height, in_height, height_scale, 1, 0, half_pixel_centers); const int64 in_row_size = in_width * channels; const int64 in_batch_num_values = in_height * in_row_size; @@ -512,10 +519,11 @@ void ResizeImage(typename TTypes::ConstTensor images, const int64 out_width, const int channels, const float height_scale, const float width_scale, const float in_min, const float in_max, + const bool half_pixel_centers, typename TTypes::Tensor* output) { ResizeImageReference(images, batch_size, in_height, in_width, out_height, out_width, channels, height_scale, width_scale, - in_min, in_max, output); + in_min, in_max, half_pixel_centers, output); } template <> @@ -525,6 +533,7 @@ void ResizeImage(typename TTypes::ConstTensor images, const int64 out_width, const int channels, const float height_scale, const float width_scale, const float in_min, const float in_max, + const bool half_pixel_centers, typename TTypes::Tensor* output) { // 30 is maximum resolution for signed int. constexpr int RESOLUTION = 30; @@ -532,10 +541,11 @@ void ResizeImage(typename TTypes::ConstTensor images, CHECK_NOTNULL(output); - const InterpolationCache xs = BuildLerpCache( - out_width, in_width, width_scale, channels, RESOLUTION); - const InterpolationCache ys = - BuildLerpCache(out_height, in_height, height_scale, 1, RESOLUTION); + const InterpolationCache xs = + BuildLerpCache(out_width, in_width, width_scale, channels, + RESOLUTION, half_pixel_centers); + const InterpolationCache ys = BuildLerpCache( + out_height, in_height, height_scale, 1, RESOLUTION, half_pixel_centers); const int64 in_row_size = in_width * channels; const int64 in_batch_num_values = in_height * in_row_size; @@ -586,6 +596,7 @@ void ResizeImage(typename TTypes::ConstTensor images, const int64 out_width, const int channels, const float height_scale, const float width_scale, const float in_min, const float in_max, + const bool half_pixel_centers, typename TTypes::Tensor* output) { // 7 is maximum resolution for unsigned byte. constexpr int RESOLUTION = 7; @@ -593,10 +604,11 @@ void ResizeImage(typename TTypes::ConstTensor images, CHECK_NOTNULL(output); - const InterpolationCache xs = BuildLerpCache( - out_width, in_width, width_scale, channels, RESOLUTION); - const InterpolationCache ys = - BuildLerpCache(out_height, in_height, height_scale, 1, RESOLUTION); + const InterpolationCache xs = + BuildLerpCache(out_width, in_width, width_scale, channels, + RESOLUTION, half_pixel_centers); + const InterpolationCache ys = BuildLerpCache( + out_height, in_height, height_scale, 1, RESOLUTION, half_pixel_centers); const int64 in_row_size = in_width * channels; const int64 in_batch_num_values = in_height * in_row_size; @@ -646,6 +658,7 @@ template void ResizeBilinear(const typename TTypes::ConstTensor& images, const float height_scale, const float width_scale, const float in_min, const float in_max, + const bool half_pixel_centers, typename TTypes::Tensor* output) { CHECK_NOTNULL(output); @@ -666,11 +679,11 @@ void ResizeBilinear(const typename TTypes::ConstTensor& images, if (USE_REFERENCE) { ResizeImageReference(images, batch_size, in_height, in_width, out_height, out_width, channels, height_scale, width_scale, - in_min, in_max, output); + in_min, in_max, half_pixel_centers, output); } else { ResizeImage(images, batch_size, in_height, in_width, out_height, out_width, channels, height_scale, width_scale, in_min, - in_max, output); + in_max, half_pixel_centers, output); } } @@ -682,6 +695,8 @@ class QuantizedResizeBilinearOp : public OpKernel { explicit QuantizedResizeBilinearOp(OpKernelConstruction* context) : OpKernel(context) { OP_REQUIRES_OK(context, context->GetAttr("align_corners", &align_corners_)); + OP_REQUIRES_OK( + context, context->GetAttr("half_pixel_centers", &half_pixel_centers_)); } void Compute(OpKernelContext* context) override { @@ -689,7 +704,7 @@ class QuantizedResizeBilinearOp : public OpKernel { const float in_min = context->input(2).flat()(0); const float in_max = context->input(3).flat()(0); - ImageResizerState st(align_corners_); + ImageResizerState st(align_corners_, false); st.ValidateAndCreateOutput(context, input); if (!context->status().ok()) return; @@ -701,7 +716,7 @@ class QuantizedResizeBilinearOp : public OpKernel { typename TTypes::Tensor output_data(st.output->tensor()); ResizeBilinear(image_data, st.height_scale, st.width_scale, in_min, - in_max, &output_data); + in_max, half_pixel_centers_, &output_data); Tensor* out_min = nullptr; OP_REQUIRES_OK(context, context->allocate_output(1, {}, &out_min)); out_min->flat()(0) = in_min; @@ -713,6 +728,7 @@ class QuantizedResizeBilinearOp : public OpKernel { private: bool align_corners_; + bool half_pixel_centers_; TF_DISALLOW_COPY_AND_ASSIGN(QuantizedResizeBilinearOp); }; diff --git a/tensorflow/core/kernels/quantized_resize_bilinear_op_test.cc b/tensorflow/core/kernels/quantized_resize_bilinear_op_test.cc index 6fc4894592..a7931cb451 100644 --- a/tensorflow/core/kernels/quantized_resize_bilinear_op_test.cc +++ b/tensorflow/core/kernels/quantized_resize_bilinear_op_test.cc @@ -65,15 +65,16 @@ float CalculateResizeScale(int64 in_size, int64 out_size, bool align_corners) { : in_size / static_cast(out_size); } -inline std::tuple GetReferenceWeight(const int64 out_size, - const int64 in_size, - const int step, - const int index, - const float scale) { - const float in = index * scale; - const int64 lower = static_cast(in); - const int64 upper = std::min(lower + 1, in_size - 1); - return std::make_tuple(lower * step, upper * step, in - lower); +inline std::tuple GetReferenceWeight( + const bool half_pixel_centers, const int64 out_size, const int64 in_size, + const int step, const int index, const float scale) { + const float in = half_pixel_centers + ? (static_cast(index) + 0.5f) * scale - 0.5f + : index * scale; + const float in_f = std::floor(in); + const int64 lower = std::max(static_cast(in_f), static_cast(0)); + const int64 upper = std::min(static_cast(std::ceil(in)), in_size - 1); + return std::make_tuple(lower * step, upper * step, in - in_f); } template @@ -105,17 +106,17 @@ float ComputeLerpReference(const float in_top_left, } template -T CalcReferenceResizedVal(const T* image_data, const int batch_size, - const int64 in_height, const int64 in_width, - const int64 out_height, const int64 out_width, - const int channels, const float height_scale, - const float width_scale, const float min, - const float max, const int b, const int64 x, - const int64 y, const int c) { - const std::tuple x_weight = - GetReferenceWeight(out_width, in_width, channels, x, width_scale); - const std::tuple y_weight = - GetReferenceWeight(out_height, in_height, 1, y, height_scale); +T CalcReferenceResizedVal(const T* image_data, const bool half_pixel_centers, + const int batch_size, const int64 in_height, + const int64 in_width, const int64 out_height, + const int64 out_width, const int channels, + const float height_scale, const float width_scale, + const float min, const float max, const int b, + const int64 x, const int64 y, const int c) { + const std::tuple x_weight = GetReferenceWeight( + half_pixel_centers, out_width, in_width, channels, x, width_scale); + const std::tuple y_weight = GetReferenceWeight( + half_pixel_centers, out_height, in_height, 1, y, height_scale); const int64 in_row_size = in_width * channels; const int64 in_batch_num_values = in_height * in_row_size; @@ -144,7 +145,8 @@ void CheckTensorValue(const T* in_data, const T* out_data, const int batch_size, const int64 in_height, const int64 in_width, const int64 out_height, const int64 out_width, const int channels, const bool align_corners, - const float min, const float max, const float tolerance, + const bool half_pixel_centers, const float min, + const float max, const float tolerance, const bool relative) { const int64 out_row_size = out_width * channels; const float height_scale = @@ -157,8 +159,9 @@ void CheckTensorValue(const T* in_data, const T* out_data, const int batch_size, for (int64 x = 0; x < out_width; ++x) { for (int c = 0; c < channels; ++c) { const T ref_qval = CalcReferenceResizedVal( - in_data, batch_size, in_height, in_width, out_height, out_width, - channels, height_scale, width_scale, min, max, b, x, y, c); + in_data, half_pixel_centers, batch_size, in_height, in_width, + out_height, out_width, channels, height_scale, width_scale, min, + max, b, x, y, c); const T qval = out_data[(b * out_height + y) * out_row_size + x * channels + c]; const float ref_val = QuantizedToFloat(ref_qval, min, max); @@ -186,6 +189,7 @@ void TestResizeBilinear(const Tensor& image_tensor, const DataType dt, const Input::Initializer& new_size, const bool show_time, const int64 iterations, const float min, const float max, + const bool half_pixel_centers, std::vector* outputs) { Scope root = Scope::NewRootScope(); @@ -195,7 +199,8 @@ void TestResizeBilinear(const Tensor& image_tensor, const DataType dt, Output in_max = ops::Const(root.WithOpName("max"), max); ops::QuantizedResizeBilinear qrb = ops::QuantizedResizeBilinear( - root.WithOpName("qrb"), placeholder, size, in_min, in_max); + root.WithOpName("qrb"), placeholder, size, in_min, in_max, + ops::QuantizedResizeBilinear::HalfPixelCenters(half_pixel_centers)); TF_EXPECT_OK(root.status()); @@ -247,7 +252,7 @@ void TestResizeBilinearOneDim() { std::vector outputs; TestResizeBilinear(image_quantized_tensor, DT_QINT32, {1, OUT_WIDTH}, false, - 1, MIN, MAX, &outputs); + 1, MIN, MAX, false, &outputs); ASSERT_EQ(3, outputs.size()); ASSERT_EQ(OUT_WIDTH, outputs.at(0).NumElements()); ASSERT_EQ(4, outputs.at(0).shape().dims()); @@ -282,13 +287,16 @@ void TestResizeBilinearOneDim() { /*out_height=*/OUT_WIDTH, /*out_width=*/1, /*channels=*/1, - /*align_corners=*/false, MIN, MAX, TOLERANCE, true); + /*align_corners=*/false, + /*half_pixel_centers=*/false, MIN, MAX, TOLERANCE, + true); } template void RunTestResizeBilinearTwoDims(int batch_size, int in_height, int in_width, int out_height, int out_width, int channels, - float tolerance, bool relative) { + float tolerance, bool relative, + const bool half_pixel_centers) { constexpr float RATIO = 100.0f; const float min = 0.0f; const float max = batch_size * in_height * in_width * channels / RATIO; @@ -298,18 +306,21 @@ void RunTestResizeBilinearTwoDims(int batch_size, int in_height, int in_width, std::vector outputs; TestResizeBilinear(image_quantized_tensor, DataTypeToEnum::value, - {out_height, out_width}, false, 1, min, max, &outputs); - CheckTensorValue(image_quantized_tensor.flat().data(), - outputs.at(0).flat().data(), batch_size, in_height, - in_width, out_height, out_width, channels, - /*align_corners=*/false, min, max, tolerance, relative); + {out_height, out_width}, false, 1, min, max, + half_pixel_centers, &outputs); + CheckTensorValue( + image_quantized_tensor.flat().data(), outputs.at(0).flat().data(), + batch_size, in_height, in_width, out_height, out_width, channels, + /*align_corners=*/false, + /*half_pixel_centers=*/half_pixel_centers, min, max, tolerance, relative); } template void RunBenchmarkResizeBilinearTwoDims(int batch_size, int in_height, int in_width, int out_height, int out_width, int channels, - int iteration) { + int iteration, + const bool half_pixel_centers) { constexpr float RATIO = 100.0f; const float min = 0.0f; const float max = batch_size * in_height * in_width * channels / RATIO; @@ -319,47 +330,57 @@ void RunBenchmarkResizeBilinearTwoDims(int batch_size, int in_height, std::vector outputs; TestResizeBilinear(image_quantized_tensor, DataTypeToEnum::value, - {out_height, out_width}, true, iteration, min, max, + {out_height, out_width}, true, iteration, min, max, false, &outputs); } template -void TestResizeBilinearTwoDimsType(const float tolerance, const bool relative) { - RunTestResizeBilinearTwoDims(1, 1, 1, 1, 1, 1, tolerance, relative); - RunTestResizeBilinearTwoDims(1, 1, 128, 1, 256, 1, tolerance, relative); - RunTestResizeBilinearTwoDims(1, 128, 1, 256, 1, 1, tolerance, relative); - RunTestResizeBilinearTwoDims(1, 128, 128, 256, 256, 1, tolerance, - relative); - RunTestResizeBilinearTwoDims(1, 256, 256, 128, 128, 1, tolerance, - relative); - RunTestResizeBilinearTwoDims(1, 1, 128, 1, 256, 2, tolerance, relative); - RunTestResizeBilinearTwoDims(1, 128, 1, 256, 1, 2, tolerance, relative); - RunTestResizeBilinearTwoDims(1, 128, 128, 256, 256, 2, tolerance, - relative); - RunTestResizeBilinearTwoDims(1, 256, 256, 128, 128, 2, tolerance, - relative); - RunTestResizeBilinearTwoDims(1, 1, 16, 1, 32, 3, tolerance, relative); - RunTestResizeBilinearTwoDims(1, 1, 128, 1, 256, 3, tolerance, relative); - RunTestResizeBilinearTwoDims(1, 128, 128, 256, 256, 3, tolerance, - relative); - RunTestResizeBilinearTwoDims(1, 256, 256, 128, 128, 3, tolerance, - relative); +void TestResizeBilinearTwoDimsType(const float tolerance, const bool relative, + const bool half_pixel_centers) { + RunTestResizeBilinearTwoDims(1, 1, 1, 1, 1, 1, tolerance, relative, + half_pixel_centers); + RunTestResizeBilinearTwoDims(1, 1, 128, 1, 256, 1, tolerance, relative, + half_pixel_centers); + RunTestResizeBilinearTwoDims(1, 128, 1, 256, 1, 1, tolerance, relative, + half_pixel_centers); + RunTestResizeBilinearTwoDims(1, 128, 128, 256, 256, 1, tolerance, relative, + half_pixel_centers); + RunTestResizeBilinearTwoDims(1, 256, 256, 128, 128, 1, tolerance, relative, + half_pixel_centers); + RunTestResizeBilinearTwoDims(1, 1, 128, 1, 256, 2, tolerance, relative, + half_pixel_centers); + RunTestResizeBilinearTwoDims(1, 128, 1, 256, 1, 2, tolerance, relative, + half_pixel_centers); + RunTestResizeBilinearTwoDims(1, 128, 128, 256, 256, 2, tolerance, relative, + half_pixel_centers); + RunTestResizeBilinearTwoDims(1, 256, 256, 128, 128, 2, tolerance, relative, + half_pixel_centers); + RunTestResizeBilinearTwoDims(1, 1, 16, 1, 32, 3, tolerance, relative, + half_pixel_centers); + RunTestResizeBilinearTwoDims(1, 1, 128, 1, 256, 3, tolerance, relative, + half_pixel_centers); + RunTestResizeBilinearTwoDims(1, 128, 128, 256, 256, 3, tolerance, relative, + half_pixel_centers); + RunTestResizeBilinearTwoDims(1, 256, 256, 128, 128, 3, tolerance, relative, + half_pixel_centers); } void TestResizeBilinearTwoDims() { - TestResizeBilinearTwoDimsType(1.0f, false); - TestResizeBilinearTwoDimsType(1.0e-5, true); - TestResizeBilinearTwoDimsType(1.0e-5, true); + for (const bool half_pixel_centers : {false, true}) { + TestResizeBilinearTwoDimsType(1.0f, false, half_pixel_centers); + TestResizeBilinearTwoDimsType(1.0e-5, true, half_pixel_centers); + TestResizeBilinearTwoDimsType(1.0e-5, true, half_pixel_centers); + } } template void RunBenchmarkResizeBilinearTwoDimsType() { constexpr int ITER = 100; - RunBenchmarkResizeBilinearTwoDims(1, 1, 1, 2, 2, 1, ITER); - RunBenchmarkResizeBilinearTwoDims(1, 128, 128, 256, 256, 1, ITER); - RunBenchmarkResizeBilinearTwoDims(1, 128, 128, 256, 256, 3, ITER); - RunBenchmarkResizeBilinearTwoDims(1, 64, 64, 128, 128, 2, ITER); - RunBenchmarkResizeBilinearTwoDims(1, 32, 32, 64, 64, 16, ITER); + RunBenchmarkResizeBilinearTwoDims(1, 1, 1, 2, 2, 1, ITER, false); + RunBenchmarkResizeBilinearTwoDims(1, 128, 128, 256, 256, 1, ITER, false); + RunBenchmarkResizeBilinearTwoDims(1, 128, 128, 256, 256, 3, ITER, false); + RunBenchmarkResizeBilinearTwoDims(1, 64, 64, 128, 128, 2, ITER, false); + RunBenchmarkResizeBilinearTwoDims(1, 32, 32, 64, 64, 16, ITER, false); } void RunBenchmarkResizeBilinearTwoDims() { diff --git a/tensorflow/core/kernels/resize_area_op.cc b/tensorflow/core/kernels/resize_area_op.cc index c996ae60b7..38bb2a9a96 100644 --- a/tensorflow/core/kernels/resize_area_op.cc +++ b/tensorflow/core/kernels/resize_area_op.cc @@ -144,7 +144,11 @@ class ResizeAreaOp : public OpKernel { void Compute(OpKernelContext* context) override { const Tensor& input = context->input(0); - ImageResizerState st(align_corners_); + // The op always did the correct thing with regard to pixel centers, so we + // always pass false here for half_pixel_centers since ImageResizerState + // enforces that if align_corners_ is true, half_pixel_centers must be + // false. + ImageResizerState st(align_corners_, /*unused half_pixel_centers=*/false); st.ValidateAndCreateOutput(context, input); if (!context->status().ok()) return; diff --git a/tensorflow/core/kernels/resize_bicubic_op.cc b/tensorflow/core/kernels/resize_bicubic_op.cc index 8380ed6d8f..17ee9dbd9c 100644 --- a/tensorflow/core/kernels/resize_bicubic_op.cc +++ b/tensorflow/core/kernels/resize_bicubic_op.cc @@ -35,25 +35,34 @@ namespace { static const int64 kTableSize = (1 << 10); -const float* InitCoeffsTable() { +const float* InitCoeffsTable(const double a) { // Allocate and initialize coefficients table using Bicubic // convolution algorithm. // https://en.wikipedia.org/wiki/Bicubic_interpolation float* coeffs_table = new float[(kTableSize + 1) * 2]; - static const double A = -0.75; for (int i = 0; i <= kTableSize; ++i) { float x = i * 1.0 / kTableSize; - coeffs_table[i * 2] = ((A + 2) * x - (A + 3)) * x * x + 1; + coeffs_table[i * 2] = ((a + 2) * x - (a + 3)) * x * x + 1; x += 1.0; - coeffs_table[i * 2 + 1] = ((A * x - 5 * A) * x + 8 * A) * x - 4 * A; + coeffs_table[i * 2 + 1] = ((a * x - 5 * a) * x + 8 * a) * x - 4 * a; } + return coeffs_table; } -const float* GetCoeffsTable() { +const float* GetCoeffsTable(const bool use_keys_cubic) { // Static so that we initialize it on first use - static const float* coeffs_table = InitCoeffsTable(); - return coeffs_table; + if (use_keys_cubic) { + // http://ieeexplore.ieee.org/document/1163711/ + // R. G. Keys. Cubic convolution interpolation for digital image + // processing. IEEE Transactions on Acoustics, Speech, and Signal + // Processing, 29(6):1153–1160, 1981. + static const float* coeffs_table = InitCoeffsTable(-0.5f); + return coeffs_table; + } else { + static const float* coeffs_table = InitCoeffsTable(-0.75f); + return coeffs_table; + } } inline int64 Bound(int64 val, int64 limit) { @@ -73,20 +82,55 @@ struct WeightsAndIndices { int advance; // advance value. }; +template inline void GetWeightsAndIndices(const float scale, const int64 out_loc, const int64 limit, WeightsAndIndices* out) { - const int64 in_loc = scale * out_loc; - const float delta = scale * out_loc - in_loc; + const Scaler scaler; + const float in_loc_f = scaler(out_loc, scale); + const int64 in_loc = std::floor(in_loc_f); + const float delta = in_loc_f - in_loc; const int64 offset = lrintf(delta * kTableSize); - const float* coeffs_table = GetCoeffsTable(); - out->weight_0 = coeffs_table[offset * 2 + 1]; - out->weight_1 = coeffs_table[offset * 2]; - out->weight_2 = coeffs_table[(kTableSize - offset) * 2]; - out->weight_3 = coeffs_table[(kTableSize - offset) * 2 + 1]; - out->index_0 = Bound(in_loc - 1, limit); - out->index_1 = Bound(in_loc, limit); - out->index_2 = Bound(in_loc + 1, limit); - out->index_3 = Bound(in_loc + 2, limit); + const float* coeffs_table = GetCoeffsTable(use_keys_cubic); + if (use_keys_cubic) { + // The legacy code placed more weight on the edge pixels, since bounding + // the set of inputs to sample could cause an edge pixel to be repeated. + // Here we change the behavior at borders to match that used by the + // scale_and_translate_op, where sampling locations outside the image have + // their weight set to 0, and the weights are renormalized so that their sum + // is 1.0. + out->index_0 = Bound(in_loc - 1, limit); + out->weight_0 = + (out->index_0 == in_loc - 1 ? coeffs_table[offset * 2 + 1] : 0.0f); + out->index_1 = Bound(in_loc, limit); + out->weight_1 = (out->index_1 == in_loc ? coeffs_table[offset * 2] : 0.0f); + out->index_2 = Bound(in_loc + 1, limit); + out->weight_2 = + (out->index_2 == in_loc + 1 ? coeffs_table[(kTableSize - offset) * 2] + : 0.0f); + out->index_3 = Bound(in_loc + 2, limit); + out->weight_3 = (out->index_3 == in_loc + 2 + ? coeffs_table[(kTableSize - offset) * 2 + 1] + : 0.0f); + + const float weight_sum = + out->weight_0 + out->weight_1 + out->weight_2 + out->weight_3; + if (std::abs(weight_sum) >= 1000.0f * std::numeric_limits::min()) { + const float one_over_weight_sum = 1.0f / weight_sum; + out->weight_0 *= one_over_weight_sum; + out->weight_1 *= one_over_weight_sum; + out->weight_2 *= one_over_weight_sum; + out->weight_3 *= one_over_weight_sum; + } + } else { + out->weight_0 = coeffs_table[offset * 2 + 1]; + out->weight_1 = coeffs_table[offset * 2]; + out->weight_2 = coeffs_table[(kTableSize - offset) * 2]; + out->weight_3 = coeffs_table[(kTableSize - offset) * 2 + 1]; + out->index_0 = Bound(in_loc - 1, limit); + out->index_1 = Bound(in_loc, limit); + out->index_2 = Bound(in_loc + 1, limit); + out->index_3 = Bound(in_loc + 2, limit); + } } template @@ -162,14 +206,25 @@ class CachedInterpolationCalculator { }; static void ComputeXWeightsAndIndices(const ImageResizerState& resizer_state, + const bool half_pixel_centers, std::vector* x_wais) { CachedInterpolationCalculator calc; - for (int64 x = 0; x < resizer_state.out_width; ++x) { - GetWeightsAndIndices(resizer_state.width_scale, x, resizer_state.in_width, - &(*x_wais)[x]); - auto& x_wai = (*x_wais)[x]; - x_wai.advance = calc.Advance(x_wai.index_0, x_wai.index_1, x_wai.index_2, - x_wai.index_3); + if (half_pixel_centers) { + for (int64 x = 0; x < resizer_state.out_width; ++x) { + GetWeightsAndIndices( + resizer_state.width_scale, x, resizer_state.in_width, &(*x_wais)[x]); + auto& x_wai = (*x_wais)[x]; + x_wai.advance = calc.Advance(x_wai.index_0, x_wai.index_1, x_wai.index_2, + x_wai.index_3); + } + } else { + for (int64 x = 0; x < resizer_state.out_width; ++x) { + GetWeightsAndIndices( + resizer_state.width_scale, x, resizer_state.in_width, &(*x_wais)[x]); + auto& x_wai = (*x_wais)[x]; + x_wai.advance = calc.Advance(x_wai.index_0, x_wai.index_1, x_wai.index_2, + x_wai.index_3); + } } // Scale the values so they can be used as offsets into buffers. for (int x = 0; x < resizer_state.out_width; ++x) { @@ -182,14 +237,27 @@ static void ComputeXWeightsAndIndices(const ImageResizerState& resizer_state, static void ComputeGradientXWeightsAndIndices( const ImageResizerGradientState& resizer_state, - std::vector* x_wais) { + const bool half_pixel_centers, std::vector* x_wais) { CachedInterpolationCalculator calc; - for (int64 x = 0; x < resizer_state.resized_width; ++x) { - GetWeightsAndIndices(resizer_state.width_scale, x, - resizer_state.original_width, &(*x_wais)[x]); - auto& x_wai = (*x_wais)[x]; - x_wai.advance = calc.Advance(x_wai.index_0, x_wai.index_1, x_wai.index_2, - x_wai.index_3); + if (half_pixel_centers) { + for (int64 x = 0; x < resizer_state.resized_width; ++x) { + GetWeightsAndIndices(resizer_state.width_scale, x, + resizer_state.original_width, + &(*x_wais)[x]); + auto& x_wai = (*x_wais)[x]; + x_wai.advance = calc.Advance(x_wai.index_0, x_wai.index_1, x_wai.index_2, + x_wai.index_3); + } + + } else { + for (int64 x = 0; x < resizer_state.resized_width; ++x) { + GetWeightsAndIndices(resizer_state.width_scale, x, + resizer_state.original_width, + &(*x_wais)[x]); + auto& x_wai = (*x_wais)[x]; + x_wai.advance = calc.Advance(x_wai.index_0, x_wai.index_1, x_wai.index_2, + x_wai.index_3); + } } // Do not scale, as we will be using these directly as tensor indices on the // gradient pass. @@ -224,10 +292,10 @@ static EIGEN_ALWAYS_INLINE float ComputeYInterpolation( template inline void interpolate_with_caching( const typename TTypes::ConstTensor& input_data, - const ImageResizerState& resizer_state, + const ImageResizerState& resizer_state, const bool half_pixel_centers, typename TTypes::Tensor output_data) { std::vector x_wais(resizer_state.out_width); - ComputeXWeightsAndIndices(resizer_state, &x_wais); + ComputeXWeightsAndIndices(resizer_state, half_pixel_centers, &x_wais); const auto num_channels = resizer_state.channels; const int64 in_row_width = resizer_state.in_width * num_channels; @@ -242,8 +310,13 @@ inline void interpolate_with_caching( for (int64 y = 0; y < resizer_state.out_height; ++y, output_y_ptr += resizer_state.out_width * num_channels) { WeightsAndIndices y_wai; - GetWeightsAndIndices(resizer_state.height_scale, y, - resizer_state.in_height, &y_wai); + if (half_pixel_centers) { + GetWeightsAndIndices( + resizer_state.height_scale, y, resizer_state.in_height, &y_wai); + } else { + GetWeightsAndIndices( + resizer_state.height_scale, y, resizer_state.in_height, &y_wai); + } // Make pointers represent offsets of data in input_b_ptr. const T* y_ptr_0 = input_b_ptr + y_wai.index_0 * in_row_width; const T* y_ptr_1 = input_b_ptr + y_wai.index_1 * in_row_width; @@ -398,6 +471,7 @@ inline void interpolate_with_caching( template inline void ResizeBicubicGrad(typename TTypes::ConstTensor input_grad, const ImageResizerGradientState& resizer_state, + const bool half_pixel_centers, typename TTypes::Tensor output_grad) { // This function computes gradients for the ResizeBicubic op by iterating over // the input_grad Tensor and using WeightsAndIndices to appropriately update @@ -411,11 +485,17 @@ inline void ResizeBicubicGrad(typename TTypes::ConstTensor input_grad, output_grad.setZero(); std::vector x_wais(resizer_state.resized_width); - ComputeGradientXWeightsAndIndices(resizer_state, &x_wais); + ComputeGradientXWeightsAndIndices(resizer_state, half_pixel_centers, &x_wais); for (int64 b = 0; b < resizer_state.batch_size; ++b) { for (int64 y = 0; y < resized_height; ++y) { WeightsAndIndices y_wai; - GetWeightsAndIndices(height_scale, y, original_height, &y_wai); + if (half_pixel_centers) { + GetWeightsAndIndices(height_scale, y, + original_height, &y_wai); + } else { + GetWeightsAndIndices(height_scale, y, + original_height, &y_wai); + } for (int64 x = 0; x < resized_width; ++x) { const WeightsAndIndices& x_wai = x_wais[x]; for (int64 c = 0; c < channels; ++c) { @@ -471,11 +551,13 @@ class ResizeBicubicOp : public OpKernel { public: explicit ResizeBicubicOp(OpKernelConstruction* context) : OpKernel(context) { OP_REQUIRES_OK(context, context->GetAttr("align_corners", &align_corners_)); + OP_REQUIRES_OK( + context, context->GetAttr("half_pixel_centers", &half_pixel_centers_)); } void Compute(OpKernelContext* context) override { const Tensor& input = context->input(0); - ImageResizerState st(align_corners_); + ImageResizerState st(align_corners_, half_pixel_centers_); st.ValidateAndCreateOutput(context, input); if (!context->status().ok()) return; @@ -483,11 +565,13 @@ class ResizeBicubicOp : public OpKernel { typename TTypes::ConstTensor input_data(input.tensor()); TTypes::Tensor output_data = st.output->tensor(); - interpolate_with_caching(input_data, st, output_data); + interpolate_with_caching(input_data, st, half_pixel_centers_, + output_data); } private: bool align_corners_; + bool half_pixel_centers_; }; template @@ -496,6 +580,8 @@ class ResizeBicubicOpGrad : public OpKernel { explicit ResizeBicubicOpGrad(OpKernelConstruction* context) : OpKernel(context) { OP_REQUIRES_OK(context, context->GetAttr("align_corners", &align_corners_)); + OP_REQUIRES_OK( + context, context->GetAttr("half_pixel_centers", &half_pixel_centers_)); } void Compute(OpKernelContext* context) override { @@ -504,7 +590,7 @@ class ResizeBicubicOpGrad : public OpKernel { const Tensor& input = context->input(0); const Tensor& original_image = context->input(1); - ImageResizerGradientState st(align_corners_); + ImageResizerGradientState st(align_corners_, half_pixel_centers_); st.ValidateAndCreateOutput(context, input, original_image); if (!context->status().ok()) return; @@ -512,11 +598,12 @@ class ResizeBicubicOpGrad : public OpKernel { TTypes::ConstTensor input_grad = input.tensor(); typename TTypes::Tensor output_grad(st.output->tensor()); - ResizeBicubicGrad(input_grad, st, output_grad); + ResizeBicubicGrad(input_grad, st, half_pixel_centers_, output_grad); } private: bool align_corners_; + bool half_pixel_centers_; }; #define REGISTER_KERNEL(T) \ diff --git a/tensorflow/core/kernels/resize_bilinear_op.cc b/tensorflow/core/kernels/resize_bilinear_op.cc index f10c9a19a7..7bc40ba139 100644 --- a/tensorflow/core/kernels/resize_bilinear_op.cc +++ b/tensorflow/core/kernels/resize_bilinear_op.cc @@ -39,11 +39,13 @@ class ResizeBilinearOp : public OpKernel { public: explicit ResizeBilinearOp(OpKernelConstruction* context) : OpKernel(context) { OP_REQUIRES_OK(context, context->GetAttr("align_corners", &align_corners_)); + OP_REQUIRES_OK( + context, context->GetAttr("half_pixel_centers", &half_pixel_centers_)); } void Compute(OpKernelContext* context) override { const Tensor& input = context->input(0); - ImageResizerState st(align_corners_); + ImageResizerState st(align_corners_, half_pixel_centers_); st.ValidateAndCreateOutput(context, input); if (!context->status().ok()) return; @@ -54,13 +56,14 @@ class ResizeBilinearOp : public OpKernel { typename TTypes::ConstTensor image_data(input.tensor()); TTypes::Tensor output_data = st.output->tensor(); - functor::ResizeBilinear()(context->eigen_device(), - image_data, st.height_scale, - st.width_scale, output_data); + functor::ResizeBilinear()( + context->eigen_device(), image_data, st.height_scale, + st.width_scale, half_pixel_centers_, output_data); } private: bool align_corners_; + bool half_pixel_centers_; }; namespace { @@ -73,17 +76,22 @@ struct CachedInterpolation { float lerp; }; -inline void compute_interpolation_weights(const int64 out_size, +template +inline void compute_interpolation_weights(const Scaler scaler, + const int64 out_size, const int64 in_size, const float scale, CachedInterpolation* interpolation) { interpolation[out_size].lower = 0; interpolation[out_size].upper = 0; for (int64 i = out_size - 1; i >= 0; --i) { - const float in = i * scale; - interpolation[i].lower = static_cast(in); - interpolation[i].upper = std::min(interpolation[i].lower + 1, in_size - 1); - interpolation[i].lerp = in - interpolation[i].lower; + const float in = scaler(i, scale); + const float in_f = std::floor(in); + interpolation[i].lower = + std::max(static_cast(in_f), static_cast(0)); + interpolation[i].upper = + std::min(static_cast(std::ceil(in)), in_size - 1); + interpolation[i].lerp = in - in_f; } } @@ -203,6 +211,7 @@ template struct ResizeBilinear { void operator()(const CPUDevice& d, typename TTypes::ConstTensor images, const float height_scale, const float width_scale, + bool half_pixel_centers, typename TTypes::Tensor output) { const int batch_size = images.dimension(0); const int64 in_height = images.dimension(1); @@ -221,11 +230,19 @@ struct ResizeBilinear { std::vector ys(out_height + 1); std::vector xs(out_width + 1); - // Compute the cached interpolation weights on the x and y dimensions. - compute_interpolation_weights(out_height, in_height, height_scale, - ys.data()); - compute_interpolation_weights(out_width, in_width, width_scale, xs.data()); - + if (half_pixel_centers) { + compute_interpolation_weights(HalfPixelScaler(), out_height, in_height, + height_scale, ys.data()); + compute_interpolation_weights(HalfPixelScaler(), out_width, in_width, + width_scale, xs.data()); + + } else { + // Compute the cached interpolation weights on the x and y dimensions. + compute_interpolation_weights(LegacyScaler(), out_height, in_height, + height_scale, ys.data()); + compute_interpolation_weights(LegacyScaler(), out_width, in_width, + width_scale, xs.data()); + } // Scale x interpolation weights to avoid a multiplication during iteration. for (int i = 0; i < xs.size(); ++i) { xs[i].lower *= channels; @@ -244,6 +261,8 @@ class ResizeBilinearOpGrad : public OpKernel { explicit ResizeBilinearOpGrad(OpKernelConstruction* context) : OpKernel(context) { OP_REQUIRES_OK(context, context->GetAttr("align_corners", &align_corners_)); + OP_REQUIRES_OK( + context, context->GetAttr("half_pixel_centers", &half_pixel_centers_)); } void Compute(OpKernelContext* context) override { @@ -252,7 +271,7 @@ class ResizeBilinearOpGrad : public OpKernel { const Tensor& input = context->input(0); const Tensor& original_image = context->input(1); - ImageResizerGradientState st(align_corners_); + ImageResizerGradientState st(align_corners_, half_pixel_centers_); st.ValidateAndCreateOutput(context, input, original_image); if (!context->status().ok()) return; @@ -260,23 +279,26 @@ class ResizeBilinearOpGrad : public OpKernel { TTypes::ConstTensor input_grad = input.tensor(); typename TTypes::Tensor output_grad(st.output->tensor()); - functor::ResizeBilinearGrad()(context->eigen_device(), - input_grad, st.height_scale, - st.width_scale, output_grad); + functor::ResizeBilinearGrad()( + context->eigen_device(), input_grad, st.height_scale, + st.width_scale, half_pixel_centers_, output_grad); } private: bool align_corners_; + bool half_pixel_centers_; }; // Partial specialization of ResizeBilinearGrad functor for a CPUDevice. namespace functor { + template struct ResizeBilinearGrad { - void operator()(const CPUDevice& d, - typename TTypes::ConstTensor input_grad, - const float height_scale, const float width_scale, - typename TTypes::Tensor output_grad) { + template + void ResizeGradCore(const Scaler& scaler, + typename TTypes::ConstTensor input_grad, + const float height_scale, const float width_scale, + typename TTypes::Tensor output_grad) { const Eigen::Index batch = output_grad.dimension(0); const Eigen::Index original_height = output_grad.dimension(1); const Eigen::Index original_width = output_grad.dimension(2); @@ -296,20 +318,22 @@ struct ResizeBilinearGrad { // + bottom_right * y * x for (Eigen::Index b = 0; b < batch; ++b) { for (Eigen::Index y = 0; y < resized_height; ++y) { - const float in_y = y * height_scale; + const float in_y = scaler(y, height_scale); const Eigen::Index top_y_index = - static_cast(floorf(in_y)); + std::max(static_cast(floorf(in_y)), + static_cast(0)); const Eigen::Index bottom_y_index = std::min( static_cast(ceilf(in_y)), original_height - 1); - const float y_lerp = in_y - top_y_index; + const float y_lerp = in_y - floorf(in_y); const float inverse_y_lerp = (1.0f - y_lerp); for (Eigen::Index x = 0; x < resized_width; ++x) { - const float in_x = x * width_scale; + const float in_x = scaler(x, width_scale); const Eigen::Index left_x_index = - static_cast(floorf(in_x)); + std::max(static_cast(floorf(in_x)), + static_cast(0)); const Eigen::Index right_x_index = std::min( static_cast(ceilf(in_x)), original_width - 1); - const float x_lerp = in_x - left_x_index; + const float x_lerp = in_x - floorf(in_x); const float inverse_x_lerp = (1.0f - x_lerp); for (Eigen::Index c = 0; c < channels; ++c) { output_grad(b, top_y_index, left_x_index, c) += @@ -325,7 +349,21 @@ struct ResizeBilinearGrad { } } } + void operator()(const CPUDevice& d, + typename TTypes::ConstTensor input_grad, + const float height_scale, const float width_scale, + const bool half_pixel_centers, + typename TTypes::Tensor output_grad) { + if (half_pixel_centers) { + return ResizeGradCore(HalfPixelScaler(), input_grad, height_scale, + width_scale, output_grad); + } else { + return ResizeGradCore(LegacyScaler(), input_grad, height_scale, + width_scale, output_grad); + } + } }; + } // namespace functor #define REGISTER_KERNEL(T) \ diff --git a/tensorflow/core/kernels/resize_bilinear_op.h b/tensorflow/core/kernels/resize_bilinear_op.h index d16ed64f14..b4d0066d4f 100644 --- a/tensorflow/core/kernels/resize_bilinear_op.h +++ b/tensorflow/core/kernels/resize_bilinear_op.h @@ -27,6 +27,7 @@ template struct ResizeBilinear { void operator()(const Device& d, typename TTypes::ConstTensor images, const float height_scale, const float width_scale, + const bool half_pixel_centers, typename TTypes::Tensor resized_images); }; @@ -35,6 +36,7 @@ struct ResizeBilinearGrad { void operator()(const Device& d, typename TTypes::ConstTensor input_grad, const float height_scale, const float width_scale, + const bool half_pixel_centers, typename TTypes::Tensor output_grad); }; diff --git a/tensorflow/core/kernels/resize_bilinear_op_gpu.cu.cc b/tensorflow/core/kernels/resize_bilinear_op_gpu.cu.cc index 935f88e540..4da2b877df 100644 --- a/tensorflow/core/kernels/resize_bilinear_op_gpu.cu.cc +++ b/tensorflow/core/kernels/resize_bilinear_op_gpu.cu.cc @@ -48,6 +48,121 @@ __global__ void ResizeBilinearKernel(const int32 nthreads, const T* images, const int y = idx % out_height; const int b = idx / out_height; + const float in_y = (static_cast(y) + 0.5f) * height_scale - 0.5f; + + const int top_y_index = in_y > 0.0 ? floorf(in_y) : 0; + const int bottom_y_index = + (in_y < in_height - 1) ? ceilf(in_y) : in_height - 1; + const float y_lerp = in_y - floorf(in_y); + + const float in_x = (static_cast(x) + 0.5f) * width_scale - 0.5f; + const int left_x_index = in_x > 0.0 ? floorf(in_x) : 0; + const int right_x_index = + (in_x < in_width - 1) ? ceilf(in_x) : in_width - 1; + const float x_lerp = in_x - left_x_index; + + const float top_left( + images[((b * in_height + top_y_index) * in_width + left_x_index) * + channels + + c]); + const float top_right( + images[((b * in_height + top_y_index) * in_width + right_x_index) * + channels + + c]); + const float bottom_left( + images[((b * in_height + bottom_y_index) * in_width + left_x_index) * + channels + + c]); + const float bottom_right( + images[((b * in_height + bottom_y_index) * in_width + right_x_index) * + channels + + c]); + + const float top = top_left + (top_right - top_left) * x_lerp; + const float bottom = bottom_left + (bottom_right - bottom_left) * x_lerp; + output[out_idx] = top + (bottom - top) * y_lerp; + } +} + +template +__global__ void ResizeBilinearGradKernel( + const int32 nthreads, const float* input_grad, float height_scale, + float width_scale, int batch, int original_height, int original_width, + int channels, int resized_height, int resized_width, T* output_grad) { + CUDA_1D_KERNEL_LOOP(in_idx, nthreads) { + // in_idx = c + channels * (x + resized_width * (y + resized_height * b)) + int idx = in_idx; + const int c = idx % channels; + idx /= channels; + const int x = idx % resized_width; + idx /= resized_width; + const int y = idx % resized_height; + const int b = idx / resized_height; + + const float original_y = + (static_cast(y) + 0.5f) * height_scale - 0.5f; + const int top_y_index = original_y > 0.0 ? floorf(original_y) : 0; + const int bottom_y_index = (original_y < original_height - 1) + ? ceilf(original_y) + : original_height - 1; + const float y_lerp = original_y - floorf(original_y); + + const float original_x = + (static_cast(x) + 0.5f) * width_scale - 0.5f; + + const int left_x_index = original_x > 0.0 ? floorf(original_x) : 0; + const int right_x_index = (original_x < original_width - 1) + ? ceilf(original_x) + : original_width - 1; + const float x_lerp = original_x - floorf(original_x); + + const float dtop = (1 - y_lerp) * input_grad[in_idx]; + CudaAtomicAdd(output_grad + + ((b * original_height + top_y_index) * original_width + + left_x_index) * + channels + + c, + static_cast((1 - x_lerp) * dtop)); + CudaAtomicAdd(output_grad + + ((b * original_height + top_y_index) * original_width + + right_x_index) * + channels + + c, + static_cast(x_lerp * dtop)); + + const float dbottom = y_lerp * input_grad[in_idx]; + CudaAtomicAdd(output_grad + + ((b * original_height + bottom_y_index) * original_width + + left_x_index) * + channels + + c, + static_cast((1 - x_lerp) * dbottom)); + CudaAtomicAdd(output_grad + + ((b * original_height + bottom_y_index) * original_width + + right_x_index) * + channels + + c, + static_cast(x_lerp * dbottom)); + } +} + +template +__global__ void LegacyResizeBilinearKernel(const int32 nthreads, + const T* images, float height_scale, + float width_scale, int batch, + int in_height, int in_width, + int channels, int out_height, + int out_width, float* output) { + CUDA_1D_KERNEL_LOOP(out_idx, nthreads) { + // out_idx = c + channels * (x + out_width * (y + out_height * b)) + int idx = out_idx; + const int c = idx % channels; + idx /= channels; + const int x = idx % out_width; + idx /= out_width; + const int y = idx % out_height; + const int b = idx / out_height; + const float in_y = y * height_scale; const int top_y_index = floorf(in_y); const int bottom_y_index = @@ -84,7 +199,7 @@ __global__ void ResizeBilinearKernel(const int32 nthreads, const T* images, } template -__global__ void ResizeBilinearGradKernel( +__global__ void LegacyResizeBilinearGradKernel( const int32 nthreads, const float* input_grad, float height_scale, float width_scale, int batch, int original_height, int original_width, int channels, int resized_height, int resized_width, T* output_grad) { @@ -151,6 +266,7 @@ template struct ResizeBilinear { void operator()(const GPUDevice& d, typename TTypes::ConstTensor images, const float height_scale, const float width_scale, + const bool half_pixel_centers, typename TTypes::Tensor output) { const int batch = images.dimension(0); const int in_height = images.dimension(1); @@ -164,11 +280,19 @@ struct ResizeBilinear { if (total_count == 0) return; CudaLaunchConfig config = GetCudaLaunchConfig(total_count, d); - TF_CHECK_OK(CudaLaunchKernel( - ResizeBilinearKernel, config.block_count, config.thread_per_block, 0, - d.stream(), config.virtual_thread_count, images.data(), height_scale, - width_scale, batch, in_height, in_width, channels, out_height, - out_width, output.data())); + if (half_pixel_centers) { + TF_CHECK_OK(CudaLaunchKernel( + ResizeBilinearKernel, config.block_count, config.thread_per_block, + 0, d.stream(), config.virtual_thread_count, images.data(), + height_scale, width_scale, batch, in_height, in_width, channels, + out_height, out_width, output.data())); + } else { + TF_CHECK_OK(CudaLaunchKernel( + LegacyResizeBilinearKernel, config.block_count, + config.thread_per_block, 0, d.stream(), config.virtual_thread_count, + images.data(), height_scale, width_scale, batch, in_height, in_width, + channels, out_height, out_width, output.data())); + } } }; @@ -178,6 +302,7 @@ struct ResizeBilinearGrad { void operator()(const GPUDevice& d, typename TTypes::ConstTensor input_grad, const float height_scale, const float width_scale, + const bool half_pixel_centers, typename TTypes::Tensor output_grad) { const int batch = output_grad.dimension(0); const int original_height = output_grad.dimension(1); @@ -201,12 +326,21 @@ struct ResizeBilinearGrad { // Accumulate. total_count = batch * resized_height * resized_width * channels; config = GetCudaLaunchConfig(total_count, d); - TF_CHECK_OK(CudaLaunchKernel( - ResizeBilinearGradKernel, config.block_count, - config.thread_per_block, 0, d.stream(), config.virtual_thread_count, - input_grad.data(), height_scale, width_scale, batch, original_height, - original_width, channels, resized_height, resized_width, - output_grad.data())); + if (half_pixel_centers) { + TF_CHECK_OK(CudaLaunchKernel( + ResizeBilinearGradKernel, config.block_count, + config.thread_per_block, 0, d.stream(), config.virtual_thread_count, + input_grad.data(), height_scale, width_scale, batch, original_height, + original_width, channels, resized_height, resized_width, + output_grad.data())); + } else { + TF_CHECK_OK(CudaLaunchKernel( + LegacyResizeBilinearGradKernel, config.block_count, + config.thread_per_block, 0, d.stream(), config.virtual_thread_count, + input_grad.data(), height_scale, width_scale, batch, original_height, + original_width, channels, resized_height, resized_width, + output_grad.data())); + } } }; diff --git a/tensorflow/core/kernels/resize_bilinear_op_test.cc b/tensorflow/core/kernels/resize_bilinear_op_test.cc index f2062915b8..dee1020d77 100644 --- a/tensorflow/core/kernels/resize_bilinear_op_test.cc +++ b/tensorflow/core/kernels/resize_bilinear_op_test.cc @@ -30,13 +30,15 @@ limitations under the License. namespace tensorflow { -class ResizeBilinearOpTest : public OpsTestBase { +class ResizeBilinearOpTestBase : public OpsTestBase { protected: - ResizeBilinearOpTest() { + explicit ResizeBilinearOpTestBase(bool half_pixel_centers) + : half_pixel_centers_(half_pixel_centers) { TF_EXPECT_OK(NodeDefBuilder("resize_bilinear_op", "ResizeBilinear") .Input(FakeInput(DT_FLOAT)) .Input(FakeInput(DT_INT32)) .Attr("align_corners", false) + .Attr("half_pixel_centers", half_pixel_centers_) .Finalize(node_def())); TF_EXPECT_OK(InitOp()); } @@ -81,17 +83,25 @@ class ResizeBilinearOpTest : public OpsTestBase { for (int b = 0; b < batch; ++b) { for (int64 y = 0; y < out_height; ++y) { - const float in_y = y * height_scale; - const int64 top_y_index = static_cast(floorf(in_y)); + const float in_y = + half_pixel_centers_ + ? (static_cast(y) + 0.5f) * height_scale - 0.5f + : y * height_scale; + const int64 top_y_index = + std::max(static_cast(floorf(in_y)), static_cast(0)); const int64 bottom_y_index = std::min(static_cast(ceilf(in_y)), in_height - 1); - const float y_lerp = in_y - top_y_index; + const float y_lerp = in_y - std::floor(in_y); for (int64 x = 0; x < out_width; ++x) { - const float in_x = x * width_scale; - const int64 left_x_index = static_cast(floorf(in_x)); + const float in_x = + half_pixel_centers_ + ? (static_cast(x) + 0.5f) * width_scale - 0.5f + : x * width_scale; + const int64 left_x_index = + std::max(static_cast(floorf(in_x)), static_cast(0)); const int64 right_x_index = std::min(static_cast(ceilf(in_x)), in_width - 1); - const float x_lerp = in_x - left_x_index; + const float x_lerp = in_x - std::floor(in_x); for (int c = 0; c < channels; ++c) { const float top_left = images(b, top_y_index, left_x_index, c); const float top_right = images(b, top_y_index, right_x_index, c); @@ -139,6 +149,17 @@ class ResizeBilinearOpTest : public OpsTestBase { } } } + bool half_pixel_centers_; +}; + +class ResizeBilinearOpTest : public ResizeBilinearOpTestBase { + public: + ResizeBilinearOpTest() : ResizeBilinearOpTestBase(false) {} +}; + +class ResizeBilinearHalfPixelCentersOpTest : public ResizeBilinearOpTestBase { + public: + ResizeBilinearHalfPixelCentersOpTest() : ResizeBilinearOpTestBase(true) {} }; class ResizeBilinearOpAlignCornersTest : public OpsTestBase { @@ -343,6 +364,14 @@ TEST_F(ResizeBilinearOpTest, TestBilinear4x4To3x3) { test::ExpectClose(expected, *GetOutput(0)); } +TEST_F(ResizeBilinearHalfPixelCentersOpTest, TestDownsamples) { + TestResize(4, 298, 297, 3, 61, 71); +} + +TEST_F(ResizeBilinearHalfPixelCentersOpTest, TestUpsamples) { + TestResize(4, 61, 71, 3, 298, 297); +} + TEST_F(ResizeBilinearOpAlignCornersTest, TestBilinearAlignCorners4x4To3x3) { // Input: // 1, 2, 3, 4 diff --git a/tensorflow/core/kernels/resize_nearest_neighbor_op.cc b/tensorflow/core/kernels/resize_nearest_neighbor_op.cc index e985d3e5a5..cf2d0e2274 100644 --- a/tensorflow/core/kernels/resize_nearest_neighbor_op.cc +++ b/tensorflow/core/kernels/resize_nearest_neighbor_op.cc @@ -40,11 +40,13 @@ class ResizeNearestNeighborOp : public OpKernel { explicit ResizeNearestNeighborOp(OpKernelConstruction* context) : OpKernel(context) { OP_REQUIRES_OK(context, context->GetAttr("align_corners", &align_corners_)); + OP_REQUIRES_OK( + context, context->GetAttr("half_pixel_centers", &half_pixel_centers_)); } void Compute(OpKernelContext* context) override { const Tensor& input = context->input(0); - ImageResizerState st(align_corners_); + ImageResizerState st(align_corners_, half_pixel_centers_); st.ValidateAndCreateOutput(context, input); if (!context->status().ok()) return; @@ -60,16 +62,34 @@ class ResizeNearestNeighborOp : public OpKernel { typename TTypes::Tensor output_data(st.output->tensor()); bool status; - if (align_corners_) { - status = - functor::ResizeNearestNeighbor()( - context->eigen_device(), input_data, st.height_scale, - st.width_scale, output_data); + if (half_pixel_centers_) { + if (align_corners_) { + status = functor::ResizeNearestNeighbor()( + context->eigen_device(), input_data, st.height_scale, + st.width_scale, output_data); + } else { + status = functor::ResizeNearestNeighbor()( + context->eigen_device(), input_data, st.height_scale, + st.width_scale, output_data); + } } else { - status = - functor::ResizeNearestNeighbor()( - context->eigen_device(), input_data, st.height_scale, - st.width_scale, output_data); + if (align_corners_) { + status = functor::ResizeNearestNeighbor()( + context->eigen_device(), input_data, st.height_scale, + st.width_scale, output_data); + } else { + status = functor::ResizeNearestNeighbor()( + context->eigen_device(), input_data, st.height_scale, + st.width_scale, output_data); + } } if (!status) { context->SetStatus( @@ -79,15 +99,41 @@ class ResizeNearestNeighborOp : public OpKernel { private: bool align_corners_; + bool half_pixel_centers_; +}; + +// Helper struct to convert a bool to the correct scaler type. +template +struct BoolToScaler {}; + +struct HalfPixelScalerForNN { + inline float operator()(const int x, const float scale) const { + // All of the nearest neigbor code below immediately follows a call to this + // function with a std::floor(), so instead of subtracting the 0.5 as we + // do in HalfPixelScale, we leave it as is, as the std::floor does the + // correct thing. + return (static_cast(x) + 0.5f) * scale; + } +}; + +template <> +struct BoolToScaler { + typedef HalfPixelScalerForNN Scaler; +}; + +template <> +struct BoolToScaler { + typedef LegacyScaler Scaler; }; // Partial specialization of ResizeNearestNeighbor functor for a CPUDevice. namespace functor { -template -struct ResizeNearestNeighbor { +template +struct ResizeNearestNeighbor { bool operator()(const CPUDevice& d, typename TTypes::ConstTensor input, const float height_scale, const float width_scale, typename TTypes::Tensor output) { + typename BoolToScaler::Scaler scaler; const Eigen::Index batch_size = input.dimension(0); const Eigen::Index in_height = input.dimension(1); const Eigen::Index in_width = input.dimension(2); @@ -98,17 +144,23 @@ struct ResizeNearestNeighbor { for (Eigen::Index b = 0; b < batch_size; ++b) { for (Eigen::Index y = 0; y < out_height; ++y) { - const Eigen::Index in_y = - std::min((align_corners) - ? static_cast(roundf(y * height_scale)) - : static_cast(floorf(y * height_scale)), - in_height - 1); + Eigen::Index in_y = std::min( + (align_corners) + ? static_cast(roundf(scaler(y, height_scale))) + : static_cast(floorf(scaler(y, height_scale))), + in_height - 1); + if (half_pixel_centers) { + in_y = std::max(static_cast(0), in_y); + } for (Eigen::Index x = 0; x < out_width; ++x) { - const Eigen::Index in_x = - std::min((align_corners) - ? static_cast(roundf(x * width_scale)) - : static_cast(floorf(x * width_scale)), - in_width - 1); + Eigen::Index in_x = std::min( + (align_corners) + ? static_cast(roundf(scaler(x, width_scale))) + : static_cast(floorf(scaler(x, width_scale))), + in_width - 1); + if (half_pixel_centers) { + in_x = std::max(static_cast(0), in_x); + } std::copy_n(&input(b, in_y, in_x, 0), channels, &output(b, y, x, 0)); } } @@ -124,6 +176,8 @@ class ResizeNearestNeighborOpGrad : public OpKernel { explicit ResizeNearestNeighborOpGrad(OpKernelConstruction* context) : OpKernel(context) { OP_REQUIRES_OK(context, context->GetAttr("align_corners", &align_corners_)); + OP_REQUIRES_OK( + context, context->GetAttr("half_pixel_centers", &half_pixel_centers_)); } void Compute(OpKernelContext* context) override { @@ -173,16 +227,36 @@ class ResizeNearestNeighborOpGrad : public OpKernel { CalculateResizeScale(out_width, in_width, align_corners_); bool status; - if (align_corners_) { - status = functor::ResizeNearestNeighborGrad()( - context->eigen_device(), input_data, height_scale, - width_scale, output_data); + if (half_pixel_centers_) { + if (align_corners_) { + status = functor::ResizeNearestNeighborGrad()( + context->eigen_device(), input_data, height_scale, + width_scale, output_data); + } else { + status = functor::ResizeNearestNeighborGrad()( + context->eigen_device(), input_data, height_scale, + width_scale, output_data); + } } else { - status = functor::ResizeNearestNeighborGrad()( - context->eigen_device(), input_data, height_scale, - width_scale, output_data); + if (align_corners_) { + status = + functor::ResizeNearestNeighborGrad()( + context->eigen_device(), input_data, height_scale, + width_scale, output_data); + } else { + status = + functor::ResizeNearestNeighborGrad()( + context->eigen_device(), input_data, height_scale, + width_scale, output_data); + } } if (!status) { context->SetStatus( @@ -192,15 +266,18 @@ class ResizeNearestNeighborOpGrad : public OpKernel { private: bool align_corners_; + bool half_pixel_centers_; }; // Partial specialization of ResizeNearestNeighborGrad functor for a CPUDevice. namespace functor { -template -struct ResizeNearestNeighborGrad { +template +struct ResizeNearestNeighborGrad { bool operator()(const CPUDevice& d, typename TTypes::ConstTensor input, const float height_scale, const float width_scale, typename TTypes::Tensor output) { + typename BoolToScaler::Scaler scaler; const Eigen::Index batch_size = input.dimension(0); const Eigen::Index in_height = input.dimension(1); const Eigen::Index in_width = input.dimension(2); @@ -213,15 +290,16 @@ struct ResizeNearestNeighborGrad { for (Eigen::Index y = 0; y < in_height; ++y) { const Eigen::Index out_y = std::min( - (align_corners) ? static_cast(roundf(y * height_scale)) - : static_cast(floorf(y * height_scale)), + (align_corners) + ? static_cast(roundf(scaler(y, height_scale))) + : static_cast(floorf(scaler(y, height_scale))), out_height - 1); for (Eigen::Index x = 0; x < in_width; ++x) { - const Eigen::Index out_x = - std::min((align_corners) - ? static_cast(roundf(x * width_scale)) - : static_cast(floorf(x * width_scale)), - out_width - 1); + const Eigen::Index out_x = std::min( + (align_corners) + ? static_cast(roundf(scaler(x, width_scale))) + : static_cast(floorf(scaler(x, width_scale))), + out_width - 1); for (Eigen::Index b = 0; b < batch_size; ++b) { for (Eigen::Index c = 0; c < channels; ++c) { output(b, out_y, out_x, c) += input(b, y, x, c); diff --git a/tensorflow/core/kernels/resize_nearest_neighbor_op.h b/tensorflow/core/kernels/resize_nearest_neighbor_op.h index 9db331ffdc..d6b053180c 100644 --- a/tensorflow/core/kernels/resize_nearest_neighbor_op.h +++ b/tensorflow/core/kernels/resize_nearest_neighbor_op.h @@ -22,14 +22,16 @@ limitations under the License. namespace tensorflow { namespace functor { -template +template struct ResizeNearestNeighbor { bool operator()(const Device& d, typename TTypes::ConstTensor input, const float height_scale, const float width_scale, typename TTypes::Tensor output); }; -template +template struct ResizeNearestNeighborGrad { bool operator()(const Device& d, typename TTypes::ConstTensor input_grad, diff --git a/tensorflow/core/kernels/resize_nearest_neighbor_op_gpu.cu.cc b/tensorflow/core/kernels/resize_nearest_neighbor_op_gpu.cu.cc index b32634eef8..d2494ea36b 100644 --- a/tensorflow/core/kernels/resize_nearest_neighbor_op_gpu.cu.cc +++ b/tensorflow/core/kernels/resize_nearest_neighbor_op_gpu.cu.cc @@ -32,7 +32,7 @@ typedef Eigen::GpuDevice GPUDevice; namespace { -template +template __global__ void ResizeNearestNeighborNHWC( const int nthreads, const T* bottom_data, const int in_height, const int in_width, const int channels, const int out_height, @@ -47,6 +47,37 @@ __global__ void ResizeNearestNeighborNHWC( int out_y = n % out_height; n /= out_height; + const T* bottom_data_n = bottom_data + n * channels * in_height * in_width; + const int in_y = + max(min(static_cast( + floorf((static_cast(out_y) + 0.5f) * height_scale)), + in_height - 1), + 0); + const int in_x = + max(min(static_cast( + floorf((static_cast(out_x) + 0.5f) * width_scale)), + in_width - 1), + 0); + const int idx = (in_y * in_width + in_x) * channels + c; + top_data[index] = ldg(bottom_data_n + idx); + } +} + +template +__global__ void LegacyResizeNearestNeighborNHWC( + const int nthreads, const T* bottom_data, const int in_height, + const int in_width, const int channels, const int out_height, + const int out_width, const float height_scale, const float width_scale, + T* top_data) { + CUDA_1D_KERNEL_LOOP(index, nthreads) { + int n = index; + int c = n % channels; + n /= channels; + int out_x = n % out_width; + n /= out_width; + int out_y = n % out_height; + n /= out_height; + const T* bottom_data_n = bottom_data + n * channels * in_height * in_width; const int in_y = min((align_corners) ? static_cast(roundf(out_y * height_scale)) @@ -61,7 +92,7 @@ __global__ void ResizeNearestNeighborNHWC( } } -template +template __global__ void ResizeNearestNeighborBackwardNHWC( const int nthreads, const T* top_diff, const int in_height, const int in_width, const int channels, const int out_height, @@ -76,6 +107,37 @@ __global__ void ResizeNearestNeighborBackwardNHWC( int in_y = n % in_height; n /= in_height; + T* bottom_diff_n = bottom_diff + n * channels * out_height * out_width; + const int out_y = + max(min(static_cast( + floorf((static_cast(in_y) + 0.5f) * height_scale)), + out_height - 1), + 0); + const int out_x = + max(min(static_cast( + floorf((static_cast(in_x) + 0.5f) * width_scale)), + out_width - 1), + 0); + const int idx = (out_y * out_width + out_x) * channels + c; + CudaAtomicAdd(bottom_diff_n + idx, ldg(top_diff + index)); + } +} + +template +__global__ void LegacyResizeNearestNeighborBackwardNHWC( + const int nthreads, const T* top_diff, const int in_height, + const int in_width, const int channels, const int out_height, + const int out_width, const float height_scale, const float width_scale, + T* bottom_diff) { + CUDA_1D_KERNEL_LOOP(index, nthreads) { + int n = index; + int c = n % channels; + n /= channels; + int in_x = n % in_width; + n /= in_width; + int in_y = n % in_height; + n /= in_height; + T* bottom_diff_n = bottom_diff + n * channels * out_height * out_width; const int out_y = min((align_corners) ? static_cast(roundf(in_y * height_scale)) @@ -95,8 +157,8 @@ __global__ void ResizeNearestNeighborBackwardNHWC( namespace functor { // Partial specialization of ResizeNearestNeighbor functor for a GPUDevice. -template -struct ResizeNearestNeighbor { +template +struct ResizeNearestNeighbor { bool operator()(const GPUDevice& d, typename TTypes::ConstTensor input, const float height_scale, const float width_scale, typename TTypes::Tensor output) { @@ -112,26 +174,38 @@ struct ResizeNearestNeighbor { if (output_size == 0) return true; CudaLaunchConfig config = GetCudaLaunchConfig(output_size, d); - TF_CHECK_OK(CudaLaunchKernel( - ResizeNearestNeighborNHWC, config.block_count, - config.thread_per_block, 0, d.stream(), output_size, input.data(), - in_height, in_width, channels, out_height, out_width, height_scale, - width_scale, output.data())); - return d.ok(); + if (half_pixel_centers) { + TF_CHECK_OK(CudaLaunchKernel( + ResizeNearestNeighborNHWC, config.block_count, + config.thread_per_block, 0, d.stream(), output_size, input.data(), + in_height, in_width, channels, out_height, out_width, height_scale, + width_scale, output.data())); + return d.ok(); + } else { + TF_CHECK_OK(CudaLaunchKernel( + LegacyResizeNearestNeighborNHWC, config.block_count, + config.thread_per_block, 0, d.stream(), output_size, input.data(), + in_height, in_width, channels, out_height, out_width, height_scale, + width_scale, output.data())); + return d.ok(); + } } }; -#define DECLARE_GPU_SPEC(T) \ - template struct ResizeNearestNeighbor; \ - template struct ResizeNearestNeighbor; +#define DECLARE_GPU_SPEC(T) \ + template struct ResizeNearestNeighbor; \ + template struct ResizeNearestNeighbor; \ + template struct ResizeNearestNeighbor; \ + template struct ResizeNearestNeighbor; TF_CALL_GPU_NUMBER_TYPES(DECLARE_GPU_SPEC); #undef DECLARE_GPU_SPEC // Partial specialization of ResizeNearestNeighborGrad functor for a GPUDevice. -template -struct ResizeNearestNeighborGrad { +template +struct ResizeNearestNeighborGrad { bool operator()(const GPUDevice& d, typename TTypes::ConstTensor input, const float height_scale, const float width_scale, typename TTypes::Tensor output) { @@ -154,18 +228,31 @@ struct ResizeNearestNeighborGrad { if (input_size == 0) return true; CudaLaunchConfig input_config = GetCudaLaunchConfig(input_size, d); - ResizeNearestNeighborBackwardNHWC - <<>>(input_config.virtual_thread_count, input.data(), - in_height, in_width, channels, out_height, out_width, - height_scale, width_scale, output.data()); - return d.ok(); + if (half_pixel_centers) { + TF_CHECK_OK(CudaLaunchKernel( + ResizeNearestNeighborBackwardNHWC, input_config.block_count, + input_config.thread_per_block, 0, d.stream(), + input_config.virtual_thread_count, input.data(), in_height, in_width, + channels, out_height, out_width, height_scale, width_scale, + output.data())); + return d.ok(); + } else { + TF_CHECK_OK(CudaLaunchKernel( + LegacyResizeNearestNeighborBackwardNHWC, + input_config.block_count, input_config.thread_per_block, 0, + d.stream(), input_config.virtual_thread_count, input.data(), + in_height, in_width, channels, out_height, out_width, height_scale, + width_scale, output.data())); + return d.ok(); + } } }; -#define DECLARE_GPU_SPEC(T) \ - template struct ResizeNearestNeighborGrad; \ - template struct ResizeNearestNeighborGrad; +#define DECLARE_GPU_SPEC(T) \ + template struct ResizeNearestNeighborGrad; \ + template struct ResizeNearestNeighborGrad; \ + template struct ResizeNearestNeighborGrad; \ + template struct ResizeNearestNeighborGrad; TF_CALL_GPU_NUMBER_TYPES(DECLARE_GPU_SPEC); diff --git a/tensorflow/core/kernels/resize_nearest_neighbor_op_test.cc b/tensorflow/core/kernels/resize_nearest_neighbor_op_test.cc index bd8a064228..734ef5da69 100644 --- a/tensorflow/core/kernels/resize_nearest_neighbor_op_test.cc +++ b/tensorflow/core/kernels/resize_nearest_neighbor_op_test.cc @@ -31,18 +31,33 @@ limitations under the License. namespace tensorflow { -class ResizeNearestNeighborOpTest : public OpsTestBase { +class ResizeNearestNeighborOpTestBase : public OpsTestBase { protected: - ResizeNearestNeighborOpTest() { + explicit ResizeNearestNeighborOpTestBase(bool half_pixel_centers) { TF_EXPECT_OK(NodeDefBuilder("resize_nn", "ResizeNearestNeighbor") .Input(FakeInput(DT_FLOAT)) .Input(FakeInput(DT_INT32)) .Attr("align_corners", false) + .Attr("half_pixel_centers", half_pixel_centers) .Finalize(node_def())); TF_EXPECT_OK(InitOp()); } }; +class ResizeNearestNeighborOpTest : public ResizeNearestNeighborOpTestBase { + protected: + ResizeNearestNeighborOpTest() : ResizeNearestNeighborOpTestBase(false) {} +}; + +class ResizeNearestNeighborHalfPixelCentersOpTest + : public ResizeNearestNeighborOpTestBase { + protected: + ResizeNearestNeighborHalfPixelCentersOpTest() + : ResizeNearestNeighborOpTestBase(true) {} +}; + +// TODO(jflynn): Add some actual tests for the half pixel centers case. + class ResizeNearestNeighborOpAlignCornersTest : public OpsTestBase { protected: ResizeNearestNeighborOpAlignCornersTest() { @@ -317,4 +332,193 @@ TEST_F(ResizeNearestNeighborOpTest, TestNearest2x2x2x2To2x3x3x2) { test::ExpectTensorEqual(expected, *GetOutput(0)); } +TEST_F(ResizeNearestNeighborHalfPixelCentersOpTest, TestNearest5x2To2x2) { + // Input: + // 1, 2 + // 3, 4 + AddInputFromArray(TensorShape({1, 2, 5, 1}), + {1, 2, 3, 4, 5, 1, 2, 3, 4, 5}); + AddInputFromArray(TensorShape({2}), {2, 2}); + TF_ASSERT_OK(RunOpKernel()); + + Tensor expected(allocator(), DT_FLOAT, TensorShape({1, 2, 2, 1})); + + // clang-format off + test::FillValues(&expected, {2, 4, 2, 4}); + + // clang-format on + test::ExpectTensorEqual(expected, *GetOutput(0)); +} + +TEST_F(ResizeNearestNeighborHalfPixelCentersOpTest, TestNearest2x2To1x1) { + // Input: + // 1, 2 + // 3, 4 + AddInputFromArray(TensorShape({1, 2, 2, 1}), {1, 2, 3, 4}); + AddInputFromArray(TensorShape({2}), {1, 1}); + TF_ASSERT_OK(RunOpKernel()); + + Tensor expected(allocator(), DT_FLOAT, TensorShape({1, 1, 1, 1})); + + // clang-format off + test::FillValues(&expected, {4}); + + // clang-format on + test::ExpectTensorEqual(expected, *GetOutput(0)); +} + +TEST_F(ResizeNearestNeighborHalfPixelCentersOpTest, TestNearest2x2To3x3) { + // Input: + // 1, 2 + // 3, 4 + AddInputFromArray(TensorShape({1, 2, 2, 1}), {1, 2, 3, 4}); + AddInputFromArray(TensorShape({2}), {3, 3}); + TF_ASSERT_OK(RunOpKernel()); + + Tensor expected(allocator(), DT_FLOAT, TensorShape({1, 3, 3, 1})); + + // clang-format off + test::FillValues(&expected, + {1, 2, 2, + 3, 4, 4, + 3, 4, 4}); + + // clang-format on + test::ExpectTensorEqual(expected, *GetOutput(0)); +} + +TEST_F(ResizeNearestNeighborHalfPixelCentersOpTest, TestNearest3x3To2x2) { + // Input: + // 1, 2, 3 + // 4, 5, 6 + // 7, 8, 9 + AddInputFromArray(TensorShape({1, 3, 3, 1}), + {1, 2, 3, 4, 5, 6, 7, 8, 9}); + AddInputFromArray(TensorShape({2}), {2, 2}); + TF_ASSERT_OK(RunOpKernel()); + + Tensor expected(allocator(), DT_FLOAT, TensorShape({1, 2, 2, 1})); + + // clang-format off + test::FillValues(&expected, + {1, 3, + 7, 9}); + + // clang-format on + test::ExpectTensorEqual(expected, *GetOutput(0)); +} + +TEST_F(ResizeNearestNeighborHalfPixelCentersOpTest, TestNearest2x2To2x5) { + // Input: + // 1, 2 + // 3, 4 + AddInputFromArray(TensorShape({1, 2, 2, 1}), {1, 2, 3, 4}); + AddInputFromArray(TensorShape({2}), {2, 5}); + TF_ASSERT_OK(RunOpKernel()); + + Tensor expected(allocator(), DT_FLOAT, TensorShape({1, 2, 5, 1})); + + // clang-format off + test::FillValues(&expected, + {1, 1, 2, 2, 2, + 3, 3, 4, 4, 4}); + + // clang-format on + test::ExpectTensorEqual(expected, *GetOutput(0)); +} + +TEST_F(ResizeNearestNeighborHalfPixelCentersOpTest, + TestNearestNeighbor4x4To3x3) { + // Input: + // 1, 2, 3, 4 + // 5, 6, 7, 8 + // 9, 10, 11, 12 + // 13, 14, 15, 16 + AddInputFromArray( + TensorShape({1, 4, 4, 1}), + {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}); + AddInputFromArray(TensorShape({2}), {3, 3}); + TF_ASSERT_OK(RunOpKernel()); + + Tensor expected(allocator(), DT_FLOAT, TensorShape({1, 3, 3, 1})); + + // clang-format off + test::FillValues(&expected, + {1, 3, 4, + 9, 11, 12, + 13, 15, 16}); + + // clang-format on + test::ExpectTensorEqual(expected, *GetOutput(0)); +} + +TEST_F(ResizeNearestNeighborHalfPixelCentersOpTest, TestNearest2x2To5x2) { + // Input: + // 1, 2 + // 3, 4 + AddInputFromArray(TensorShape({1, 2, 2, 1}), {1, 2, 3, 4}); + AddInputFromArray(TensorShape({2}), {5, 2}); + TF_ASSERT_OK(RunOpKernel()); + + Tensor expected(allocator(), DT_FLOAT, TensorShape({1, 5, 2, 1})); + + // clang-format off + test::FillValues(&expected, + {1, 2, + 1, 2, + 3, 4, + 3, 4, + 3, 4}); + + // clang-format on + test::ExpectTensorEqual(expected, *GetOutput(0)); +} + +TEST_F(ResizeNearestNeighborHalfPixelCentersOpTest, TestNearest2x2To4x4) { + // Input: + // 1, 2 + // 3, 4 + AddInputFromArray(TensorShape({1, 2, 2, 1}), {1, 2, 3, 4}); + AddInputFromArray(TensorShape({2}), {4, 4}); + TF_ASSERT_OK(RunOpKernel()); + + Tensor expected(allocator(), DT_FLOAT, TensorShape({1, 4, 4, 1})); + + // clang-format off + test::FillValues(&expected, + {1, 1, 2, 2, + 1, 1, 2, 2, + 3, 3, 4, 4, + 3, 3, 4, 4}); + + // clang-format on + test::ExpectTensorEqual(expected, *GetOutput(0)); +} + +TEST_F(ResizeNearestNeighborHalfPixelCentersOpTest, + TestNearest2x2x2x2To2x3x3x2) { + // Input: + // [ [ 1, 1 ], [ 2, 2], + // [ 3, 3 ], [ 4, 4] ], + // [ [ 5, 5 ], [ 6, 6], + // [ 7, 7 ], [ 8, 8] ] + AddInputFromArray(TensorShape({2, 2, 2, 2}), + {1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}); + AddInputFromArray(TensorShape({2}), {3, 3}); + TF_ASSERT_OK(RunOpKernel()); + + Tensor expected(allocator(), DT_FLOAT, TensorShape({2, 3, 3, 2})); + + // clang-format off + test::FillValues(&expected, + {1, 1, 2, 2, 2, 2, + 3, 3, 4, 4, 4, 4, + 3, 3, 4, 4, 4, 4, + 5, 5, 6, 6, 6, 6, + 7, 7, 8, 8, 8, 8, + 7, 7, 8, 8, 8, 8}); + + // clang-format on + test::ExpectTensorEqual(expected, *GetOutput(0)); +} } // namespace tensorflow diff --git a/tensorflow/core/ops/image_ops.cc b/tensorflow/core/ops/image_ops.cc index f451987a9b..58fc1d53a0 100644 --- a/tensorflow/core/ops/image_ops.cc +++ b/tensorflow/core/ops/image_ops.cc @@ -213,6 +213,7 @@ REGISTER_OP("ResizeBicubic") .Output("resized_images: float") .Attr("T: {int8, uint8, int16, uint16, int32, int64, half, float, double}") .Attr("align_corners: bool = false") + .Attr("half_pixel_centers: bool = false") .SetShapeFn(ResizeShapeFn); // -------------------------------------------------------------------------- @@ -222,6 +223,7 @@ REGISTER_OP("ResizeBicubicGrad") .Output("output: T") .Attr("T: {float, double}") .Attr("align_corners: bool = false") + .Attr("half_pixel_centers: bool = false") .SetShapeFn([](InferenceContext* c) { c->set_output(0, c->input(1)); return Status::OK(); @@ -236,6 +238,7 @@ REGISTER_OP("ResizeBilinear") "T: {int8, uint8, int16, uint16, int32, int64, bfloat16, half, " "float, double}") .Attr("align_corners: bool = false") + .Attr("half_pixel_centers: bool = false") .SetShapeFn(ResizeShapeFn); // -------------------------------------------------------------------------- @@ -262,6 +265,7 @@ REGISTER_OP("QuantizedResizeBilinear") .Output("out_max: float") .Attr("T: {quint8, qint32, float}") .Attr("align_corners: bool = false") + .Attr("half_pixel_centers: bool = false") .SetShapeFn([](InferenceContext* c) { TF_RETURN_IF_ERROR(ResizeShapeFn(c)); ShapeHandle min_shape; @@ -280,6 +284,7 @@ REGISTER_OP("ResizeBilinearGrad") .Output("output: T") .Attr("T: {float, bfloat16, half, double}") .Attr("align_corners: bool = false") + .Attr("half_pixel_centers: bool = false") .SetShapeFn([](InferenceContext* c) { c->set_output(0, c->input(1)); return Status::OK(); @@ -306,6 +311,7 @@ REGISTER_OP("ResizeNearestNeighbor") .Output("resized_images: T") .Attr("T: {int8, uint8, int16, uint16, int32, int64, half, float, double}") .Attr("align_corners: bool = false") + .Attr("half_pixel_centers: bool = false") .SetShapeFn(ResizeShapeFn); // -------------------------------------------------------------------------- @@ -315,6 +321,7 @@ REGISTER_OP("ResizeNearestNeighborGrad") .Output("output: T") .Attr("T: {uint8, int8, int32, half, float, double}") .Attr("align_corners: bool = false") + .Attr("half_pixel_centers: bool = false") .SetShapeFn([](InferenceContext* c) { ShapeHandle input; TF_RETURN_IF_ERROR(c->WithRank(c->input(0), 4, &input)); diff --git a/tensorflow/python/ops/image_grad.py b/tensorflow/python/ops/image_grad.py index 102181e68b..4925209b7d 100644 --- a/tensorflow/python/ops/image_grad.py +++ b/tensorflow/python/ops/image_grad.py @@ -44,7 +44,8 @@ def _ResizeNearestNeighborGrad(op, grad): grads = gen_image_ops.resize_nearest_neighbor_grad( grad, image_shape, - align_corners=op.get_attr("align_corners")) + align_corners=op.get_attr("align_corners"), + half_pixel_centers=op.get_attr("half_pixel_centers")) return [grads, None] @@ -60,7 +61,10 @@ def _ResizeBilinearGrad(op, grad): The gradients w.r.t. the input. """ grad0 = gen_image_ops.resize_bilinear_grad( - grad, op.inputs[0], align_corners=op.get_attr("align_corners")) + grad, + op.inputs[0], + align_corners=op.get_attr("align_corners"), + half_pixel_centers=op.get_attr("half_pixel_centers")) return [grad0, None] @@ -79,7 +83,10 @@ def _ResizeBicubicGrad(op, grad): grad0 = None if op.inputs[0].dtype in allowed_types: grad0 = gen_image_ops.resize_bicubic_grad( - grad, op.inputs[0], align_corners=op.get_attr("align_corners")) + grad, + op.inputs[0], + align_corners=op.get_attr("align_corners"), + half_pixel_centers=op.get_attr("half_pixel_centers")) return [grad0, None] diff --git a/tensorflow/python/ops/image_ops_impl.py b/tensorflow/python/ops/image_ops_impl.py index d99e10b7ac..3fc5628208 100644 --- a/tensorflow/python/ops/image_ops_impl.py +++ b/tensorflow/python/ops/image_ops_impl.py @@ -2986,6 +2986,45 @@ def sobel_edges(image): return output +def resize_bicubic(images, + size, + align_corners=False, + name=None, + half_pixel_centers=False): + return gen_image_ops.resize_bicubic( + images=images, + size=size, + align_corners=align_corners, + half_pixel_centers=half_pixel_centers, + name=name) + + +def resize_bilinear(images, + size, + align_corners=False, + name=None, + half_pixel_centers=False): + return gen_image_ops.resize_bilinear( + images=images, + size=size, + align_corners=align_corners, + half_pixel_centers=half_pixel_centers, + name=name) + + +def resize_nearest_neighbor(images, + size, + align_corners=False, + name=None, + half_pixel_centers=False): + return gen_image_ops.resize_nearest_neighbor( + images=images, + size=size, + align_corners=align_corners, + half_pixel_centers=half_pixel_centers, + name=name) + + resize_area_deprecation = deprecation.deprecated( date=None, instructions=( @@ -2998,14 +3037,14 @@ resize_bicubic_deprecation = deprecation.deprecated( instructions=( 'Use `tf.image.resize(...method=ResizeMethod.BICUBIC...)` instead.')) tf_export(v1=['image.resize_bicubic'])( - resize_bicubic_deprecation(gen_image_ops.resize_bicubic)) + resize_bicubic_deprecation(resize_bicubic)) resize_bilinear_deprecation = deprecation.deprecated( date=None, instructions=( 'Use `tf.image.resize(...method=ResizeMethod.BILINEAR...)` instead.')) tf_export(v1=['image.resize_bilinear'])( - resize_bilinear_deprecation(gen_image_ops.resize_bilinear)) + resize_bilinear_deprecation(resize_bilinear)) resize_nearest_neighbor_deprecation = deprecation.deprecated( date=None, @@ -3013,7 +3052,7 @@ resize_nearest_neighbor_deprecation = deprecation.deprecated( 'Use `tf.image.resize(...method=ResizeMethod.NEAREST_NEIGHBOR...)` ' 'instead.')) tf_export(v1=['image.resize_nearest_neighbor'])( - resize_nearest_neighbor_deprecation(gen_image_ops.resize_nearest_neighbor)) + resize_nearest_neighbor_deprecation(resize_nearest_neighbor)) @tf_export('image.crop_and_resize', v1=[]) diff --git a/tensorflow/tools/api/golden/v1/tensorflow.image.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.image.pbtxt index 102ca132a2..6741875b12 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.image.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.image.pbtxt @@ -186,11 +186,11 @@ tf_module { } member_method { name: "resize_bicubic" - argspec: "args=[\'images\', \'size\', \'align_corners\', \'name\'], varargs=None, keywords=None, defaults=[\'False\', \'None\'], " + argspec: "args=[\'images\', \'size\', \'align_corners\', \'name\', \'half_pixel_centers\'], varargs=None, keywords=None, defaults=[\'False\', \'None\', \'False\'], " } member_method { name: "resize_bilinear" - argspec: "args=[\'images\', \'size\', \'align_corners\', \'name\'], varargs=None, keywords=None, defaults=[\'False\', \'None\'], " + argspec: "args=[\'images\', \'size\', \'align_corners\', \'name\', \'half_pixel_centers\'], varargs=None, keywords=None, defaults=[\'False\', \'None\', \'False\'], " } member_method { name: "resize_image_with_crop_or_pad" @@ -206,7 +206,7 @@ tf_module { } member_method { name: "resize_nearest_neighbor" - argspec: "args=[\'images\', \'size\', \'align_corners\', \'name\'], varargs=None, keywords=None, defaults=[\'False\', \'None\'], " + argspec: "args=[\'images\', \'size\', \'align_corners\', \'name\', \'half_pixel_centers\'], varargs=None, keywords=None, defaults=[\'False\', \'None\', \'False\'], " } member_method { name: "rgb_to_grayscale" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.raw_ops.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.raw_ops.pbtxt index 080afef764..b3fdc16237 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.raw_ops.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.raw_ops.pbtxt @@ -2354,7 +2354,7 @@ tf_module { } member_method { name: "QuantizedResizeBilinear" - argspec: "args=[\'images\', \'size\', \'min\', \'max\', \'align_corners\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'images\', \'size\', \'min\', \'max\', \'align_corners\', \'half_pixel_centers\'], varargs=None, keywords=None, defaults=None" } member_method { name: "QueueClose" @@ -2682,27 +2682,27 @@ tf_module { } member_method { name: "ResizeBicubic" - argspec: "args=[\'images\', \'size\', \'align_corners\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'images\', \'size\', \'align_corners\', \'half_pixel_centers\'], varargs=None, keywords=None, defaults=None" } member_method { name: "ResizeBicubicGrad" - argspec: "args=[\'grads\', \'original_image\', \'align_corners\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'grads\', \'original_image\', \'align_corners\', \'half_pixel_centers\'], varargs=None, keywords=None, defaults=None" } member_method { name: "ResizeBilinear" - argspec: "args=[\'images\', \'size\', \'align_corners\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'images\', \'size\', \'align_corners\', \'half_pixel_centers\'], varargs=None, keywords=None, defaults=None" } member_method { name: "ResizeBilinearGrad" - argspec: "args=[\'grads\', \'original_image\', \'align_corners\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'grads\', \'original_image\', \'align_corners\', \'half_pixel_centers\'], varargs=None, keywords=None, defaults=None" } member_method { name: "ResizeNearestNeighbor" - argspec: "args=[\'images\', \'size\', \'align_corners\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'images\', \'size\', \'align_corners\', \'half_pixel_centers\'], varargs=None, keywords=None, defaults=None" } member_method { name: "ResizeNearestNeighborGrad" - argspec: "args=[\'grads\', \'size\', \'align_corners\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'grads\', \'size\', \'align_corners\', \'half_pixel_centers\'], varargs=None, keywords=None, defaults=None" } member_method { name: "ResourceApplyAdaMax" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.raw_ops.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.raw_ops.pbtxt index 080afef764..b3fdc16237 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.raw_ops.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.raw_ops.pbtxt @@ -2354,7 +2354,7 @@ tf_module { } member_method { name: "QuantizedResizeBilinear" - argspec: "args=[\'images\', \'size\', \'min\', \'max\', \'align_corners\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'images\', \'size\', \'min\', \'max\', \'align_corners\', \'half_pixel_centers\'], varargs=None, keywords=None, defaults=None" } member_method { name: "QueueClose" @@ -2682,27 +2682,27 @@ tf_module { } member_method { name: "ResizeBicubic" - argspec: "args=[\'images\', \'size\', \'align_corners\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'images\', \'size\', \'align_corners\', \'half_pixel_centers\'], varargs=None, keywords=None, defaults=None" } member_method { name: "ResizeBicubicGrad" - argspec: "args=[\'grads\', \'original_image\', \'align_corners\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'grads\', \'original_image\', \'align_corners\', \'half_pixel_centers\'], varargs=None, keywords=None, defaults=None" } member_method { name: "ResizeBilinear" - argspec: "args=[\'images\', \'size\', \'align_corners\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'images\', \'size\', \'align_corners\', \'half_pixel_centers\'], varargs=None, keywords=None, defaults=None" } member_method { name: "ResizeBilinearGrad" - argspec: "args=[\'grads\', \'original_image\', \'align_corners\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'grads\', \'original_image\', \'align_corners\', \'half_pixel_centers\'], varargs=None, keywords=None, defaults=None" } member_method { name: "ResizeNearestNeighbor" - argspec: "args=[\'images\', \'size\', \'align_corners\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'images\', \'size\', \'align_corners\', \'half_pixel_centers\'], varargs=None, keywords=None, defaults=None" } member_method { name: "ResizeNearestNeighborGrad" - argspec: "args=[\'grads\', \'size\', \'align_corners\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'grads\', \'size\', \'align_corners\', \'half_pixel_centers\'], varargs=None, keywords=None, defaults=None" } member_method { name: "ResourceApplyAdaMax" -- GitLab From a6bf9c8476a4acd37fe5f400bc01d9d4beacdfc2 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 26 Feb 2019 10:08:35 -0800 Subject: [PATCH 561/766] Add posix implementation for platform/numa.h functions, relying on hwloc. PiperOrigin-RevId: 235742876 --- .bazelrc | 1 + configure.py | 1 + tensorflow/BUILD | 6 + tensorflow/core/BUILD | 49 +++--- .../core/platform/default/build_config.bzl | 29 ++++ tensorflow/core/platform/posix/port.cc | 140 +++++++++++++++++- tensorflow/tools/lib_package/BUILD | 2 + tensorflow/tools/pip_package/BUILD | 1 + 8 files changed, 200 insertions(+), 29 deletions(-) diff --git a/.bazelrc b/.bazelrc index 17285afdb3..1741091242 100644 --- a/.bazelrc +++ b/.bazelrc @@ -67,6 +67,7 @@ build:sycl_trisycl --define=using_sycl=true --define=using_trisycl=true build:gdr --define=with_gdr_support=true build:ngraph --define=with_ngraph_support=true build:verbs --define=with_verbs_support=true +build:numa --define=with_numa_support=true # Options to disable default on features build:noaws --define=no_aws_support=true diff --git a/configure.py b/configure.py index 3eb09a1ae9..673825c0ad 100644 --- a/configure.py +++ b/configure.py @@ -1751,6 +1751,7 @@ def main(): config_info_line('gdr', 'Build with GDR support.') config_info_line('verbs', 'Build with libverbs support.') config_info_line('ngraph', 'Build with Intel nGraph support.') + config_info_line('numa', 'Build with NUMA support.') config_info_line( 'dynamic_kernels', '(Experimental) Build kernels into separate shared objects.') diff --git a/tensorflow/BUILD b/tensorflow/BUILD index f53982f1ef..e1d988ac65 100644 --- a/tensorflow/BUILD +++ b/tensorflow/BUILD @@ -304,6 +304,12 @@ config_setting( visibility = ["//visibility:public"], ) +config_setting( + name = "with_numa_support", + define_values = {"with_numa_support": "true"}, + visibility = ["//visibility:public"], +) + # Crosses between framework_shared_object and a bunch of other configurations # due to limitations in nested select() statements. config_setting( diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD index 64aed375b2..8f5de68322 100644 --- a/tensorflow/core/BUILD +++ b/tensorflow/core/BUILD @@ -128,6 +128,9 @@ load( "tf_additional_libdevice_srcs", "tf_additional_minimal_lib_srcs", "tf_additional_mpi_lib_defines", + "tf_additional_numa_deps", + "tf_additional_numa_lib_defines", + "tf_additional_numa_copts", "tf_additional_proto_hdrs", "tf_additional_proto_srcs", "tf_additional_test_deps", @@ -388,15 +391,15 @@ cc_library( ":platform_port_hdrs", ":platform_port_internal_hdrs", ], - copts = tf_copts(), + copts = tf_copts() + tf_additional_numa_copts(), visibility = ["//tensorflow/core:__subpackages__"], deps = [ ":lib_platform", ":platform_base", - "//tensorflow/core/platform/default/build_config:port", "@com_google_absl//absl/base", + "//tensorflow/core/platform/default/build_config:port", "@snappy", - ], + ] + tf_additional_numa_deps(), ) filegroup( @@ -2278,11 +2281,14 @@ LIB_INTERNAL_PUBLIC_HEADERS = tf_additional_lib_hdrs() + [ ] # Replicated for lib_internal and lib_internal_impl. -LIB_INTERNAL_DEFINES = (tf_additional_lib_defines() + [ - "TF_USE_SNAPPY", - ] + tf_additional_verbs_lib_defines() + - tf_additional_mpi_lib_defines() + - tf_additional_gdr_lib_defines()) +LIB_INTERNAL_DEFINES = ( + tf_additional_lib_defines() + [ + "TF_USE_SNAPPY", + ] + tf_additional_verbs_lib_defines() + + tf_additional_mpi_lib_defines() + + tf_additional_gdr_lib_defines() + + tf_additional_numa_lib_defines() +) cc_library( name = "lib_internal", @@ -2355,19 +2361,20 @@ cc_library( copts = tf_copts(), defines = LIB_INTERNAL_DEFINES, deps = tf_additional_lib_deps() + [ - ":lib_hash_crc32c_accelerate_internal", - ":lib_proto_parsing", - ":abi", - ":core_stringpiece", - "@com_google_absl//absl/memory", - "@com_google_absl//absl/strings", - "//third_party/eigen3", - "//tensorflow/core/platform/default/build_config:platformlib", - "@snappy", - "@zlib_archive//:zlib", - "@double_conversion//:double-conversion", - "@protobuf_archive//:protobuf", - ] + tf_protos_all_impl() + tf_protos_grappler_impl(), + ":lib_hash_crc32c_accelerate_internal", + ":lib_proto_parsing", + ":abi", + ":core_stringpiece", + "@com_google_absl//absl/memory", + "@com_google_absl//absl/strings", + "//third_party/eigen3", + "//tensorflow/core/platform/default/build_config:platformlib", + "@snappy", + "@zlib_archive//:zlib", + "@double_conversion//:double-conversion", + "@protobuf_archive//:protobuf", + ] + tf_protos_all_impl() + tf_protos_grappler_impl() + + tf_additional_numa_deps(), ) # File compiled with extra flags to get cpu-specific acceleration. diff --git a/tensorflow/core/platform/default/build_config.bzl b/tensorflow/core/platform/default/build_config.bzl index f9ac4ff0bc..f6f449a95d 100644 --- a/tensorflow/core/platform/default/build_config.bzl +++ b/tensorflow/core/platform/default/build_config.bzl @@ -725,6 +725,12 @@ def tf_additional_gdr_lib_defines(): "//conditions:default": [], }) +def tf_additional_numa_lib_defines(): + return select({ + "//tensorflow:with_numa_support": ["TENSORFLOW_USE_NUMA"], + "//conditions:default": [], + }) + def tf_py_clif_cc(name, visibility = None, **kwargs): pass @@ -757,3 +763,26 @@ def tf_additional_binary_deps(): "//third_party/mkl:intel_binary_blob", ], ) + +def tf_additional_numa_deps(): + return select({ + "//tensorflow:android": [], + "//tensorflow:ios": [], + "//tensorflow:windows": [], + "//tensorflow:darwin": [], + "//conditions:default": [ + "@hwloc", + ], + }) + +def tf_additional_numa_copts(): + return select({ + "//tensorflow:android": [], + "//tensorflow:ios": [], + "//tensorflow:windows": [], + "//tensorflow:darwin": [], + "//conditions:default": [ + "-Ithird_party/hwloc/hwloc-master/include", + "-DTENSORFLOW_USE_NUMA", + ], + }) diff --git a/tensorflow/core/platform/posix/port.cc b/tensorflow/core/platform/posix/port.cc index 807e008322..1561632a49 100644 --- a/tensorflow/core/platform/posix/port.cc +++ b/tensorflow/core/platform/posix/port.cc @@ -45,6 +45,10 @@ limitations under the License. #include #endif +#if TENSORFLOW_USE_NUMA +#include "hwloc.h" // TF:hwloc +#endif + namespace tensorflow { namespace port { @@ -115,16 +119,94 @@ int NumHyperthreadsPerCore() { return (ht_per_core > 0) ? ht_per_core : 1; } -bool NUMAEnabled() { - // Not yet implemented: coming soon. - return false; +#ifdef TENSORFLOW_USE_NUMA +namespace { +static hwloc_topology_t hwloc_topology_handle; + +bool HaveHWLocTopology() { + // One time initialization + static bool init = []() { + if (hwloc_topology_init(&hwloc_topology_handle)) { + LOG(ERROR) << "Call to hwloc_topology_init() failed"; + return false; + } + if (hwloc_topology_load(hwloc_topology_handle)) { + LOG(ERROR) << "Call to hwloc_topology_load() failed"; + return false; + } + return true; + }(); + return init; +} + +// Return the first hwloc object of the given type whose os_index +// matches 'index'. +hwloc_obj_t GetHWLocTypeIndex(hwloc_obj_type_t tp, int index) { + hwloc_obj_t obj = nullptr; + if (index >= 0) { + while ((obj = hwloc_get_next_obj_by_type(hwloc_topology_handle, tp, obj)) != + nullptr) { + if (obj->os_index == index) break; + } + } + return obj; } +} // namespace +#endif // TENSORFLOW_USE_NUMA + +bool NUMAEnabled() { return (NUMANumNodes() > 1); } -int NUMANumNodes() { return 1; } +int NUMANumNodes() { +#ifdef TENSORFLOW_USE_NUMA + if (HaveHWLocTopology()) { + int num_numanodes = + hwloc_get_nbobjs_by_type(hwloc_topology_handle, HWLOC_OBJ_NUMANODE); + return std::max(1, num_numanodes); + } else { + return 1; + } +#else + return 1; +#endif // TENSORFLOW_USE_NUMA +} -void NUMASetThreadNodeAffinity(int node) {} +void NUMASetThreadNodeAffinity(int node) { +#ifdef TENSORFLOW_USE_NUMA + if (HaveHWLocTopology()) { + // Find the corresponding NUMA node topology object. + hwloc_obj_t obj = GetHWLocTypeIndex(HWLOC_OBJ_NUMANODE, node); + if (obj) { + hwloc_set_cpubind(hwloc_topology_handle, obj->cpuset, + HWLOC_CPUBIND_THREAD | HWLOC_CPUBIND_STRICT); + } else { + LOG(ERROR) << "Could not find hwloc NUMA node " << node; + } + } +#endif // TENSORFLOW_USE_NUMA +} -int NUMAGetThreadNodeAffinity() { return kNUMANoAffinity; } +int NUMAGetThreadNodeAffinity() { + int node_index = kNUMANoAffinity; +#ifdef TENSORFLOW_USE_NUMA + if (HaveHWLocTopology()) { + hwloc_cpuset_t thread_cpuset = hwloc_bitmap_alloc(); + hwloc_get_cpubind(hwloc_topology_handle, thread_cpuset, + HWLOC_CPUBIND_THREAD); + hwloc_obj_t obj = nullptr; + // Return the first NUMA node whose cpuset is a (non-proper) superset of + // that of the current thread. + while ((obj = hwloc_get_next_obj_by_type( + hwloc_topology_handle, HWLOC_OBJ_NUMANODE, obj)) != nullptr) { + if (hwloc_bitmap_isincluded(thread_cpuset, obj->cpuset)) { + node_index = obj->os_index; + break; + } + } + hwloc_bitmap_free(thread_cpuset); + } +#endif // TENSORFLOW_USE_NUMA + return node_index; +} void* AlignedMalloc(size_t size, int minimum_alignment) { #if defined(__ANDROID__) @@ -154,12 +236,54 @@ void* Realloc(void* ptr, size_t size) { return realloc(ptr, size); } void Free(void* ptr) { free(ptr); } void* NUMAMalloc(int node, size_t size, int minimum_alignment) { +#ifdef TENSORFLOW_USE_NUMA + if (HaveHWLocTopology()) { + hwloc_obj_t numa_node = GetHWLocTypeIndex(HWLOC_OBJ_NUMANODE, node); + if (numa_node) { + return hwloc_alloc_membind(hwloc_topology_handle, size, + numa_node->nodeset, HWLOC_MEMBIND_BIND, + HWLOC_MEMBIND_BYNODESET); + } else { + LOG(ERROR) << "Failed to find hwloc NUMA node " << node; + } + } +#endif // TENSORFLOW_USE_NUMA return AlignedMalloc(size, minimum_alignment); } -void NUMAFree(void* ptr, size_t size) { Free(ptr); } +void NUMAFree(void* ptr, size_t size) { +#ifdef TENSORFLOW_USE_NUMA + if (HaveHWLocTopology()) { + hwloc_free(hwloc_topology_handle, ptr, size); + return; + } +#endif // TENSORFLOW_USE_NUMA + Free(ptr); +} -int NUMAGetMemAffinity(const void* addr) { return kNUMANoAffinity; } +int NUMAGetMemAffinity(const void* addr) { + int node = kNUMANoAffinity; +#ifdef TENSORFLOW_USE_NUMA + if (HaveHWLocTopology() && addr) { + hwloc_nodeset_t nodeset = hwloc_bitmap_alloc(); + if (!hwloc_get_area_memlocation(hwloc_topology_handle, addr, 4, nodeset, + HWLOC_MEMBIND_BYNODESET)) { + hwloc_obj_t obj = nullptr; + while ((obj = hwloc_get_next_obj_by_type( + hwloc_topology_handle, HWLOC_OBJ_NUMANODE, obj)) != nullptr) { + if (hwloc_bitmap_isincluded(nodeset, obj->nodeset)) { + node = obj->os_index; + break; + } + } + hwloc_bitmap_free(nodeset); + } else { + LOG(ERROR) << "Failed call to hwloc_get_area_memlocation."; + } + } +#endif // TENSORFLOW_USE_NUMA + return node; +} void MallocExtension_ReleaseToSystem(std::size_t num_bytes) { // No-op. diff --git a/tensorflow/tools/lib_package/BUILD b/tensorflow/tools/lib_package/BUILD index 0c81ebeefb..525c05b369 100644 --- a/tensorflow/tools/lib_package/BUILD +++ b/tensorflow/tools/lib_package/BUILD @@ -130,6 +130,7 @@ genrule( "@gemmlowp//:LICENSE", "@gif_archive//:COPYING", "@highwayhash//:LICENSE", + "@hwloc//:LICENSE", "@icu//:icu4c/LICENSE", "@jpeg//:LICENSE.md", "@llvm//:LICENSE.TXT", @@ -199,6 +200,7 @@ genrule( "@gemmlowp//:LICENSE", "@gif_archive//:COPYING", "@highwayhash//:LICENSE", + "@hwloc//:LICENSE", "@icu//:icu4j/main/shared/licenses/LICENSE", "@jpeg//:LICENSE.md", "@llvm//:LICENSE.TXT", diff --git a/tensorflow/tools/pip_package/BUILD b/tensorflow/tools/pip_package/BUILD index 90dfca2b44..88f13a051e 100644 --- a/tensorflow/tools/pip_package/BUILD +++ b/tensorflow/tools/pip_package/BUILD @@ -171,6 +171,7 @@ filegroup( "@gemmlowp//:LICENSE", "@gif_archive//:COPYING", "@highwayhash//:LICENSE", + "@hwloc//:LICENSE", "@icu//:icu4c/LICENSE", "@jpeg//:LICENSE.md", "@keras_applications_archive//:LICENSE", -- GitLab From 336daca63422eb0ab38f44b62c09d354ddd7d42a Mon Sep 17 00:00:00 2001 From: Thomas O'Malley Date: Tue, 26 Feb 2019 10:17:50 -0800 Subject: [PATCH 562/766] Add `.consumers()` method for CompositeTensors. PiperOrigin-RevId: 235744696 --- .../python/framework/composite_tensor.py | 17 ++++++++++++++ tensorflow/python/framework/sparse_tensor.py | 12 ---------- .../python/framework/sparse_tensor_test.py | 23 ++++++++++--------- .../python/ops/ragged/ragged_tensor_test.py | 11 +++++++++ .../v1/tensorflow.-indexed-slices.pbtxt | 4 ++++ .../golden/v1/tensorflow.-ragged-tensor.pbtxt | 4 ++++ .../v2/tensorflow.-indexed-slices.pbtxt | 4 ++++ .../golden/v2/tensorflow.-ragged-tensor.pbtxt | 4 ++++ 8 files changed, 56 insertions(+), 23 deletions(-) diff --git a/tensorflow/python/framework/composite_tensor.py b/tensorflow/python/framework/composite_tensor.py index 5c9a292e36..e0e9985506 100644 --- a/tensorflow/python/framework/composite_tensor.py +++ b/tensorflow/python/framework/composite_tensor.py @@ -23,6 +23,7 @@ import abc import six from tensorflow.python import pywrap_tensorflow +from tensorflow.python.util import nest @six.add_metaclass(abc.ABCMeta) @@ -91,6 +92,22 @@ class CompositeTensor(object): """Returns True if this tensor's components belong to a TF graph.""" raise NotImplementedError("CompositeTensor._is_symbolic_tensor") + def consumers(self): + """Returns a list of `Operation`s that consume this `CompositeTensor`. + + Returns: + A list of `Operation`s. + + Raises: + RuntimeError: If this method is called while executing eagerly. + """ + consumers = nest.flatten([ + component.consumers() + for component in self._to_components() + if getattr(component, "graph", None) is not None + ]) + return list(set(consumers)) + pywrap_tensorflow.RegisterType("CompositeTensor", CompositeTensor) diff --git a/tensorflow/python/framework/sparse_tensor.py b/tensorflow/python/framework/sparse_tensor.py index c69fa41677..b21b109de6 100644 --- a/tensorflow/python/framework/sparse_tensor.py +++ b/tensorflow/python/framework/sparse_tensor.py @@ -193,18 +193,6 @@ class SparseTensor(_TensorLike, composite_tensor.CompositeTensor): """The `Graph` that contains the index, value, and dense_shape tensors.""" return self._indices.graph - def consumers(self): - """Returns a list of `Operation`s that consume this `SparseTensor`. - - Returns: - A list of `Operation`s. - """ - values_consumers = set(self._values.consumers()) - indices_consumers = set(self._indices.consumers()) - dense_shape_consumers = set(self._dense_shape.consumers()) - return list(values_consumers \ - .union(indices_consumers, dense_shape_consumers)) - def __str__(self): return "SparseTensor(indices=%s, values=%s, dense_shape=%s)" % ( self._indices, self._values, self._dense_shape) diff --git a/tensorflow/python/framework/sparse_tensor_test.py b/tensorflow/python/framework/sparse_tensor_test.py index a999c12ca8..03aa63b624 100644 --- a/tensorflow/python/framework/sparse_tensor_test.py +++ b/tensorflow/python/framework/sparse_tensor_test.py @@ -20,6 +20,7 @@ from __future__ import print_function import numpy as np +from tensorflow.python.eager import context from tensorflow.python.framework import dtypes from tensorflow.python.framework import ops from tensorflow.python.framework import sparse_tensor @@ -65,18 +66,18 @@ class SparseTensorTest(test_util.TensorFlowTestCase): sparse_tensor.is_sparse( sparse_tensor.SparseTensorValue([[0]], [0], [1]))) - @test_util.run_deprecated_v1 def testConsumers(self): - sp = sparse_tensor.SparseTensor([[0, 0], [1, 2]], [1.0, 3.0], [3, 4]) - w = ops.convert_to_tensor(np.ones([4, 1], np.float32)) - out = sparse_ops.sparse_tensor_dense_matmul(sp, w) - self.assertEqual(len(sp.consumers()), 1) - self.assertEqual(sp.consumers()[0], out.op) - - dense = sparse_ops.sparse_tensor_to_dense(sp) - self.assertEqual(len(sp.consumers()), 2) - self.assertTrue(dense.op in sp.consumers()) - self.assertTrue(out.op in sp.consumers()) + with context.graph_mode(): + sp = sparse_tensor.SparseTensor([[0, 0], [1, 2]], [1.0, 3.0], [3, 4]) + w = ops.convert_to_tensor(np.ones([4, 1], np.float32)) + out = sparse_ops.sparse_tensor_dense_matmul(sp, w) + self.assertEqual(len(sp.consumers()), 1) + self.assertEqual(sp.consumers()[0], out.op) + + dense = sparse_ops.sparse_tensor_to_dense(sp) + self.assertEqual(len(sp.consumers()), 2) + self.assertIn(dense.op, sp.consumers()) + self.assertIn(out.op, sp.consumers()) class ConvertToTensorOrSparseTensorTest(test_util.TensorFlowTestCase): diff --git a/tensorflow/python/ops/ragged/ragged_tensor_test.py b/tensorflow/python/ops/ragged/ragged_tensor_test.py index 62b7a6b1bc..5d55afcc6b 100644 --- a/tensorflow/python/ops/ragged/ragged_tensor_test.py +++ b/tensorflow/python/ops/ragged/ragged_tensor_test.py @@ -1220,5 +1220,16 @@ class RaggedTensorTest(ragged_test_util.RaggedTensorTestCase, self.assertAllEqual(elem, values[i]) i += 1 + def testConsumers(self): + if context.executing_eagerly(): + return + + a = RaggedTensor.from_row_splits( + array_ops.placeholder(dtypes.int32, shape=[None], name='a.values'), + array_ops.placeholder(dtypes.int64, name='a.row_splits')) + ragged_math_ops.reduce_sum(a) + self.assertLen(a.consumers(), 1) + + if __name__ == '__main__': googletest.main() diff --git a/tensorflow/tools/api/golden/v1/tensorflow.-indexed-slices.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.-indexed-slices.pbtxt index 5b47c718a5..148e6b83b3 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.-indexed-slices.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.-indexed-slices.pbtxt @@ -40,4 +40,8 @@ tf_class { name: "__init__" argspec: "args=[\'self\', \'values\', \'indices\', \'dense_shape\'], varargs=None, keywords=None, defaults=[\'None\'], " } + member_method { + name: "consumers" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } } diff --git a/tensorflow/tools/api/golden/v1/tensorflow.-ragged-tensor.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.-ragged-tensor.pbtxt index 481a8c73ac..feb831fb6b 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.-ragged-tensor.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.-ragged-tensor.pbtxt @@ -39,6 +39,10 @@ tf_class { name: "bounding_shape" argspec: "args=[\'self\', \'axis\', \'name\'], varargs=None, keywords=None, defaults=[\'None\', \'None\'], " } + member_method { + name: "consumers" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } member_method { name: "from_nested_row_lengths" argspec: "args=[\'cls\', \'flat_values\', \'nested_row_lengths\', \'name\'], varargs=None, keywords=None, defaults=[\'None\'], " diff --git a/tensorflow/tools/api/golden/v2/tensorflow.-indexed-slices.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.-indexed-slices.pbtxt index 5b47c718a5..148e6b83b3 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.-indexed-slices.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.-indexed-slices.pbtxt @@ -40,4 +40,8 @@ tf_class { name: "__init__" argspec: "args=[\'self\', \'values\', \'indices\', \'dense_shape\'], varargs=None, keywords=None, defaults=[\'None\'], " } + member_method { + name: "consumers" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.-ragged-tensor.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.-ragged-tensor.pbtxt index 481a8c73ac..feb831fb6b 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.-ragged-tensor.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.-ragged-tensor.pbtxt @@ -39,6 +39,10 @@ tf_class { name: "bounding_shape" argspec: "args=[\'self\', \'axis\', \'name\'], varargs=None, keywords=None, defaults=[\'None\', \'None\'], " } + member_method { + name: "consumers" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } member_method { name: "from_nested_row_lengths" argspec: "args=[\'cls\', \'flat_values\', \'nested_row_lengths\', \'name\'], varargs=None, keywords=None, defaults=[\'None\'], " -- GitLab From ba843434822a7de44d74f7620582f5240d23b26f Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 26 Feb 2019 10:22:24 -0800 Subject: [PATCH 563/766] Update ops-related pbtxt files. PiperOrigin-RevId: 235745592 --- .../core/ops/compat/ops_history.v1.pbtxt | 318 ++++++++++++++++++ tensorflow/core/ops/ops.pbtxt | 49 +++ 2 files changed, 367 insertions(+) diff --git a/tensorflow/core/ops/compat/ops_history.v1.pbtxt b/tensorflow/core/ops/compat/ops_history.v1.pbtxt index 68bdf49118..d4d4aa04be 100644 --- a/tensorflow/core/ops/compat/ops_history.v1.pbtxt +++ b/tensorflow/core/ops/compat/ops_history.v1.pbtxt @@ -48936,6 +48936,62 @@ op { } } } +op { + name: "QuantizedResizeBilinear" + input_arg { + name: "images" + type_attr: "T" + } + input_arg { + name: "size" + type: DT_INT32 + } + input_arg { + name: "min" + type: DT_FLOAT + } + input_arg { + name: "max" + type: DT_FLOAT + } + output_arg { + name: "resized_images" + type_attr: "T" + } + output_arg { + name: "out_min" + type: DT_FLOAT + } + output_arg { + name: "out_max" + type: DT_FLOAT + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_QUINT8 + type: DT_QINT32 + type: DT_FLOAT + } + } + } + attr { + name: "align_corners" + type: "bool" + default_value { + b: false + } + } + attr { + name: "half_pixel_centers" + type: "bool" + default_value { + b: false + } + } +} op { name: "QueueClose" input_arg { @@ -52528,6 +52584,84 @@ op { } } } +op { + name: "ResizeBicubic" + input_arg { + name: "images" + type_attr: "T" + } + input_arg { + name: "size" + type: DT_INT32 + } + output_arg { + name: "resized_images" + type: DT_FLOAT + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_INT8 + type: DT_UINT8 + type: DT_INT16 + type: DT_UINT16 + type: DT_INT32 + type: DT_INT64 + type: DT_HALF + type: DT_FLOAT + type: DT_DOUBLE + } + } + } + attr { + name: "align_corners" + type: "bool" + default_value { + b: false + } + } + attr { + name: "half_pixel_centers" + type: "bool" + default_value { + b: false + } + } +} +op { + name: "ResizeBicubicGrad" + input_arg { + name: "grads" + type: DT_FLOAT + } + input_arg { + name: "original_image" + type_attr: "T" + } + output_arg { + name: "output" + type_attr: "T" + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_FLOAT + type: DT_DOUBLE + } + } + } + attr { + name: "align_corners" + type: "bool" + default_value { + b: false + } + } +} op { name: "ResizeBicubicGrad" input_arg { @@ -52559,6 +52693,13 @@ op { b: false } } + attr { + name: "half_pixel_centers" + type: "bool" + default_value { + b: false + } + } } op { name: "ResizeBilinear" @@ -52677,6 +52818,86 @@ op { } } } +op { + name: "ResizeBilinear" + input_arg { + name: "images" + type_attr: "T" + } + input_arg { + name: "size" + type: DT_INT32 + } + output_arg { + name: "resized_images" + type: DT_FLOAT + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_INT8 + type: DT_UINT8 + type: DT_INT16 + type: DT_UINT16 + type: DT_INT32 + type: DT_INT64 + type: DT_BFLOAT16 + type: DT_HALF + type: DT_FLOAT + type: DT_DOUBLE + } + } + } + attr { + name: "align_corners" + type: "bool" + default_value { + b: false + } + } + attr { + name: "half_pixel_centers" + type: "bool" + default_value { + b: false + } + } +} +op { + name: "ResizeBilinearGrad" + input_arg { + name: "grads" + type: DT_FLOAT + } + input_arg { + name: "original_image" + type_attr: "T" + } + output_arg { + name: "output" + type_attr: "T" + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_FLOAT + type: DT_HALF + type: DT_DOUBLE + } + } + } + attr { + name: "align_corners" + type: "bool" + default_value { + b: false + } + } +} op { name: "ResizeBilinearGrad" input_arg { @@ -52697,6 +52918,7 @@ op { allowed_values { list { type: DT_FLOAT + type: DT_BFLOAT16 type: DT_HALF type: DT_DOUBLE } @@ -52743,6 +52965,13 @@ op { b: false } } + attr { + name: "half_pixel_centers" + type: "bool" + default_value { + b: false + } + } } op { name: "ResizeNearestNeighbor" @@ -52821,6 +53050,52 @@ op { } } } +op { + name: "ResizeNearestNeighbor" + input_arg { + name: "images" + type_attr: "T" + } + input_arg { + name: "size" + type: DT_INT32 + } + output_arg { + name: "resized_images" + type_attr: "T" + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_INT8 + type: DT_UINT8 + type: DT_INT16 + type: DT_UINT16 + type: DT_INT32 + type: DT_INT64 + type: DT_HALF + type: DT_FLOAT + type: DT_DOUBLE + } + } + } + attr { + name: "align_corners" + type: "bool" + default_value { + b: false + } + } + attr { + name: "half_pixel_centers" + type: "bool" + default_value { + b: false + } + } +} op { name: "ResizeNearestNeighborGrad" input_arg { @@ -52857,6 +53132,49 @@ op { } } } +op { + name: "ResizeNearestNeighborGrad" + input_arg { + name: "grads" + type_attr: "T" + } + input_arg { + name: "size" + type: DT_INT32 + } + output_arg { + name: "output" + type_attr: "T" + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_UINT8 + type: DT_INT8 + type: DT_INT32 + type: DT_HALF + type: DT_FLOAT + type: DT_DOUBLE + } + } + } + attr { + name: "align_corners" + type: "bool" + default_value { + b: false + } + } + attr { + name: "half_pixel_centers" + type: "bool" + default_value { + b: false + } + } +} op { name: "ResourceApplyAdaMax" input_arg { diff --git a/tensorflow/core/ops/ops.pbtxt b/tensorflow/core/ops/ops.pbtxt index b2e7ff1b2e..a6c4d24330 100644 --- a/tensorflow/core/ops/ops.pbtxt +++ b/tensorflow/core/ops/ops.pbtxt @@ -25640,6 +25640,13 @@ op { b: false } } + attr { + name: "half_pixel_centers" + type: "bool" + default_value { + b: false + } + } } op { name: "QueueClose" @@ -28081,6 +28088,13 @@ op { b: false } } + attr { + name: "half_pixel_centers" + type: "bool" + default_value { + b: false + } + } } op { name: "ResizeBicubicGrad" @@ -28113,6 +28127,13 @@ op { b: false } } + attr { + name: "half_pixel_centers" + type: "bool" + default_value { + b: false + } + } } op { name: "ResizeBilinear" @@ -28153,6 +28174,13 @@ op { b: false } } + attr { + name: "half_pixel_centers" + type: "bool" + default_value { + b: false + } + } } op { name: "ResizeBilinearGrad" @@ -28187,6 +28215,13 @@ op { b: false } } + attr { + name: "half_pixel_centers" + type: "bool" + default_value { + b: false + } + } } op { name: "ResizeNearestNeighbor" @@ -28226,6 +28261,13 @@ op { b: false } } + attr { + name: "half_pixel_centers" + type: "bool" + default_value { + b: false + } + } } op { name: "ResizeNearestNeighborGrad" @@ -28262,6 +28304,13 @@ op { b: false } } + attr { + name: "half_pixel_centers" + type: "bool" + default_value { + b: false + } + } } op { name: "ResourceApplyAdaMax" -- GitLab From fe9a8ffe092643669202f90e7a2fb37587a059bd Mon Sep 17 00:00:00 2001 From: Benjamin Kramer Date: Tue, 26 Feb 2019 10:59:53 -0800 Subject: [PATCH 564/766] [TF:XLA] Add bfloat16 to the set of supported types of the CPU backend We cast them to F32 like we do on GPUs. BF16 is not very interesting for CPUs right now but it also just works, so allow it. PiperOrigin-RevId: 235753159 --- tensorflow/compiler/jit/xla_cpu_device.cc | 5 +++-- tensorflow/compiler/tests/BUILD | 4 ---- tensorflow/compiler/tf2xla/xla_op_registry.h | 4 ++-- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/tensorflow/compiler/jit/xla_cpu_device.cc b/tensorflow/compiler/jit/xla_cpu_device.cc index 94dc61d55f..345e87a573 100644 --- a/tensorflow/compiler/jit/xla_cpu_device.cc +++ b/tensorflow/compiler/jit/xla_cpu_device.cc @@ -83,9 +83,10 @@ REGISTER_LOCAL_DEVICE_FACTORY(DEVICE_XLA_CPU, XlaCpuDeviceFactory); // Kernel registrations -constexpr std::array kAllXlaCpuTypes = { +constexpr std::array kAllXlaCpuTypes = { {DT_UINT8, DT_QUINT8, DT_INT8, DT_QINT8, DT_INT32, DT_QINT32, DT_INT64, - DT_HALF, DT_FLOAT, DT_DOUBLE, DT_COMPLEX64, DT_COMPLEX128, DT_BOOL}}; + DT_HALF, DT_FLOAT, DT_DOUBLE, DT_COMPLEX64, DT_COMPLEX128, DT_BOOL, + DT_BFLOAT16}}; REGISTER_XLA_LAUNCH_KERNEL(DEVICE_XLA_CPU, XlaLocalLaunchOp, kAllXlaCpuTypes); REGISTER_XLA_COMPILE_KERNEL(DEVICE_XLA_CPU, XlaCompileOp, kAllXlaCpuTypes); diff --git a/tensorflow/compiler/tests/BUILD b/tensorflow/compiler/tests/BUILD index adf91f100b..36f9469372 100644 --- a/tensorflow/compiler/tests/BUILD +++ b/tensorflow/compiler/tests/BUILD @@ -1209,10 +1209,6 @@ tf_xla_py_test( name = "quantized_ops_test", size = "medium", srcs = ["quantized_ops_test.py"], - disabled_backends = [ - "cpu", - "cpu_ondemand", - ], deps = [ ":xla_test", "//tensorflow/compiler/tf2xla/python:xla", diff --git a/tensorflow/compiler/tf2xla/xla_op_registry.h b/tensorflow/compiler/tf2xla/xla_op_registry.h index c5e078a02d..80d022b592 100644 --- a/tensorflow/compiler/tf2xla/xla_op_registry.h +++ b/tensorflow/compiler/tf2xla/xla_op_registry.h @@ -51,10 +51,10 @@ constexpr std::array kNumericTypes = { {DT_UINT8, DT_UINT32, DT_UINT64, DT_INT8, DT_INT32, DT_INT64, DT_HALF, DT_FLOAT, DT_DOUBLE, DT_COMPLEX64, DT_COMPLEX128, DT_BFLOAT16}}; -constexpr std::array kCpuAllTypes = { +constexpr std::array kCpuAllTypes = { {DT_UINT8, DT_QUINT8, DT_UINT32, DT_UINT64, DT_INT8, DT_QINT8, DT_INT32, DT_QINT32, DT_INT64, DT_HALF, DT_FLOAT, DT_DOUBLE, DT_COMPLEX64, - DT_COMPLEX128, DT_BOOL}}; + DT_COMPLEX128, DT_BOOL, DT_BFLOAT16}}; constexpr std::array kGpuAllTypes = { {DT_UINT8, DT_QUINT8, DT_UINT32, DT_UINT64, DT_INT8, DT_QINT8, DT_INT32, -- GitLab From 21454269bf93a2f2f4b8b2caf129e64a879e3823 Mon Sep 17 00:00:00 2001 From: Nick Felt Date: Tue, 26 Feb 2019 11:05:48 -0800 Subject: [PATCH 565/766] Update tensorboard nightly dependency to 1.14.x Now that TB 1.13.0 has been released, our nightlies have moved up to 1.14.x alphas. PiperOrigin-RevId: 235754542 --- 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 6e5a371ffc..1014f0d532 100644 --- a/tensorflow/tools/pip_package/setup.py +++ b/tensorflow/tools/pip_package/setup.py @@ -87,7 +87,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.13.0a0, < 1.14.0a0' + REQUIRED_PACKAGES[i] = 'tb-nightly >= 1.14.0a0, < 1.15.0a0' elif 'tensorflow_estimator' in pkg and '2.0' in project_name: REQUIRED_PACKAGES[i] = 'tensorflow-estimator-2.0-preview' elif 'tensorflow_estimator' in pkg: -- GitLab From 2622a4be555917f9b7f096e130e71118dd27af9d Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 26 Feb 2019 11:19:24 -0800 Subject: [PATCH 566/766] Go: Update generated wrapper functions for TensorFlow ops. PiperOrigin-RevId: 235757391 --- tensorflow/go/op/wrappers.go | 56 ++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/tensorflow/go/op/wrappers.go b/tensorflow/go/op/wrappers.go index 5f07b534fa..2b729855c7 100644 --- a/tensorflow/go/op/wrappers.go +++ b/tensorflow/go/op/wrappers.go @@ -13502,6 +13502,14 @@ func ResizeBicubicAlignCorners(value bool) ResizeBicubicAttr { } } +// ResizeBicubicHalfPixelCenters sets the optional half_pixel_centers attribute to value. +// If not specified, defaults to false +func ResizeBicubicHalfPixelCenters(value bool) ResizeBicubicAttr { + return func(m optionalAttr) { + m["half_pixel_centers"] = value + } +} + // Resize `images` to `size` using bicubic interpolation. // // Input images can be of different types but output images are always float. @@ -20798,6 +20806,14 @@ func QuantizedResizeBilinearAlignCorners(value bool) QuantizedResizeBilinearAttr } } +// QuantizedResizeBilinearHalfPixelCenters sets the optional half_pixel_centers attribute to value. +// If not specified, defaults to false +func QuantizedResizeBilinearHalfPixelCenters(value bool) QuantizedResizeBilinearAttr { + return func(m optionalAttr) { + m["half_pixel_centers"] = value + } +} + // Resize quantized `images` to `size` using quantized bilinear interpolation. // // Input images and output images must be quantized types. @@ -31504,6 +31520,14 @@ func ResizeBilinearAlignCorners(value bool) ResizeBilinearAttr { } } +// ResizeBilinearHalfPixelCenters sets the optional half_pixel_centers attribute to value. +// If not specified, defaults to false +func ResizeBilinearHalfPixelCenters(value bool) ResizeBilinearAttr { + return func(m optionalAttr) { + m["half_pixel_centers"] = value + } +} + // Resize `images` to `size` using bilinear interpolation. // // Input images can be of different types but output images are always float. @@ -33398,6 +33422,14 @@ func ResizeBilinearGradAlignCorners(value bool) ResizeBilinearGradAttr { } } +// ResizeBilinearGradHalfPixelCenters sets the optional half_pixel_centers attribute to value. +// If not specified, defaults to false +func ResizeBilinearGradHalfPixelCenters(value bool) ResizeBilinearGradAttr { + return func(m optionalAttr) { + m["half_pixel_centers"] = value + } +} + // Computes the gradient of bilinear interpolation. // // Arguments: @@ -36117,6 +36149,14 @@ func ResizeBicubicGradAlignCorners(value bool) ResizeBicubicGradAttr { } } +// ResizeBicubicGradHalfPixelCenters sets the optional half_pixel_centers attribute to value. +// If not specified, defaults to false +func ResizeBicubicGradHalfPixelCenters(value bool) ResizeBicubicGradAttr { + return func(m optionalAttr) { + m["half_pixel_centers"] = value + } +} + // Computes the gradient of bicubic interpolation. // // Arguments: @@ -36160,6 +36200,14 @@ func ResizeNearestNeighborAlignCorners(value bool) ResizeNearestNeighborAttr { } } +// ResizeNearestNeighborHalfPixelCenters sets the optional half_pixel_centers attribute to value. +// If not specified, defaults to false +func ResizeNearestNeighborHalfPixelCenters(value bool) ResizeNearestNeighborAttr { + return func(m optionalAttr) { + m["half_pixel_centers"] = value + } +} + // Resize `images` to `size` using nearest neighbor interpolation. // // Arguments: @@ -36202,6 +36250,14 @@ func ResizeNearestNeighborGradAlignCorners(value bool) ResizeNearestNeighborGrad } } +// ResizeNearestNeighborGradHalfPixelCenters sets the optional half_pixel_centers attribute to value. +// If not specified, defaults to false +func ResizeNearestNeighborGradHalfPixelCenters(value bool) ResizeNearestNeighborGradAttr { + return func(m optionalAttr) { + m["half_pixel_centers"] = value + } +} + // Computes the gradient of nearest neighbor interpolation. // // Arguments: -- GitLab From 200948a42d3a2b13803b1b18721adba52b9e55b6 Mon Sep 17 00:00:00 2001 From: Jian Li Date: Tue, 26 Feb 2019 11:34:34 -0800 Subject: [PATCH 567/766] Create Int8 Concatenation. PiperOrigin-RevId: 235760587 --- tensorflow/lite/kernels/concatenation.cc | 23 ++++- tensorflow/lite/kernels/concatenation_test.cc | 93 ++++++++++++------- tensorflow/lite/kernels/register.cc | 4 +- tensorflow/lite/toco/tflite/operator.cc | 6 ++ tensorflow/lite/toco/tflite/operator_test.cc | 4 + 5 files changed, 95 insertions(+), 35 deletions(-) diff --git a/tensorflow/lite/kernels/concatenation.cc b/tensorflow/lite/kernels/concatenation.cc index a8dd160c8d..76d906fa6d 100644 --- a/tensorflow/lite/kernels/concatenation.cc +++ b/tensorflow/lite/kernels/concatenation.cc @@ -58,8 +58,8 @@ TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { TF_LITE_ENSURE_EQ(context, params->activation, kTfLiteActNone); TF_LITE_ENSURE(context, input_type == kTfLiteFloat32 || input_type == kTfLiteUInt8 || - input_type == kTfLiteInt16 || input_type == kTfLiteInt32 || - input_type == kTfLiteInt64); + input_type == kTfLiteInt8 || input_type == kTfLiteInt16 || + input_type == kTfLiteInt32 || input_type == kTfLiteInt64); // Output dimensions will match input dimensions, except 'axis', which // will be the sum of inputs @@ -85,6 +85,18 @@ TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { TfLiteTensor* output = &context->tensors[node->outputs->data[0]]; TF_LITE_ENSURE_EQ(context, output->type, input_type); + if (input_type == kTfLiteInt8) { + // Make sure there is no re-scaling needed for Int8 quantized kernel. This + // is a restriction we introduced to Int8 kernels. + VectorOfTensors all_inputs(*context, *node->inputs); + for (int i = 0; i < node->inputs->size; ++i) { + TfLiteTensor* t = &context->tensors[node->inputs->data[i]]; + TF_LITE_ENSURE_EQ(context, t->params.scale, output->params.scale); + TF_LITE_ENSURE_EQ(context, t->params.zero_point, + output->params.zero_point); + } + } + return context->ResizeTensor(context, output, output_size); } @@ -148,6 +160,13 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { TF_LITE_CONCATENATION_QUANTIZED(optimized_ops); } break; + case kTfLiteInt8: { + if (kernel_type == kReference) { + TF_LITE_CONCATENATION(reference_ops, int8_t); + } else { + TF_LITE_CONCATENATION(optimized_ops, int8_t); + } + } break; case kTfLiteInt64: if (kernel_type == kReference) { TF_LITE_CONCATENATION(reference_ops, int64_t); diff --git a/tensorflow/lite/kernels/concatenation_test.cc b/tensorflow/lite/kernels/concatenation_test.cc index 422380a03e..dab77d612d 100644 --- a/tensorflow/lite/kernels/concatenation_test.cc +++ b/tensorflow/lite/kernels/concatenation_test.cc @@ -78,13 +78,18 @@ class QuantizedConcatenationOpModel : public BaseConcatenationOpModel { .Union()); BuildInterpreter(all_input_shapes); } + template void SetInput(int index, std::initializer_list data) { - QuantizeAndPopulate(index, data); + QuantizeAndPopulate(index, data); } - std::vector GetOutput() { return ExtractVector(output_); } + template + std::vector GetOutput() { + return ExtractVector(output_); + } + template std::vector GetDequantizedOutput() { - return Dequantize(ExtractVector(output_), - GetScale(output_), GetZeroPoint(output_)); + return Dequantize(ExtractVector(output_), GetScale(output_), + GetZeroPoint(output_)); } }; @@ -165,25 +170,47 @@ TEST(ConcatenationOpTest, FourInputs) { })); } -TEST(ConcatenationOpTest, FourInputsQuantized) { +TEST(ConcatenationOpTest, FourInputsQuantizedUint8) { QuantizedConcatenationOpModel m0({TensorType_UINT8, {2, 1, 2}, -12.7, 12.8}, /*axis=*/2, /*num_inputs=*/4); - m0.SetInput(0, {1.0f, 3.0f, 4.0f, 7.0f}); - m0.SetInput(1, {1.1f, 3.1f, 4.1f, 7.1f}); - m0.SetInput(2, {1.2f, 3.2f, 4.2f, 7.2f}); - m0.SetInput(3, {1.3f, 3.3f, 4.3f, 7.3f}); + m0.SetInput(0, {1.0f, 3.0f, 4.0f, 7.0f}); + m0.SetInput(1, {1.1f, 3.1f, 4.1f, 7.1f}); + m0.SetInput(2, {1.2f, 3.2f, 4.2f, 7.2f}); + m0.SetInput(3, {1.3f, 3.3f, 4.3f, 7.3f}); m0.Invoke(); - EXPECT_THAT(m0.GetDequantizedOutput(), + EXPECT_THAT(m0.GetDequantizedOutput(), ElementsAreArray(ArrayFloatNear({ 1.0f, 3.0f, 1.1f, 3.1f, 1.2f, 3.2f, 1.3f, 3.3f, // 4.0f, 7.0f, 4.1f, 7.1f, 4.2f, 7.2f, 4.3f, 7.3f, // }))); - EXPECT_THAT(m0.GetOutput(), ElementsAreArray({ - 137, 157, 138, 158, 139, 159, 140, 160, // - 167, 197, 168, 198, 169, 199, 170, 200, // - })); + EXPECT_THAT(m0.GetOutput(), + ElementsAreArray({ + 137, 157, 138, 158, 139, 159, 140, 160, // + 167, 197, 168, 198, 169, 199, 170, 200, // + })); +} + +TEST(ConcatenationOpTest, FourInputsQuantizedInt8) { + QuantizedConcatenationOpModel m0({TensorType_INT8, {2, 1, 2}, -12.7, 12.8}, + /*axis=*/2, + /*num_inputs=*/4); + + m0.SetInput(0, {1.0f, 3.0f, 4.0f, 7.0f}); + m0.SetInput(1, {1.1f, 3.1f, 4.1f, 7.1f}); + m0.SetInput(2, {1.2f, 3.2f, 4.2f, 7.2f}); + m0.SetInput(3, {1.3f, 3.3f, 4.3f, 7.3f}); + m0.Invoke(); + EXPECT_THAT(m0.GetDequantizedOutput(), + ElementsAreArray(ArrayFloatNear({ + 1, 3, 1.1, 3.1, 1.2, 3.2, 1.3, 3.3, // + 4, 7, 4.1, 7.1, 4.2, 7.2, 4.3, 7.3 // + }))); + EXPECT_THAT(m0.GetOutput(), ElementsAreArray({ + 9, 29, 10, 30, 11, 31, 12, 32, // + 39, 69, 40, 70, 41, 71, 42, 72, // + })); } TEST(ConcatenationOpTest, FourInputsQuantizedMixedRange) { @@ -194,20 +221,21 @@ TEST(ConcatenationOpTest, FourInputsQuantizedMixedRange) { /*axis=*/2, /*num_inputs=*/4, {TensorType_UINT8, {2, 1, 2}, -12.7, 12.8}); - m0.SetInput(0, {1.0f, 3.0f, 4.0f, 7.0f}); - m0.SetInput(1, {1.1f, 3.1f, 4.1f, 7.1f}); - m0.SetInput(2, {1.2f, 3.2f, 4.2f, 7.2f}); - m0.SetInput(3, {1.3f, 3.3f, 4.3f, 7.3f}); + m0.SetInput(0, {1.0f, 3.0f, 4.0f, 7.0f}); + m0.SetInput(1, {1.1f, 3.1f, 4.1f, 7.1f}); + m0.SetInput(2, {1.2f, 3.2f, 4.2f, 7.2f}); + m0.SetInput(3, {1.3f, 3.3f, 4.3f, 7.3f}); m0.Invoke(); - EXPECT_THAT(m0.GetDequantizedOutput(), + EXPECT_THAT(m0.GetDequantizedOutput(), ElementsAreArray(ArrayFloatNear({ 1.0f, 3.0f, 1.1f, 3.1f, 1.2f, 3.2f, 1.3f, 3.3f, // 4.0f, 7.0f, 4.1f, 7.1f, 4.2f, 7.2f, 4.3f, 7.3f, // }))); - EXPECT_THAT(m0.GetOutput(), ElementsAreArray({ - 137, 157, 138, 158, 139, 159, 140, 160, // - 167, 197, 168, 198, 169, 199, 170, 200, // - })); + EXPECT_THAT(m0.GetOutput(), + ElementsAreArray({ + 137, 157, 138, 158, 139, 159, 140, 160, // + 167, 197, 168, 198, 169, 199, 170, 200, // + })); } TEST(ConcatenationOpTest, FourInputsQuantizedMixedRangeClampingLogic) { @@ -218,22 +246,23 @@ TEST(ConcatenationOpTest, FourInputsQuantizedMixedRangeClampingLogic) { /*axis=*/2, /*num_inputs=*/4, {TensorType_UINT8, {2, 1, 2}, -1., 1.}); - m0.SetInput(0, {1.0f, -3.0f, -4.0f, -7.0f}); - m0.SetInput(1, {1.1f, 3.1f, 4.1f, 7.1f}); - m0.SetInput(2, {1.2f, -3.2f, -4.2f, 7.2f}); - m0.SetInput(3, {1.3f, 3.3f, 4.3f, 7.3f}); + m0.SetInput(0, {1.0f, -3.0f, -4.0f, -7.0f}); + m0.SetInput(1, {1.1f, 3.1f, 4.1f, 7.1f}); + m0.SetInput(2, {1.2f, -3.2f, -4.2f, 7.2f}); + m0.SetInput(3, {1.3f, 3.3f, 4.3f, 7.3f}); m0.Invoke(); - EXPECT_THAT(m0.GetDequantizedOutput(), + EXPECT_THAT(m0.GetDequantizedOutput(), ElementsAreArray(ArrayFloatNear( { 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, // -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, // }, 4e-3))); - EXPECT_THAT(m0.GetOutput(), ElementsAreArray({ - 255, 0, 255, 255, 255, 0, 255, 255, // - 0, 0, 255, 255, 0, 255, 255, 255, // - })); + EXPECT_THAT(m0.GetOutput(), + ElementsAreArray({ + 255, 0, 255, 255, 255, 0, 255, 255, // + 0, 0, 255, 255, 0, 255, 255, 255, // + })); } } // namespace diff --git a/tensorflow/lite/kernels/register.cc b/tensorflow/lite/kernels/register.cc index 1f717521b7..eada211125 100644 --- a/tensorflow/lite/kernels/register.cc +++ b/tensorflow/lite/kernels/register.cc @@ -218,7 +218,9 @@ BuiltinOpResolver::BuiltinOpResolver() { AddBuiltin(BuiltinOperator_SOFTMAX, Register_SOFTMAX(), /* min_version */ 1, /* max_version */ 2); - AddBuiltin(BuiltinOperator_CONCATENATION, Register_CONCATENATION()); + AddBuiltin(BuiltinOperator_CONCATENATION, Register_CONCATENATION(), + /* min_version */ 1, + /* max_version */ 2); AddBuiltin(BuiltinOperator_ADD, Register_ADD(), /* min_version */ 1, /* max_version */ 2); diff --git a/tensorflow/lite/toco/tflite/operator.cc b/tensorflow/lite/toco/tflite/operator.cc index 03e634400c..56ff1be956 100644 --- a/tensorflow/lite/toco/tflite/operator.cc +++ b/tensorflow/lite/toco/tflite/operator.cc @@ -378,6 +378,12 @@ class Concatenation } int GetVersion(const OperatorSignature& op_signature) const override { + const string& input_name = op_signature.op->inputs[0]; + const Array& input_array = op_signature.model->GetArray(input_name); + // If the op take int8 input, it is version 2. + if (input_array.data_type == ArrayDataType::kInt8) { + return 2; + } return 1; } }; diff --git a/tensorflow/lite/toco/tflite/operator_test.cc b/tensorflow/lite/toco/tflite/operator_test.cc index 3c469e350c..dcc2827855 100644 --- a/tensorflow/lite/toco/tflite/operator_test.cc +++ b/tensorflow/lite/toco/tflite/operator_test.cc @@ -863,6 +863,10 @@ TEST_F(OperatorTest, VersioningPadV2Test) { SimpleVersioningTest(); } +TEST_F(OperatorTest, VersioningConcatenationTest) { + SimpleVersioningTest(); +} + TEST_F(OperatorTest, VersioningSelectTest) { SimpleVersioningTest(); } -- GitLab From f338860b44014392b3f1dadfb13925b81929caab Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 26 Feb 2019 11:37:49 -0800 Subject: [PATCH 568/766] PR #25929: Fix warning in threadpool_dataset_op.cc Please approve this CL. It will be submitted automatically, and its GitHub pull request will be marked as merged. Imported from GitHub PR #25929 num_threads should be greater than 1 and its initialised with 0 Copybara import of the project: - 0f4cb7537755c3ccba5a2d68314cdf7b9885307c Fix warning in threadpool_dataset_op.cc by Albin Joy - 730e2d6066a5f54de76088fd7c822de0d991bc59 Merge 0f4cb7537755c3ccba5a2d68314cdf7b9885307c into 57e8d... by Albin Joy COPYBARA_INTEGRATE_REVIEW=https://github.com/tensorflow/tensorflow/pull/25929 from joyalbin:fix_threadpool_dataset_op 0f4cb7537755c3ccba5a2d68314cdf7b9885307c PiperOrigin-RevId: 235761317 --- .../core/kernels/data/experimental/threadpool_dataset_op.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/core/kernels/data/experimental/threadpool_dataset_op.cc b/tensorflow/core/kernels/data/experimental/threadpool_dataset_op.cc index e8fd051a82..d017d5ed40 100644 --- a/tensorflow/core/kernels/data/experimental/threadpool_dataset_op.cc +++ b/tensorflow/core/kernels/data/experimental/threadpool_dataset_op.cc @@ -347,7 +347,7 @@ class PrivateThreadPoolDatasetOp : public UnaryDatasetOpKernel { void MakeDataset(OpKernelContext* ctx, DatasetBase* input, DatasetBase** output) override { - int64 num_threads; + int64 num_threads = 0; OP_REQUIRES_OK( ctx, ParseScalarArgument(ctx, "num_threads", &num_threads)); OP_REQUIRES(ctx, num_threads >= 1, -- GitLab From 6338cc61ab62f3eba7f2465cc2dc100a39c55035 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 26 Feb 2019 11:58:18 -0800 Subject: [PATCH 569/766] Disable flaky asan tests. PiperOrigin-RevId: 235764998 --- tensorflow/core/kernels/data/BUILD | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tensorflow/core/kernels/data/BUILD b/tensorflow/core/kernels/data/BUILD index d22bf0dc57..ce91358805 100644 --- a/tensorflow/core/kernels/data/BUILD +++ b/tensorflow/core/kernels/data/BUILD @@ -444,6 +444,7 @@ tf_cc_test( name = "sparse_tensor_slice_dataset_op_test", size = "small", srcs = ["sparse_tensor_slice_dataset_op_test.cc"], + tags = ["noasan"], # TODO(b/125441500): Re-enable after fixing. deps = [ ":dataset_test_base", ":dataset_utils", @@ -484,6 +485,7 @@ tf_cc_test( name = "tensor_slice_dataset_op_test", size = "small", srcs = ["tensor_slice_dataset_op_test.cc"], + tags = ["noasan"], # TODO(b/125441500): Re-enable after fixing. deps = [ ":dataset_test_base", ":dataset_utils", -- GitLab From 5beabd0c7d0d4dc3610360b1df4dfa55111577f4 Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Tue, 26 Feb 2019 12:15:25 -0800 Subject: [PATCH 570/766] Automatically pick up task_type and task_index for num_accelerators in TFConfigResolver PiperOrigin-RevId: 235768451 --- .../tfconfig_cluster_resolver.py | 9 ++++ .../tfconfig_cluster_resolver_test.py | 54 ++++++++++++++++++- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/distribute/cluster_resolver/tfconfig_cluster_resolver.py b/tensorflow/python/distribute/cluster_resolver/tfconfig_cluster_resolver.py index 0cd8239160..c9b6191a1c 100644 --- a/tensorflow/python/distribute/cluster_resolver/tfconfig_cluster_resolver.py +++ b/tensorflow/python/distribute/cluster_resolver/tfconfig_cluster_resolver.py @@ -112,6 +112,15 @@ class TFConfigClusterResolver(ClusterResolver): def rpc_layer(self, rpc_layer): self._rpc_layer = rpc_layer + def num_accelerators(self, + task_type=None, + task_id=None, + config_proto=None): + task_type = self.task_type if task_type is None else task_type + task_id = self.task_id if task_id is None else task_id + return super(TFConfigClusterResolver, self).num_accelerators( + task_type, task_id, config_proto) + def cluster_spec(self): """Returns a ClusterSpec based on the TF_CONFIG environment variable. diff --git a/tensorflow/python/distribute/cluster_resolver/tfconfig_cluster_resolver_test.py b/tensorflow/python/distribute/cluster_resolver/tfconfig_cluster_resolver_test.py index 65e75d4dd3..b68d8bcd0e 100644 --- a/tensorflow/python/distribute/cluster_resolver/tfconfig_cluster_resolver_test.py +++ b/tensorflow/python/distribute/cluster_resolver/tfconfig_cluster_resolver_test.py @@ -1,13 +1,13 @@ # Copyright 2018 The TensorFlow Authors. All Rights Reserved. # -# Licensed under the Apache License, Version 2.0 (the "License"); +# 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, +# 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. @@ -20,11 +20,17 @@ from __future__ import print_function import os +from tensorflow.python import eager +from tensorflow.python.client import session from tensorflow.python.distribute.cluster_resolver import TFConfigClusterResolver +from tensorflow.python.framework import test_util from tensorflow.python.platform import test from tensorflow.python.training import server_lib +mock = test.mock + +@test_util.run_all_in_graph_and_eager_modes class TFConfigClusterResolverTest(test.TestCase): def _verifyClusterSpecEquality(self, cluster_spec, expected_proto): @@ -237,6 +243,50 @@ class TFConfigClusterResolverTest(test.TestCase): cluster_resolver = TFConfigClusterResolver() self.assertEqual('', cluster_resolver.master()) + @mock.patch.object(eager.context, 'list_devices') + @mock.patch.object(session.BaseSession, 'list_devices') + def testNumAcceleratorsFilterTasksByEnvVar(self, mock_list_devices, + mock_eager_list_devices): + os.environ['TF_CONFIG'] = """ + { + "cluster": { + "worker1": ["w10:2222"], + "worker2": ["w21:2222", "w22:2222", "w23:2222", "w24:2222"] + }, + "rpc_layer": "grpc", + "task": { + "type": "worker1", + "index": "0" + } + } + """ + + device_names = [ + '/job:worker1/task:0/device:TPU:0', + '/job:worker1/task:0/device:TPU:1', + '/job:worker1/task:0/device:GPU:0', + '/job:worker1/task:0/device:GPU:1', + '/job:worker2/task:1/device:TPU:2', + '/job:worker2/task:2/device:TPU:3', + '/job:worker2/task:3/device:GPU:2', + '/job:worker2/task:4/device:GPU:3', + ] + device_list = [ + session._DeviceAttributes(name, name[27:30], 1024, 0) + for name in device_names + ] + mock_eager_list_devices.return_value = device_names + mock_list_devices.return_value = device_list + + resolver = TFConfigClusterResolver() + + # By default we read from TF_CONFIG + self.assertEqual(resolver.num_accelerators(), {'TPU': 2, 'GPU': 2}) + + # Override still works when we want it to + self.assertEqual(resolver.num_accelerators(task_type='worker2', task_id=3), + {'GPU': 1}) + if __name__ == '__main__': test.main() -- GitLab From a08bdbf68593e873affbce07335331faef5bc07f Mon Sep 17 00:00:00 2001 From: Bixia Zheng Date: Tue, 26 Feb 2019 12:19:58 -0800 Subject: [PATCH 571/766] [XLA] Add two routines to support mutation of HloComputation to support HLO fuzzing. Add HloModule::mutable_computation for getting a mutable computation of an HloModule. Previously, HloComputation::AddParameter only works for fused computation. This change makes it work for non-fused computation. PiperOrigin-RevId: 235769280 --- .../compiler/xla/service/hlo_computation.cc | 18 ++++++++++++++++++ .../compiler/xla/service/hlo_computation.h | 11 ++++++++++- tensorflow/compiler/xla/service/hlo_module.h | 6 ++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/tensorflow/compiler/xla/service/hlo_computation.cc b/tensorflow/compiler/xla/service/hlo_computation.cc index 817e15f9ff..48a51d302b 100644 --- a/tensorflow/compiler/xla/service/hlo_computation.cc +++ b/tensorflow/compiler/xla/service/hlo_computation.cc @@ -124,6 +124,24 @@ HloInstruction* HloComputation::AddParameter( return instructions_.back().get(); } +HloInstruction* HloComputation::AddEntryComputationParameter( + std::unique_ptr instruction) { + CHECK_EQ(instruction->opcode(), HloOpcode::kParameter); + CHECK_EQ(instruction->parameter_number(), num_parameters()); + CHECK(parent()->entry_computation() == this); + + HloModuleConfig config = parent()->config(); + config.mutable_entry_computation_layout()->add_parameter_layout( + ShapeLayout(instruction->shape())); + parent()->set_config(config); + + instruction->set_parent(this); + param_instructions_.push_back(instruction.get()); + AddInstructionInternal(std::move(instruction)); + + return instructions_.back().get(); +} + Status HloComputation::RemoveParameter(int64 param_no) { CHECK_GE(param_no, 0); CHECK_LT(param_no, param_instructions_.size()); diff --git a/tensorflow/compiler/xla/service/hlo_computation.h b/tensorflow/compiler/xla/service/hlo_computation.h index 212dfa15a1..a48cfa1f1b 100644 --- a/tensorflow/compiler/xla/service/hlo_computation.h +++ b/tensorflow/compiler/xla/service/hlo_computation.h @@ -117,11 +117,20 @@ class HloComputation { // instruction. Status RemoveUnusedParameters(); - // Add new parameter instruction to the computation. + // Adds a new parameter instruction to a fusion computation. + // // This should be a new parameter. Instruction will be appended to parameters // and inserted to the instruction list. HloInstruction* AddParameter(std::unique_ptr instruction); + // Adds a new parameter instruction to the entry computation and update + // the parent module config to reflect the change. + // + // This should be a new parameter. Instruction will be appended to parameters + // and inserted to the instruction list. + HloInstruction* AddEntryComputationParameter( + std::unique_ptr instruction); + // Remove an instruction from the computation. The instruction must have no // users. Instruction is deallocated with this call. Status RemoveInstruction(HloInstruction* instruction); diff --git a/tensorflow/compiler/xla/service/hlo_module.h b/tensorflow/compiler/xla/service/hlo_module.h index b6fe6a5cdb..2c63247eea 100644 --- a/tensorflow/compiler/xla/service/hlo_module.h +++ b/tensorflow/compiler/xla/service/hlo_module.h @@ -167,6 +167,12 @@ class HloModule { // Gets the number of computations in this module. int64 computation_count() const { return computations_.size(); } + // Returns the mutable computation for the given index. + HloComputation* mutable_computation(int64 idx) { + CHECK(idx >= 0 && idx < computations_.size()); + return computations_[idx].get(); + } + // Gets the number of instructions in this module. int64 instruction_count() const; -- GitLab From e2ed767080336c8efac28b5c687e47bc5921cb92 Mon Sep 17 00:00:00 2001 From: Eugene Zhulenev Date: Tue, 26 Feb 2019 12:25:03 -0800 Subject: [PATCH 572/766] Cleanup eigen_contraction_kernel PiperOrigin-RevId: 235770156 --- tensorflow/core/kernels/eigen_contraction_kernel.h | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tensorflow/core/kernels/eigen_contraction_kernel.h b/tensorflow/core/kernels/eigen_contraction_kernel.h index 4089eec59e..3e5c270256 100644 --- a/tensorflow/core/kernels/eigen_contraction_kernel.h +++ b/tensorflow/core/kernels/eigen_contraction_kernel.h @@ -107,6 +107,9 @@ template struct mkldnn_gemm_kernel { + static_assert(!ConjugateLhs, "MKL-DNN kernel doesn't support ConjugateLhs"); + static_assert(!ConjugateRhs, "MKL-DNN kernel doesn't support ConjugateRhs"); + EIGEN_DONT_INLINE void operator()(const OutputMapper& output, const float* blockA, const float* blockB, const IndexType rows, @@ -122,11 +125,11 @@ struct mkldnn_gemm_kernel(cols); const int k = static_cast(depth); - const char transposeA = ConjugateLhs ? 'Y' : 'N'; - const char transposeB = ConjugateRhs ? 'Y' : 'N'; + const char transposeA = 'N'; + const char transposeB = 'N'; - const int ldA = ConjugateLhs ? k : m; - const int ldB = ConjugateRhs ? n : k; + const int ldA = m; + const int ldB = k; const int ldC = static_cast(output.stride()); const float beta = 1.0; -- GitLab From 74655dd35f38bfdb11f77c48b510111d468913f7 Mon Sep 17 00:00:00 2001 From: Jian Li Date: Tue, 26 Feb 2019 12:36:51 -0800 Subject: [PATCH 573/766] Update operator_test for Min PiperOrigin-RevId: 235772052 --- tensorflow/lite/toco/tflite/operator_test.cc | 23 ++++---------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/tensorflow/lite/toco/tflite/operator_test.cc b/tensorflow/lite/toco/tflite/operator_test.cc index dcc2827855..a536a7dbe5 100644 --- a/tensorflow/lite/toco/tflite/operator_test.cc +++ b/tensorflow/lite/toco/tflite/operator_test.cc @@ -323,25 +323,6 @@ TEST_F(OperatorTest, VersioningMaxTest) { EXPECT_EQ(op->GetVersion(int8_signature), 2); } -TEST_F(OperatorTest, VersioningMinTest) { - TensorFlowMinimumOperator min_op; - min_op.inputs = {"input1"}; - auto operator_by_type_map = BuildOperatorByTypeMap(false /*enable_flex_ops*/); - const BaseOperator* op = operator_by_type_map.at(min_op.type).get(); - - Model uint8_model; - Array& uint8_array = uint8_model.GetOrCreateArray(min_op.inputs[0]); - uint8_array.data_type = ArrayDataType::kUint8; - OperatorSignature uint8_signature = {.op = &min_op, .model = &uint8_model}; - EXPECT_EQ(op->GetVersion(uint8_signature), 1); - - Model int8_model; - Array& int8_array = int8_model.GetOrCreateArray(min_op.inputs[0]); - int8_array.data_type = ArrayDataType::kInt8; - OperatorSignature int8_signature = {.op = &min_op, .model = &int8_model}; - EXPECT_EQ(op->GetVersion(int8_signature), 2); -} - TEST_F(OperatorTest, BuiltinReshape) { TensorFlowReshapeOperator op; op.shape = {1, 2, 4, 5, 8}; @@ -851,6 +832,10 @@ TEST_F(OperatorTest, VersioningL2NormTest) { SimpleOutputVersioningTest(); } +TEST_F(OperatorTest, VersioningMinTest) { + SimpleVersioningTest(); +} + TEST_F(OperatorTest, VersioningAddTest) { SimpleVersioningTest(); } TEST_F(OperatorTest, VersioningSubTest) { SimpleVersioningTest(); } -- GitLab From 231761f671f5ec9860c3df1e99350137cc3b8af5 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 26 Feb 2019 12:37:26 -0800 Subject: [PATCH 574/766] SQUEEZE operator's input #1 can be nullptr. When num_squeeze_dims is zero the data pointer should be nullptr. This addresses the following error when running NNAPIDelegate.SqueezeSimpleTest: ANeuralNetworksModel_setOperandValue setting operand 1 which has operand type that is not fully specified PiperOrigin-RevId: 235772184 --- tensorflow/lite/delegates/nnapi/nnapi_delegate.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/delegates/nnapi/nnapi_delegate.cc b/tensorflow/lite/delegates/nnapi/nnapi_delegate.cc index 86fe7c5140..30fd2c010c 100644 --- a/tensorflow/lite/delegates/nnapi/nnapi_delegate.cc +++ b/tensorflow/lite/delegates/nnapi/nnapi_delegate.cc @@ -648,7 +648,7 @@ class NNAPIDelegateKernel { // Note that we add the squeeze dimensions even if the dimensions // were unspecified (empty), as NNAPI requires the operand. mapping_args.builder->AddVectorInt32Operand( - builtin->squeeze_dims, + builtin->num_squeeze_dims ? builtin->squeeze_dims : nullptr, static_cast(builtin->num_squeeze_dims)); return ANEURALNETWORKS_SQUEEZE; }; -- GitLab From 1bbca7cfb6f01eb3536577102e3e0088c57c6a31 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 26 Feb 2019 12:45:38 -0800 Subject: [PATCH 575/766] tf.max will return -inf if the input is empty, which results in exception in tf.range() below. PiperOrigin-RevId: 235773577 --- tensorflow/python/ops/array_ops.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/python/ops/array_ops.py b/tensorflow/python/ops/array_ops.py index 801e41922f..b64bab4988 100644 --- a/tensorflow/python/ops/array_ops.py +++ b/tensorflow/python/ops/array_ops.py @@ -3091,6 +3091,7 @@ def sequence_mask(lengths, maxlen=None, dtype=dtypes.bool, name=None): if maxlen is None: maxlen = gen_math_ops._max(lengths, _all_dimensions(lengths)) + maxlen = gen_math_ops.maximum(constant(0, maxlen.dtype), maxlen) else: maxlen = ops.convert_to_tensor(maxlen) if maxlen.get_shape().ndims is not None and maxlen.get_shape().ndims != 0: -- GitLab From 3bf1ea84e9164e50507cf2d3351071d9848bc967 Mon Sep 17 00:00:00 2001 From: Shashi Shekhar Date: Tue, 26 Feb 2019 12:53:03 -0800 Subject: [PATCH 576/766] Update documentation. PiperOrigin-RevId: 235774836 --- tensorflow/lite/g3doc/faq.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tensorflow/lite/g3doc/faq.md b/tensorflow/lite/g3doc/faq.md index 5808391bdd..627a71edd8 100644 --- a/tensorflow/lite/g3doc/faq.md +++ b/tensorflow/lite/g3doc/faq.md @@ -29,7 +29,8 @@ detect compatibility issues early on. Since the number of TensorFlow Lite operations is smaller than TensorFlow's, some inference models may not be able to convert. For unimplemented operations, take a look at the question on -[missing operators](faq.md#why-is-some-operator-not-implemented-in-tensorflow-lite). +[missing operators](faq.md#why-are-some-operations-not-implemented-in-tensorflow-lite). +Unsupported operators include embeddings and LSTM/RNNs. For conversion issues not related to missing operations, search our [GitHub issues](https://github.com/tensorflow/tensorflow/issues?q=label%3Acomp%3Alite+) or file a [new one](https://github.com/tensorflow/tensorflow/issues). -- GitLab From 95beb365762c8d7d97f189c515cd4080cd2e80dd Mon Sep 17 00:00:00 2001 From: Dong Lin Date: Tue, 26 Feb 2019 13:26:01 -0800 Subject: [PATCH 577/766] Update BUILD file to run benchmark defined in tensorflow/examples PiperOrigin-RevId: 235780776 --- tensorflow/python/BUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/python/BUILD b/tensorflow/python/BUILD index 52fd243500..4d86fa61c4 100644 --- a/tensorflow/python/BUILD +++ b/tensorflow/python/BUILD @@ -12,6 +12,7 @@ visibility = [ "//tensorflow_models:__subpackages__", "//tensorflow_model_optimization:__subpackages__", "//third_party/py/cleverhans:__subpackages__", + "//third_party/py/tensorflow_examples:__subpackages__", # TODO(aselle): to pass open source test. "//bazel_pip/tensorflow/lite/toco/python:__pkg__", ] -- GitLab From 23f598c1c21d733f3c901583b45e9476778818fa Mon Sep 17 00:00:00 2001 From: Dan Moldovan Date: Tue, 26 Feb 2019 13:35:07 -0800 Subject: [PATCH 578/766] Multiple consistency fixes. Submitting in one change to make sure they work well together. Ensure consistency of handling bound methods between autograph and tf.function. Fix the detection of wrapt used on methods. Fix rewrap to consistently replace the target function even if it was not a decorator. This is a resubmission of the previous change with the same name. Difference from previous: covers the case when a wrapt-decorated method is converted independently, rather than being annotated. See the two new tests in user_decorators_test for details. PiperOrigin-RevId: 235782711 --- tensorflow/python/autograph/impl/api.py | 47 +++++++++++++------ tensorflow/python/autograph/impl/api_test.py | 15 ------ .../python/autograph/pyct/inspect_utils.py | 13 +++-- .../autograph/pyct/inspect_utils_test.py | 11 ++--- tensorflow/python/data/ops/dataset_ops.py | 3 ++ .../data/ops/multi_device_iterator_ops.py | 25 +++++++--- tensorflow/python/eager/def_function.py | 3 +- tensorflow/python/eager/function.py | 27 ++++++++--- tensorflow/python/framework/func_graph.py | 3 +- tensorflow/python/util/tf_decorator.py | 14 +++++- 10 files changed, 105 insertions(+), 56 deletions(-) diff --git a/tensorflow/python/autograph/impl/api.py b/tensorflow/python/autograph/impl/api.py index d0f52f52eb..f178221e85 100644 --- a/tensorflow/python/autograph/impl/api.py +++ b/tensorflow/python/autograph/impl/api.py @@ -28,6 +28,7 @@ from enum import Enum # pylint:disable=g-bad-import-order import numpy as np +import six # pylint:enable=g-bad-import-order @@ -181,20 +182,39 @@ def _call_unconverted(f, args, kwargs): Returns: The return value of f(*args, **kwargs). """ - # TODO(mdan): This may be inconsistent in certain situations. - # If the function had already been annotated with @tf.function, it - # may be bound to the incorrect object. It's unclear if those situations - # are possible, but if they happen, we need to check if f is bound - # to a shim like WeakrefSelf and unpack it. - - if tf_inspect.ismethod(f) and args: - f_self = inspect_utils.getmethodself(f) - if args[0] is f_self: - args = args[1:] + if inspect_utils.istfmethodtarget(f): + return f.__self__.call(args, kwargs) return f(*args, **kwargs) +def _is_known_loaded_type(f, module_name, entity_name): + """Tests whether the function or method is an instance of a known type.""" + if (module_name not in sys.modules or + not hasattr(sys.modules[module_name], entity_name)): + return False + type_entity = getattr(sys.modules[module_name], entity_name) + if isinstance(f, type_entity): + # The method if of this type. Example: + # + # o = ClassType() + # function(o.method)() + return True + if tf_inspect.ismethod(f): + f = six.get_unbound_function(f) + # The the unbound method if of this type. Example: + # + # class ClassType: + # @function + # def method(self): + # ... + # o = ClassType() + # o.method() + if isinstance(f, type_entity): + return True + return False + + def converted_call(f, owner, options, args, kwargs): """Compiles a function call inline. For internal use only.""" logging.log(1, @@ -219,13 +239,12 @@ def converted_call(f, owner, options, args, kwargs): return py_builtins.overload_of(f)(*args, **kwargs) # TODO(b/122265385): Remove this bypass. - if ('wrapt' in sys.modules and - hasattr(sys.modules['wrapt'], 'FunctionWrapper') and - isinstance(f, sys.modules['wrapt'].FunctionWrapper)): + if (_is_known_loaded_type(f, 'wrapt', 'FunctionWrapper') or + _is_known_loaded_type(f, 'wrapt', 'BoundFunctionWrapper')): logging.warn( 'Entity {} appears to be decorated by wrapt, which is not yet supported' ' by AutoGraph. The function will be called without transformation.' - ' You may however apply AutoGraph before the decorator.'.format(f), 1) + ' You may however apply AutoGraph before the decorator.'.format(f)) logging.log(2, 'Permanently whitelisted: %s: wrapt decorated', f) return _call_unconverted(f, args, kwargs) diff --git a/tensorflow/python/autograph/impl/api_test.py b/tensorflow/python/autograph/impl/api_test.py index ab24281924..00d5c4c2fd 100644 --- a/tensorflow/python/autograph/impl/api_test.py +++ b/tensorflow/python/autograph/impl/api_test.py @@ -402,21 +402,6 @@ class ApiTest(test.TestCase): self.evaluate(variables.global_variables_initializer()) self.assertAllEqual([[0.0, 0.0]], self.evaluate(x)) - def test_converted_call_whitelisted_method_extra_self(self): - - opts = converter.ConversionOptions() - - model = sequential.Sequential([ - core.Dense(2) - ]) - - x = api.converted_call(model.call, None, opts, - (model, constant_op.constant([[0.0]])), - {'training': True}) - - self.evaluate(variables.global_variables_initializer()) - self.assertAllEqual([[0.0, 0.0]], self.evaluate(x)) - def test_converted_call_whitelisted_method_via_owner(self): opts = converter.ConversionOptions() diff --git a/tensorflow/python/autograph/pyct/inspect_utils.py b/tensorflow/python/autograph/pyct/inspect_utils.py index eab01ee9cd..007c963d8c 100644 --- a/tensorflow/python/autograph/pyct/inspect_utils.py +++ b/tensorflow/python/autograph/pyct/inspect_utils.py @@ -191,9 +191,12 @@ def getdefiningclass(m, owner_class): return owner_class -def isweakrefself(m): - """Tests whether an object is a "weakref self" wrapper, see getmethodself.""" - return hasattr(m, '__self__') and hasattr(m.__self__, 'ag_self_weakref__') +def istfmethodtarget(m): + """Tests whether an object is a `function.TfMethodTarget`.""" + # See eager.function.TfMethodTarget for more details. + return (hasattr(m, '__self__') and + hasattr(m.__self__, 'weakrefself_target__') and + hasattr(m.__self__, 'weakrefself_func__')) def getmethodself(m): @@ -206,8 +209,8 @@ def getmethodself(m): # A fallback allowing methods to be actually bound to a type different # than __self__. This is useful when a strong reference from the method # to the object is not desired, for example when caching is involved. - if isweakrefself(m): - return m.__self__.ag_self_weakref__() + if istfmethodtarget(m): + return m.__self__.target return m.__self__ diff --git a/tensorflow/python/autograph/pyct/inspect_utils_test.py b/tensorflow/python/autograph/pyct/inspect_utils_test.py index 4c4c0977b0..0d0754ba15 100644 --- a/tensorflow/python/autograph/pyct/inspect_utils_test.py +++ b/tensorflow/python/autograph/pyct/inspect_utils_test.py @@ -28,6 +28,7 @@ import six from tensorflow.python import lib from tensorflow.python.autograph.pyct import inspect_utils +from tensorflow.python.eager import function from tensorflow.python.framework import constant_op from tensorflow.python.platform import test @@ -358,15 +359,13 @@ class InspectUtilsTest(test.TestCase): def test_getmethodclass_weakref_mechanism(self): test_obj = TestClass() - class WeakrefWrapper(object): - - def __init__(self): - self.ag_self_weakref__ = weakref.ref(test_obj) - def test_fn(self): return self - bound_method = types.MethodType(test_fn, WeakrefWrapper()) + bound_method = types.MethodType( + test_fn, + function.TfMethodTarget( + weakref.ref(test_obj), test_obj.member_function)) self.assertEqual(inspect_utils.getmethodclass(bound_method), TestClass) def test_getmethodclass_no_bool_conversion(self): diff --git a/tensorflow/python/data/ops/dataset_ops.py b/tensorflow/python/data/ops/dataset_ops.py index 2355a0abea..c0b120bf3c 100644 --- a/tensorflow/python/data/ops/dataset_ops.py +++ b/tensorflow/python/data/ops/dataset_ops.py @@ -2366,6 +2366,8 @@ class StructuredFunctionWrapper(object): else: defun_kwargs.update({"func_name": func_name}) + # TODO(b/124254153): Enable autograph once the overhead is low enough. + # TODO(mdan): Make sure autograph recurses into _wrapper_helper when on. @eager_function.defun_with_attributes( input_signature=[ tensor_spec.TensorSpec(input_shape, input_type) # pylint: disable=g-complex-comprehension @@ -2373,6 +2375,7 @@ class StructuredFunctionWrapper(object): self._input_structure._flat_shapes, self._input_structure._flat_types) ], + autograph=False, attributes=defun_kwargs) def wrapper_fn(*args): # pylint: disable=missing-docstring ret = _wrapper_helper(*args) diff --git a/tensorflow/python/data/ops/multi_device_iterator_ops.py b/tensorflow/python/data/ops/multi_device_iterator_ops.py index 36e1e311d3..f253f8bf42 100644 --- a/tensorflow/python/data/ops/multi_device_iterator_ops.py +++ b/tensorflow/python/data/ops/multi_device_iterator_ops.py @@ -42,13 +42,15 @@ class _PerDeviceGenerator(dataset_ops.DatasetV2): gen_dataset_ops.multi_device_iterator_to_string_handle( multi_device_iterator_resource)) - @function.defun() + # TODO(b/124254153): Enable autograph once the overhead is low enough. + @function.defun(autograph=False) # Pure graph code. def _init_func(): return multi_device_iterator_string_handle init_func_concrete = _init_func._get_concrete_function_internal() # pylint: disable=protected-access - @function.defun() + # TODO(b/124254153): Enable autograph once the overhead is low enough. + @function.defun(autograph=False) # Pure graph code. def _remote_init_func(): return functional_ops.remote_call( target=source_device, @@ -59,7 +61,10 @@ class _PerDeviceGenerator(dataset_ops.DatasetV2): self._init_func = _remote_init_func._get_concrete_function_internal() # pylint: disable=protected-access self._init_captured_args = self._init_func.captured_inputs - @function.defun(input_signature=[tensor_spec.TensorSpec([], dtypes.string)]) + # TODO(b/124254153): Enable autograph once the overhead is low enough. + @function.defun( + input_signature=[tensor_spec.TensorSpec([], dtypes.string)], + autograph=False) # Pure graph code. def _next_func(string_handle): # pylint: disable=protected-access multi_device_iterator = ( @@ -76,9 +81,11 @@ class _PerDeviceGenerator(dataset_ops.DatasetV2): next_func_concrete = _next_func._get_concrete_function_internal() # pylint: disable=protected-access + # TODO(b/124254153): Enable autograph once the overhead is low enough. @function.defun_with_attributes( input_signature=[tensor_spec.TensorSpec([], dtypes.string)], - attributes={"experimental_ints_on_device": True}) + attributes={"experimental_ints_on_device": True}, + autograph=False) # Pure graph code. def _remote_next_func(string_handle): return functional_ops.remote_call( target=source_device, @@ -94,13 +101,19 @@ class _PerDeviceGenerator(dataset_ops.DatasetV2): if arg == incarnation_id: self._incarnation_id_index = i - @function.defun(input_signature=[tensor_spec.TensorSpec([], dtypes.string)]) + # TODO(b/124254153): Enable autograph once the overhead is low enough. + @function.defun( + input_signature=[tensor_spec.TensorSpec([], dtypes.string)], + autograph=False) # Pure graph code. def _finalize_func(unused_string_handle): return array_ops.constant(0, dtypes.int64) finalize_func_concrete = _finalize_func._get_concrete_function_internal() # pylint: disable=protected-access - @function.defun(input_signature=[tensor_spec.TensorSpec([], dtypes.string)]) + # TODO(b/124254153): Enable autograph once the overhead is low enough. + @function.defun( + input_signature=[tensor_spec.TensorSpec([], dtypes.string)], + autograph=False) # Pure graph code. def _remote_finalize_func(string_handle): return functional_ops.remote_call( target=source_device, diff --git a/tensorflow/python/eager/def_function.py b/tensorflow/python/eager/def_function.py index 1d54973487..801e16f744 100644 --- a/tensorflow/python/eager/def_function.py +++ b/tensorflow/python/eager/def_function.py @@ -514,7 +514,8 @@ class Function(object): """Make and call a `ConcreteFunction` which initializes variables.""" # Note: using defun here avoids an infinite recursion. - @function_lib.defun + # Note: there is no reason not to autograph once the overhead is negligible. + @function_lib.defun(autograph=False) # tf.function internal, pure graph def initialize_variables(): for v, init in initializer_map.items(): with ops.init_scope(): diff --git a/tensorflow/python/eager/function.py b/tensorflow/python/eager/function.py index 6922f79a02..25a1080c04 100644 --- a/tensorflow/python/eager/function.py +++ b/tensorflow/python/eager/function.py @@ -61,6 +61,7 @@ from tensorflow.python.util import tf_inspect FORWARD_FUNCTION_ATTRIBUTE_NAME = "forward_function_name" BACKWARD_FUNCTION_ATTRIBUTE_NAME = "backward_function_name" + class CacheKey( collections.namedtuple("CacheKey", [ "input_signature", "parent_graph", "device_functions", @@ -2019,13 +2020,23 @@ def defun_with_attributes(func=None, # When a method is bound to objects of this type, it allows AutoGraph to -# recover a weak reference the original method's self pointer. This uses the -# mechanism from pyct.inspect_utils.getmethodclass. +# recover a weak reference the original method's self pointer, so that it can +# execute it consistent with class_method_to_instance_method's +# bound_method_wrapper. # TODO(b/119246461): This is not pretty. Use a descriptor instead? -class _WeakrefSelf(object): +class TfMethodTarget(object): + """Binding target for methods replaced by function and defun.""" + + def __init__(self, target, original_python_function): + self.weakrefself_target__ = target + self.weakrefself_func__ = weakref.ref(original_python_function) + + @property + def target(self): + return self.weakrefself_target__() - def __init__(self, target): - self.ag_self_weakref__ = target + def call(self, args, kwargs): + return self.weakrefself_func__()(*args, **kwargs) def class_method_to_instance_method(original_function, instance): @@ -2034,8 +2045,9 @@ def class_method_to_instance_method(original_function, instance): # Note: while we could bind to a weakref proxy instead, that causes the # bound method to be unhashable. - bound_method = types_lib.MethodType(original_function.python_function, - _WeakrefSelf(weak_instance)) + bound_method = types_lib.MethodType( + original_function.python_function, + TfMethodTarget(weak_instance, original_function.python_function)) # original_function is expected to be of one of the two `Function` types # (defined either in function.py or def_function.py). @@ -2053,6 +2065,7 @@ def class_method_to_instance_method(original_function, instance): if wrapped_fn is strong_bound_method_wrapper.__original_wrapped__: # If __wrapped__ was not replaced, then call original_function. + # TODO(mdan): For better consistency, use the wrapper's call(). wrapped_fn = original_function.python_function if tf_inspect.ismethod(wrapped_fn): wrapped_fn = six.get_unbound_function(wrapped_fn) diff --git a/tensorflow/python/framework/func_graph.py b/tensorflow/python/framework/func_graph.py index 91d80a388a..fc02835399 100644 --- a/tensorflow/python/framework/func_graph.py +++ b/tensorflow/python/framework/func_graph.py @@ -679,7 +679,8 @@ def func_graph_from_py_func(name, # Wrapping around a decorator allows checks like tf_inspect.getargspec # to be accurate. converted_func = tf_decorator.make_decorator(original_func, wrapper) - tf_decorator.rewrap(python_func, original_func, converted_func) + python_func = tf_decorator.rewrap(python_func, original_func, + converted_func) func_outputs = python_func(*func_args, **func_kwargs) diff --git a/tensorflow/python/util/tf_decorator.py b/tensorflow/python/util/tf_decorator.py index 6c71f70c4e..cf380509e9 100644 --- a/tensorflow/python/util/tf_decorator.py +++ b/tensorflow/python/util/tf_decorator.py @@ -138,6 +138,10 @@ def rewrap(decorator_func, previous_target, new_target): decorator_func: Callable returned by `wrap`. previous_target: Callable that needs to be replaced. new_target: Callable to replace previous_target with. + + Returns: + The updated decorator. If decorator_func is not a tf_decorator, new_target + is returned. """ # Because the process mutates the decorator, we only need to alter the # innermost function that wraps previous_target. @@ -150,9 +154,15 @@ def rewrap(decorator_func, previous_target, new_target): if target.decorated_target is previous_target: break cur = target.decorated_target + assert cur is not None + # If decorator_func is not a decorator, new_target replaces it directly. if innermost_decorator is None: - return + # Consistency check. The caller should always pass the result of + # tf_decorator.unwrap as previous_target. If decorator_func is not a + # decorator, that will have returned decorator_func itself. + assert decorator_func is previous_target + return new_target target.decorated_target = new_target @@ -168,6 +178,8 @@ def rewrap(decorator_func, previous_target, new_target): else: innermost_decorator.__wrapped__ = new_target + return decorator_func + def unwrap(maybe_tf_decorator): """Unwraps an object into a list of TFDecorators and a final target. -- GitLab From e5c53b63b098cf67a32f4102f97852683cdbee6c Mon Sep 17 00:00:00 2001 From: Allen Lavoie Date: Tue, 26 Feb 2019 13:38:44 -0800 Subject: [PATCH 579/766] Respect optionally restored attributes for name-based checkpoints Was getting in the way of Model.load_weights(). PiperOrigin-RevId: 235783442 --- .../optimizer_v2/checkpointable_utils_test.py | 3 +-- tensorflow/python/training/tracking/util.py | 13 ++++++++++--- tensorflow/python/training/tracking/util_test.py | 10 ++++------ .../tracking/util_with_v1_optimizers_test.py | 9 +++------ 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/tensorflow/contrib/optimizer_v2/checkpointable_utils_test.py b/tensorflow/contrib/optimizer_v2/checkpointable_utils_test.py index b2ea3daf82..1e210ec666 100644 --- a/tensorflow/contrib/optimizer_v2/checkpointable_utils_test.py +++ b/tensorflow/contrib/optimizer_v2/checkpointable_utils_test.py @@ -704,8 +704,7 @@ class CheckpointCompatibilityTests(test.TestCase): if context.executing_eagerly(): self._check_sentinels(root) if context.executing_eagerly(): - with self.assertRaisesRegexp(AssertionError, "OBJECT_CONFIG_JSON"): - status.assert_consumed() + status.assert_consumed() else: # When graph building, we haven't read any keys, so we don't know # whether the restore will be complete. diff --git a/tensorflow/python/training/tracking/util.py b/tensorflow/python/training/tracking/util.py index f5f70a443c..1ef114d09b 100644 --- a/tensorflow/python/training/tracking/util.py +++ b/tensorflow/python/training/tracking/util.py @@ -201,8 +201,12 @@ class _NameBasedRestoreCoordinator(object): # fails. saveable = saveable_factory() except TypeError: - self.unused_attributes.setdefault(trackable, []).append( - attribute_name) + # Even if we can't name this object, we should construct it and check + # whether it's optional to restore it. If it's optional we don't need + # to make assertions fail. + if not saveable_factory("").optional_restore: + self.unused_attributes.setdefault(trackable, []).append( + attribute_name) continue else: saveable = saveable_factory @@ -237,7 +241,10 @@ class _NameBasedRestoreCoordinator(object): else: tensor_missing = True - if not tensor_missing: + if tensor_missing: + # Record that this variable didn't match so assertions will fail. + self.unused_attributes.setdefault(trackable, []).append(saveable.name) + else: # Ignores values missing from the checkpoint, as with object-based # restore. Status assertions can be used to check exact matches, # although it's unlikely to ever happen for name-based checkpoints. diff --git a/tensorflow/python/training/tracking/util_test.py b/tensorflow/python/training/tracking/util_test.py index e2878915f8..88b7fe4e98 100644 --- a/tensorflow/python/training/tracking/util_test.py +++ b/tensorflow/python/training/tracking/util_test.py @@ -1477,12 +1477,9 @@ class CheckpointCompatibilityTests(test.TestCase): if context.executing_eagerly(): self._check_sentinels(root) if context.executing_eagerly(): - with self.assertRaisesRegexp(AssertionError, "OBJECT_CONFIG_JSON"): - status.assert_consumed() - with self.assertRaisesRegexp(AssertionError, "OBJECT_CONFIG_JSON"): - status.assert_existing_objects_matched() - with self.assertRaisesRegexp(AssertionError, "OBJECT_CONFIG_JSON"): - status.assert_nontrivial_match() + status.assert_consumed() + status.assert_existing_objects_matched() + status.assert_nontrivial_match() else: # When graph building, we haven't read any keys, so we don't know # whether the restore will be complete. @@ -1497,6 +1494,7 @@ class CheckpointCompatibilityTests(test.TestCase): self._set_sentinels(root) status = object_saver.restore(save_path) status.initialize_or_restore() + status.assert_nontrivial_match() self._check_sentinels(root) # Check that there is no error when keys are missing from the name-based # checkpoint. diff --git a/tensorflow/python/training/tracking/util_with_v1_optimizers_test.py b/tensorflow/python/training/tracking/util_with_v1_optimizers_test.py index 793929380f..37325c574e 100644 --- a/tensorflow/python/training/tracking/util_with_v1_optimizers_test.py +++ b/tensorflow/python/training/tracking/util_with_v1_optimizers_test.py @@ -870,12 +870,9 @@ class CheckpointCompatibilityTests(test.TestCase): if context.executing_eagerly(): self._check_sentinels(root) if context.executing_eagerly(): - with self.assertRaisesRegexp(AssertionError, "OBJECT_CONFIG_JSON"): - status.assert_consumed() - with self.assertRaisesRegexp(AssertionError, "OBJECT_CONFIG_JSON"): - status.assert_existing_objects_matched() - with self.assertRaisesRegexp(AssertionError, "OBJECT_CONFIG_JSON"): - status.assert_nontrivial_match() + status.assert_consumed() + status.assert_existing_objects_matched() + status.assert_nontrivial_match() else: # When graph building, we haven't read any keys, so we don't know # whether the restore will be complete. -- GitLab From 4ea3fca0902e84a1e6aa1876c34a9f78bfd9e440 Mon Sep 17 00:00:00 2001 From: Shivani Agrawal Date: Tue, 26 Feb 2019 13:40:50 -0800 Subject: [PATCH 580/766] [data-sats] uses `dataset` node name as prefix instead of the op name, to identify the component correctly in metrics, for pipelines with repeated components. PiperOrigin-RevId: 235783896 --- tensorflow/core/framework/dataset.cc | 11 +- tensorflow/core/framework/dataset.h | 23 +++- tensorflow/core/kernels/data/BUILD | 13 ++ .../core/kernels/data/cache_dataset_ops.cc | 4 +- .../core/kernels/data/captured_function.cc | 11 +- .../core/kernels/data/experimental/BUILD | 2 + .../experimental/map_and_batch_dataset_op.cc | 10 +- .../experimental/parse_example_dataset_op.cc | 16 ++- .../core/kernels/data/filter_dataset_op.cc | 18 +-- .../core/kernels/data/map_dataset_op_test.cc | 23 ++-- .../data/parallel_interleave_dataset_op.cc | 13 +- .../kernels/data/parallel_map_iterator.cc | 9 +- .../core/kernels/data/prefetch_dataset_op.cc | 15 +-- .../kernels/data/range_dataset_op_test.cc | 2 +- .../core/kernels/data/shuffle_dataset_op.cc | 4 +- .../sparse_tensor_slice_dataset_op_test.cc | 2 +- tensorflow/core/kernels/data/stats_utils.cc | 73 +++++++++++ tensorflow/core/kernels/data/stats_utils.h | 68 ++++++++++ .../data/tensor_slice_dataset_op_test.cc | 2 +- .../kernel_tests/stats_dataset_ops_test.py | 118 ++++++++++++------ .../kernel_tests/stats_dataset_test_base.py | 59 +++++---- 21 files changed, 358 insertions(+), 138 deletions(-) create mode 100644 tensorflow/core/kernels/data/stats_utils.cc create mode 100644 tensorflow/core/kernels/data/stats_utils.h diff --git a/tensorflow/core/framework/dataset.cc b/tensorflow/core/framework/dataset.cc index 5fed06ed6e..f5a1db35f9 100644 --- a/tensorflow/core/framework/dataset.cc +++ b/tensorflow/core/framework/dataset.cc @@ -191,13 +191,13 @@ Status GraphDefBuilderWrapper::AddDataset( const std::vector>>& list_inputs, const std::vector>& attrs, Node** output) { - const string& name = dataset->name(); + const string& type_string = dataset->type_string(); std::unique_ptr opts( new GraphDefBuilder::Options(b_->opts())); // TODO(srbs|mrry): Not all datasets have output_types and output_shapes // attributes defined. It will be nice to have a consistent pattern. - bool has_output_types_attr = HasAttr(name, "output_types"); - bool has_output_shapes_attr = HasAttr(name, "output_shapes"); + bool has_output_types_attr = HasAttr(type_string, "output_types"); + bool has_output_shapes_attr = HasAttr(type_string, "output_shapes"); if (has_output_shapes_attr) { opts.reset(new GraphDefBuilder::Options( opts->WithAttr("output_shapes", dataset->output_shapes()))); @@ -214,7 +214,8 @@ Status GraphDefBuilderWrapper::AddDataset( return errors::Internal("AddDataset: Failed to build Options with error ", opts->StatusToString()); } - NodeBuilder node_builder(opts->GetNameForOp(name), name, opts->op_registry()); + NodeBuilder node_builder(opts->GetNameForOp(type_string), type_string, + opts->op_registry()); { size_t total_size = inputs.size() + list_inputs.size(); auto inputs_iter = inputs.begin(); @@ -239,7 +240,7 @@ Status GraphDefBuilderWrapper::AddDataset( } *output = opts->FinalizeBuilder(&node_builder); if (*output == nullptr) { - return errors::Internal("AddDataset: Failed to build ", name, + return errors::Internal("AddDataset: Failed to build ", type_string, " op with error ", opts->StatusToString()); } return Status::OK(); diff --git a/tensorflow/core/framework/dataset.h b/tensorflow/core/framework/dataset.h index cca10fa49e..a93d5adb23 100644 --- a/tensorflow/core/framework/dataset.h +++ b/tensorflow/core/framework/dataset.h @@ -524,16 +524,20 @@ class IteratorBase { class DatasetContext { public: struct Params { - string name; + string type_string; // op type name of this dataset. + string node_name; // graph node name of this dataset op, uniquely + // identifying the dataset in the graph. }; explicit DatasetContext(Params params) : params_(std::move(params)) {} explicit DatasetContext(OpKernelContext* ctx) { - params_.name = ctx->op_kernel().type_string(); + params_.type_string = ctx->op_kernel().type_string(); + params_.node_name = ctx->op_kernel().name(); } - const string& name() const { return params_.name; } + const string& type_string() const { return params_.type_string; } + const string& node_name() const { return params_.node_name; } private: Params params_; @@ -569,9 +573,15 @@ class DatasetBase : public core::RefCounted { // format. TF_EXPORT static const char kDatasetGraphOutputNodeKey[]; - explicit DatasetBase(DatasetContext&& ctx) : name_(ctx.name()) {} + explicit DatasetBase(DatasetContext&& ctx) + : type_string_(ctx.type_string()), node_name_(ctx.node_name()) {} - const string& name() const { return name_; } + // Op type name of this dataset. + const string& type_string() const { return type_string_; } + + // Graph node name of this dataset op, uniquely identifying the dataset in + // the graph. + const string& node_name() const { return node_name_; } // Returns a new iterator for iterating over the range of elements in // this dataset. @@ -650,7 +660,8 @@ class DatasetBase : public core::RefCounted { }; } - const string name_; + const string type_string_; + const string node_name_; }; // Represents an iterator that is associated with a particular dataset. diff --git a/tensorflow/core/kernels/data/BUILD b/tensorflow/core/kernels/data/BUILD index ce91358805..abb78ee425 100644 --- a/tensorflow/core/kernels/data/BUILD +++ b/tensorflow/core/kernels/data/BUILD @@ -66,12 +66,20 @@ tf_cc_test( ], ) +cc_library( + name = "stats_utils", + srcs = ["stats_utils.cc"], + hdrs = ["stats_utils.h"], + deps = ["//tensorflow/core:lib"], +) + cc_library( name = "captured_function", srcs = ["captured_function.cc"], hdrs = ["captured_function.h"], deps = [ ":single_threaded_executor", + ":stats_utils", "//tensorflow/core:core_cpu_internal", "//tensorflow/core:framework", "//tensorflow/core:lib", @@ -183,6 +191,7 @@ tf_kernel_library( deps = [ ":captured_function", ":dataset_utils", + ":stats_utils", "//tensorflow/core:core_cpu_internal", "//tensorflow/core:dataset_ops_op_lib", "//tensorflow/core:framework", @@ -226,6 +235,7 @@ tf_cc_test( ":iterator_ops", ":map_dataset_op", ":range_dataset_op", + ":stats_utils", "//tensorflow/core:core_cpu_internal", "//tensorflow/core:dataset_ops_op_lib", "//tensorflow/core:framework", @@ -243,6 +253,7 @@ cc_library( srcs = ["parallel_map_iterator.cc"], hdrs = ["parallel_map_iterator.h"], deps = [ + ":stats_utils", "//tensorflow/core:core_cpu_internal", "//tensorflow/core:dataset_ops_op_lib", "//tensorflow/core:framework", @@ -315,6 +326,7 @@ tf_kernel_library( deps = [ ":captured_function", ":dataset_utils", + ":stats_utils", "//tensorflow/core:core_cpu_internal", "//tensorflow/core:dataset_ops_op_lib", "//tensorflow/core:framework", @@ -348,6 +360,7 @@ tf_kernel_library( hdrs = ["prefetch_dataset_op.h"], deps = [ ":prefetch_autotuner", + ":stats_utils", "//tensorflow/core:core_cpu_internal", "//tensorflow/core:dataset_ops_op_lib", "//tensorflow/core:framework", diff --git a/tensorflow/core/kernels/data/cache_dataset_ops.cc b/tensorflow/core/kernels/data/cache_dataset_ops.cc index 3714d28212..2149c7fbe5 100644 --- a/tensorflow/core/kernels/data/cache_dataset_ops.cc +++ b/tensorflow/core/kernels/data/cache_dataset_ops.cc @@ -697,8 +697,8 @@ class CacheDatasetOp : public UnaryDatasetOpKernel { // Use the resource manager in the iterator context to get / create // a cache. ResourceMgr* mgr = ctx->resource_mgr(); - const string name = - strings::StrCat(prefix(), "::", dataset()->name(), "::MemoryCache"); + const string name = strings::StrCat( + prefix(), "::", dataset()->node_name(), "::MemoryCache"); TF_RETURN_IF_ERROR(mgr->LookupOrCreate( "tf_data", name, &cache_, [](MemoryCache** cache) { *cache = new MemoryCache(); diff --git a/tensorflow/core/kernels/data/captured_function.cc b/tensorflow/core/kernels/data/captured_function.cc index 99b745b4c4..a9095e4fd0 100644 --- a/tensorflow/core/kernels/data/captured_function.cc +++ b/tensorflow/core/kernels/data/captured_function.cc @@ -21,6 +21,7 @@ limitations under the License. #include "tensorflow/core/framework/cancellation.h" #include "tensorflow/core/framework/function_handle_cache.h" #include "tensorflow/core/framework/stats_aggregator.h" +#include "tensorflow/core/kernels/data/stats_utils.h" #include "tensorflow/core/lib/gtl/optional.h" #include "tensorflow/core/lib/random/random.h" #include "tensorflow/core/lib/strings/strcat.h" @@ -446,12 +447,14 @@ void InstantiatedCapturedFunction::RunAsync( s = frame->ConsumeRetvals(rets); } delete frame; - + // TODO(shivaniagrawal): add the dataset name containing this function, + // make it dataset()->node_name() + captured_func_->func().name(). if (stats_aggregator) { + string prefix_with_func_name = strings::StrCat( + str_util::Split(prefix, "::", str_util::SkipEmpty()).back(), + "::", captured_func_->func().name()); stats_aggregator->AddToHistogram( - strings::StrCat( - str_util::Split(prefix, "::", str_util::SkipEmpty()).back(), - "::", captured_func_->func().name(), "::execution_time"), + stats_utils::ExecutionTimeHistogramName(prefix_with_func_name), {static_cast(stats_collector->processing_time())}); } if (model) { diff --git a/tensorflow/core/kernels/data/experimental/BUILD b/tensorflow/core/kernels/data/experimental/BUILD index 4f59e63e37..84ef89c26a 100644 --- a/tensorflow/core/kernels/data/experimental/BUILD +++ b/tensorflow/core/kernels/data/experimental/BUILD @@ -129,6 +129,7 @@ tf_kernel_library( "//tensorflow/core/kernels:inplace_ops", "//tensorflow/core/kernels/data:captured_function", "//tensorflow/core/kernels/data:dataset_utils", + "//tensorflow/core/kernels/data:stats_utils", ], ) @@ -205,6 +206,7 @@ tf_kernel_library( "//tensorflow/core:framework", "//tensorflow/core:functional_ops_op_lib", "//tensorflow/core/kernels/data:parallel_map_iterator", + "//tensorflow/core/kernels/data:stats_utils", ], ) diff --git a/tensorflow/core/kernels/data/experimental/map_and_batch_dataset_op.cc b/tensorflow/core/kernels/data/experimental/map_and_batch_dataset_op.cc index 77c1749632..a37334ac17 100644 --- a/tensorflow/core/kernels/data/experimental/map_and_batch_dataset_op.cc +++ b/tensorflow/core/kernels/data/experimental/map_and_batch_dataset_op.cc @@ -25,6 +25,7 @@ limitations under the License. #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/kernels/data/captured_function.h" #include "tensorflow/core/kernels/data/dataset_utils.h" +#include "tensorflow/core/kernels/data/stats_utils.h" #include "tensorflow/core/kernels/inplace_ops_functor.h" #include "tensorflow/core/lib/core/blocking_counter.h" #include "tensorflow/core/lib/core/errors.h" @@ -263,9 +264,6 @@ class MapAndBatchDatasetOp : public UnaryDatasetOpKernel { (params.dataset->num_parallel_calls_ + params.dataset->batch_size_ - 1) / params.dataset->batch_size_)) { - std::vector components = - str_util::Split(params.prefix, "::", str_util::SkipEmpty()); - key_prefix_ = components.back(); } ~Iterator() override { @@ -403,7 +401,7 @@ class MapAndBatchDatasetOp : public UnaryDatasetOpKernel { const auto& stats_aggregator = ctx->stats_aggregator(); if (stats_aggregator) { stats_aggregator->AddScalar( - strings::StrCat(key_prefix_, "::thread_utilization"), + stats_utils::ThreadUtilizationScalarName(dataset()->node_name()), static_cast(num_calls_) / static_cast(num_parallel_calls_->value)); } @@ -648,7 +646,8 @@ class MapAndBatchDatasetOp : public UnaryDatasetOpKernel { if (stats_aggregator) { mutex_lock l(*mu_); stats_aggregator->AddScalar( - strings::StrCat(key_prefix_, "::thread_utilization"), + stats_utils::ThreadUtilizationScalarName( + dataset()->node_name()), static_cast(num_calls_) / static_cast(num_parallel_calls_->value)); } @@ -805,7 +804,6 @@ class MapAndBatchDatasetOp : public UnaryDatasetOpKernel { int64 waiting_ GUARDED_BY(*mu_) = 0; // Identifies the maximum number of batch results to store. int64 max_batch_results_ GUARDED_BY(*mu_); - string key_prefix_; std::unique_ptr instantiated_captured_func_; }; diff --git a/tensorflow/core/kernels/data/experimental/parse_example_dataset_op.cc b/tensorflow/core/kernels/data/experimental/parse_example_dataset_op.cc index 0057405734..c207cf7ae4 100644 --- a/tensorflow/core/kernels/data/experimental/parse_example_dataset_op.cc +++ b/tensorflow/core/kernels/data/experimental/parse_example_dataset_op.cc @@ -17,6 +17,7 @@ limitations under the License. #include "tensorflow/core/common_runtime/device.h" #include "tensorflow/core/framework/stats_aggregator.h" #include "tensorflow/core/kernels/data/parallel_map_iterator.h" +#include "tensorflow/core/kernels/data/stats_utils.h" #include "tensorflow/core/util/example_proto_fast_parsing.h" namespace tensorflow { @@ -332,23 +333,26 @@ class ParseExampleDatasetOp : public UnaryDatasetOpKernel { << ", got " << serialized_sparse.shape().DebugString() << ")."; } - // TODO(b/111553342): User provided tags instead of fixed tag. + // TODO(b/123360128): Add component name to streamz metrics without + // breaking TFX metrics. if (stats_aggregator) { stats_aggregator->IncrementCounter( - "examples_count", "trainer", + stats_utils::kExamplesCount, "trainer", example_result.feature_stats.size()); for (example::PerExampleFeatureStats feature_stats : example_result.feature_stats) { stats_aggregator->AddToHistogram( - "features", + stats_utils::FeatureHistogramName(dataset_->node_name()), {static_cast(feature_stats.features_count)}); stats_aggregator->IncrementCounter( - "features_count", "trainer", feature_stats.features_count); + stats_utils::kFeaturesCount, "trainer", + feature_stats.features_count); stats_aggregator->IncrementCounter( - "feature_values_count", "trainer", + stats_utils::kFeatureValuesCount, "trainer", feature_stats.feature_values_count); stats_aggregator->AddToHistogram( - "feature-values", + stats_utils::FeatureValueHistogramName( + dataset_->node_name()), {static_cast(feature_stats.feature_values_count)}); } } diff --git a/tensorflow/core/kernels/data/filter_dataset_op.cc b/tensorflow/core/kernels/data/filter_dataset_op.cc index 483d42c809..8615e1b45b 100644 --- a/tensorflow/core/kernels/data/filter_dataset_op.cc +++ b/tensorflow/core/kernels/data/filter_dataset_op.cc @@ -19,6 +19,7 @@ limitations under the License. #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/kernels/data/captured_function.h" #include "tensorflow/core/kernels/data/dataset_utils.h" +#include "tensorflow/core/kernels/data/stats_utils.h" #include "tensorflow/core/lib/gtl/cleanup.h" #include "tensorflow/core/lib/random/random.h" #include "tensorflow/core/lib/strings/str_util.h" @@ -167,9 +168,6 @@ class FilterDatasetOp : public UnaryDatasetOpKernel { filtered_elements_(0), dropped_elements_(0), filter_pred_(std::move(filter_pred)) { - std::vector components = - str_util::Split(params.prefix, "::", str_util::SkipEmpty()); - prefix_end_ = components.back(); } Status Initialize(IteratorContext* ctx) override { @@ -213,13 +211,15 @@ class FilterDatasetOp : public UnaryDatasetOpKernel { mutex_lock l(mu_); dropped_elements_++; stats_aggregator->AddScalar( - strings::StrCat(prefix_end_, "::dropped_elements"), + stats_utils::DroppedElementsScalarName( + dataset()->node_name()), static_cast((dropped_elements_))); // TODO(shivaniagrawal): multiple pipelines would collect // aggregated number of dropped elements for all the pipelines, // exploit tagged_context here. - stats_aggregator->IncrementCounter( - prefix_end_, "dropped_elements", static_cast(1)); + stats_aggregator->IncrementCounter(dataset()->node_name(), + stats_utils::kDroppedElements, + static_cast(1)); } } } while (!matched); @@ -229,12 +229,13 @@ class FilterDatasetOp : public UnaryDatasetOpKernel { mutex_lock l(mu_); filtered_elements_++; stats_aggregator->AddScalar( - strings::StrCat(prefix_end_, "::filtered_elements"), + stats_utils::FilterdElementsScalarName(dataset()->node_name()), static_cast((filtered_elements_))); // TODO(shivaniagrawal): multiple pipelines would collect aggregated // number of filtered elements for all the pipelines, exploit // tagged_context here. - stats_aggregator->IncrementCounter(prefix_end_, "filtered_elements", + stats_aggregator->IncrementCounter(dataset()->node_name(), + stats_utils::kFilteredElements, static_cast(1)); } *end_of_sequence = false; @@ -281,7 +282,6 @@ class FilterDatasetOp : public UnaryDatasetOpKernel { int64 filtered_elements_ GUARDED_BY(mu_); int64 dropped_elements_ GUARDED_BY(mu_); const FilterIteratorPredicate filter_pred_; - string prefix_end_; std::unique_ptr instantiated_captured_func_; }; diff --git a/tensorflow/core/kernels/data/map_dataset_op_test.cc b/tensorflow/core/kernels/data/map_dataset_op_test.cc index f9c1cf4936..724937eddd 100644 --- a/tensorflow/core/kernels/data/map_dataset_op_test.cc +++ b/tensorflow/core/kernels/data/map_dataset_op_test.cc @@ -25,6 +25,7 @@ limitations under the License. #include "tensorflow/core/kernels/data/dataset_test_base.h" #include "tensorflow/core/kernels/data/dataset_utils.h" #include "tensorflow/core/kernels/data/iterator_ops.h" +#include "tensorflow/core/kernels/data/stats_utils.h" #include "tensorflow/core/kernels/ops_testutil.h" #include "tensorflow/core/platform/test.h" #include "tensorflow/core/util/ptr_util.h" @@ -123,7 +124,7 @@ TEST_P(DatasetGetNextTest, GetNext) { std::unique_ptr map_kernel; TF_ASSERT_OK(CreateMapDatasetOpKernel( - range_dataset->name(), test_params.func_name, &map_kernel)); + range_dataset->node_name(), test_params.func_name, &map_kernel)); std::unique_ptr map_context; TF_ASSERT_OK( CreateMapDatasetContext(range_dataset, map_kernel.get(), &map_context)); @@ -180,7 +181,7 @@ TEST_F(MapDatasetOpTest, DatasetName) { std::unique_ptr map_kernel; TF_ASSERT_OK(CreateMapDatasetOpKernel( - range_dataset->name(), func_def.signature().name(), &map_kernel)); + range_dataset->node_name(), func_def.signature().name(), &map_kernel)); std::unique_ptr map_context; TF_ASSERT_OK( CreateMapDatasetContext(range_dataset, map_kernel.get(), &map_context)); @@ -189,7 +190,7 @@ TEST_F(MapDatasetOpTest, DatasetName) { CreateDataset(map_kernel.get(), map_context.get(), &map_dataset)); core::ScopedUnref scored_unref_map_dataset(map_dataset); - EXPECT_EQ(map_dataset->name(), kOpName); + EXPECT_EQ(map_dataset->type_string(), kOpName); } TEST_F(MapDatasetOpTest, DatasetOutputDtypes) { @@ -207,7 +208,7 @@ TEST_F(MapDatasetOpTest, DatasetOutputDtypes) { std::unique_ptr map_kernel; TF_ASSERT_OK(CreateMapDatasetOpKernel( - range_dataset->name(), func_def.signature().name(), &map_kernel)); + range_dataset->node_name(), func_def.signature().name(), &map_kernel)); std::unique_ptr map_context; TF_ASSERT_OK( CreateMapDatasetContext(range_dataset, map_kernel.get(), &map_context)); @@ -235,7 +236,7 @@ TEST_F(MapDatasetOpTest, DatasetOutputShapes) { std::unique_ptr map_kernel; TF_ASSERT_OK(CreateMapDatasetOpKernel( - range_dataset->name(), func_def.signature().name(), &map_kernel)); + range_dataset->node_name(), func_def.signature().name(), &map_kernel)); std::unique_ptr map_context; TF_ASSERT_OK( CreateMapDatasetContext(range_dataset, map_kernel.get(), &map_context)); @@ -287,7 +288,7 @@ TEST_P(DatasetCardinalityTest, Cardinality) { std::unique_ptr map_kernel; TF_ASSERT_OK(CreateMapDatasetOpKernel( - range_dataset->name(), func_def.signature().name(), &map_kernel)); + range_dataset->node_name(), func_def.signature().name(), &map_kernel)); std::unique_ptr map_context; TF_ASSERT_OK( CreateMapDatasetContext(range_dataset, map_kernel.get(), &map_context)); @@ -319,7 +320,7 @@ TEST_F(MapDatasetOpTest, DatasetSave) { std::unique_ptr map_kernel; TF_ASSERT_OK(CreateMapDatasetOpKernel( - range_dataset->name(), func_def.signature().name(), &map_kernel)); + range_dataset->node_name(), func_def.signature().name(), &map_kernel)); std::unique_ptr map_context; TF_ASSERT_OK( CreateMapDatasetContext(range_dataset, map_kernel.get(), &map_context)); @@ -351,7 +352,7 @@ TEST_F(MapDatasetOpTest, IteratorOutputDtypes) { std::unique_ptr map_kernel; TF_ASSERT_OK(CreateMapDatasetOpKernel( - range_dataset->name(), func_def.signature().name(), &map_kernel)); + range_dataset->node_name(), func_def.signature().name(), &map_kernel)); std::unique_ptr map_context; TF_ASSERT_OK( CreateMapDatasetContext(range_dataset, map_kernel.get(), &map_context)); @@ -384,7 +385,7 @@ TEST_F(MapDatasetOpTest, IteratorOutputShapes) { std::unique_ptr map_kernel; TF_ASSERT_OK(CreateMapDatasetOpKernel( - range_dataset->name(), func_def.signature().name(), &map_kernel)); + range_dataset->node_name(), func_def.signature().name(), &map_kernel)); std::unique_ptr map_context; TF_ASSERT_OK( CreateMapDatasetContext(range_dataset, map_kernel.get(), &map_context)); @@ -421,7 +422,7 @@ TEST_F(MapDatasetOpTest, IteratorOutputPrefix) { std::unique_ptr map_kernel; TF_ASSERT_OK(CreateMapDatasetOpKernel( - range_dataset->name(), func_def.signature().name(), &map_kernel)); + range_dataset->node_name(), func_def.signature().name(), &map_kernel)); std::unique_ptr map_context; TF_ASSERT_OK( CreateMapDatasetContext(range_dataset, map_kernel.get(), &map_context)); @@ -481,7 +482,7 @@ TEST_P(IteratorRoundtripTest, Roundtrip) { std::unique_ptr map_kernel; TF_ASSERT_OK(CreateMapDatasetOpKernel( - range_dataset->name(), test_params.func_name, &map_kernel)); + range_dataset->node_name(), test_params.func_name, &map_kernel)); std::unique_ptr map_context; TF_ASSERT_OK( CreateMapDatasetContext(range_dataset, map_kernel.get(), &map_context)); diff --git a/tensorflow/core/kernels/data/parallel_interleave_dataset_op.cc b/tensorflow/core/kernels/data/parallel_interleave_dataset_op.cc index ddd81d4596..a4b614289b 100644 --- a/tensorflow/core/kernels/data/parallel_interleave_dataset_op.cc +++ b/tensorflow/core/kernels/data/parallel_interleave_dataset_op.cc @@ -26,6 +26,7 @@ limitations under the License. #include "tensorflow/core/framework/types.h" #include "tensorflow/core/kernels/data/captured_function.h" #include "tensorflow/core/kernels/data/dataset_utils.h" +#include "tensorflow/core/kernels/data/stats_utils.h" #include "tensorflow/core/lib/core/threadpool.h" #include "tensorflow/core/lib/gtl/cleanup.h" #include "tensorflow/core/lib/random/random.h" @@ -211,9 +212,6 @@ class ParallelInterleaveDatasetOp : public UnaryDatasetOpKernel { "data_parallel_interleave_worker_pool", port::NumSchedulableCPUs() /* num_threads */, false /* low_latency_hint */)) { - std::vector components = - str_util::Split(params.prefix, "::", str_util::SkipEmpty()); - key_prefix_ = components.back(); } ~ParallelInterleaveIterator() override { @@ -506,7 +504,8 @@ class ParallelInterleaveDatasetOp : public UnaryDatasetOpKernel { const auto& stats_aggregator = ctx->stats_aggregator(); if (stats_aggregator) { stats_aggregator->AddScalar( - strings::StrCat(key_prefix_, "::thread_utilization"), + stats_utils::ThreadUtilizationScalarName( + dataset()->node_name()), static_cast(num_calls_) / static_cast(num_parallel_calls_->value)); } @@ -567,7 +566,7 @@ class ParallelInterleaveDatasetOp : public UnaryDatasetOpKernel { const auto& stats_aggregator = ctx->stats_aggregator(); if (stats_aggregator) { stats_aggregator->AddScalar( - strings::StrCat(key_prefix_, "::thread_utilization"), + stats_utils::ThreadUtilizationScalarName(dataset()->node_name()), static_cast(num_calls_) / static_cast(num_parallel_calls_->value)); } @@ -620,7 +619,8 @@ class ParallelInterleaveDatasetOp : public UnaryDatasetOpKernel { const auto& stats_aggregator = ctx->stats_aggregator(); if (stats_aggregator) { stats_aggregator->AddScalar( - strings::StrCat(key_prefix_, "::thread_utilization"), + stats_utils::ThreadUtilizationScalarName( + dataset()->node_name()), static_cast(num_calls_) / static_cast(num_parallel_calls_->value)); } @@ -917,7 +917,6 @@ class ParallelInterleaveDatasetOp : public UnaryDatasetOpKernel { // Identifies whether background threads should be cancelled. bool cancelled_ GUARDED_BY(*mu_) = false; - string key_prefix_; std::unique_ptr instantiated_captured_func_; }; diff --git a/tensorflow/core/kernels/data/parallel_map_iterator.cc b/tensorflow/core/kernels/data/parallel_map_iterator.cc index be91de12fe..1f10804205 100644 --- a/tensorflow/core/kernels/data/parallel_map_iterator.cc +++ b/tensorflow/core/kernels/data/parallel_map_iterator.cc @@ -22,6 +22,7 @@ limitations under the License. #include #include "tensorflow/core/framework/stats_aggregator.h" +#include "tensorflow/core/kernels/data/stats_utils.h" #include "tensorflow/core/lib/gtl/cleanup.h" #include "tensorflow/core/platform/cpu_info.h" @@ -57,9 +58,7 @@ class ParallelMapIterator : public DatasetBaseIterator { params.num_parallel_calls, mu_, cond_var_)), sloppy_(params.sloppy), preserve_cardinality_(params.preserve_cardinality) { - std::vector components = - str_util::Split(base_params.prefix, "::", str_util::SkipEmpty()); - key_prefix_ = components.back(); + key_prefix_ = base_params.dataset->node_name(); } ~ParallelMapIterator() override { @@ -206,7 +205,7 @@ class ParallelMapIterator : public DatasetBaseIterator { const auto& stats_aggregator = ctx->stats_aggregator(); if (stats_aggregator) { stats_aggregator->AddScalar( - strings::StrCat(key_prefix_, "::thread_utilization"), + stats_utils::ThreadUtilizationScalarName(key_prefix_), static_cast(num_calls_) / static_cast(num_parallel_calls_->value)); } @@ -301,7 +300,7 @@ class ParallelMapIterator : public DatasetBaseIterator { const auto& stats_aggregator = ctx->stats_aggregator(); if (stats_aggregator) { stats_aggregator->AddScalar( - strings::StrCat(key_prefix_, "::thread_utilization"), + stats_utils::ThreadUtilizationScalarName(key_prefix_), static_cast(num_calls_) / static_cast(num_parallel_calls_->value)); } diff --git a/tensorflow/core/kernels/data/prefetch_dataset_op.cc b/tensorflow/core/kernels/data/prefetch_dataset_op.cc index f0e835a27c..e479b9ff5e 100644 --- a/tensorflow/core/kernels/data/prefetch_dataset_op.cc +++ b/tensorflow/core/kernels/data/prefetch_dataset_op.cc @@ -20,6 +20,7 @@ limitations under the License. #include "tensorflow/core/framework/partial_tensor_shape.h" #include "tensorflow/core/framework/stats_aggregator.h" #include "tensorflow/core/framework/tensor.h" +#include "tensorflow/core/kernels/data/stats_utils.h" #include "tensorflow/core/lib/core/error_codes.pb.h" #include "tensorflow/core/lib/gtl/cleanup.h" #include "tensorflow/core/lib/strings/str_util.h" @@ -80,9 +81,6 @@ class PrefetchDatasetOp::Dataset : public DatasetBase { explicit Iterator(const Params& params) : DatasetIterator(params), auto_tuner_(params.dataset->buffer_size_) { - std::vector components = - str_util::Split(params.prefix, "::", str_util::SkipEmpty()); - prefix_end_ = components.back(); } ~Iterator() override { @@ -143,10 +141,10 @@ class PrefetchDatasetOp::Dataset : public DatasetBase { mutex_lock l(mu_); if (stats_aggregator) { stats_aggregator->AddScalar( - strings::StrCat(prefix_end_, "::buffer_size"), + stats_utils::BufferSizeScalarName(dataset()->node_name()), static_cast(buffer_.size())); stats_aggregator->AddScalar( - strings::StrCat(prefix_end_, "::buffer_capacity"), + stats_utils::BufferCapacityScalarName(dataset()->node_name()), static_cast(auto_tuner_.buffer_limit())); } return input_impl_->GetNext(ctx, out_tensors, end_of_sequence); @@ -236,14 +234,14 @@ class PrefetchDatasetOp::Dataset : public DatasetBase { const auto& stats_aggregator = ctx->stats_aggregator(); if (stats_aggregator) { stats_aggregator->AddToHistogram( - strings::StrCat(prefix_end_, "::buffer_utilization"), + stats_utils::BufferUtilizationHistogramName(dataset()->node_name()), {static_cast(buffer_.size()) / static_cast(auto_tuner_.buffer_limit())}); stats_aggregator->AddScalar( - strings::StrCat(prefix_end_, "::buffer_size"), + stats_utils::BufferSizeScalarName(dataset()->node_name()), static_cast(buffer_.size())); stats_aggregator->AddScalar( - strings::StrCat(prefix_end_, "::buffer_capacity"), + stats_utils::BufferCapacityScalarName(dataset()->node_name()), static_cast(auto_tuner_.buffer_limit())); } // A new element is available. Forward the status from computing it, and @@ -375,7 +373,6 @@ class PrefetchDatasetOp::Dataset : public DatasetBase { mutex parent_mu_ ACQUIRED_BEFORE(mu_); std::unique_ptr input_impl_ GUARDED_BY(parent_mu_); condition_variable cond_var_; - string prefix_end_; PrefetchAutotuner auto_tuner_ GUARDED_BY(mu_); std::deque buffer_ GUARDED_BY(mu_); std::unique_ptr prefetch_thread_ GUARDED_BY(mu_); diff --git a/tensorflow/core/kernels/data/range_dataset_op_test.cc b/tensorflow/core/kernels/data/range_dataset_op_test.cc index 0bbc09a212..29de955e2a 100644 --- a/tensorflow/core/kernels/data/range_dataset_op_test.cc +++ b/tensorflow/core/kernels/data/range_dataset_op_test.cc @@ -136,7 +136,7 @@ TEST_F(RangeDatasetOpTest, DatasetName) { CreateDataset(range_kernel.get(), range_context.get(), &range_dataset)); core::ScopedUnref scored_unref(range_dataset); - EXPECT_EQ(range_dataset->name(), kOpName); + EXPECT_EQ(range_dataset->type_string(), kOpName); } TEST_F(RangeDatasetOpTest, DatasetOutputDtypes) { diff --git a/tensorflow/core/kernels/data/shuffle_dataset_op.cc b/tensorflow/core/kernels/data/shuffle_dataset_op.cc index e0c435718a..1a193b1d23 100644 --- a/tensorflow/core/kernels/data/shuffle_dataset_op.cc +++ b/tensorflow/core/kernels/data/shuffle_dataset_op.cc @@ -468,8 +468,8 @@ class ShuffleDatasetOp : public ShuffleDatasetOpBase { // resource_mgr. ResourceMgr* mgr = ctx->resource_mgr(); RandomSeedGenerator* seed_generator; - const string name = strings::StrCat(prefix(), "::", dataset()->name(), - "::RandomSeedGenerator"); + const string name = strings::StrCat( + prefix(), "::", dataset()->type_string(), "::RandomSeedGenerator"); int64 dataset_seed, dataset_seed2; { diff --git a/tensorflow/core/kernels/data/sparse_tensor_slice_dataset_op_test.cc b/tensorflow/core/kernels/data/sparse_tensor_slice_dataset_op_test.cc index d1d01f792f..cbae237245 100644 --- a/tensorflow/core/kernels/data/sparse_tensor_slice_dataset_op_test.cc +++ b/tensorflow/core/kernels/data/sparse_tensor_slice_dataset_op_test.cc @@ -194,7 +194,7 @@ TEST_F(SparseTensorSliceDatasetOpTest, DatasetName) { CreateDataset(dataset_kernel.get(), dataset_kernel_ctx.get(), &dataset)); core::ScopedUnref scoped_unref(dataset); - EXPECT_EQ(dataset->name(), kOpName); + EXPECT_EQ(dataset->type_string(), kOpName); } struct DatasetOutputDtypesTest : SparseTensorSliceDatasetOpTest, diff --git a/tensorflow/core/kernels/data/stats_utils.cc b/tensorflow/core/kernels/data/stats_utils.cc new file mode 100644 index 0000000000..eefd92bc66 --- /dev/null +++ b/tensorflow/core/kernels/data/stats_utils.cc @@ -0,0 +1,73 @@ +/* Copyright 2019 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/stats_utils.h" + +#include "tensorflow/core/lib/strings/strcat.h" + +namespace tensorflow { +namespace data { +namespace stats_utils { + +ABSL_CONST_INIT const char kDelimiter[] = "::"; +ABSL_CONST_INIT const char kExecutionTime[] = "execution_time"; +ABSL_CONST_INIT const char kThreadUtilization[] = "thread_utilization"; +ABSL_CONST_INIT const char kBufferSize[] = "buffer_size"; +ABSL_CONST_INIT const char kBufferCapacity[] = "buffer_capacity"; +ABSL_CONST_INIT const char kBufferUtilization[] = "buffer_utilization"; +ABSL_CONST_INIT const char kFilteredElements[] = "filtered_elements"; +ABSL_CONST_INIT const char kDroppedElements[] = "dropped_elements"; +ABSL_CONST_INIT const char kFeaturesCount[] = "features_count"; +ABSL_CONST_INIT const char kFeatureValuesCount[] = "feature_values_count"; +ABSL_CONST_INIT const char kExamplesCount[] = "examples_count"; + +string ExecutionTimeHistogramName(const string& prefix) { + return strings::StrCat(prefix, kDelimiter, kExecutionTime); +} + +string ThreadUtilizationScalarName(const string& prefix) { + return strings::StrCat(prefix, kDelimiter, kThreadUtilization); +} + +string BufferSizeScalarName(const string& prefix) { + return strings::StrCat(prefix, kDelimiter, kBufferSize); +} + +string BufferCapacityScalarName(const string& prefix) { + return strings::StrCat(prefix, kDelimiter, kBufferCapacity); +} + +string BufferUtilizationHistogramName(const string& prefix) { + return strings::StrCat(prefix, kDelimiter, kBufferUtilization); +} + +string FilterdElementsScalarName(const string& prefix) { + return strings::StrCat(prefix, kDelimiter, kFilteredElements); +} + +string DroppedElementsScalarName(const string& prefix) { + return strings::StrCat(prefix, kDelimiter, kDroppedElements); +} + +string FeatureHistogramName(const string& prefix) { + return strings::StrCat(prefix, kDelimiter, kFeaturesCount); +} + +string FeatureValueHistogramName(const string& prefix) { + return strings::StrCat(prefix, kDelimiter, kFeatureValuesCount); +} + +} // namespace stats_utils +} // namespace data +} // namespace tensorflow diff --git a/tensorflow/core/kernels/data/stats_utils.h b/tensorflow/core/kernels/data/stats_utils.h new file mode 100644 index 0000000000..a7c6190bf7 --- /dev/null +++ b/tensorflow/core/kernels/data/stats_utils.h @@ -0,0 +1,68 @@ +/* Copyright 2019 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_STATS_UTILS_H_ +#define TENSORFLOW_CORE_KERNELS_DATA_STATS_UTILS_H_ + +#include "tensorflow/core/platform/types.h" + +namespace tensorflow { +namespace data { +namespace stats_utils { +extern const char kDelimiter[]; +extern const char kExecutionTime[]; +extern const char kThreadUtilization[]; +extern const char kBufferSize[]; +extern const char kBufferCapacity[]; +extern const char kBufferUtilization[]; +extern const char kFilteredElements[]; +extern const char kDroppedElements[]; +extern const char kFeaturesCount[]; +extern const char kFeatureValuesCount[]; +extern const char kExamplesCount[]; + +// Name for tf.data function execution time (in ns) histogram metrics. +string ExecutionTimeHistogramName(const string& prefix); + +// Name for thread utilization (ratio of threads being used and maximum number +// of threads allocated) scalar metrics. +string ThreadUtilizationScalarName(const string& prefix); + +// Name for buffer size scalar metrics. +string BufferSizeScalarName(const string& prefix); + +// Name for buffer capacity (maximum allocated buffer size) scalar metrics. +string BufferCapacityScalarName(const string& prefix); + +// Name for buffer utilization (ratio of buffer size and maximum allocated +// buffer size.) histogram metrics. +string BufferUtilizationHistogramName(const string& prefix); + +// Name for filtered elements scalar metrics. +string FilterdElementsScalarName(const string& prefix); + +// Name for dropped elements scalar mereics. +string DroppedElementsScalarName(const string& prefix); + +// Name for features count histogram metrics. +string FeatureHistogramName(const string& prefix); + +// Name for feature-values count histogram metrics. +string FeatureValueHistogramName(const string& prefix); + +} // namespace stats_utils +} // namespace data +} // namespace tensorflow + +#endif // TENSORFLOW_CORE_KERNELS_DATA_STATS_UTILS_H_ diff --git a/tensorflow/core/kernels/data/tensor_slice_dataset_op_test.cc b/tensorflow/core/kernels/data/tensor_slice_dataset_op_test.cc index b0e09b4545..591ef2f011 100644 --- a/tensorflow/core/kernels/data/tensor_slice_dataset_op_test.cc +++ b/tensorflow/core/kernels/data/tensor_slice_dataset_op_test.cc @@ -221,7 +221,7 @@ TEST_F(TensorSliceDatasetOpTest, DatasetName) { &tensor_slice_dataset)); core::ScopedUnref scored_unref(tensor_slice_dataset); - EXPECT_EQ(tensor_slice_dataset->name(), kOpName); + EXPECT_EQ(tensor_slice_dataset->type_string(), kOpName); } struct DatasetOutputDtypesTest : TensorSliceDatasetOpTest, diff --git a/tensorflow/python/data/experimental/kernel_tests/stats_dataset_ops_test.py b/tensorflow/python/data/experimental/kernel_tests/stats_dataset_ops_test.py index c53ac82c6e..94c7ad221e 100644 --- a/tensorflow/python/data/experimental/kernel_tests/stats_dataset_ops_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/stats_dataset_ops_test.py @@ -59,8 +59,7 @@ def function_apply_options(dataset, aggregator, prefix="", counter_prefix=""): ) class StatsDatasetTest(stats_dataset_test_base.StatsDatasetTestBase): - @test_util.run_v1_only("b/123901126") - def testSkipEagerBytesProduced(self, dataset_transformation): + def testBytesProduced(self, dataset_transformation): aggregator = stats_aggregator.StatsAggregator() dataset = dataset_ops.Dataset.range(100).map( lambda x: array_ops.tile([x], ops.convert_to_tensor([x]))).apply( @@ -109,17 +108,24 @@ class StatsDatasetTest(stats_dataset_test_base.StatsDatasetTestBase): self.assertAllEqual( np.array([i] * i, dtype=np.int64), self.evaluate(next_element())) summary_str = self.evaluate(aggregator.get_summary()) - self._assertSummaryHasCount(summary_str, "Prefetch::buffer_utilization", - float(i + 1)) - self._assertSummaryContains(summary_str, "Prefetch::buffer_capacity") - self._assertSummaryContains(summary_str, "Prefetch::buffer_size") - self._assertSummaryHasRange(summary_str, "Prefetch::buffer_utilization", - 0, 1) + self._assertSummaryHasCount( + summary_str, + self.regexForNodeName("PrefetchDataset", "buffer_utilization"), + float(i + 1)) + self._assertSummaryContains( + summary_str, + self.regexForNodeName("PrefetchDataset", "buffer_capacity")) + self._assertSummaryContains( + summary_str, self.regexForNodeName("PrefetchDataset", "buffer_size")) + self._assertSummaryHasRange( + summary_str, + self.regexForNodeName("PrefetchDataset", "buffer_utilization"), 0, 1) with self.assertRaises(errors.OutOfRangeError): self.evaluate(next_element()) summary_str = self.evaluate(aggregator.get_summary()) - self._assertSummaryHasCount(summary_str, "Prefetch::buffer_utilization", - 100) + self._assertSummaryHasCount( + summary_str, + self.regexForNodeName("PrefetchDataset", "buffer_utilization"), 100) def testPrefetchBufferScalars(self, dataset_transformation): aggregator = stats_aggregator.StatsAggregator() @@ -132,9 +138,12 @@ class StatsDatasetTest(stats_dataset_test_base.StatsDatasetTestBase): self.assertAllEqual( np.array([i] * i, dtype=np.int64), self.evaluate(next_element())) summary_str = self.evaluate(aggregator.get_summary()) - self._assertSummaryHasScalarValue(summary_str, - "Prefetch::buffer_capacity", 1) - self._assertSummaryHasScalarValue(summary_str, "Prefetch::buffer_size", 1) + self._assertSummaryHasScalarValue( + summary_str, + self.regexForNodeName("PrefetchDataset", "buffer_capacity"), 1) + self._assertSummaryHasScalarValue( + summary_str, self.regexForNodeName("PrefetchDataset", "buffer_size"), + 1) with self.assertRaises(errors.OutOfRangeError): self.evaluate(next_element()) @@ -149,18 +158,23 @@ class StatsDatasetTest(stats_dataset_test_base.StatsDatasetTestBase): self.assertEqual(i * 3, self.evaluate(next_element())) summary_str = self.evaluate(aggregator.get_summary()) if i is not 0: - self._assertSummaryHasScalarValue(summary_str, - "Filter::dropped_elements", - float(i * 2)) + self._assertSummaryHasScalarValue( + summary_str, + self.regexForNodeName("FilterDataset", "dropped_elements"), + float(i * 2)) self._assertSummaryHasScalarValue( - summary_str, "Filter::filtered_elements", float(i + 1)) + summary_str, + self.regexForNodeName("FilterDataset", "filtered_elements"), + float(i + 1)) with self.assertRaises(errors.OutOfRangeError): self.evaluate(next_element()) summary_str = self.evaluate(aggregator.get_summary()) - self._assertSummaryHasScalarValue(summary_str, "Filter::dropped_elements", - 67.0) - self._assertSummaryHasScalarValue(summary_str, "Filter::filtered_elements", - 34.0) + self._assertSummaryHasScalarValue( + summary_str, self.regexForNodeName("FilterDataset", "dropped_elements"), + 67.0) + self._assertSummaryHasScalarValue( + summary_str, self.regexForNodeName("FilterDataset", + "filtered_elements"), 34.0) def testMapBufferUtilization(self, dataset_transformation): @@ -170,8 +184,7 @@ class StatsDatasetTest(stats_dataset_test_base.StatsDatasetTestBase): num_parallel_calls=4) self._testParallelCallsStats( - dataset_fn, - "ParallelMap", + dataset_fn, {self.regexForNodeName("ParallelMapDataset")}, 10, dataset_transformation, function_processing_time=True) @@ -187,8 +200,7 @@ class StatsDatasetTest(stats_dataset_test_base.StatsDatasetTestBase): return dataset.with_options(options) self._testParallelCallsStats( - dataset_fn, - "ParallelMap", + dataset_fn, {self.regexForNodeName("ParallelMapDataset")}, 10, dataset_transformation, function_processing_time=True) @@ -209,8 +221,9 @@ class StatsDatasetTest(stats_dataset_test_base.StatsDatasetTestBase): options.experimental_autotune = True return dataset.with_options(options) - self._testParallelCallsStats(dataset_fn, "ParallelInterleaveV2", 10, - dataset_transformation) + self._testParallelCallsStats( + dataset_fn, {self.regexForNodeName("ParallelInterleaveDatasetV2")}, 10, + dataset_transformation) def testMapAndBatchAutoTuneBufferUtilization(self, dataset_transformation): @@ -226,8 +239,7 @@ class StatsDatasetTest(stats_dataset_test_base.StatsDatasetTestBase): num_output = 100 // 16 + 1 self._testParallelCallsStats( - dataset_fn, - "MapAndBatch", + dataset_fn, {self.regexForNodeName("ExperimentalMapAndBatchDataset")}, num_output, dataset_transformation, check_elements=False, @@ -355,6 +367,32 @@ class StatsDatasetTest(stats_dataset_test_base.StatsDatasetTestBase): self.evaluate(aggregator.get_summary()), "dataset2_record_latency", 100.0) + def testMultiplePrefetchStats(self, dataset_transformation): + + aggregator = stats_aggregator.StatsAggregator() + dataset = dataset_ops.Dataset.range(10).prefetch( + 2).map(lambda x: math_ops.add(x, 2)).prefetch(1) + + dataset = dataset_transformation(dataset, aggregator) + next_element = self.getNext(dataset, requires_initialization=True) + + for i in range(10): + self.assertEqual(i + 2, self.evaluate(next_element())) + summary_str = self.evaluate(aggregator.get_summary()) + # TODO(shivaniagarwal): using exact name of prefetch node than the regex, + # to differentiate between two prefetch. This might break in future, at + # which point, it would be best to disable this test. + self._assertSummaryHasScalarValue( + summary_str, "PrefetchDataset/_5::buffer_capacity", 2) + self._assertSummaryContains(summary_str, + "PrefetchDataset/_5::buffer_size") + self._assertSummaryHasScalarValue( + summary_str, "PrefetchDataset/_8::buffer_capacity", 1) + self._assertSummaryContains(summary_str, + "PrefetchDataset/_8::buffer_size") + with self.assertRaises(errors.OutOfRangeError): + self.evaluate(next_element()) + @test_util.run_all_in_graph_and_eager_modes @parameterized.named_parameters( @@ -385,8 +423,7 @@ class FeatureStatsDatasetTest( num_output = total_records // batch_size + 1 self._testParallelCallsStats( - dataset_fn, - "ParseExample", + dataset_fn, {self.regexForNodeName("ExperimentalParseExampleDataset")}, num_output, dataset_transformation, check_elements=False) @@ -402,16 +439,21 @@ class FeatureStatsDatasetTest( with self.assertRaises(errors.OutOfRangeError): self.evaluate(next_element()) self._assertSummaryHasCount( - self.evaluate(aggregator.get_summary()), "record_stats_features", - total_records) + self.evaluate(aggregator.get_summary()), + self.regexForNodeName("record_stats_ExperimentalParseExampleDataset", + "features_count"), total_records) self._assertSummaryHasCount( - self.evaluate(aggregator.get_summary()), "record_stats_feature-values", - total_records) + self.evaluate(aggregator.get_summary()), + self.regexForNodeName("record_stats_ExperimentalParseExampleDataset", + "feature_values_count"), total_records) self._assertSummaryHasSum( - self.evaluate(aggregator.get_summary()), "record_stats_features", - total_records * 4) + self.evaluate(aggregator.get_summary()), + self.regexForNodeName("record_stats_ExperimentalParseExampleDataset", + "features_count"), total_records * 4) self._assertSummaryHasSum( - self.evaluate(aggregator.get_summary()), "record_stats_feature-values", + self.evaluate(aggregator.get_summary()), + self.regexForNodeName("record_stats_ExperimentalParseExampleDataset", + "feature_values_count"), self._sum_keywords(1) * num_epochs + 3 * total_records) diff --git a/tensorflow/python/data/experimental/kernel_tests/stats_dataset_test_base.py b/tensorflow/python/data/experimental/kernel_tests/stats_dataset_test_base.py index f5a15f4c84..f55b96a903 100644 --- a/tensorflow/python/data/experimental/kernel_tests/stats_dataset_test_base.py +++ b/tensorflow/python/data/experimental/kernel_tests/stats_dataset_test_base.py @@ -17,6 +17,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +import re import numpy as np from tensorflow.core.framework import summary_pb2 @@ -28,30 +29,30 @@ from tensorflow.python.framework import errors class StatsDatasetTestBase(test_base.DatasetTestBase): """Base class for testing statistics gathered in `StatsAggregator`.""" - def _assertSummaryContains(self, summary_str, tag): - summary_proto = summary_pb2.Summary() - summary_proto.ParseFromString(summary_str) - for value in summary_proto.value: - if tag == value.tag: - return - self.fail("Expected tag %r not found in summary %r" % (tag, summary_proto)) + def regexForNodeName(self, op_name, stats_type=""): + return "".join([op_name, r"/_\d+::", stats_type]) - def _assertSummaryHasCount(self, summary_str, tag, expected_value): + def _assertSummaryContains(self, summary_str, tag): summary_proto = summary_pb2.Summary() summary_proto.ParseFromString(summary_str) for value in summary_proto.value: - if tag == value.tag: - self.assertEqual(expected_value, value.histo.num) + if re.match(tag, value.tag): return self.fail("Expected tag %r not found in summary %r" % (tag, summary_proto)) - def _assertSummaryHasCountMoreOrEqualGeneralisedTag(self, summary_str, tag, - expected_value): + def _assertSummaryHasCount(self, + summary_str, + tag, + expected_value, + greater_than=False): summary_proto = summary_pb2.Summary() summary_proto.ParseFromString(summary_str) for value in summary_proto.value: - if tag in value.tag: - self.assertGreaterEqual(value.histo.num, expected_value) + if re.match(tag, value.tag): + if greater_than: + self.assertGreaterEqual(value.histo.num, expected_value) + else: + self.assertEqual(expected_value, value.histo.num) return self.fail("Expected tag %r not found in summary %r" % (tag, summary_proto)) @@ -59,7 +60,7 @@ class StatsDatasetTestBase(test_base.DatasetTestBase): summary_proto = summary_pb2.Summary() summary_proto.ParseFromString(summary_str) for value in summary_proto.value: - if tag == value.tag: + if re.match(tag, value.tag): self.assertLessEqual(min_value, value.histo.min) self.assertGreaterEqual(max_value, value.histo.max) return @@ -69,7 +70,7 @@ class StatsDatasetTestBase(test_base.DatasetTestBase): summary_proto = summary_pb2.Summary() summary_proto.ParseFromString(summary_str) for value in summary_proto.value: - if tag == value.tag: + if re.match(tag, value.tag): self.assertEqual(expected_value, value.histo.sum) return self.fail("Expected tag %r not found in summary %r" % (tag, summary_proto)) @@ -78,14 +79,14 @@ class StatsDatasetTestBase(test_base.DatasetTestBase): summary_proto = summary_pb2.Summary() summary_proto.ParseFromString(summary_str) for value in summary_proto.value: - if tag == value.tag: + if re.match(tag, value.tag): self.assertEqual(expected_value, value.simple_value) return self.fail("Expected tag %r not found in summary %r" % (tag, summary_proto)) def _testParallelCallsStats(self, dataset_fn, - dataset_name, + dataset_names, num_output, dataset_transformation, function_processing_time=False, @@ -100,14 +101,22 @@ class StatsDatasetTestBase(test_base.DatasetTestBase): if check_elements: self.assertAllEqual(np.array([i] * i, dtype=np.int64), next_) summary_str = self.evaluate(aggregator.get_summary()) - if function_processing_time: - self._assertSummaryHasCountMoreOrEqualGeneralisedTag( - summary_str, "::execution_time", float(i + 1)) - self._assertSummaryContains(summary_str, - dataset_name + "::thread_utilization") + for dataset_name in dataset_names: + if function_processing_time: + self._assertSummaryHasCount( + summary_str, + r"(.*)::execution_time$", + float(i + 1), + greater_than=True) + self._assertSummaryContains(summary_str, + dataset_name + "thread_utilization") with self.assertRaises(errors.OutOfRangeError): self.evaluate(next_element()) if function_processing_time: summary_str = self.evaluate(aggregator.get_summary()) - self._assertSummaryHasCountMoreOrEqualGeneralisedTag( - summary_str, "::execution_time", float(num_output)) + for dataset_name in dataset_names: + self._assertSummaryHasCount( + summary_str, + r"(.*)::execution_time$", + float(num_output), + greater_than=True) -- GitLab From 61e6c43d8865fd4c885dbc9fdbe17201d7bea877 Mon Sep 17 00:00:00 2001 From: Sanjoy Das Date: Tue, 26 Feb 2019 13:43:36 -0800 Subject: [PATCH 581/766] [TF:XLA] Bump open source llvm revision to r354852 PiperOrigin-RevId: 235784471 --- tensorflow/workspace.bzl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl index 68d680abcd..2a1db33b9c 100755 --- a/tensorflow/workspace.bzl +++ b/tensorflow/workspace.bzl @@ -513,11 +513,11 @@ def tf_workspace(path_prefix = "", tf_repo_name = ""): tf_http_archive( name = "llvm", build_file = clean_dep("//third_party/llvm:llvm.autogenerated.BUILD"), - sha256 = "cf564d5b14db5e2a66b098d8eb1e6e288e7ea7f8aea1a8d1bbf3f1c8a6cca37f", - strip_prefix = "llvm-78f7736cbd364bcf143ced306d3219fa1dc77ad1", + sha256 = "5015c1bf87255222de1e4179b0cb13da3e5d33489a7b46276781d00b576a57da", + strip_prefix = "llvm-6656fc5a7891f33ea9bd30e77d463129a6ad666a", urls = [ - "https://mirror.bazel.build/github.com/llvm-mirror/llvm/archive/78f7736cbd364bcf143ced306d3219fa1dc77ad1.tar.gz", - "https://github.com/llvm-mirror/llvm/archive/78f7736cbd364bcf143ced306d3219fa1dc77ad1.tar.gz", + "https://mirror.bazel.build/github.com/llvm-mirror/llvm/archive/6656fc5a7891f33ea9bd30e77d463129a6ad666a.tar.gz", + "https://github.com/llvm-mirror/llvm/archive/6656fc5a7891f33ea9bd30e77d463129a6ad666a.tar.gz", ], ) -- GitLab From e7d82e218d538c83cea259cf0f834d406350c911 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 26 Feb 2019 13:48:32 -0800 Subject: [PATCH 582/766] Remove even further unnecessary namespaces. PiperOrigin-RevId: 235785443 --- tensorflow/contrib/verbs/grpc_verbs_service.h | 6 ------ tensorflow/contrib/verbs/grpc_verbs_service_impl.h | 8 -------- 2 files changed, 14 deletions(-) diff --git a/tensorflow/contrib/verbs/grpc_verbs_service.h b/tensorflow/contrib/verbs/grpc_verbs_service.h index 444c863b94..e616778665 100644 --- a/tensorflow/contrib/verbs/grpc_verbs_service.h +++ b/tensorflow/contrib/verbs/grpc_verbs_service.h @@ -25,12 +25,6 @@ limitations under the License. #include "tensorflow/core/distributed_runtime/rpc/grpc_call.h" #include "tensorflow/core/lib/core/refcount.h" -namespace grpc { -class ServerBuilder; -class ServerCompletionQueue; -class Alarm; -} // namespace grpc - namespace tensorflow { class GrpcVerbsService : public AsyncServiceInterface { diff --git a/tensorflow/contrib/verbs/grpc_verbs_service_impl.h b/tensorflow/contrib/verbs/grpc_verbs_service_impl.h index cfb9b7ddd7..2432c34ae2 100644 --- a/tensorflow/contrib/verbs/grpc_verbs_service_impl.h +++ b/tensorflow/contrib/verbs/grpc_verbs_service_impl.h @@ -27,14 +27,6 @@ limitations under the License. #include "tensorflow/contrib/verbs/verbs_service.pb.h" -namespace grpc { -class CompletionQueue; -class Channel; -class RpcService; -class ServerCompletionQueue; -class ServerContext; -} // namespace grpc - namespace tensorflow { namespace grpc { -- GitLab From 91b3bf05c99fb64148e656e4f6ef753d2fff19e7 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 26 Feb 2019 14:06:34 -0800 Subject: [PATCH 583/766] Tool to test that tflite writer library roundtrips a given TFLite model correctly. PiperOrigin-RevId: 235789112 --- tensorflow/lite/experimental/writer/BUILD | 10 ++++ .../lite/experimental/writer/writer_test.cc | 52 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 tensorflow/lite/experimental/writer/writer_test.cc diff --git a/tensorflow/lite/experimental/writer/BUILD b/tensorflow/lite/experimental/writer/BUILD index 6aa81ff173..9ba74d0e91 100644 --- a/tensorflow/lite/experimental/writer/BUILD +++ b/tensorflow/lite/experimental/writer/BUILD @@ -47,6 +47,16 @@ cc_binary( ], ) +cc_binary( + name = "writer_test", + srcs = ["writer_test.cc"], + deps = [ + ":writer_lib", + "//tensorflow/lite:framework", + "//tensorflow/lite/kernels:builtin_ops", + ], +) + cc_test( name = "writer_lib_test", size = "small", diff --git a/tensorflow/lite/experimental/writer/writer_test.cc b/tensorflow/lite/experimental/writer/writer_test.cc new file mode 100644 index 0000000000..bc9b25db33 --- /dev/null +++ b/tensorflow/lite/experimental/writer/writer_test.cc @@ -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. +==============================================================================*/ +// Loads the input tflite file into interpreter, serializes it back to a tflite +// buffer, and then verifies that the generated output can be loaded back into +// an interpreter and the model prepared (i.e., AllocateTensors returns ok). +// +// Usage: +// writer_test + +#include + +#include "tensorflow/lite/experimental/writer/writer_lib.h" +#include "tensorflow/lite/kernels/register.h" +#include "tensorflow/lite/model.h" + +int main(int argc, char* argv[]) { + if (argc != 2) { + fprintf(stderr, "Usage: %s input_file\n", argv[0]); + return 1; + } + std::unique_ptr model = + tflite::FlatBufferModel::BuildFromFile(argv[1]); + std::unique_ptr interpreter; + tflite::ops::builtin::BuiltinOpResolver builtin_op_resolver; + tflite::InterpreterBuilder(*model, builtin_op_resolver)(&interpreter); + tflite::InterpreterWriter writer(interpreter.get()); + std::unique_ptr output_buffer; + size_t output_buffer_size; + writer.GetBuffer(&output_buffer, &output_buffer_size); + + // Verify the generated model. + model = tflite::FlatBufferModel::BuildFromBuffer( + reinterpret_cast(output_buffer.get()), output_buffer_size); + tflite::InterpreterBuilder(*model, builtin_op_resolver)(&interpreter); + if (interpreter->AllocateTensors() != kTfLiteOk) { + fprintf(stderr, "AllocateTensors failed on the round-tripped model.\n"); + return 1; + } + return 0; +} -- GitLab From 8da8c0edabee44c155b5399b79b77b8661130458 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 26 Feb 2019 14:10:50 -0800 Subject: [PATCH 584/766] Changing tf function call for checking valid tf installation. PiperOrigin-RevId: 235789966 --- tensorflow/tools/ci_build/builds/pip_new.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tensorflow/tools/ci_build/builds/pip_new.sh b/tensorflow/tools/ci_build/builds/pip_new.sh index ed687bf846..c1bbd7ef05 100755 --- a/tensorflow/tools/ci_build/builds/pip_new.sh +++ b/tensorflow/tools/ci_build/builds/pip_new.sh @@ -308,7 +308,7 @@ test_pip_virtualenv_clean() { pushd "${TMP_DIR}" # Run a quick check on tensorflow installation. - RET_VAL=$(python -c "import tensorflow as tf; t1=tf.constant([1,2,3,4]); t2=tf.constant([5,6,7,8]); print(tf.add(t1,t2))") + RET_VAL=$(python -c "import tensorflow as tf; t1=tf.constant([1,2,3,4]); t2=tf.constant([5,6,7,8]); print(tf.add(t1,t2).shape)") # Deactivate virtualenv. deactivate || source deactivate || die "FAILED: Unable to deactivate from existing virtualenv." @@ -318,7 +318,7 @@ test_pip_virtualenv_clean() { sudo rm -rf "${TMP_DIR}" "${CLEAN_VENV_DIR}" # Check result to see if tensorflow is properly installed. - if [[ ${RET_VAL} == *'Tensor("Add:0", shape=(4,), dtype=int32)'* ]]; then + if [[ ${RET_VAL} == *'(4,)'* ]]; then echo "PIP test on clean virtualenv PASSED." return 0 else @@ -342,14 +342,14 @@ test_pip_virtualenv_non_clean() { pushd "${TMP_DIR}" # Run a quick check on tensorflow installation. - RET_VAL=$(python -c "import tensorflow as tf; t1=tf.constant([1,2,3,4]); t2=tf.constant([5,6,7,8]); print(tf.add(t1,t2))") + RET_VAL=$(python -c "import tensorflow as tf; t1=tf.constant([1,2,3,4]); t2=tf.constant([5,6,7,8]); print(tf.add(t1,t2).shape)") # Return to original directory. Remove temp dirs. popd sudo rm -rf "${TMP_DIR}" # Check result to see if tensorflow is properly installed. - if ! [[ ${RET_VAL} == *'Tensor("Add:0", shape=(4,), dtype=int32)'* ]]; then + if ! [[ ${RET_VAL} == *'(4,)'* ]]; then echo "PIP test on virtualenv (non-clean) FAILED" return 1 fi -- GitLab From 3ad94f61cde880fc4c27200d8b2cd4e1e602d684 Mon Sep 17 00:00:00 2001 From: Yu-Cheng Ling Date: Tue, 26 Feb 2019 14:15:25 -0800 Subject: [PATCH 585/766] Use TFLite 1.13.1 CocoaPod in iOS examples This is the first release after moving out of contrib/ so we can remove the contrib/ in header paths. PiperOrigin-RevId: 235790952 --- .../examples/ios/camera/CameraExampleViewController.mm | 7 ------- tensorflow/lite/examples/ios/camera/Podfile | 2 +- tensorflow/lite/examples/ios/simple/Podfile | 2 +- .../lite/examples/ios/simple/RunModelViewController.mm | 8 ++++---- 4 files changed, 6 insertions(+), 13 deletions(-) diff --git a/tensorflow/lite/examples/ios/camera/CameraExampleViewController.mm b/tensorflow/lite/examples/ios/camera/CameraExampleViewController.mm index 4f6fcaa96c..e5c230e65e 100644 --- a/tensorflow/lite/examples/ios/camera/CameraExampleViewController.mm +++ b/tensorflow/lite/examples/ios/camera/CameraExampleViewController.mm @@ -23,12 +23,6 @@ #include #include -#if TFLITE_USE_CONTRIB_LITE -#include "tensorflow/contrib/lite/kernels/register.h" -#include "tensorflow/contrib/lite/model.h" -#include "tensorflow/contrib/lite/op_resolver.h" -#include "tensorflow/contrib/lite/string_util.h" -#else #include "tensorflow/lite/kernels/register.h" #include "tensorflow/lite/model.h" #include "tensorflow/lite/op_resolver.h" @@ -36,7 +30,6 @@ #if TFLITE_USE_GPU_DELEGATE #include "tensorflow/lite/delegates/gpu/metal_delegate.h" #endif -#endif #define LOG(x) std::cerr diff --git a/tensorflow/lite/examples/ios/camera/Podfile b/tensorflow/lite/examples/ios/camera/Podfile index 2e15cc63de..0bff676159 100644 --- a/tensorflow/lite/examples/ios/camera/Podfile +++ b/tensorflow/lite/examples/ios/camera/Podfile @@ -9,5 +9,5 @@ target 'tflite_camera_example' # Note: TFLite GPU Delegate binary isn't releast yet, and we're working # on it. - pod 'TensorFlowLite', '1.12.0' + pod 'TensorFlowLite', '1.13.1' # pod 'TensorFlowLiteGpuExperimental', '0.0.1' diff --git a/tensorflow/lite/examples/ios/simple/Podfile b/tensorflow/lite/examples/ios/simple/Podfile index 931b72c1f5..d9e6a6586b 100644 --- a/tensorflow/lite/examples/ios/simple/Podfile +++ b/tensorflow/lite/examples/ios/simple/Podfile @@ -2,4 +2,4 @@ platform :ios, '8.0' inhibit_all_warnings! target 'tflite_simple_example' - pod 'TensorFlowLite', '1.12.0' + pod 'TensorFlowLite', '1.13.1' diff --git a/tensorflow/lite/examples/ios/simple/RunModelViewController.mm b/tensorflow/lite/examples/ios/simple/RunModelViewController.mm index 32da7f7e4f..b90f2d3cfd 100644 --- a/tensorflow/lite/examples/ios/simple/RunModelViewController.mm +++ b/tensorflow/lite/examples/ios/simple/RunModelViewController.mm @@ -22,10 +22,10 @@ #include #include -#include "tensorflow/contrib/lite/kernels/register.h" -#include "tensorflow/contrib/lite/model.h" -#include "tensorflow/contrib/lite/op_resolver.h" -#include "tensorflow/contrib/lite/string_util.h" +#include "tensorflow/lite/kernels/register.h" +#include "tensorflow/lite/model.h" +#include "tensorflow/lite/op_resolver.h" +#include "tensorflow/lite/string_util.h" #include "ios_image_load.h" -- GitLab From 4608048810f3a972b6e2ed36f25f1bd96aaf333c Mon Sep 17 00:00:00 2001 From: Guangda Lai Date: Tue, 26 Feb 2019 14:19:15 -0800 Subject: [PATCH 586/766] Add more validation to BatchMatMul converter. This also fix batch_matmul_test so the expected engines to run are the same as the engines to build. After this we'll be able to remove ExpectedEnginesToRun(). PiperOrigin-RevId: 235791754 --- .../tf2tensorrt/convert/convert_nodes.cc | 94 +++++++++++-------- .../tf2tensorrt/convert/convert_nodes.h | 5 + .../tf2tensorrt/convert/convert_nodes_test.cc | 55 +++++++---- .../tensorrt/test/batch_matmul_test.py | 11 +-- 4 files changed, 96 insertions(+), 69 deletions(-) diff --git a/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.cc b/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.cc index e26f650aba..f4c5433349 100644 --- a/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.cc +++ b/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.cc @@ -137,6 +137,17 @@ Status TensorShapeArrayToTrtDims(const std::vector& shape, return Status::OK(); } +// TODO(laigd): use this utility function in more places. +Status RemoveBatchDimension(nvinfer1::Dims* dims) { + if (dims->nbDims < 2) { + return errors::InvalidArgument( + "Dropping batch dimension requires dims with rank>=2."); + } + std::copy(dims->d + 1, dims->d + dims->nbDims, dims->d); + dims->nbDims--; + return Status::OK(); +} + void GetOutputProperties(const grappler::GraphProperties& graph_properties, const Node* node, const int out_port, PartialTensorShape* shape, DataType* dtype) { @@ -1193,6 +1204,7 @@ Status Converter::GetWeightRange(const TRT_ShapedWeights& weights, Status Converter::PrepareTensorForShape(const TRT_TensorOrWeights& input, const nvinfer1::Dims& dims, + const bool validation_only, const nvinfer1::ITensor** tensor) { // If -1 is not used for one of the dims, we can check if the shapes are // compatible. @@ -1209,6 +1221,10 @@ Status Converter::PrepareTensorForShape(const TRT_TensorOrWeights& input, DebugString(input.GetTrtDims()), " vs ", DebugString(dims), ")"); } + if (validation_only) { + *tensor = nullptr; + return Status::OK(); + } if (input.is_tensor()) { if (DimsEqual(input.GetTrtDims(), dims)) { @@ -1908,10 +1924,10 @@ Status BinaryTensorOpTensor(OpConverterParams* params, const nvinfer1::ITensor* tensor_l = nullptr; const nvinfer1::ITensor* tensor_r = nullptr; status = params->converter->PrepareTensorForShape( - operand_l, broadcasted_dims_l, &tensor_l); + operand_l, broadcasted_dims_l, /*validation_only=*/false, &tensor_l); if (status.ok()) { status = params->converter->PrepareTensorForShape( - operand_r, broadcasted_dims_r, &tensor_r); + operand_r, broadcasted_dims_r, /*validation_only=*/false, &tensor_r); } if (!status.ok()) { return errors::Internal("Failed to convert binary op ", node_def.name(), @@ -2100,7 +2116,7 @@ Status ConvertReshape(OpConverterParams* params) { // Start conversion. const nvinfer1::ITensor* output_tensor = nullptr; TF_RETURN_IF_ERROR(params->converter->PrepareTensorForShape( - input_tensor, reshape_dims, &output_tensor)); + input_tensor, reshape_dims, /*validation_only=*/false, &output_tensor)); params->outputs->push_back( TRT_TensorOrWeights(const_cast(output_tensor))); return Status::OK(); @@ -2151,7 +2167,7 @@ Status ConvertExpandDims(OpConverterParams* params) { /*ignore_first_dim=*/true)); const nvinfer1::ITensor* output_tensor = nullptr; TF_RETURN_IF_ERROR(params->converter->PrepareTensorForShape( - input_tensor, new_dims, &output_tensor)); + input_tensor, new_dims, /*validation_only=*/false, &output_tensor)); params->outputs->push_back( TRT_TensorOrWeights(const_cast(output_tensor))); return Status::OK(); @@ -2210,7 +2226,7 @@ Status ConvertSqueeze(OpConverterParams* params) { /*ignore_first_dim=*/true)); const nvinfer1::ITensor* output_tensor = nullptr; TF_RETURN_IF_ERROR(params->converter->PrepareTensorForShape( - input_tensor, new_dims, &output_tensor)); + input_tensor, new_dims, /*validation_only=*/false, &output_tensor)); params->outputs->push_back( TRT_TensorOrWeights(const_cast(output_tensor))); return Status::OK(); @@ -2362,7 +2378,7 @@ Status ConvertStridedSliceHelper(OpConverterParams* params, if (need_reshape) { const nvinfer1::ITensor* output_tensor = nullptr; TF_RETURN_IF_ERROR(params->converter->PrepareTensorForShape( - input, reshape_dims, &output_tensor)); + input, reshape_dims, /*validation_only=*/false, &output_tensor)); tensor = const_cast(output_tensor); } if (need_transpose) { @@ -2407,7 +2423,8 @@ Status ConvertStridedSliceHelper(OpConverterParams* params, /*ignore_first_dim=*/true)); const nvinfer1::ITensor* output_tensor = nullptr; TF_RETURN_IF_ERROR(params->converter->PrepareTensorForShape( - TRT_TensorOrWeights(tensor), new_dims, &output_tensor)); + TRT_TensorOrWeights(tensor), new_dims, /*validation_only=*/false, + &output_tensor)); tensor = const_cast(output_tensor); } @@ -3672,7 +3689,7 @@ Status ConvertMatMulHelper(OpConverterParams* params, input_dim.d[input_dim.nbDims++] = 1; } TF_RETURN_IF_ERROR(params->converter->PrepareTensorForShape( - tensor_input, input_dim, &tensor)); + tensor_input, input_dim, /*validation_only=*/false, &tensor)); nvinfer1::IFullyConnectedLayer* layer = params->converter->network()->addFullyConnected( @@ -3685,7 +3702,8 @@ Status ConvertMatMulHelper(OpConverterParams* params, auto output_dim = output_tensor->getDimensions(); output_dim.nbDims = 1; TF_RETURN_IF_ERROR(params->converter->PrepareTensorForShape( - TRT_TensorOrWeights(output_tensor), output_dim, &temp_tensor)); + TRT_TensorOrWeights(output_tensor), output_dim, /*validation_only=*/false, + &temp_tensor)); output_tensor = const_cast(temp_tensor); params->outputs->push_back(TRT_TensorOrWeights(output_tensor)); return Status::OK(); @@ -3729,6 +3747,10 @@ Status ConvertBatchMatMul(OpConverterParams* params) { " inputs but expected 2, at ", node_def.name()); } + if (inputs[0].is_weights() && inputs[1].is_weights()) { + return errors::InvalidArgument( + "All inputs are weights, but Grappler is expected to fold them."); + } TFAttrs attrs(node_def); const DataType tf_dtype = attrs.get("T"); @@ -3751,43 +3773,33 @@ Status ConvertBatchMatMul(OpConverterParams* params) { node_def.name()); } } - if (params->validation_only) return Status::OK(); - // TODO(laigd): avoid duplicating code, and add tests. - // TODO(laigd): should we add the following chesks to validator as well? - auto dims_l = inputs.at(0).GetTrtDims(); - auto dims_r = inputs.at(1).GetTrtDims(); - if (inputs.at(0).is_weights()) { - if (inputs.at(0).GetTrtDims().d[0] != 1) { - return errors::InvalidArgument( - "Input 0 as weight assumes broadcast across batch for MatMul, at: " + - node_def.name()); - } else { - for (int i = 0; i < dims_l.nbDims - 1; i++) { - dims_l.d[i] = dims_l.d[i + 1]; - } - dims_l.nbDims--; - } - } - if (inputs.at(1).is_weights()) { - if (inputs.at(1).GetTrtDims().d[0] != 1) { - return errors::InvalidArgument( - "Input 1 as weight assumes broadcast across batch for MatMul, at: " + - node_def.name()); - } else { - for (int i = 0; i < dims_r.nbDims - 1; i++) { - dims_r.d[i] = dims_r.d[i + 1]; + auto get_tensor_with_proper_dims = [params]( + const TRT_TensorOrWeights& input, + const nvinfer1::ITensor** tensor) { + auto dims = input.GetTrtDims(); + if (input.is_weights()) { + // The other operand must be a tensor, this is ensured by earlier checks. + // Checks that the batch dimension is not changed by broadcasting. + if (dims.d[0] != 1) { + return errors::InvalidArgument( + "Input weight attempts to broadcast across batch dimension for " + "BatchMatMul, at ", + params->node_def.name()); } - dims_r.nbDims--; + // Remove the batch dimension from the weights. + TF_RETURN_IF_ERROR(RemoveBatchDimension(&dims)); } - } - + // Create tensor and reshape if necessary. + TF_RETURN_IF_ERROR(params->converter->PrepareTensorForShape( + input, dims, params->validation_only, tensor)); + return Status::OK(); + }; const nvinfer1::ITensor* tensor_l; const nvinfer1::ITensor* tensor_r; - TF_RETURN_IF_ERROR(params->converter->PrepareTensorForShape( - inputs.at(0), dims_l, &tensor_l)); - TF_RETURN_IF_ERROR(params->converter->PrepareTensorForShape( - inputs.at(1), dims_r, &tensor_r)); + TF_RETURN_IF_ERROR(get_tensor_with_proper_dims(inputs.at(0), &tensor_l)); + TF_RETURN_IF_ERROR(get_tensor_with_proper_dims(inputs.at(1), &tensor_r)); + if (params->validation_only) return Status::OK(); nvinfer1::IMatrixMultiplyLayer* layer = params->converter->network()->addMatrixMultiply( diff --git a/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.h b/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.h index 35b44cfeca..6333d9130a 100644 --- a/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.h +++ b/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.h @@ -486,8 +486,13 @@ class Converter { const nvinfer1::ITensor** output_tensor); // Converts 'input' into 'tensor' with shape specified by 'dims'. + // + // If validation_only is true, it doesn't do the conversion but only do some + // minimum validation for the eligibility of the conversion, and *tensor will + // be set to nullptr. Status PrepareTensorForShape(const TRT_TensorOrWeights& input, const nvinfer1::Dims& dims, + const bool validation_only, const nvinfer1::ITensor** tensor); // Return OK if the broadcast scheme is supported and compute the shapes after diff --git a/tensorflow/compiler/tf2tensorrt/convert/convert_nodes_test.cc b/tensorflow/compiler/tf2tensorrt/convert/convert_nodes_test.cc index 2f02d366a4..bc94b28fa5 100644 --- a/tensorflow/compiler/tf2tensorrt/convert/convert_nodes_test.cc +++ b/tensorflow/compiler/tf2tensorrt/convert/convert_nodes_test.cc @@ -711,23 +711,34 @@ TEST_F(ConverterTest, PrepareTensorForShape_Tensor) { TRT_TensorOrWeights tw(input_tensor); const nvinfer1::ITensor* output_tensor = nullptr; - // Shape size doesn't match. - ExpectStatus(converter_->PrepareTensorForShape(tw, GetTestDims({2, 3, 6}), - &output_tensor), - error::INVALID_ARGUMENT, "Reshape shapes are not compatible"); - - // TODO(aaroey): we should check the case where uninferred dimensions are not - // an exact divisor of input dim ensions, e.g. for dims {-1, 7}. - - // Infer shape, ok. - TF_EXPECT_OK(converter_->PrepareTensorForShape(tw, GetTestDims({-1, 2}), - &output_tensor)); - ExpectTrtDimsEqualsArray({15, 2}, output_tensor->getDimensions()); + for (bool validation_only : {false, true}) { + // Shape size doesn't match. + ExpectStatus( + converter_->PrepareTensorForShape(tw, GetTestDims({2, 3, 6}), + validation_only, &output_tensor), + error::INVALID_ARGUMENT, "Reshape shapes are not compatible"); + + // TODO(aaroey): we should check the case where uninferred dimensions are + // not an exact divisor of input dim ensions, e.g. for dims {-1, 7}. + + // Infer shape, ok. + TF_EXPECT_OK(converter_->PrepareTensorForShape( + tw, GetTestDims({-1, 2}), validation_only, &output_tensor)); + if (validation_only) { + EXPECT_EQ(nullptr, output_tensor); + } else { + ExpectTrtDimsEqualsArray({15, 2}, output_tensor->getDimensions()); + } - // Regular shape. - TF_EXPECT_OK(converter_->PrepareTensorForShape(tw, GetTestDims({10, 3}), - &output_tensor)); - ExpectTrtDimsEqualsArray({10, 3}, output_tensor->getDimensions()); + // Regular shape. + TF_EXPECT_OK(converter_->PrepareTensorForShape( + tw, GetTestDims({10, 3}), validation_only, &output_tensor)); + if (validation_only) { + EXPECT_EQ(nullptr, output_tensor); + } else { + ExpectTrtDimsEqualsArray({10, 3}, output_tensor->getDimensions()); + } + } } TEST_F(ConverterTest, PrepareTensorForShape_Weights) { @@ -735,9 +746,15 @@ TEST_F(ConverterTest, PrepareTensorForShape_Weights) { weight_store_->GetTempWeights(DT_FLOAT, GetTestDims({2, 3, 5})); TRT_TensorOrWeights tw(weights); const nvinfer1::ITensor* output_tensor = nullptr; - TF_EXPECT_OK(converter_->PrepareTensorForShape(tw, GetTestDims({10, 3}), - &output_tensor)); - ExpectTrtDimsEqualsArray({10, 3}, output_tensor->getDimensions()); + for (bool validation_only : {false, true}) { + TF_EXPECT_OK(converter_->PrepareTensorForShape( + tw, GetTestDims({10, 3}), validation_only, &output_tensor)); + if (validation_only) { + EXPECT_EQ(nullptr, output_tensor); + } else { + ExpectTrtDimsEqualsArray({10, 3}, output_tensor->getDimensions()); + } + } } TEST_F(ConverterTest, MaybeUpdateBatchSize) { diff --git a/tensorflow/python/compiler/tensorrt/test/batch_matmul_test.py b/tensorflow/python/compiler/tensorrt/test/batch_matmul_test.py index 49ad09aea3..cd72b3fa68 100644 --- a/tensorflow/python/compiler/tensorrt/test/batch_matmul_test.py +++ b/tensorflow/python/compiler/tensorrt/test/batch_matmul_test.py @@ -69,7 +69,7 @@ class BatchMatMulTest(trt_test.TfTrtIntegrationTestBase): out = x1 + x2 + x3 array_ops.squeeze(out, name=output_name) return trt_test.TfTrtIntegrationTestParams( - gdef=g.as_graph_def(), + gdef=g.as_graph_def(add_shapes=True), input_names=[input_name, w1_name, w2_name], input_dims=[[input_dims, w1_dims, w2_dims]], output_names=[output_name], @@ -77,14 +77,7 @@ class BatchMatMulTest(trt_test.TfTrtIntegrationTestBase): def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" - if (run_params.dynamic_engine and - not trt_test.IsQuantizationMode(run_params.precision_mode)): - return ["TRTEngineOp_0", "TRTEngineOp_1"] - return ["TRTEngineOp_1"] - - def ExpectedEnginesToRun(self, run_params): - """Return the expected engines to run.""" - return ["TRTEngineOp_1"] + return ["TRTEngineOp_0", "TRTEngineOp_1"] if __name__ == "__main__": -- GitLab From a6953fb989c182035addf841b5250ae98a906f66 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 26 Feb 2019 14:30:27 -0800 Subject: [PATCH 587/766] Internal Change PiperOrigin-RevId: 235794109 --- .../tools/ci_build/windows/gpu/pip/build_tf_windows.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tensorflow/tools/ci_build/windows/gpu/pip/build_tf_windows.sh b/tensorflow/tools/ci_build/windows/gpu/pip/build_tf_windows.sh index efdd5f13c8..1f49e0cc55 100644 --- a/tensorflow/tools/ci_build/windows/gpu/pip/build_tf_windows.sh +++ b/tensorflow/tools/ci_build/windows/gpu/pip/build_tf_windows.sh @@ -60,6 +60,7 @@ RELEASE_BUILD=0 TEST_TARGET="//${PY_TEST_DIR}/tensorflow/python/..." PROJECT_NAME="" EXTRA_BUILD_FLAGS="" +EXTRA_TEST_FLAGS="" # --skip_test Skip running tests # --enable_remote_cache Add options to enable remote cache for build and test @@ -89,6 +90,13 @@ while [[ $# -gt 0 ]]; do fi PROJECT_NAME="$1" ;; + --extra_test_flags) + shift + if [[ -z "$1" ]]; then + break + fi + EXTRA_TEST_FLAGS="$1" + ;; *) esac shift @@ -157,6 +165,7 @@ TF_GPU_COUNT=${TF_GPU_COUNT:-4} # GPU tests are very flaky when running concurrently, so set local_test_jobs=1 bazel test --announce_rc --config=opt -k --test_output=errors \ --test_env=TF_GPU_COUNT \ + ${EXTRA_TEST_FLAGS} \ --run_under=//tensorflow/tools/ci_build/gpu_build:parallel_gpu_execute \ --define=no_tensorflow_py_deps=true --test_lang_filters=py \ --test_tag_filters=-no_pip,-no_windows,-no_windows_gpu,-no_gpu,-no_pip_gpu,-no_oss \ -- GitLab From 1736c17f51594525ff81203af592ae74ded195e9 Mon Sep 17 00:00:00 2001 From: Eugene Brevdo Date: Tue, 26 Feb 2019 14:32:03 -0800 Subject: [PATCH 588/766] [TF function] Small perf improvement and tests of nested shape relaxation. PiperOrigin-RevId: 235794439 --- tensorflow/python/eager/function.py | 10 ++++------ tensorflow/python/eager/function_test.py | 25 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/tensorflow/python/eager/function.py b/tensorflow/python/eager/function.py index 25a1080c04..d1127fd0f4 100644 --- a/tensorflow/python/eager/function.py +++ b/tensorflow/python/eager/function.py @@ -62,13 +62,11 @@ FORWARD_FUNCTION_ATTRIBUTE_NAME = "forward_function_name" BACKWARD_FUNCTION_ATTRIBUTE_NAME = "backward_function_name" -class CacheKey( - collections.namedtuple("CacheKey", [ - "input_signature", "parent_graph", "device_functions", - "colocation_stack", "uses_xla"])): +CacheKey = collections.namedtuple("CacheKey", [ + "input_signature", "parent_graph", "device_functions", + "colocation_stack", "uses_xla"]) - def replace(self, *args, **kwargs): - return self._replace(*args, **kwargs) +CacheKey.replace = CacheKey._replace # pylint: disable=protected-access def _flat_shape_list(*params): diff --git a/tensorflow/python/eager/function_test.py b/tensorflow/python/eager/function_test.py index dc5b03bdf7..d86c1001e6 100644 --- a/tensorflow/python/eager/function_test.py +++ b/tensorflow/python/eager/function_test.py @@ -209,6 +209,31 @@ class FunctionTest(test.TestCase, parameterized.TestCase): with self.assertRaisesRegexp(errors.InvalidArgumentError, r'MatMul'): fn(array_ops.ones((3, 4))) + def testNestedShapeFunctionRelaxation(self): + + got_shape = [None] + + # The inner function will go through shape relaxation because the shapes it + # receives will be [1], [2], [3], ... + @def_function.function + def bar(x_shape): + got_shape[0] = x_shape._shape_tuple() + return x_shape + + # The outer function will not go through shape relaxation because the shapes + # it receives will be [1], [[1]], [[[1]]], ... + @def_function.function + def foo(ones): + return bar(array_ops.shape(ones)) + + for rank in range(1, 6): + x_shape = self.evaluate(foo(array_ops.ones([1] * rank))) + self.assertAllEqual(x_shape, [1] * rank) + if rank < 3: + self.assertEqual(got_shape[0], (rank,)) + else: + self.assertEqual(got_shape[0], (None,)) + def testWastedAdd(self): @def_function.function() -- GitLab From 36b4a3cc0f7dc0a30e7e71d87b9431a53f1f95af Mon Sep 17 00:00:00 2001 From: Suharsh Sivakumar Date: Tue, 26 Feb 2019 14:40:29 -0800 Subject: [PATCH 589/766] Update ops compatibility docs. PiperOrigin-RevId: 235796367 --- tensorflow/lite/g3doc/tf_ops_compatibility.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/tensorflow/lite/g3doc/tf_ops_compatibility.md b/tensorflow/lite/g3doc/tf_ops_compatibility.md index 1065c93a8b..de0f0164dc 100644 --- a/tensorflow/lite/g3doc/tf_ops_compatibility.md +++ b/tensorflow/lite/g3doc/tf_ops_compatibility.md @@ -8,7 +8,9 @@ operations are mapped to their TensorFlow Lite counterparts. Since the set of TensorFlow Lite operations is smaller than TensorFlow's, not every model is convertible. Even for supported operations, very specific usage patterns are sometimes expected, for performance reasons. We expect to expand -the set of supported operations in future TensorFlow Lite releases. +the set of supported operations in future TensorFlow Lite releases. Additional +ops can be included by [using select TensorFlow ops](using_select_tf_ops.md), at +the cost of binary size. The best way to understand how to build a TensorFlow model that can be used with TensorFlow Lite is to carefully consider how operations are converted and @@ -17,15 +19,15 @@ optimized, along with the limitations imposed by this process. ## Supported Types Most TensorFlow Lite operations target both floating-point (float32) and -quantized (uint8) inference, but usually there is little or no support for other -types like tf.float16 and strings. +quantized (uint8, int8) inference, but many ops do not yet for other types like +tf.float16 and strings. Apart from using different version of the operations, the other difference between floating-point and quantized models lies in the way they are converted. -Quantized conversion expect the models to be annotated with "fake quantization" -nodes that record the dynamic range of the tensors. Without that information TF -Lite is not able to accurately quantize a model, which means that proper -quantized training is necessary before conversion. +Quantized conversion requires dynamic range information for tensors. This +requires "fake-quantization" during model training, getting range information +via a calibration data set, or doing "on-the-fly" range estimation. See +[quantization](performance/model_optimization.md). ## Data Format and Broadcasting @@ -89,7 +91,8 @@ be simply removed from the graph (tf.identity), replaced by tensors some supported operations may sometimes be removed through one of these processes. -Here is a list of TensorFlow operations that are usually removed from the graph: +Here is a non-exhaustive list of TensorFlow operations that are usually removed +from the graph: * [tf.add](https://www.tensorflow.org/api_docs/python/tf/add) * [tf.check_numerics](https://www.tensorflow.org/api_docs/python/tf/check_numerics) -- GitLab From aceb9f7176d85ef3739cf3cef38b8aa50a0c570f Mon Sep 17 00:00:00 2001 From: Zhenyu Tan Date: Tue, 26 Feb 2019 14:41:27 -0800 Subject: [PATCH 590/766] Expose Kmeans experimental endpoint. PiperOrigin-RevId: 235796556 --- tensorflow/python/ops/clustering_ops.py | 16 +-- ...flow.estimator.experimental.-k-means.pbtxt | 115 ++++++++++++++++++ .../tensorflow.estimator.experimental.pbtxt | 4 + ...flow.estimator.experimental.-k-means.pbtxt | 115 ++++++++++++++++++ .../tensorflow.estimator.experimental.pbtxt | 4 + 5 files changed, 246 insertions(+), 8 deletions(-) create mode 100644 tensorflow/tools/api/golden/v1/tensorflow.estimator.experimental.-k-means.pbtxt create mode 100644 tensorflow/tools/api/golden/v2/tensorflow.estimator.experimental.-k-means.pbtxt diff --git a/tensorflow/python/ops/clustering_ops.py b/tensorflow/python/ops/clustering_ops.py index d48b89cbac..1ca375c314 100644 --- a/tensorflow/python/ops/clustering_ops.py +++ b/tensorflow/python/ops/clustering_ops.py @@ -21,6 +21,7 @@ from __future__ import print_function from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes from tensorflow.python.framework import ops +from tensorflow.python.framework import random_seed as random_seed_ops from tensorflow.python.ops import array_ops from tensorflow.python.ops import check_ops from tensorflow.python.ops import control_flow_ops @@ -143,7 +144,7 @@ class KMeans(object): self._distance_metric = distance_metric self._use_mini_batch = use_mini_batch self._mini_batch_steps_per_iteration = int(mini_batch_steps_per_iteration) - self._random_seed = random_seed + self._seed = random_seed_ops.get_seed(random_seed)[0] self._kmeans_plus_plus_num_retries = kmeans_plus_plus_num_retries self._kmc2_chain_length = kmc2_chain_length @@ -364,8 +365,8 @@ class KMeans(object): update_in_steps) = self._create_variables(num_clusters) init_op = _InitializeClustersOpFactory( self._inputs, num_clusters, initial_clusters, self._distance_metric, - self._random_seed, self._kmeans_plus_plus_num_retries, - self._kmc2_chain_length, cluster_centers_var, cluster_centers_updated, + self._seed, self._kmeans_plus_plus_num_retries, self._kmc2_chain_length, + cluster_centers_var, cluster_centers_updated, cluster_centers_initialized).op() cluster_centers = cluster_centers_var @@ -584,7 +585,7 @@ class _InitializeClustersOpFactory(object): self._num_clusters = num_clusters self._initial_clusters = initial_clusters self._distance_metric = distance_metric - self._random_seed = random_seed + self._seed = random_seed self._kmeans_plus_plus_num_retries = kmeans_plus_plus_num_retries self._kmc2_chain_length = kmc2_chain_length self._cluster_centers = cluster_centers @@ -601,7 +602,7 @@ class _InitializeClustersOpFactory(object): array_ops.reshape(self._num_remaining, [-1]), minval=0, maxval=math_ops.cast(self._num_data, dtypes.int64), - seed=self._random_seed, + seed=self._seed, dtype=dtypes.int64) return embedding_lookup(self._inputs, indices, partition_strategy='div') @@ -612,8 +613,7 @@ class _InitializeClustersOpFactory(object): if self._distance_metric == COSINE_DISTANCE: inp = nn_impl.l2_normalize(inp, dim=1) return gen_clustering_ops.kmeans_plus_plus_initialization( - inp, - math_ops.to_int64(self._num_remaining), self._random_seed, + inp, math_ops.to_int64(self._num_remaining), self._seed, self._kmeans_plus_plus_num_retries) def _kmc2_multiple_centers(self): @@ -670,7 +670,7 @@ class _InitializeClustersOpFactory(object): subset, self._cluster_centers, 1) # Sample index of new center using k-MC2 Markov chain. new_center_index = gen_clustering_ops.kmc2_chain_initialization( - array_ops.squeeze(distances), self._random_seed) + array_ops.squeeze(distances), self._seed) # Extract actual new center. newly_sampled_center = array_ops.reshape(subset[new_center_index], [1, -1]) diff --git a/tensorflow/tools/api/golden/v1/tensorflow.estimator.experimental.-k-means.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.estimator.experimental.-k-means.pbtxt new file mode 100644 index 0000000000..fd425e46db --- /dev/null +++ b/tensorflow/tools/api/golden/v1/tensorflow.estimator.experimental.-k-means.pbtxt @@ -0,0 +1,115 @@ +path: "tensorflow.estimator.experimental.KMeans" +tf_class { + is_instance: "" + is_instance: "" + is_instance: "" + is_instance: "" + member { + name: "ALL_DISTANCES" + mtype: "" + } + member { + name: "CLUSTER_CENTERS_VAR_NAME" + mtype: "" + } + member { + name: "CLUSTER_INDEX" + mtype: "" + } + member { + name: "COSINE_DISTANCE" + mtype: "" + } + member { + name: "KMEANS_PLUS_PLUS_INIT" + mtype: "" + } + member { + name: "RANDOM_INIT" + mtype: "" + } + member { + name: "SCORE" + mtype: "" + } + member { + name: "SQUARED_EUCLIDEAN_DISTANCE" + mtype: "" + } + member { + name: "config" + mtype: "" + } + member { + name: "model_dir" + mtype: "" + } + member { + name: "model_fn" + mtype: "" + } + member { + name: "params" + mtype: "" + } + member_method { + name: "__init__" + argspec: "args=[\'self\', \'num_clusters\', \'model_dir\', \'initial_clusters\', \'distance_metric\', \'seed\', \'use_mini_batch\', \'mini_batch_steps_per_iteration\', \'kmeans_plus_plus_num_retries\', \'relative_tolerance\', \'config\', \'feature_columns\'], varargs=None, keywords=None, defaults=[\'None\', \'random\', \'squared_euclidean\', \'None\', \'True\', \'1\', \'2\', \'None\', \'None\', \'None\'], " + } + member_method { + name: "cluster_centers" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "eval_dir" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'None\'], " + } + member_method { + name: "evaluate" + argspec: "args=[\'self\', \'input_fn\', \'steps\', \'hooks\', \'checkpoint_path\', \'name\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'None\', \'None\'], " + } + member_method { + name: "experimental_export_all_saved_models" + argspec: "args=[\'self\', \'export_dir_base\', \'input_receiver_fn_map\', \'assets_extra\', \'as_text\', \'checkpoint_path\'], varargs=None, keywords=None, defaults=[\'None\', \'False\', \'None\'], " + } + member_method { + name: "export_saved_model" + argspec: "args=[\'self\', \'export_dir_base\', \'serving_input_receiver_fn\', \'assets_extra\', \'as_text\', \'checkpoint_path\', \'experimental_mode\'], varargs=None, keywords=None, defaults=[\'None\', \'False\', \'None\', \'infer\'], " + } + member_method { + name: "export_savedmodel" + argspec: "args=[\'self\', \'export_dir_base\', \'serving_input_receiver_fn\', \'assets_extra\', \'as_text\', \'checkpoint_path\', \'strip_default_attrs\'], varargs=None, keywords=None, defaults=[\'None\', \'False\', \'None\', \'False\'], " + } + member_method { + name: "get_variable_names" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "get_variable_value" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "latest_checkpoint" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "predict" + argspec: "args=[\'self\', \'input_fn\', \'predict_keys\', \'hooks\', \'checkpoint_path\', \'yield_single_examples\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'None\', \'True\'], " + } + member_method { + name: "predict_cluster_index" + argspec: "args=[\'self\', \'input_fn\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "score" + argspec: "args=[\'self\', \'input_fn\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "train" + argspec: "args=[\'self\', \'input_fn\', \'hooks\', \'steps\', \'max_steps\', \'saving_listeners\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'None\', \'None\'], " + } + member_method { + name: "transform" + argspec: "args=[\'self\', \'input_fn\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/v1/tensorflow.estimator.experimental.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.estimator.experimental.pbtxt index b1bd5a2661..b247485d89 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.estimator.experimental.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.estimator.experimental.pbtxt @@ -4,6 +4,10 @@ tf_module { name: "InMemoryEvaluatorHook" mtype: "" } + member { + name: "KMeans" + mtype: "" + } member { name: "LinearSDCA" mtype: "" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.estimator.experimental.-k-means.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.estimator.experimental.-k-means.pbtxt new file mode 100644 index 0000000000..fd425e46db --- /dev/null +++ b/tensorflow/tools/api/golden/v2/tensorflow.estimator.experimental.-k-means.pbtxt @@ -0,0 +1,115 @@ +path: "tensorflow.estimator.experimental.KMeans" +tf_class { + is_instance: "" + is_instance: "" + is_instance: "" + is_instance: "" + member { + name: "ALL_DISTANCES" + mtype: "" + } + member { + name: "CLUSTER_CENTERS_VAR_NAME" + mtype: "" + } + member { + name: "CLUSTER_INDEX" + mtype: "" + } + member { + name: "COSINE_DISTANCE" + mtype: "" + } + member { + name: "KMEANS_PLUS_PLUS_INIT" + mtype: "" + } + member { + name: "RANDOM_INIT" + mtype: "" + } + member { + name: "SCORE" + mtype: "" + } + member { + name: "SQUARED_EUCLIDEAN_DISTANCE" + mtype: "" + } + member { + name: "config" + mtype: "" + } + member { + name: "model_dir" + mtype: "" + } + member { + name: "model_fn" + mtype: "" + } + member { + name: "params" + mtype: "" + } + member_method { + name: "__init__" + argspec: "args=[\'self\', \'num_clusters\', \'model_dir\', \'initial_clusters\', \'distance_metric\', \'seed\', \'use_mini_batch\', \'mini_batch_steps_per_iteration\', \'kmeans_plus_plus_num_retries\', \'relative_tolerance\', \'config\', \'feature_columns\'], varargs=None, keywords=None, defaults=[\'None\', \'random\', \'squared_euclidean\', \'None\', \'True\', \'1\', \'2\', \'None\', \'None\', \'None\'], " + } + member_method { + name: "cluster_centers" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "eval_dir" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'None\'], " + } + member_method { + name: "evaluate" + argspec: "args=[\'self\', \'input_fn\', \'steps\', \'hooks\', \'checkpoint_path\', \'name\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'None\', \'None\'], " + } + member_method { + name: "experimental_export_all_saved_models" + argspec: "args=[\'self\', \'export_dir_base\', \'input_receiver_fn_map\', \'assets_extra\', \'as_text\', \'checkpoint_path\'], varargs=None, keywords=None, defaults=[\'None\', \'False\', \'None\'], " + } + member_method { + name: "export_saved_model" + argspec: "args=[\'self\', \'export_dir_base\', \'serving_input_receiver_fn\', \'assets_extra\', \'as_text\', \'checkpoint_path\', \'experimental_mode\'], varargs=None, keywords=None, defaults=[\'None\', \'False\', \'None\', \'infer\'], " + } + member_method { + name: "export_savedmodel" + argspec: "args=[\'self\', \'export_dir_base\', \'serving_input_receiver_fn\', \'assets_extra\', \'as_text\', \'checkpoint_path\', \'strip_default_attrs\'], varargs=None, keywords=None, defaults=[\'None\', \'False\', \'None\', \'False\'], " + } + member_method { + name: "get_variable_names" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "get_variable_value" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "latest_checkpoint" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "predict" + argspec: "args=[\'self\', \'input_fn\', \'predict_keys\', \'hooks\', \'checkpoint_path\', \'yield_single_examples\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'None\', \'True\'], " + } + member_method { + name: "predict_cluster_index" + argspec: "args=[\'self\', \'input_fn\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "score" + argspec: "args=[\'self\', \'input_fn\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "train" + argspec: "args=[\'self\', \'input_fn\', \'hooks\', \'steps\', \'max_steps\', \'saving_listeners\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'None\', \'None\'], " + } + member_method { + name: "transform" + argspec: "args=[\'self\', \'input_fn\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.estimator.experimental.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.estimator.experimental.pbtxt index b1bd5a2661..b247485d89 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.estimator.experimental.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.estimator.experimental.pbtxt @@ -4,6 +4,10 @@ tf_module { name: "InMemoryEvaluatorHook" mtype: "" } + member { + name: "KMeans" + mtype: "" + } member { name: "LinearSDCA" mtype: "" -- GitLab From 5ae8f95351311d290134001c0c9b1cbd20503feb Mon Sep 17 00:00:00 2001 From: Gaurav Jain Date: Tue, 26 Feb 2019 14:43:15 -0800 Subject: [PATCH 591/766] Modify benchmarks to use new execution_mode API PiperOrigin-RevId: 235796898 --- tensorflow/python/eager/benchmarks_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/eager/benchmarks_test.py b/tensorflow/python/eager/benchmarks_test.py index f44185df0b..8a1319f9ef 100644 --- a/tensorflow/python/eager/benchmarks_test.py +++ b/tensorflow/python/eager/benchmarks_test.py @@ -146,7 +146,7 @@ class MicroBenchmarks(test.Benchmark): def _run(self, func, num_iters, execution_mode=None): # call func to maybe warm up the GPU ctx = context.context() - with ctx.execution_mode(execution_mode): + with context.execution_mode(execution_mode): func() if execution_mode == context.ASYNC: ctx.async_wait() -- GitLab From 7f312d6ba07347a6d892f0eccd28b360024e1bc2 Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Tue, 26 Feb 2019 14:45:01 -0800 Subject: [PATCH 592/766] Filter out `XLA_CPU' from list of accelerators PiperOrigin-RevId: 235797266 --- .../python/distribute/cluster_resolver/cluster_resolver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/distribute/cluster_resolver/cluster_resolver.py b/tensorflow/python/distribute/cluster_resolver/cluster_resolver.py index ca6b60ea5b..c636c98254 100644 --- a/tensorflow/python/distribute/cluster_resolver/cluster_resolver.py +++ b/tensorflow/python/distribute/cluster_resolver/cluster_resolver.py @@ -51,7 +51,7 @@ def get_accelerator_devices(master, config_proto): device_match = DEVICE_TYPE_REGEX.match(name) if device_match: device_type = device_match.group(1) - if device_type == 'CPU': # CPUs are not accelerators + if device_type == 'CPU' or device_type == 'XLA_CPU': # Filter CPUs continue devices.append(session._DeviceAttributes(name, device_type, 0, 0)) # pylint: disable=protected-access return devices -- GitLab From b0cc808bc6d56aa1e89bfd7ef340eacd71167fd4 Mon Sep 17 00:00:00 2001 From: Scott Zhu Date: Tue, 26 Feb 2019 14:51:29 -0800 Subject: [PATCH 593/766] Update the default RNNCell in tf 2.0 API. 1. Make the tf.nn.rnn_cell.RNNCell to be v1 only since it not properly behave in v2, eg still use scope, etc. 2. Add the tf.keras.layers.AbstractRNNCell as the preferred implementation of RNN cell. The cell has same method comparing to nn.rnn.RNNCell. The difference are base classes, and variable scope usage. This cell will be used in tf.contrib to replace the usage of LayerRNNCell as base class. PiperOrigin-RevId: 235798594 --- tensorflow/python/keras/layers/__init__.py | 1 + tensorflow/python/keras/layers/recurrent.py | 88 ++++++++ .../python/keras/layers/recurrent_test.py | 55 +++++ tensorflow/python/ops/rnn_cell_impl.py | 6 +- ...w.keras.layers.-abstract-r-n-n-cell.pbtxt} | 25 +-- .../golden/v1/tensorflow.keras.layers.pbtxt | 4 + ...ow.keras.layers.-abstract-r-n-n-cell.pbtxt | 195 ++++++++++++++++++ .../golden/v2/tensorflow.keras.layers.pbtxt | 4 + .../golden/v2/tensorflow.nn.rnn_cell.pbtxt | 4 - tensorflow/tools/compatibility/renames_v2.py | 2 + .../tools/compatibility/tf_upgrade_v2.py | 2 +- .../tools/compatibility/tf_upgrade_v2_test.py | 7 + 12 files changed, 367 insertions(+), 26 deletions(-) rename tensorflow/tools/api/golden/{v2/tensorflow.nn.rnn_cell.-r-n-n-cell.pbtxt => v1/tensorflow.keras.layers.-abstract-r-n-n-cell.pbtxt} (84%) create mode 100644 tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-abstract-r-n-n-cell.pbtxt diff --git a/tensorflow/python/keras/layers/__init__.py b/tensorflow/python/keras/layers/__init__.py index 88fbaca3ea..8689e0d2c8 100644 --- a/tensorflow/python/keras/layers/__init__.py +++ b/tensorflow/python/keras/layers/__init__.py @@ -145,6 +145,7 @@ from tensorflow.python.keras.layers.pooling import GlobalMaxPool3D # Recurrent layers. from tensorflow.python.keras.layers.recurrent import RNN +from tensorflow.python.keras.layers.recurrent import AbstractRNNCell from tensorflow.python.keras.layers.recurrent import StackedRNNCells from tensorflow.python.keras.layers.recurrent import SimpleRNNCell from tensorflow.python.keras.layers.recurrent import GRUCell diff --git a/tensorflow/python/keras/layers/recurrent.py b/tensorflow/python/keras/layers/recurrent.py index ab8469a611..416c7da7de 100644 --- a/tensorflow/python/keras/layers/recurrent.py +++ b/tensorflow/python/keras/layers/recurrent.py @@ -1006,6 +1006,94 @@ class RNN(Layer): return updates + self._updates +@keras_export('keras.layers.AbstractRNNCell') +class AbstractRNNCell(Layer): + """Abstract object representing an RNN cell. + + This is the base class for implementing RNN cells with custom behavior. + + Every `RNNCell` must have the properties below and implement `call` with + the signature `(output, next_state) = call(input, state)`. + + Examples: + + ```python + class MinimalRNNCell(AbstractRNNCell): + + def __init__(self, units, **kwargs): + self.units = units + super(MinimalRNNCell, self).__init__(**kwargs) + + @property + def state_size(self): + return self.units + + def build(self, input_shape): + self.kernel = self.add_weight(shape=(input_shape[-1], self.units), + initializer='uniform', + name='kernel') + self.recurrent_kernel = self.add_weight( + shape=(self.units, self.units), + initializer='uniform', + name='recurrent_kernel') + self.built = True + + def call(self, inputs, states): + prev_output = states[0] + h = K.dot(inputs, self.kernel) + output = h + K.dot(prev_output, self.recurrent_kernel) + return output, output + ``` + + This definition of cell differs from the definition used in the literature. + In the literature, 'cell' refers to an object with a single scalar output. + This definition refers to a horizontal array of such units. + + An RNN cell, in the most abstract setting, is anything that has + a state and performs some operation that takes a matrix of inputs. + This operation results in an output matrix with `self.output_size` columns. + If `self.state_size` is an integer, this operation also results in a new + state matrix with `self.state_size` columns. If `self.state_size` is a + (possibly nested tuple of) TensorShape object(s), then it should return a + matching structure of Tensors having shape `[batch_size].concatenate(s)` + for each `s` in `self.batch_size`. + """ + + def call(self, inputs, states): + """The function that contains the logic for one RNN step calculation. + + Args: + inputs: the input tensor, which is a slide from the overall RNN input by + the time dimension (usually the second dimension). + states: the state tensor from previous step, which has the same shape + as `(batch, state_size)`. In the case of timestep 0, it will be the + initial state user specified, or zero filled tensor otherwise. + + Returns: + A tuple of two tensors: + 1. output tensor for the current timestep, with size `output_size`. + 2. state tensor for next step, which has the shape of `state_size`. + """ + raise NotImplementedError('Abstract method') + + @property + def state_size(self): + """size(s) of state(s) used by this cell. + + It can be represented by an Integer, a TensorShape or a tuple of Integers + or TensorShapes. + """ + raise NotImplementedError('Abstract method') + + @property + def output_size(self): + """Integer or TensorShape: size of outputs produced by this cell.""" + raise NotImplementedError('Abstract method') + + def get_initial_state(self, inputs=None, batch_size=None, dtype=None): + return _generate_zero_filled_state_for_cell(self, inputs, batch_size, dtype) + + @keras_export('keras.layers.SimpleRNNCell') class SimpleRNNCell(Layer): """Cell class for SimpleRNN. diff --git a/tensorflow/python/keras/layers/recurrent_test.py b/tensorflow/python/keras/layers/recurrent_test.py index 4b7aeb9f22..1d7b1c6898 100644 --- a/tensorflow/python/keras/layers/recurrent_test.py +++ b/tensorflow/python/keras/layers/recurrent_test.py @@ -218,6 +218,61 @@ class RNNTest(keras_parameterized.TestCase): y_np_2 = model.predict(x_np) self.assertAllClose(y_np, y_np_2, atol=1e-4) + def test_minimal_rnn_cell_abstract_rnn_cell(self): + + class MinimalRNNCell(keras.layers.AbstractRNNCell): + + def __init__(self, units, **kwargs): + self.units = units + super(MinimalRNNCell, self).__init__(**kwargs) + + @property + def state_size(self): + return self.units + + def build(self, input_shape): + self.kernel = self.add_weight(shape=(input_shape[-1], self.units), + initializer='uniform', + name='kernel') + self.recurrent_kernel = self.add_weight( + shape=(self.units, self.units), + initializer='uniform', + name='recurrent_kernel') + self.built = True + + def call(self, inputs, states): + prev_output = states[0] + h = keras.backend.dot(inputs, self.kernel) + output = h + keras.backend.dot(prev_output, self.recurrent_kernel) + return output, output + + @property + def output_size(self): + return self.units + + cell = MinimalRNNCell(32) + x = keras.Input((None, 5)) + layer = keras.layers.RNN(cell) + y = layer(x) + model = keras.models.Model(x, y) + model.compile( + optimizer="rmsprop", + loss="mse", + run_eagerly=testing_utils.should_run_eagerly()) + model.train_on_batch(np.zeros((6, 5, 5)), np.zeros((6, 32))) + + # Test stacking. + cells = [MinimalRNNCell(8), + MinimalRNNCell(16), + MinimalRNNCell(32)] + layer = keras.layers.RNN(cells) + y = layer(x) + model = keras.models.Model(x, y) + model.compile(optimizer='rmsprop', + loss='mse', + run_eagerly=testing_utils.should_run_eagerly()) + model.train_on_batch(np.zeros((6, 5, 5)), np.zeros((6, 32))) + def test_rnn_with_time_major(self): batch = 10 time_step = 5 diff --git a/tensorflow/python/ops/rnn_cell_impl.py b/tensorflow/python/ops/rnn_cell_impl.py index f33362a4e0..e35dc8b824 100644 --- a/tensorflow/python/ops/rnn_cell_impl.py +++ b/tensorflow/python/ops/rnn_cell_impl.py @@ -180,7 +180,7 @@ def _zero_state_tensors(state_size, batch_size, dtype): return nest.map_structure(get_state_shape, state_size) -@tf_export("nn.rnn_cell.RNNCell") +@tf_export(v1=["nn.rnn_cell.RNNCell"]) class RNNCell(base_layer.Layer): """Abstract object representing an RNN cell. @@ -207,7 +207,9 @@ class RNNCell(base_layer.Layer): super(RNNCell, self).__init__( trainable=trainable, name=name, dtype=dtype, **kwargs) # Attribute that indicates whether the cell is a TF RNN cell, due the slight - # difference between TF and Keras RNN cell. + # difference between TF and Keras RNN cell. Notably the state is not wrapped + # in a list for TF cell where they are single tensor state, whereas keras + # cell will wrap the state into a list, and call() will have to unwrap them. self._is_tf_rnn_cell = True def __call__(self, inputs, state, scope=None): diff --git a/tensorflow/tools/api/golden/v2/tensorflow.nn.rnn_cell.-r-n-n-cell.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-abstract-r-n-n-cell.pbtxt similarity index 84% rename from tensorflow/tools/api/golden/v2/tensorflow.nn.rnn_cell.-r-n-n-cell.pbtxt rename to tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-abstract-r-n-n-cell.pbtxt index a9f7e85b14..526d65fbf6 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.nn.rnn_cell.-r-n-n-cell.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.-abstract-r-n-n-cell.pbtxt @@ -1,7 +1,6 @@ -path: "tensorflow.nn.rnn_cell.RNNCell" +path: "tensorflow.keras.layers.AbstractRNNCell" tf_class { - is_instance: "" - is_instance: "" + is_instance: "" is_instance: "" is_instance: "" is_instance: "" @@ -17,10 +16,6 @@ tf_class { name: "dynamic" mtype: "" } - member { - name: "graph" - mtype: "" - } member { name: "inbound_nodes" mtype: "" @@ -73,10 +68,6 @@ tf_class { name: "output_size" mtype: "" } - member { - name: "scope_name" - mtype: "" - } member { name: "state_size" mtype: "" @@ -103,7 +94,7 @@ tf_class { } member_method { name: "__init__" - argspec: "args=[\'self\', \'trainable\', \'name\', \'dtype\'], varargs=None, keywords=kwargs, defaults=[\'True\', \'None\', \'None\'], " + argspec: "args=[\'self\', \'trainable\', \'name\', \'dtype\', \'dynamic\'], varargs=None, keywords=kwargs, defaults=[\'True\', \'None\', \'None\', \'False\'], " } member_method { name: "add_loss" @@ -123,7 +114,7 @@ tf_class { } member_method { name: "add_weight" - argspec: "args=[\'self\', \'name\', \'shape\', \'dtype\', \'initializer\', \'regularizer\', \'trainable\', \'constraint\', \'use_resource\', \'synchronization\', \'aggregation\', \'partitioner\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'None\', \'None\', \'None\', \'None\', \'VariableSynchronization.AUTO\', \'VariableAggregation.NONE\', \'None\'], " + argspec: "args=[\'self\', \'name\', \'shape\', \'dtype\', \'initializer\', \'regularizer\', \'trainable\', \'constraint\', \'partitioner\', \'use_resource\', \'synchronization\', \'aggregation\'], varargs=None, keywords=kwargs, defaults=[\'None\', \'None\', \'None\', \'None\', \'None\', \'None\', \'None\', \'None\', \'None\', \'VariableSynchronization.AUTO\', \'VariableAggregation.NONE\'], " } member_method { name: "apply" @@ -131,11 +122,11 @@ tf_class { } member_method { name: "build" - argspec: "args=[\'self\', \'_\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'self\', \'input_shape\'], varargs=None, keywords=None, defaults=None" } member_method { name: "call" - argspec: "args=[\'self\', \'inputs\'], varargs=None, keywords=kwargs, defaults=None" + argspec: "args=[\'self\', \'inputs\', \'states\'], varargs=None, keywords=None, defaults=None" } member_method { name: "compute_mask" @@ -201,8 +192,4 @@ tf_class { name: "set_weights" argspec: "args=[\'self\', \'weights\'], varargs=None, keywords=None, defaults=None" } - member_method { - name: "zero_state" - argspec: "args=[\'self\', \'batch_size\', \'dtype\'], varargs=None, keywords=None, defaults=None" - } } diff --git a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.pbtxt index cc0fdabee1..fe9085bb99 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.keras.layers.pbtxt @@ -1,5 +1,9 @@ path: "tensorflow.keras.layers" tf_module { + member { + name: "AbstractRNNCell" + mtype: "" + } member { name: "Activation" mtype: "" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-abstract-r-n-n-cell.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-abstract-r-n-n-cell.pbtxt new file mode 100644 index 0000000000..526d65fbf6 --- /dev/null +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.-abstract-r-n-n-cell.pbtxt @@ -0,0 +1,195 @@ +path: "tensorflow.keras.layers.AbstractRNNCell" +tf_class { + is_instance: "" + is_instance: "" + is_instance: "" + is_instance: "" + member { + name: "activity_regularizer" + mtype: "" + } + member { + name: "dtype" + mtype: "" + } + member { + name: "dynamic" + mtype: "" + } + member { + name: "inbound_nodes" + mtype: "" + } + member { + name: "input" + mtype: "" + } + member { + name: "input_mask" + mtype: "" + } + member { + name: "input_shape" + mtype: "" + } + member { + name: "losses" + mtype: "" + } + member { + name: "name" + mtype: "" + } + member { + name: "non_trainable_variables" + mtype: "" + } + member { + name: "non_trainable_weights" + mtype: "" + } + member { + name: "outbound_nodes" + mtype: "" + } + member { + name: "output" + mtype: "" + } + member { + name: "output_mask" + mtype: "" + } + member { + name: "output_shape" + mtype: "" + } + member { + name: "output_size" + mtype: "" + } + member { + name: "state_size" + mtype: "" + } + member { + name: "trainable_variables" + mtype: "" + } + member { + name: "trainable_weights" + mtype: "" + } + member { + name: "updates" + mtype: "" + } + member { + name: "variables" + mtype: "" + } + member { + name: "weights" + mtype: "" + } + member_method { + name: "__init__" + argspec: "args=[\'self\', \'trainable\', \'name\', \'dtype\', \'dynamic\'], varargs=None, keywords=kwargs, defaults=[\'True\', \'None\', \'None\', \'False\'], " + } + member_method { + name: "add_loss" + argspec: "args=[\'self\', \'losses\', \'inputs\'], varargs=None, keywords=None, defaults=[\'None\'], " + } + member_method { + name: "add_metric" + argspec: "args=[\'self\', \'value\', \'aggregation\', \'name\'], varargs=None, keywords=None, defaults=[\'None\', \'None\'], " + } + member_method { + name: "add_update" + argspec: "args=[\'self\', \'updates\', \'inputs\'], varargs=None, keywords=None, defaults=[\'None\'], " + } + member_method { + name: "add_variable" + argspec: "args=[\'self\'], varargs=args, keywords=kwargs, defaults=None" + } + member_method { + name: "add_weight" + argspec: "args=[\'self\', \'name\', \'shape\', \'dtype\', \'initializer\', \'regularizer\', \'trainable\', \'constraint\', \'partitioner\', \'use_resource\', \'synchronization\', \'aggregation\'], varargs=None, keywords=kwargs, defaults=[\'None\', \'None\', \'None\', \'None\', \'None\', \'None\', \'None\', \'None\', \'None\', \'VariableSynchronization.AUTO\', \'VariableAggregation.NONE\'], " + } + member_method { + name: "apply" + argspec: "args=[\'self\', \'inputs\'], varargs=args, keywords=kwargs, defaults=None" + } + member_method { + name: "build" + argspec: "args=[\'self\', \'input_shape\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "call" + argspec: "args=[\'self\', \'inputs\', \'states\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "compute_mask" + argspec: "args=[\'self\', \'inputs\', \'mask\'], varargs=None, keywords=None, defaults=[\'None\'], " + } + member_method { + name: "compute_output_shape" + argspec: "args=[\'self\', \'input_shape\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "count_params" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "from_config" + argspec: "args=[\'cls\', \'config\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "get_config" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "get_initial_state" + argspec: "args=[\'self\', \'inputs\', \'batch_size\', \'dtype\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'None\'], " + } + member_method { + name: "get_input_at" + argspec: "args=[\'self\', \'node_index\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "get_input_mask_at" + argspec: "args=[\'self\', \'node_index\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "get_input_shape_at" + argspec: "args=[\'self\', \'node_index\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "get_losses_for" + argspec: "args=[\'self\', \'inputs\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "get_output_at" + argspec: "args=[\'self\', \'node_index\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "get_output_mask_at" + argspec: "args=[\'self\', \'node_index\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "get_output_shape_at" + argspec: "args=[\'self\', \'node_index\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "get_updates_for" + argspec: "args=[\'self\', \'inputs\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "get_weights" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "set_weights" + argspec: "args=[\'self\', \'weights\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.pbtxt index f878c46059..d5b7af5a75 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.keras.layers.pbtxt @@ -1,5 +1,9 @@ path: "tensorflow.keras.layers" tf_module { + member { + name: "AbstractRNNCell" + mtype: "" + } member { name: "Activation" mtype: "" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.nn.rnn_cell.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.nn.rnn_cell.pbtxt index 87062b1a5c..23721823f9 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.nn.rnn_cell.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.nn.rnn_cell.pbtxt @@ -4,8 +4,4 @@ tf_module { name: "DeviceWrapper" mtype: "" } - member { - name: "RNNCell" - mtype: "" - } } diff --git a/tensorflow/tools/compatibility/renames_v2.py b/tensorflow/tools/compatibility/renames_v2.py index 18bc7046e5..60567d9f07 100644 --- a/tensorflow/tools/compatibility/renames_v2.py +++ b/tensorflow/tools/compatibility/renames_v2.py @@ -24,6 +24,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function + renames = { 'tf.AUTO_REUSE': 'tf.compat.v1.AUTO_REUSE', 'tf.AttrValue': 'tf.compat.v1.AttrValue', @@ -447,6 +448,7 @@ renames = { 'tf.nn.rnn_cell.LSTMStateTuple': 'tf.compat.v1.nn.rnn_cell.LSTMStateTuple', 'tf.nn.rnn_cell.MultiRNNCell': 'tf.compat.v1.nn.rnn_cell.MultiRNNCell', 'tf.nn.rnn_cell.ResidualWrapper': 'tf.compat.v1.nn.rnn_cell.ResidualWrapper', + 'tf.nn.rnn_cell.RNNCell': 'tf.compat.v1.nn.rnn_cell.RNNCell', 'tf.nn.static_bidirectional_rnn': 'tf.compat.v1.nn.static_bidirectional_rnn', 'tf.nn.static_rnn': 'tf.compat.v1.nn.static_rnn', 'tf.nn.uniform_candidate_sampler': 'tf.random.uniform_candidate_sampler', diff --git a/tensorflow/tools/compatibility/tf_upgrade_v2.py b/tensorflow/tools/compatibility/tf_upgrade_v2.py index 6832a3a0d7..bafebb9999 100644 --- a/tensorflow/tools/compatibility/tf_upgrade_v2.py +++ b/tensorflow/tools/compatibility/tf_upgrade_v2.py @@ -599,7 +599,7 @@ class TFAPIChangeSpec(ast_edits.APIChangeSpec): "tf.contrib.saved_model.save_keras_model": "tf.keras.experimental.export_saved_model", "tf.contrib.rnn.RNNCell": - "tf.nn.rnn_cell.RNNCell", + "tf.compat.v1.nn.rnn_cell.RNNCell", "tf.contrib.rnn.LSTMStateTuple": "tf.nn.rnn_cell.LSTMStateTuple", "tf.contrib.rnn.BasicLSTMCell": diff --git a/tensorflow/tools/compatibility/tf_upgrade_v2_test.py b/tensorflow/tools/compatibility/tf_upgrade_v2_test.py index 89fe045bd5..66f9345536 100644 --- a/tensorflow/tools/compatibility/tf_upgrade_v2_test.py +++ b/tensorflow/tools/compatibility/tf_upgrade_v2_test.py @@ -1236,6 +1236,13 @@ def _log_prob(self, x): _, _, _, new_text = self._upgrade(text) self.assertEqual(expected, new_text) + def test_contrib_rnn_cell(self): + text = "tf.contrib.rnn.RNNCell" + expected = "tf.compat.v1.nn.rnn_cell.RNNCell" + # pylint: enable=line-too-long + _, _, _, new_text = self._upgrade(text) + self.assertEqual(expected, new_text) + def test_flags_bare(self): _, _, errors, _ = self._upgrade("tf.flags") self.assertIn("tf.flags has been removed", errors[0]) -- GitLab From 1c2c4a91b6acbcb2893ed1731c545294d052e604 Mon Sep 17 00:00:00 2001 From: Zhenyu Tan Date: Tue, 26 Feb 2019 15:03:40 -0800 Subject: [PATCH 594/766] Add train_in_memory arg into core. PiperOrigin-RevId: 235801041 --- .../v1/tensorflow.estimator.-boosted-trees-classifier.pbtxt | 2 +- .../v1/tensorflow.estimator.-boosted-trees-regressor.pbtxt | 2 +- .../v2/tensorflow.estimator.-boosted-trees-classifier.pbtxt | 2 +- .../v2/tensorflow.estimator.-boosted-trees-regressor.pbtxt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tensorflow/tools/api/golden/v1/tensorflow.estimator.-boosted-trees-classifier.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.estimator.-boosted-trees-classifier.pbtxt index e138ce936e..216854587d 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.estimator.-boosted-trees-classifier.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.estimator.-boosted-trees-classifier.pbtxt @@ -23,7 +23,7 @@ tf_class { } member_method { name: "__init__" - argspec: "args=[\'self\', \'feature_columns\', \'n_batches_per_layer\', \'model_dir\', \'n_classes\', \'weight_column\', \'label_vocabulary\', \'n_trees\', \'max_depth\', \'learning_rate\', \'l1_regularization\', \'l2_regularization\', \'tree_complexity\', \'min_node_weight\', \'config\', \'center_bias\', \'pruning_mode\', \'quantile_sketch_epsilon\'], varargs=None, keywords=None, defaults=[\'None\', \'\', \'None\', \'None\', \'100\', \'6\', \'0.1\', \'0.0\', \'0.0\', \'0.0\', \'0.0\', \'None\', \'False\', \'none\', \'0.01\'], " + argspec: "args=[\'self\', \'feature_columns\', \'n_batches_per_layer\', \'model_dir\', \'n_classes\', \'weight_column\', \'label_vocabulary\', \'n_trees\', \'max_depth\', \'learning_rate\', \'l1_regularization\', \'l2_regularization\', \'tree_complexity\', \'min_node_weight\', \'config\', \'center_bias\', \'pruning_mode\', \'quantile_sketch_epsilon\', \'train_in_memory\'], varargs=None, keywords=None, defaults=[\'None\', \'\', \'None\', \'None\', \'100\', \'6\', \'0.1\', \'0.0\', \'0.0\', \'0.0\', \'0.0\', \'None\', \'False\', \'none\', \'0.01\', \'False\'], " } member_method { name: "eval_dir" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.estimator.-boosted-trees-regressor.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.estimator.-boosted-trees-regressor.pbtxt index eae0a292a9..25d021ad8d 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.estimator.-boosted-trees-regressor.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.estimator.-boosted-trees-regressor.pbtxt @@ -23,7 +23,7 @@ tf_class { } member_method { name: "__init__" - argspec: "args=[\'self\', \'feature_columns\', \'n_batches_per_layer\', \'model_dir\', \'label_dimension\', \'weight_column\', \'n_trees\', \'max_depth\', \'learning_rate\', \'l1_regularization\', \'l2_regularization\', \'tree_complexity\', \'min_node_weight\', \'config\', \'center_bias\', \'pruning_mode\', \'quantile_sketch_epsilon\'], varargs=None, keywords=None, defaults=[\'None\', \'\', \'None\', \'100\', \'6\', \'0.1\', \'0.0\', \'0.0\', \'0.0\', \'0.0\', \'None\', \'False\', \'none\', \'0.01\'], " + argspec: "args=[\'self\', \'feature_columns\', \'n_batches_per_layer\', \'model_dir\', \'label_dimension\', \'weight_column\', \'n_trees\', \'max_depth\', \'learning_rate\', \'l1_regularization\', \'l2_regularization\', \'tree_complexity\', \'min_node_weight\', \'config\', \'center_bias\', \'pruning_mode\', \'quantile_sketch_epsilon\', \'train_in_memory\'], varargs=None, keywords=None, defaults=[\'None\', \'\', \'None\', \'100\', \'6\', \'0.1\', \'0.0\', \'0.0\', \'0.0\', \'0.0\', \'None\', \'False\', \'none\', \'0.01\', \'False\'], " } member_method { name: "eval_dir" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.estimator.-boosted-trees-classifier.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.estimator.-boosted-trees-classifier.pbtxt index e138ce936e..216854587d 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.estimator.-boosted-trees-classifier.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.estimator.-boosted-trees-classifier.pbtxt @@ -23,7 +23,7 @@ tf_class { } member_method { name: "__init__" - argspec: "args=[\'self\', \'feature_columns\', \'n_batches_per_layer\', \'model_dir\', \'n_classes\', \'weight_column\', \'label_vocabulary\', \'n_trees\', \'max_depth\', \'learning_rate\', \'l1_regularization\', \'l2_regularization\', \'tree_complexity\', \'min_node_weight\', \'config\', \'center_bias\', \'pruning_mode\', \'quantile_sketch_epsilon\'], varargs=None, keywords=None, defaults=[\'None\', \'\', \'None\', \'None\', \'100\', \'6\', \'0.1\', \'0.0\', \'0.0\', \'0.0\', \'0.0\', \'None\', \'False\', \'none\', \'0.01\'], " + argspec: "args=[\'self\', \'feature_columns\', \'n_batches_per_layer\', \'model_dir\', \'n_classes\', \'weight_column\', \'label_vocabulary\', \'n_trees\', \'max_depth\', \'learning_rate\', \'l1_regularization\', \'l2_regularization\', \'tree_complexity\', \'min_node_weight\', \'config\', \'center_bias\', \'pruning_mode\', \'quantile_sketch_epsilon\', \'train_in_memory\'], varargs=None, keywords=None, defaults=[\'None\', \'\', \'None\', \'None\', \'100\', \'6\', \'0.1\', \'0.0\', \'0.0\', \'0.0\', \'0.0\', \'None\', \'False\', \'none\', \'0.01\', \'False\'], " } member_method { name: "eval_dir" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.estimator.-boosted-trees-regressor.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.estimator.-boosted-trees-regressor.pbtxt index eae0a292a9..25d021ad8d 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.estimator.-boosted-trees-regressor.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.estimator.-boosted-trees-regressor.pbtxt @@ -23,7 +23,7 @@ tf_class { } member_method { name: "__init__" - argspec: "args=[\'self\', \'feature_columns\', \'n_batches_per_layer\', \'model_dir\', \'label_dimension\', \'weight_column\', \'n_trees\', \'max_depth\', \'learning_rate\', \'l1_regularization\', \'l2_regularization\', \'tree_complexity\', \'min_node_weight\', \'config\', \'center_bias\', \'pruning_mode\', \'quantile_sketch_epsilon\'], varargs=None, keywords=None, defaults=[\'None\', \'\', \'None\', \'100\', \'6\', \'0.1\', \'0.0\', \'0.0\', \'0.0\', \'0.0\', \'None\', \'False\', \'none\', \'0.01\'], " + argspec: "args=[\'self\', \'feature_columns\', \'n_batches_per_layer\', \'model_dir\', \'label_dimension\', \'weight_column\', \'n_trees\', \'max_depth\', \'learning_rate\', \'l1_regularization\', \'l2_regularization\', \'tree_complexity\', \'min_node_weight\', \'config\', \'center_bias\', \'pruning_mode\', \'quantile_sketch_epsilon\', \'train_in_memory\'], varargs=None, keywords=None, defaults=[\'None\', \'\', \'None\', \'100\', \'6\', \'0.1\', \'0.0\', \'0.0\', \'0.0\', \'0.0\', \'None\', \'False\', \'none\', \'0.01\', \'False\'], " } member_method { name: "eval_dir" -- GitLab From 4294edea3e80c746eac400262628f852b66d02e5 Mon Sep 17 00:00:00 2001 From: Sourabh Bajaj Date: Tue, 26 Feb 2019 15:05:05 -0800 Subject: [PATCH 595/766] Move TPU strategy from contrib to core PiperOrigin-RevId: 235801299 --- tensorflow/contrib/distribute/python/BUILD | 12 +- .../contrib/distribute/python/tpu_strategy.py | 778 +---------------- .../api_def/base_api/api_def_AllToAll.pbtxt | 1 + .../base_api/api_def_CollectivePermute.pbtxt | 1 + .../api_def_ConfigureDistributedTPU.pbtxt | 1 + .../base_api/api_def_CrossReplicaSum.pbtxt | 1 + ..._def_EnqueueTPUEmbeddingIntegerBatch.pbtxt | 1 + ...i_def_EnqueueTPUEmbeddingSparseBatch.pbtxt | 1 + ...EnqueueTPUEmbeddingSparseTensorBatch.pbtxt | 1 + .../base_api/api_def_InfeedDequeue.pbtxt | 1 + .../base_api/api_def_InfeedDequeueTuple.pbtxt | 1 + .../base_api/api_def_InfeedEnqueue.pbtxt | 1 + .../base_api/api_def_InfeedEnqueueTuple.pbtxt | 1 + ...i_def_LoadTPUEmbeddingADAMParameters.pbtxt | 1 + ...mbeddingADAMParametersGradAccumDebug.pbtxt | 1 + ...f_LoadTPUEmbeddingAdadeltaParameters.pbtxt | 1 + ...dingAdadeltaParametersGradAccumDebug.pbtxt | 1 + ...ef_LoadTPUEmbeddingAdagradParameters.pbtxt | 1 + ...ddingAdagradParametersGradAccumDebug.pbtxt | 1 + ...PUEmbeddingCenteredRMSPropParameters.pbtxt | 1 + ...i_def_LoadTPUEmbeddingFTRLParameters.pbtxt | 1 + ...mbeddingFTRLParametersGradAccumDebug.pbtxt | 1 + ...PUEmbeddingMDLAdagradLightParameters.pbtxt | 1 + ...f_LoadTPUEmbeddingMomentumParameters.pbtxt | 1 + ...dingMomentumParametersGradAccumDebug.pbtxt | 1 + ...PUEmbeddingProximalAdagradParameters.pbtxt | 1 + ...ximalAdagradParametersGradAccumDebug.pbtxt | 1 + ...ef_LoadTPUEmbeddingRMSPropParameters.pbtxt | 1 + ...ddingRMSPropParametersGradAccumDebug.pbtxt | 1 + ...gStochasticGradientDescentParameters.pbtxt | 1 + .../base_api/api_def_OutfeedDequeue.pbtxt | 1 + .../api_def_OutfeedDequeueTuple.pbtxt | 1 + .../base_api/api_def_OutfeedEnqueue.pbtxt | 1 + .../api_def_OutfeedEnqueueTuple.pbtxt | 1 + .../api_def_RecvTPUEmbeddingActivations.pbtxt | 1 + ...f_RetrieveTPUEmbeddingADAMParameters.pbtxt | 1 + ...mbeddingADAMParametersGradAccumDebug.pbtxt | 1 + ...trieveTPUEmbeddingAdadeltaParameters.pbtxt | 1 + ...dingAdadeltaParametersGradAccumDebug.pbtxt | 1 + ...etrieveTPUEmbeddingAdagradParameters.pbtxt | 1 + ...ddingAdagradParametersGradAccumDebug.pbtxt | 1 + ...PUEmbeddingCenteredRMSPropParameters.pbtxt | 1 + ...f_RetrieveTPUEmbeddingFTRLParameters.pbtxt | 1 + ...mbeddingFTRLParametersGradAccumDebug.pbtxt | 1 + ...PUEmbeddingMDLAdagradLightParameters.pbtxt | 1 + ...trieveTPUEmbeddingMomentumParameters.pbtxt | 1 + ...dingMomentumParametersGradAccumDebug.pbtxt | 1 + ...PUEmbeddingProximalAdagradParameters.pbtxt | 1 + ...ximalAdagradParametersGradAccumDebug.pbtxt | 1 + ...etrieveTPUEmbeddingRMSPropParameters.pbtxt | 1 + ...ddingRMSPropParametersGradAccumDebug.pbtxt | 1 + ...gStochasticGradientDescentParameters.pbtxt | 1 + .../api_def_SendTPUEmbeddingGradients.pbtxt | 1 + .../api_def_ShutdownDistributedTPU.pbtxt | 1 + .../api_def_TPUCompilationResult.pbtxt | 1 + .../api_def_TPUEmbeddingActivations.pbtxt | 1 + .../base_api/api_def_TPUOrdinalSelector.pbtxt | 1 + .../base_api/api_def_TPUPartitionedCall.pbtxt | 1 + .../base_api/api_def_TPUReplicate.pbtxt | 1 + .../api_def_TPUReplicateMetadata.pbtxt | 1 + .../base_api/api_def_TPUReplicatedInput.pbtxt | 1 + .../api_def_TPUReplicatedOutput.pbtxt | 1 + .../base_api/api_def_WorkerHeartbeat.pbtxt | 1 + tensorflow/python/distribute/BUILD | 20 + .../python/distribute/experimental/BUILD | 1 + .../distribute/experimental/__init__.py | 1 + tensorflow/python/distribute/tpu_strategy.py | 795 ++++++++++++++++++ .../api/golden/v1/tensorflow.raw_ops.pbtxt | 244 ++++++ .../api/golden/v2/tensorflow.raw_ops.pbtxt | 244 ++++++ 69 files changed, 1370 insertions(+), 786 deletions(-) create mode 100644 tensorflow/python/distribute/tpu_strategy.py diff --git a/tensorflow/contrib/distribute/python/BUILD b/tensorflow/contrib/distribute/python/BUILD index 2ab94d0056..88d6c7a6d2 100644 --- a/tensorflow/contrib/distribute/python/BUILD +++ b/tensorflow/contrib/distribute/python/BUILD @@ -260,18 +260,8 @@ py_library( srcs = ["tpu_strategy.py"], visibility = ["//tensorflow:internal"], deps = [ - ":one_device_strategy", "//tensorflow/contrib/tpu:tpu_lib", - "//tensorflow/python:constant_op", - "//tensorflow/python:control_flow_ops", - "//tensorflow/python:dtypes", - "//tensorflow/python:framework_ops", - "//tensorflow/python:tensor_util", - "//tensorflow/python:util", - "//tensorflow/python/distribute:input_lib", - "//tensorflow/python/distribute:numpy_dataset", - "//tensorflow/python/distribute:reduce_util", - "//tensorflow/python/distribute:values", + "//tensorflow/python/distribute:tpu_strategy", ], ) diff --git a/tensorflow/contrib/distribute/python/tpu_strategy.py b/tensorflow/contrib/distribute/python/tpu_strategy.py index 051a3890d7..2b1ce92ff1 100644 --- a/tensorflow/contrib/distribute/python/tpu_strategy.py +++ b/tensorflow/contrib/distribute/python/tpu_strategy.py @@ -21,778 +21,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -import collections -import copy - -from tensorflow.contrib.tpu.python.ops import tpu_ops -from tensorflow.contrib.tpu.python.tpu import device_assignment as device_assignment_lib -from tensorflow.contrib.tpu.python.tpu import functional as tpu_functional_ops -from tensorflow.contrib.tpu.python.tpu import topology -from tensorflow.contrib.tpu.python.tpu import tpu -from tensorflow.contrib.tpu.python.tpu import tpu_system_metadata as tpu_system_metadata_lib -from tensorflow.contrib.tpu.python.tpu import training_loop -from tensorflow.core.protobuf import config_pb2 -from tensorflow.python.client import session as session_lib -from tensorflow.python.distribute import cross_device_ops as cross_device_ops_lib -from tensorflow.python.distribute import device_util -from tensorflow.python.distribute import distribute_lib -from tensorflow.python.distribute import input_lib -from tensorflow.python.distribute import numpy_dataset -from tensorflow.python.distribute import reduce_util -from tensorflow.python.distribute import values -from tensorflow.python.distribute.cluster_resolver import TPUClusterResolver -from tensorflow.python.eager import context -from tensorflow.python.eager import function -from tensorflow.python.eager import tape -from tensorflow.python.framework import constant_op -from tensorflow.python.framework import device as tf_device -from tensorflow.python.framework import dtypes -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 control_flow_ops -from tensorflow.python.ops import math_ops -from tensorflow.python.ops import variable_scope as vs -from tensorflow.python.platform import tf_logging as logging -from tensorflow.python.util import compat -from tensorflow.python.util import nest - - -def initialize_tpu_system(cluster_resolver=None): - """Initialize the TPU devices in a separate session and graph. - - Args: - cluster_resolver: A tf.contrib.cluster_resolver.TPUClusterResolver, - which provides information about the TPU cluster. - Returns: - The tf.contrib.tpu.Topology object for the topology of the TPU cluster. - """ - if cluster_resolver is None: - cluster_resolver = TPUClusterResolver("") - master = cluster_resolver.master() - - logging.info("Initializing the TPU system.") - - if context.executing_eagerly(): - # This function looks as it is for the following non-intuitive reasons. - # tpu.initialize_system creates a dummy op whose sole purpose is to trigger - # DistributedTPURewritePass. This pass actually adds real ops that - # initialize the TPU system. Thus, we can't simply run tpu.initialize_system - # eagerly. We need to wrap it in defun and trigger the rewrite passes on it. - # The easiest way to trigger a rewrite is to run the function with - # TPUPartitionedCallOp. - @function.defun - def _tpu_init_fn(): - return tpu.initialize_system() - - # We can't call _tpu_init_fn normally (because it contains just a dummy op, - # see above) but need to define it to get it added to eager context - # and get its assigned name. - # pylint: disable=protected-access - graph_func = _tpu_init_fn._get_concrete_function_internal() - func_name = compat.as_str(graph_func._inference_function.name) - # pylint: enable=protected-access - - output = tpu_functional_ops.TPUPartitionedCall( - args=[], device_ordinal=0, Tout=[dtypes.string], f=func_name) - serialized_topology = output[0].numpy() - else: - session_config = config_pb2.ConfigProto(allow_soft_placement=True) - with ops.Graph().as_default(): - with session_lib.Session(config=session_config, target=master) as sess: - serialized_topology = sess.run(tpu.initialize_system()) - - logging.info("Finished initializing TPU system.") - return topology.Topology(serialized=serialized_topology) - - -def get_tpu_system_metadata(tpu_cluster_resolver): - """Retrieves TPU system metadata given a TPUClusterResolver.""" - master = tpu_cluster_resolver.master() - - # pylint: disable=protected-access - cluster_spec = tpu_cluster_resolver.cluster_spec() - cluster_def = cluster_spec.as_cluster_def() if cluster_spec else None - tpu_system_metadata = ( - tpu_system_metadata_lib._query_tpu_system_metadata( - master, - cluster_def=cluster_def, - query_topology=False)) - - return tpu_system_metadata - - -# TODO(jhseu): Deduplicate with MirroredStrategy? -def _create_tpu_mirrored_variable( # pylint: disable=missing-docstring - strategy, device_map, logical_device, real_mirrored_creator, - *args, **kwargs): - # Figure out what collections this variable should be added to. - # We'll add the TPUMirroredVariable to those collections instead. - var_collections = kwargs.pop("collections", None) - if var_collections is None: - var_collections = [ops.GraphKeys.GLOBAL_VARIABLES] - kwargs["collections"] = [] - - # TODO(jhseu): Should we have different behavior for different - # synchronization settings? - - # Get aggregation value - # TODO(jhseu): Support aggregation in a replica context. - aggregation = kwargs.pop("aggregation", vs.VariableAggregation.NONE) - if aggregation not in [ - vs.VariableAggregation.NONE, - vs.VariableAggregation.SUM, - vs.VariableAggregation.MEAN, - vs.VariableAggregation.ONLY_FIRST_REPLICA, - ]: - raise ValueError("Invalid variable aggregation mode: {} for variable: {}" - .format(aggregation, kwargs["name"])) - - # Ignore user-specified caching device, not needed for mirrored variables. - kwargs.pop("caching_device", None) - - # 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(): - devices = device_map.logical_to_actual_devices(logical_device) - value_list = real_mirrored_creator(devices, *args, **kwargs) - result = values.TPUMirroredVariable( - strategy, device_map, value_list, aggregation, - logical_device=logical_device) - - if not (context.executing_eagerly() or ops.inside_function()): - 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): - var_collections.append(ops.GraphKeys.TRAINABLE_VARIABLES) - l = g.get_collection_ref(ops.GraphKeys.TRAINABLE_VARIABLES) - for v in value_list: - l.remove(v) - g.add_to_collections(var_collections, result) - return result - - -class TPUStrategy(distribute_lib.DistributionStrategy): - """TPU distribution strategy implementation.""" - - def __init__(self, - tpu_cluster_resolver=None, - steps_per_run=None, - device_assignment=None): - """Initializes the TPUStrategy object. - - Args: - tpu_cluster_resolver: A tf.contrib.cluster_resolver.TPUClusterResolver, - which provides information about the TPU cluster. - steps_per_run: Number of steps to run on device before returning to the - host. Note that this can have side-effects on performance, hooks, - metrics, summaries etc. - This parameter is only used when Distribution Strategy is used with - estimator or keras. - device_assignment: Optional `tf.contrib.tpu.DeviceAssignment` to specify - the placement of replicas on the TPU cluster. Currently only supports - the usecase of using a single core within a TPU cluster. - """ - super(TPUStrategy, self).__init__(TPUExtended( - self, tpu_cluster_resolver, steps_per_run, device_assignment)) - - @property - def steps_per_run(self): - """DEPRECATED: use .extended.steps_per_run instead.""" - return self._extended.steps_per_run - - # TODO(cjfj): Modify `_call_for_each_replica` in `TPUExtended` such that this - # can use the default implementation. - # This implementation runs a single step. It does not use infeed or outfeed. - def experimental_run(self, fn, input_iterator=None): - """See base class.""" - if context.executing_eagerly() and not ops.inside_function(): - raise NotImplementedError( - "Eager mode not supported in TPUStrategy outside TF functions.") - - if input_iterator is None: - inputs = [] - else: - inputs = input_iterator.get_next() - - result = [None] - def replicated_fn(replica_id, replica_input): - """Wraps user function to provide replica ID and `Tensor` inputs.""" - with _TPUReplicaContext(self, replica_id_in_sync_group=replica_id): - if input_iterator is None: - result[0] = fn() - else: - result[0] = fn(replica_input) - return result[0] - - replicate_inputs = [] # By replica. - for i in range(self.num_replicas_in_sync): - replicate_inputs.append( - [constant_op.constant(i, dtype=dtypes.int32), - values.select_replica(i, inputs)]) - - with self.scope(): - replicate_outputs = tpu.replicate(replicated_fn, replicate_inputs) - - # Workaround for `tpu.replicate` behaviour when single `Tensor` returned. - replicate_outputs = [ - nest.pack_sequence_as(result[0], nest.flatten(replica_outputs)) - for replica_outputs in replicate_outputs] - - device_map = self.extended._device_map # pylint: disable=protected-access - return values.regroup(device_map, replicate_outputs) - - -class TPUExtended(distribute_lib.DistributionStrategyExtended): - """Implementation of TPUStrategy.""" - - def __init__(self, - container_strategy, - tpu_cluster_resolver=None, - steps_per_run=None, - device_assignment=None): - super(TPUExtended, self).__init__(container_strategy) - - if tpu_cluster_resolver is None: - tpu_cluster_resolver = TPUClusterResolver("") - - if steps_per_run is None: - # TODO(frankchn): Warn when we are being used by DS/Keras and this is - # not specified. - steps_per_run = 1 - - self._tpu_cluster_resolver = tpu_cluster_resolver - self._tpu_metadata = get_tpu_system_metadata(self._tpu_cluster_resolver) - self._device_assignment = device_assignment - - # Device assignment is currently only supported for 1 core case. - if self._device_assignment: - assert isinstance(self._device_assignment, - device_assignment_lib.DeviceAssignment) - if self._device_assignment.num_replicas != 1: - raise ValueError("Device assignment is only supported for a single " - "core single replica case currently.") - if self._device_assignment.num_cores_per_replica != 1: - raise ValueError("Device assignment is only supported for a single " - "core single replica case currently.") - if not all(self._device_assignment.core_assignment[0][0] == [0, 0, 0]): - raise ValueError("Device assignment is only supported for a single " - "core single replica case currently.") - - # TODO(jhseu): Switch to DeviceAssignment to support pods and model - # parallelism. - self._device_index = { - d.name: i for i, d in enumerate(self._tpu_metadata.devices) - if "device:TPU:" in d.name - } - self._host_device = self.get_host_cpu_device(0) - self._tpu_devices = tuple(sorted(self._device_index.keys())) - # Only create variables for the number of replicas we're running. - self._tpu_devices = self._tpu_devices[:self._num_replicas_in_sync] - self._device_map = values.ReplicaDeviceMap(self._tpu_devices) - - # Preload the data onto the TPUs. - input_worker_devices = collections.OrderedDict() - for tpu_device in self._tpu_devices: - host_device = _get_host_for_device(tpu_device) - input_worker_devices.setdefault(host_device, []) - input_worker_devices[host_device].append(tpu_device) - self._input_workers = input_lib.InputWorkers( - self._device_map, tuple(input_worker_devices.items())) - - # TODO(sourabhbajaj): Remove this once performance of running one step - # at a time is comparable to multiple steps. - self.steps_per_run = steps_per_run - self._require_static_shapes = True - - def _validate_colocate_with_variable(self, colocate_with_variable): - values.validate_colocate_tpu_variable(colocate_with_variable, self) - - def _get_enqueue_op_per_host(self, host_id, multi_worker_iterator, - input_shapes, iterations): - """Create an enqueue op for a single host identified using host_id. - - The while_loop op returned will run `iterations` times and in each run - enqueue batches for each shard. - - Args: - host_id: integer, id of the host to run the enqueue ops on. - multi_worker_iterator: MultiWorkerDataIterator to read the input data. - input_shapes: shape of inputs to be enqueue on the queue. This is same as - the value of `nest.flatten(iterator.output_shapes)`. - iterations: integer, number of iterations to be run; determines the - number of batches to be enqueued. - - Returns: - while_loop_op running `iterations` times; in each run we enqueue a batch - on the infeed queue from the host with id `host_id` for each device shard. - """ - host = self.get_host_cpu_device(host_id) - # TODO(sourabhbajaj): Possibly make changes to MultiWorkerDataset - # to work with TPU Prefetch so clean up this code. - iterator = ( - multi_worker_iterator.get_iterator(self.get_host(host_id))._iterator) # pylint: disable=protected-access - - def _infeed_enqueue_ops_fn(): - """Enqueue ops for one iteration.""" - control_deps = [] - sharded_inputs = [] - enqueue_ops = [] - - with ops.device(host): - for _ in range(self.num_replicas_per_host): - # Use control dependencies to ensure a deterministic ordering. - with ops.control_dependencies(control_deps): - inputs = nest.flatten(iterator.get_next()) - control_deps.extend(inputs) - sharded_inputs.append(inputs) - - for core_id, shard_input in enumerate(sharded_inputs): - enqueue_ops.append( - tpu_ops.infeed_enqueue_tuple( - inputs=shard_input, - shapes=input_shapes, - device_ordinal=core_id)) - return enqueue_ops - - def enqueue_ops_loop_body(i): - """Callable for the loop body of the while_loop instantiated below.""" - with ops.control_dependencies(_infeed_enqueue_ops_fn()): - return i + 1 - - with ops.device(host): - enqueue_op_per_host = control_flow_ops.while_loop( - lambda i: i < iterations, - enqueue_ops_loop_body, - [constant_op.constant(0)], - parallel_iterations=1) - - return enqueue_op_per_host - - def _make_dataset_iterator(self, dataset): - """Make iterators for each of the TPU hosts.""" - return input_lib.DatasetIterator(dataset, self._input_workers, - self._num_replicas_in_sync) - - def _make_input_fn_iterator( - self, - input_fn, - replication_mode=distribute_lib.InputReplicationMode.PER_WORKER): - input_contexts = [] - num_workers = self._input_workers.num_workers - for i in range(num_workers): - input_contexts.append(distribute_lib.InputContext( - num_input_pipelines=num_workers, - input_pipeline_id=i, - num_replicas_in_sync=self._num_replicas_in_sync)) - return input_lib.InputFunctionIterator( - input_fn, self._input_workers, input_contexts) - - def _experimental_make_numpy_dataset(self, numpy_input, session): - return numpy_dataset.one_host_numpy_dataset( - numpy_input, numpy_dataset.SingleDevice(self.get_host_cpu_device(0)), - session) - - # TODO(priyag): Deal with OutOfRange errors once b/111349762 is fixed. - # TODO(sourabhbajaj): Remove the initial_loop_values parameter when we have - # a mechanism to infer the outputs of `fn`. Pending b/110550782. - def _experimental_run_steps_on_iterator( - self, fn, multi_worker_iterator, iterations, initial_loop_values=None): - output_shapes = multi_worker_iterator.output_shapes - shapes = nest.flatten(output_shapes) - if any(not s.is_fully_defined() for s in shapes): - raise ValueError( - "TPU currently requires fully defined shapes. Either use " - "set_shape() on the input tensors or use " - "dataset.batch(..., drop_remainder=True).") - - # Wrap `fn` for repeat. - if initial_loop_values is None: - initial_loop_values = {} - initial_loop_values = nest.flatten(initial_loop_values) - ctx = input_lib.MultiStepContext() - - def run_fn(inputs): - """Single step on the TPU device.""" - fn_result = fn(ctx, inputs) - flat_last_step_outputs = nest.flatten(ctx.last_step_outputs) - if flat_last_step_outputs: - with ops.control_dependencies([fn_result]): - return [array_ops.identity(f) for f in flat_last_step_outputs] - else: - return fn_result - - # We capture the control_flow_context at this point, before we run `fn` - # inside a while_loop and TPU replicate context. This is useful in cases - # where we might need to exit these contexts and get back to the outer - # context to do some things, for e.g. create an op which should be - # evaluated only once at the end of the loop on the host. One such usage - # is in creating metrics' value op. - self._outer_control_flow_context = ( - ops.get_default_graph()._get_control_flow_context()) # pylint: disable=protected-access - - def rewrite_fn(*args): - """The rewritten step fn running on TPU.""" - del args - - per_replica_inputs = multi_worker_iterator.get_next() - replicate_inputs = [] - for replica_id in range(self._num_replicas_in_sync): - select_replica = lambda x: values.select_replica(replica_id, x) # pylint: disable=cell-var-from-loop - replicate_inputs.append((nest.map_structure( - select_replica, per_replica_inputs),)) - - replicate_outputs = tpu.replicate(run_fn, replicate_inputs) - - # If run_fn has tensor outputs, tpu.replicate returns a list of list. We - # will flatten it in this case. If run_fn has no tensor outputs, - # tpu.replicate returns a list of no_ops, we will keep the output as it - # is. - if isinstance(replicate_outputs[0], list): - replicate_outputs = nest.flatten(replicate_outputs) - - return replicate_outputs - - # TODO(sourabhbajaj): The input to while loop should be based on the - # output type of the step_fn - assert isinstance(initial_loop_values, list) - initial_loop_values = initial_loop_values * self._num_replicas_in_sync - - # Put the while loop op on host 0. - with ops.device(self.get_host_cpu_device(0)): - replicate_outputs = training_loop.repeat(iterations, rewrite_fn, - initial_loop_values) - - del self._outer_control_flow_context - ctx.run_op = control_flow_ops.group(replicate_outputs) - - if isinstance(replicate_outputs, list): - # Filter out any ops from the outputs, typically this would be the case - # when there were no tensor outputs. - last_step_tensor_outputs = [ - x for x in replicate_outputs if not isinstance(x, ops.Operation) - ] - - # Outputs are currently of the structure (flattened) - # [output0_device0, output1_device0, output2_device0, - # output0_device1, output1_device1, output2_device1, - # ...] - # Convert this to the following structure instead: (grouped by output) - # [[output0_device0, output0_device1], - # [output1_device0, output1_device1], - # [output2_device0, output2_device1]] - output_num = len(last_step_tensor_outputs) // self._num_replicas_in_sync - last_step_tensor_outputs = [ - last_step_tensor_outputs[i::output_num] for i in range(output_num) - ] - else: - # no tensors returned. - last_step_tensor_outputs = [] - - _set_last_step_outputs(ctx, last_step_tensor_outputs) - return ctx - - def _call_for_each_replica(self, fn, args, kwargs): - # TODO(jhseu): Consider making it so call_for_each_replica implies that - # we're in a tpu.rewrite(), and update TPUMirroredVariable accordingly. - with _TPUReplicaContext(self._container_strategy()): - return fn(*args, **kwargs) - - def _experimental_initialize_system(self): - """Experimental method added to be used by Estimator. - - This is a private method only to be used by Estimator. Other frameworks - should directly be calling `tf.contrib.distribute.initialize_tpu_system` - """ - initialize_tpu_system(self._tpu_cluster_resolver) - - def _create_variable(self, next_creator, *args, **kwargs): - """Create a TPUMirroredVariable. See `DistributionStrategy.scope`.""" - colocate_with = kwargs.pop("colocate_with", None) - if colocate_with is None: - device_map = self._device_map - logical_device = 0 # TODO(josh11b): Get logical device from scope here. - elif isinstance(colocate_with, numpy_dataset.SingleDevice): - with ops.device(colocate_with.device): - return next_creator(*args, **kwargs) - else: - device_map = colocate_with.device_map - logical_device = colocate_with.logical_device - - def _real_mirrored_creator(devices, *args, **kwargs): # pylint: disable=g-missing-docstring - value_list = [] - for i, d in enumerate(devices): - with ops.device(d): - if i > 0: - # Give replicas meaningful distinct names: - var0name = value_list[0].name.split(":")[0] - # We append a / to variable names created on replicas with id > 0 to - # ensure that we ignore the name scope and instead use the given - # name as the absolute name of the variable. - kwargs["name"] = "%s/replica_%d/" % (var0name, i) - # Initialize replicas with the same value: - if context.executing_eagerly() or ops.inside_function(): - with ops.init_scope(): - kwargs["initial_value"] = array_ops.identity( - value_list[0].value()) - else: - def initial_value_fn(device=d): - with ops.device(device): - return array_ops.identity(value_list[0].initial_value) - kwargs["initial_value"] = initial_value_fn - with context.device_policy(context.DEVICE_PLACEMENT_SILENT): - v = next_creator(*args, **kwargs) - assert not isinstance(v, values.TPUMirroredVariable) - value_list.append(v) - return value_list - - return _create_tpu_mirrored_variable( - self._container_strategy(), device_map, logical_device, - _real_mirrored_creator, *args, **kwargs) - - def _reduce_to(self, reduce_op, value, destinations): - if values._enclosing_tpu_context() is not None: # pylint: disable=protected-access - if reduce_op == reduce_util.ReduceOp.MEAN: - # TODO(jhseu): Revisit once we support model-parallelism. - value *= (1. / self._num_replicas_in_sync) - elif reduce_op != reduce_util.ReduceOp.SUM: - raise NotImplementedError( - "Currently only support sum & mean in TPUStrategy.") - return tpu_ops.cross_replica_sum(value) - - if not isinstance(value, values.DistributedValues): - # This function handles reducing values that are not PerReplica or - # Mirrored values. For example, the same value could be present on all - # replicas in which case `value` would be a single value or value could - # be 0. - return cross_device_ops_lib.reduce_non_distributed_value( - reduce_op, self._device_map, value, destinations) - - devices = cross_device_ops_lib.get_devices_from(destinations) - if len(devices) != 1: - raise ValueError("Multiple devices are not supported for TPUStrategy") - - # Always performs the reduction on the TPU host. - with ops.device(self._host_device): - output = math_ops.add_n(value.values) - if reduce_op == reduce_util.ReduceOp.MEAN: - output *= (1. / len(value.values)) - - # If necessary, copy to requested destination. - dest_canonical = device_util.canonicalize(devices[0]) - host_canonical = device_util.canonicalize(self._host_device) - - if dest_canonical != host_canonical: - with ops.device(devices[0]): - output = array_ops.identity(output) - - return output - - def _update(self, var, fn, args, kwargs, group): - assert isinstance(var, values.TPUMirroredVariable) - if values._enclosing_tpu_context() is not None: # pylint: disable=protected-access - if group: - return fn(var, *args, **kwargs) - else: - return (fn(var, *args, **kwargs),) - - # Otherwise, we revert to MirroredStrategy behavior and update each variable - # directly. - updates = [] - for i, (d, v) in enumerate(zip(var.devices, var.values)): - name = "update_%d" % i - with ops.device(d), distribute_lib.UpdateContext(d), ops.name_scope(name): - # If args and kwargs are not mirrored, the value is returned as is. - updates.append(fn(v, - *values.select_device_mirrored(d, args), - **values.select_device_mirrored(d, kwargs))) - return values.update_regroup(self, self._device_map, updates, group) - - def read_var(self, var): - assert isinstance(var, values.TPUMirroredVariable) - return var.read_value() - - def _unwrap(self, val): - if isinstance(val, values.DistributedValues): - # Return in a deterministic order. - return tuple(val.get(device=d) for d in sorted(val.devices)) - elif isinstance(val, list): - # TODO(josh11b): We need to remove this case; per device values should - # be represented using a PerReplica wrapper instead of a list with - # one entry per device. - return tuple(val) - elif isinstance(val, values.TPUMirroredVariable): - # pylint: disable=protected-access - if values._enclosing_tpu_context() is not None: - return (val,) - return val.values - return (val,) - - def value_container(self, value): - return value - - def _broadcast_to(self, tensor, destinations): - del destinations - return tensor - - @property - def num_hosts(self): - if self._device_assignment is None: - return self._tpu_metadata.num_hosts - - return len(set([self._device_assignment.host_device(r) - for r in range(self._device_assignment.num_replicas)])) - - @property - def num_replicas_per_host(self): - if self._device_assignment is None: - return self._tpu_metadata.num_of_cores_per_host - - # TODO(sourabhbajaj): Remove this method we use inputs and remove infeed - # as the computation of num_replicas_per_host is not a constant - # when using device_assignment. This is a temporary workaround to support - # StatefulRNN as everything is 1 in that case. - # This method needs to take host_id as input for correct computation. - max_models_per_host = (self._tpu_metadata.num_of_cores_per_host // - self._device_assignment.num_cores_per_replica) - models_per_host = min(self._device_assignment.num_replicas, - max_models_per_host) - return models_per_host * self._device_assignment.num_cores_per_replica - - @property - def _num_replicas_in_sync(self): - if self._device_assignment is None: - return self._tpu_metadata.num_cores - return (self._device_assignment.num_replicas * - self._device_assignment.num_cores_per_replica) - - @property - def experimental_between_graph(self): - return False - - @property - def experimental_should_init(self): - return True - - @property - def should_checkpoint(self): - return True - - @property - def should_save_summary(self): - return True - - @property - def worker_devices(self): - return self._tpu_devices - - @property - def parameter_devices(self): - return self._tpu_devices - - def non_slot_devices(self, var_list): - return self._host_device - - def _update_non_slot(self, colocate_with, fn, args, kwargs, group): - del colocate_with - with ops.device(self._host_device), distribute_lib.UpdateContext( - self._host_device): - result = fn(*args, **kwargs) - if group: - return result - else: - return nest.map_structure(self._unwrap, result) - - def get_host(self, host_id): - if self._tpu_cluster_resolver.get_master() in ("", "local"): - return "/replica:0/task:0" - job_name = self._tpu_cluster_resolver.get_job_name() or "tpu_worker" - return "/job:%s/task:%d" % (job_name, host_id) - - def get_host_cpu_device(self, host_id): - return self.get_host(host_id) + "/device:CPU:0" - - def _configure(self, - session_config=None, - cluster_spec=None, - task_type=None, - task_id=None): - del cluster_spec, task_type, task_id - if session_config: - session_config.CopyFrom(self._update_config_proto(session_config)) - - def _update_config_proto(self, config_proto): - updated_config = copy.deepcopy(config_proto) - updated_config.isolate_session_state = True - cluster_spec = self._tpu_cluster_resolver.cluster_spec() - if cluster_spec: - updated_config.cluster_def.CopyFrom(cluster_spec.as_cluster_def()) - return updated_config - - # TODO(priyag): Delete this once all strategies use global batch size. - @property - def _global_batch_size(self): - """`make_dataset_iterator` and `make_numpy_iterator` use global batch size. - - `make_input_fn_iterator` assumes per-replica batching. - - Returns: - Boolean. - """ - return True - - -class _TPUReplicaContext(distribute_lib.ReplicaContext): - """Replication Context class for TPU Strategy.""" - - # TODO(sourabhbajaj): Call for each replica should be updating this. - # TODO(b/118385803): Always properly initialize replica_id. - def __init__(self, strategy, replica_id_in_sync_group=None): - if replica_id_in_sync_group is None: - replica_id_in_sync_group = constant_op.constant(0, dtypes.int32) - distribute_lib.ReplicaContext.__init__( - self, strategy, replica_id_in_sync_group=replica_id_in_sync_group) - - @property - def devices(self): - distribute_lib.require_replica_context(self) - ds = self._strategy - replica_id = tensor_util.constant_value(self._replica_id_in_sync_group) - - if replica_id is None: # Non-constant `Tensor` inside `tpu.replicate`. - # TODO(cjfj): Return other devices when model parallelism is supported. - return (tpu.core(0),) - else: - return (ds.extended.worker_devices[replica_id],) - - -def _get_host_for_device(device): - spec = tf_device.DeviceSpec.from_string(device) - return tf_device.DeviceSpec( - job=spec.job, replica=spec.replica, task=spec.task, - device_type="CPU", device_index=0).to_string() - - -def _set_last_step_outputs(ctx, last_step_tensor_outputs): - """Sets the last step outputs on the given context.""" - # Convert replicate_outputs to the original dict structure of - # last_step_outputs. - last_step_tensor_outputs_dict = nest.pack_sequence_as( - ctx.last_step_outputs, last_step_tensor_outputs) - - for name, reduce_op in ctx._last_step_outputs_reduce_ops.items(): # pylint: disable=protected-access - output = last_step_tensor_outputs_dict[name] - # For outputs that have already been reduced, take the first value - # from the list as each value should be the same. Else return the full - # list of values. - # TODO(josh11b): If reduce_op is NONE, we should return a PerReplica - # value. - if reduce_op is not None: - # TODO(priyag): Should this return the element or a list with 1 element - last_step_tensor_outputs_dict[name] = output[0] - ctx._set_last_step_outputs(last_step_tensor_outputs_dict) # pylint: disable=protected-access +# pylint: disable=unused-import +from tensorflow.python.distribute.tpu_strategy import initialize_tpu_system +from tensorflow.python.distribute.tpu_strategy import TPUStrategy diff --git a/tensorflow/core/api_def/base_api/api_def_AllToAll.pbtxt b/tensorflow/core/api_def/base_api/api_def_AllToAll.pbtxt index d6f28bd022..e585bae4a3 100644 --- a/tensorflow/core/api_def/base_api/api_def_AllToAll.pbtxt +++ b/tensorflow/core/api_def/base_api/api_def_AllToAll.pbtxt @@ -1,5 +1,6 @@ op { graph_op_name: "AllToAll" + visibility: HIDDEN in_arg { name: "input" description: <